<?php
/**
 * @package     JCE
 * @subpackage  Editor
 *
 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
 * @copyright   Copyright (c) 2009-2024 Ryan Demmer. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Client\ClientHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Filesystem\Path;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Uri\Uri;

class WFMediaManager extends WFMediaManagerBase
{
    public $can_edit_images = 0;

    public $show_view_mode = 0;

    protected $exifCache = array();

    public function __construct($config = array())
    {
        parent::__construct($config);

        $app = Factory::getApplication();

        $request = WFRequest::getInstance();
        $layout = $app->input->getCmd('slot', 'plugin');

        if ($layout === 'plugin') {
            $this->addFileBrowserEvent('onBeforeUpload', array($this, 'onBeforeUpload'));
            $this->addFileBrowserEvent('onUpload', array($this, 'onUpload'));

            if ($app->input->getCmd('action') === 'thumbnail') {
                Session::checkToken('request') or jexit(Text::_('JINVALID_TOKEN'));

                $file = $app->input->get('img', '', 'STRING');

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

                // clean path
                $file = WFUtility::makeSafe($file);

                if ($file && preg_match('/\.(jpg|jpeg|png|gif|tiff|bmp|webp)$/i', $file)) {
                    return $this->createCacheThumb(rawurldecode($file));
                }
            }

            if ($this->get('can_edit_images') && $this->getParam('thumbnail_editor', 1)) {
                $request->setRequest(array($this, 'createThumbnail'));
                $request->setRequest(array($this, 'createThumbnails'));
                $request->setRequest(array($this, 'deleteThumbnail'));
            }

            if ($this->get('can_edit_images') && $this->checkAccess('image_editor', 1)) {
                $request->setRequest(array($this, 'resizeImages'));
            }

            $this->addFileBrowserEvent('onFilesDelete', array($this, 'onFilesDelete'));
            $this->addFileBrowserEvent('onGetItems', array($this, 'processListItems'));

        } else {
            $request->setRequest(array($this, 'applyImageEdit'));
            $request->setRequest(array($this, 'saveTextFile'));
        }

        $request->setRequest(array($this, 'saveImageEdit'));
        $request->setRequest(array($this, 'cleanEditorTmp'));
    }

    /**
     * Display the plugin.
     */
    public function display()
    {
        $document = WFDocument::getInstance();
        $layout = Factory::getApplication()->input->getCmd('slot', 'plugin');

        // Plugin
        if ($layout === 'plugin') {
            if ($this->get('can_edit_images')) {
                if ($this->checkAccess('image_editor', 1)) {
                    $this->addFileBrowserButton('file', 'image_editor', array('action' => 'editImage', 'title' => Text::_('WF_BUTTON_EDIT_IMAGE'), 'restrict' => 'jpg,jpeg,png,gif,webp', 'multiple' => true));
                }

                if ($this->checkAccess('thumbnail_editor', 1)) {
                    $this->addFileBrowserButton('file', 'thumb_create', array('action' => 'createThumbnail', 'title' => Text::_('WF_BUTTON_CREATE_THUMBNAIL'), 'trigger' => true, 'multiple' => true, 'icon' => 'thumbnail'));
                    $this->addFileBrowserButton('file', 'thumb_delete', array('action' => 'deleteThumbnail', 'title' => Text::_('WF_BUTTON_DELETE_THUMBNAIL'), 'trigger' => true, 'multiple' => true, 'icon' => 'thumbnail-remove'));
                }
            }

            if ($this->checkAccess('text_editor', 0)) {
                $this->addFileBrowserButton('file', 'text_editor', array('action' => 'editText', 'title' => Text::_('WF_BUTTON_EDIT_FILE'), 'restrict' => 'txt,json,html,htm,xml,md,csv,css,scss,less,js,ts'));
            }

            // get parent display data
            parent::display();

            // add pro scripts
            $document->addScript(
                array(
                    'js/widget', 
                    'js/transform', 
                    'js/thumbnail'
                ), 
                'pro'
            );
            $document->addStyleSheet(
                array(
                    'css/manager', 
                    'css/transform'
                ), 
                'pro'
            );
        }

        // Image Editor
        if ($layout === 'editor.image') {
            if ($this->checkAccess('image_editor', 1) === false) {
                throw new Exception(Text::_('JERROR_ALERTNOAUTHOR'));
            }

            // cleanup tmp files
            $this->cleanTempDirectory();

            $view = $this->getView();
            $view->setLayout('image');
            $view->addTemplatePath(WF_EDITOR_PRO_LIBRARIES . '/views/editor/image/tmpl');

            $lists = array();

            $lists['resize'] = $this->getPresetsList('resize');
            $lists['crop'] = $this->getPresetsList('crop');

            $view->lists = $lists;

            // get parent display data
            parent::display();

            $document->addScript(
                array(
                    'js/transform',
                    'js/editor/image.min',
                ), 'pro'
            );

            $document->addStyleSheet(
                array(
                    'css/transform', 
                    'css/editor/image.min'
                ), 
                'pro'
            );

            $document->addScriptDeclaration('jQuery(document).ready(function($){ImageEditor.init({"site" : "' . Uri::root() . '", "root" : "' . Uri::root(true) . '"})});');
            $document->setTitle(Text::_('WF_MANAGER_IMAGE_EDITOR'));
        }

        if ($layout === 'editor.text') {
            if ($this->checkAccess('text_editor', 0) === false) {
                throw new Exception(Text::_('JERROR_ALERTNOAUTHOR'));
            }

            $view = $this->getView();
            $view->setLayout('text');
            $view->addTemplatePath(WF_EDITOR_PRO_LIBRARIES . '/views/editor/text/tmpl');

            // get parent display data
            parent::display();

            $theme = $this->getParam('editor.text_editor_theme', 'codemirror');

            $document->addScript(array(
                'js/editor/text.min',
            ), 'pro');

            $document->addScript(array(
                'vendor/beautify/beautify.min',
                'vendor/codemirror/js/script.min',
            ), 'pro');

            $document->addStyleSheet(array(
                'css/editor/text.min',
            ), 'pro');

            $document->addStyleSheet(array(
                'vendor/codemirror/css/style.min'
            ), 'pro');

            $settings = array(
                'highlight' => $this->getParam('editor.text_editor_highlight', 1),
                'linenumbers' => $this->getParam('editor.text_editor_numbers', 1),
                'wrap' => $this->getParam('editor.text_editor_wrap', 1),
                'format' => $this->getParam('editor.text_editor_format', 1),
                'tag_closing' => $this->getParam('editor.text_editor_tag_closing', 1),
                'site' => Uri::root(),
                'root' => Uri::root(true),
            );

            $font_size = $this->getParam('editor.text_editor_font_size', '', '');

            if ($font_size) {
                $font_size = preg_replace('/\D/', '', $font_size);
                $settings['font_size'] = (int) $font_size;
            }

            $theme = $this->getParam('editor.text_editor_theme', 'codemirror');

            // legacy themes that don't have an equivalent in CodeMirror 6
            $legacyThemes = array('custom', 'ambiance', 'blackboard', 'eclipse', 'lesser-dark', 'monokai', 'textmate');

            if (in_array($theme, $legacyThemes)) {
                $theme = 'codemirror';
            }

            $settings['theme'] = $theme;

            $document->addScriptDeclaration('jQuery(document).ready(function($){CodeEditor.init(' . json_encode($settings) . ')});');

            $document->setTitle(Text::_('WF_MANAGER_TEXT_EDITOR'));
        }
    }

    public function getPresetsList($type)
    {
        $list = array();

        switch ($type) {
            case 'resize':
                $list = $this->getParam('editor.resize_presets', '320x240,640x480,800x600,1024x768');

                if (is_string($list)) {
                    $list = explode(',', $list);
                }

                break;
            case 'crop':
                $list = $this->getParam('editor.crop_presets', '4:3,16:9,20:30,320x240,240x320,640x480,480x640,800x600,1024x768');

                if (is_string($list)) {
                    $list = explode(',', $list);
                }

                break;
        }

        return $list;
    }

    private function isFtp()
    {
        // Initialize variables

        $FTPOptions = ClientHelper::getCredentials('ftp');

        return $FTPOptions['enabled'] == 1;
    }

    private static function convertIniValue($value)
    {
        $suffix = '';

        preg_match('#([0-9]+)\s?([a-z]+)#i', $value, $matches);

        // get unit
        if (isset($matches[2])) {
            $suffix = $matches[2];
        }
        // get value
        if (isset($matches[1])) {
            $value = (int) $matches[1];
        }

        // Convert to bytes
        switch (strtolower($suffix)) {
            case 'g':
            case 'gb':
                $value *= 1073741824;
                break;
            case 'm':
            case 'mb':
                $value *= 1048576;
                break;
            case 'k':
            case 'kb':
                $value *= 1024;
                break;
        }

        return (int) $value;
    }

    private static function checkMem($image)
    {
        $channels = ($image['mime'] == 'image/png') ? 4 : 3;

        if (function_exists('memory_get_usage')) {
            // try ini_get
            $limit = ini_get('memory_limit');

            // try get_cfg_var
            if (empty($limit)) {
                $limit = get_cfg_var('memory_limit');
            }

            // no limit set...
            if ($limit === '-1') {
                return true;
            }

            // can't get from ini, assume low value of 32M
            if (empty($limit)) {
                $limit = 32 * 1048576;
            } else {
                $limit = self::convertIniValue($limit);
            }

            // get memory used so far
            $used = memory_get_usage(true);

            return $image[0] * $image[1] * $channels * 1.7 < $limit - $used;
        }

        return true;
    }

    /**
     * Get and temporarily store the exif data of an image
     *
     * @param [String] $file The aboslute path to the image
     * @param [String] $key The key to store the data under
     * @return void
     */
    protected function getExifData($file, $key = null)
    {
        // use file name as key
        if (empty($key)) {
            $key = $file;
        }

        if (array_key_exists($key, $this->exifCache)) {
            return $this->exifCache[$key];
        }

        $exif = null;

        if (!function_exists('exif_read_data') || !is_file($file)) {
            return $exif;
        }

        $exif = @exif_read_data($file);

        if ($exif && is_array($exif) && array_key_exists('EXIF', $exif)) {
            $this->exifCache[$key] = $exif;
        }

        return $exif;
    }

    protected function cleanExifString($string)
    {
        $string = (string) filter_var($string, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_BACKTICK);
        return htmlspecialchars(strip_tags($string));
    }

    protected function getImageDescription($image)
    {
        $description = '';

        // must be a jpeg
        if (!preg_match('#\.(jpg|jpeg)$#', strtolower($image))) {
            return $description;
        }

        $data = $this->getExifData($image, WFUtility::mb_basename($image));

        if (!empty($data) && isset($data['ImageDescription'])) {
            $description = $this->cleanExifString($data['ImageDescription']);
        }

        return $description;
    }

    public function onBeforeUpload(&$file, &$dir, &$name)
    {
        $app = Factory::getApplication();

        $mimetype = $app->input->get('mimetype', '', 'STRING');

        if ($mimetype) {
            $ext = basename($mimetype);

            // must be an image
            if ($ext && in_array(strtolower($ext), ['jpg', 'jpeg', 'png', 'apng', 'webp'])) {
                // remove extension
                $name = WFUtility::stripExtension($name);

                // rebuild file name - name + extension
                $name = $name . '.' . $ext;
            }
        }

        $extension = WFUtility::getExtension($file['name'], true);

        // check for and reset image orientation
        if ($extension == 'jpg' || $extension == 'jpeg') {

            // store exif data
            $exif = $this->getExifData($file['tmp_name'], $file['name']);

            $remove_exif = (bool) $this->getParam('editor.upload_remove_exif', false);

            // data exists and we are allowed to remove it
            if ($exif && $remove_exif) {
                if (false == $this->removeExifData($file['tmp_name'])) {
                    throw new InvalidArgumentException(Text::_('WF_MANAGER_UPLOAD_EXIF_REMOVE_ERROR'));
                }
            }
        }
    }

    /**
     * Manipulate file and folder list.
     *
     * @param  array file/folder array reference
     *
     * @since  1.5
     */
    public function processListItems(&$result)
    {
        $browser = $this->getFileBrowser();

        if (empty($result['files'])) {
            return;
        }

        // clean cache
        $filesystem = $browser->getFileSystem();

        for ($i = 0; $i < count($result['files']); ++$i) {
            $file = $result['files'][$i];

            if (empty($file['id'])) {
                continue;
            }

            // only some image types
            if (!preg_match('#\.(jpg|jpeg|png|webp)$#i', $file['id'])) {
                continue;
            }

            $thumbnail = $this->getThumbnail($file['id']);

            $classes = array();
            $properties = array();
            $trigger = array();

            // add thumbnail properties
            if ($thumbnail && $thumbnail != $file['id']) {
                $classes[] = 'thumbnail';
                $properties['thumbnail-src'] = WFUtility::makePath($filesystem->getRootDir(), $thumbnail, '/');

                $dim = @getimagesize(WFUtility::makePath($browser->getBaseDir(), $thumbnail));

                if ($dim) {
                    $properties['thumbnail-width'] = $dim[0];
                    $properties['thumbnail-height'] = $dim[1];
                }
                $trigger[] = 'thumb_delete';
            } else {
                $trigger[] = 'thumb_create';
            }

            // add trigger properties
            $properties['trigger'] = implode(',', $trigger);

            $image = $filesystem->toAbsolute($file['id']);
            $description = $this->getImageDescription($image);

            if ($description) {
                $properties['description'] = $description;
            }

            $result['files'][$i] = array_merge($file,
                array(
                    'classes' => implode(' ', array_merge(explode(' ', $file['classes']), $classes)),
                    'properties' => array_merge($file['properties'], $properties),
                )
            );
        }
    }

    protected function getImageLab($file)
    {
        static $instance = array();

        if (!isset($instance[$file])) {
            $browser = $this->getFileBrowser();
            $filesystem = $browser->getFileSystem();

            if (!$filesystem->is_file($file)) {
                return false;
            }

            // get the image as data
            $data = $filesystem->read($file);

            if (!$data) {
                return null;
            }

            try {
                $image = new WFImage(null, array(
                    'preferImagick' => (bool) $this->getParam('editor.prefer_imagick', true),
                    'removeExif' => (bool) $this->getParam('editor.upload_remove_exif', false),
                    'resampleImage' => (bool) $this->getParam('editor.resample_image', false),
                ));

                $image->loadString($data);

                // get extension
                $extension = WFUtility::getExtension($file);

                // set image type
                $image->setType($extension);

                // correct orientation
                $image->orientate();

                // create backup of original image resource
                $image->backup();

                // store instance
                $instance[$file] = $image;

            } catch (Exception $e) {
                $instance[$file] = null;
                $browser->setResult($e->getMessage(), 'error');
            }
        }

        return $instance[$file];
    }

    /**
     * Resize an image
     *
     * @param [String] $file A relative path of an image
     * @param [Array] $options An array of options for resizing
     * @param [Array] $cache A persistent cache to add the image to
     * @return void
     */
    protected function resizeImage($file, $options, &$cache)
    {
        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        // get imagelab instance
        $instance = $this->getImageLab($file);

        // no instance was created, perhaps due to memory error?
        if (!$instance) {
            return false;
        }

        // get width
        $width = $instance->getWidth();

        // get height
        $height = $instance->getHeight();

        // get extension
        $extension = WFUtility::getExtension($file);

        // get passed in options
        extract($options);

        $count = max(count($resize_width), count($resize_height));

        for ($i = 0; $i < $count; $i++) {
            // need at least one value
            if (!empty($resize_width[$i]) || !empty($resize_height[$i])) {

                // calculate width if not set
                if (empty($resize_width[$i])) {
                    $resize_width[$i] = round($resize_height[$i] / $height * $width, 0);
                }

                // calculate height if not set
                if (empty($resize_height[$i])) {
                    $resize_height[$i] = round($resize_width[$i] / $width * $height, 0);
                }

                // get scale based on aspect ratio
                $scale = ($width > $height) ? $resize_width[$i] / $width : $resize_height[$i] / $height;

                // don't allow scale up
                if ($scale > 1 && !$browser->get('upload_resize_enlarge')) {
                    continue;
                }

                $destination = '';

                // get file path
                $path = WFUtility::mb_dirname($file);

                // get file name
                $name = WFUtility::mb_basename($file);

                // remove file extension
                $name = WFUtility::stripExtension($name);

                // default crop
                if (!isset($resize_crop[$i])) {
                    $resize_crop[$i] = 0;
                }

                $suffix = '';

                // default suffix
                if (empty($resize_suffix[$i])) {
                    $resize_suffix[$i] = '';
                }

                // default quality
                if (empty($resize_quality[$i])) {
                    $resize_quality[$i] = 100;
                }

                // create suffix based on width/height values for images after first
                if (empty($resize_suffix[$i]) && $i > 0) {
                    $suffix = '_' . $resize_width[$i] . '_' . $resize_height[$i];
                } else {
                    // replace width and height variables
                    $suffix = str_replace(array('$width', '$height'), array($resize_width[$i], $resize_height[$i]), $resize_suffix[$i]);
                }

                $name .= $suffix . '.' . $extension;

                // validate name
                WFUtility::checkPath($name);

                // create new destination
                $destination = WFUtility::makePath($path, $name);

                // trim
                $destination = trim($destination, '/');

                if ($resize_crop[$i]) {
                    $instance->fit($resize_width[$i], $resize_height[$i]);
                } else {
                    $instance->resize($resize_width[$i], $resize_height[$i]);
                }

                $data = $instance->toString($extension, array('quality' => $resize_quality[$i]));

                // restore image lab instance
                $instance->restore();

                if ($data) {
                    // write to file
                    if ($filesystem->write($destination, $data)) {
                        $cache[$destination] = $data;
                    } else {
                        $browser->setResult(Text::_('WF_MANAGER_RESIZE_ERROR'), 'error');
                    }
                }
            }
        }

        return true;
    }

    /**
     * Resize an array of images
     *
     * @param [Array] $files An array of relative image paths
     * @return void
     */
    public function resizeImages($files)
    {
        $files = (array) $files;

        $app = Factory::getApplication();
        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        // default resize crop
        $resize_crop = $browser->get('upload_resize_crop');

        // get parameter values, allow empty but fallback to system default
        $resize_width = $browser->get('upload_resize_width');
        $resize_height = $browser->get('upload_resize_height');
        $resize_suffix = $browser->get('upload_resize_suffix');
        $resize_quality = $browser->get('upload_resize_quality');

        // both values cannot be empty
        if (empty($resize_width) && empty($resize_height)) {
            $resize_width = 640;
            $resize_height = 480;
        }

        if (!is_array($resize_crop)) {
            $resize_crop = explode(',', (string) $resize_crop);
        }

        $resize_crop = array_map('intval', $resize_crop);

        if (!is_array($resize_width)) {
            $resize_width = explode(',', (string) $resize_width);
        }

        if (!is_array($resize_height)) {
            $resize_height = explode(',', (string) $resize_height);
        }

        if (!is_array($resize_suffix)) {
            $resize_suffix = explode(',', (string) $resize_suffix);
        }

        if (!is_array($resize_quality)) {
            $resize_quality = explode(',', (string) $resize_quality);
        }

        $resize_quality = array_map('intval', $resize_quality);

        foreach (array('resize_width', 'resize_height', 'resize_crop') as $var) {
            $$var = $app->input->get($var, array(), 'array');
            // pass each value through intval
            $$var = array_map('intval', $$var);
        }

        $resize_suffix = $app->input->get('resize_suffix', array(), 'array');

        // clean suffix
        $resize_suffix = WFUtility::makeSafe($resize_suffix);

        $cache = array();

        foreach ($files as $file) {
            // check path
            WFUtility::checkPath($file);

            // create resize options array
            $options = compact(array('resize_width', 'resize_height', 'resize_crop', 'resize_suffix', 'resize_quality'));

            $this->resizeImage($file, $options, $cache);
        }

        return $browser->getResult();
    }

    protected function resizeUploadImage($file, &$cache)
    {
        $app = Factory::getApplication();

        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        // resize state
        $resize = (int) $browser->get('upload_resize_state');

        // resize crop
        $resize_crop = $browser->get('upload_resize_crop');

        // resize quality
        $resize_quality = $browser->get('upload_resize_quality');

        // resize suffix
        $resize_suffix = $browser->get('upload_resize_suffix');

        // get parameter values, allow empty but fallback to system default
        $resize_width = $browser->get('upload_resize_width');
        $resize_height = $browser->get('upload_resize_height');

        // both values cannot be empty
        if (empty($resize_width) && empty($resize_height)) {
            $resize_width = 640;
            $resize_height = 480;
        }

        if (!is_array($resize_width)) {
            $resize_width = explode(',', (string) $resize_width);
        }

        if (!is_array($resize_height)) {
            $resize_height = explode(',', (string) $resize_height);
        }

        if (!is_array($resize_crop)) {
            $resize_crop = explode(',', (string) $resize_crop);
        }

        // convert to integer
        $resize_crop = array_map('intval', $resize_crop);

        if (!is_array($resize_quality)) {
            $resize_quality = explode(',', (string) $resize_quality);
        }

        // convert to integer
        $resize_quality = array_map('intval', $resize_quality);

        if (!is_array($resize_suffix)) {
            $resize_suffix = explode(',', (string) $resize_suffix);
        }

        // dialog/form upload
        if ($app->input->getInt('inline', 0) === 0) {
            $file_resize = false;

            // Resize options visible
            if ((bool) $browser->get('upload_resize')) {
                $resize = $app->input->getInt('upload_resize_state', 0);

                // set empty default values
                $file_resize_width = array();
                $file_resize_height = array();
                $file_resize_crop = array();

                foreach (array('resize_width', 'resize_height', 'resize_crop', 'file_resize_width', 'file_resize_height', 'file_resize_crop') as $var) {
                    $$var = $app->input->get('upload_' . $var, array(), 'array');
                    // pass each value through intval
                    $$var = array_map('intval', $$var);
                }

                $resize_suffix = $app->input->get('upload_resize_suffix', array(), 'array');

                // clean suffix
                $resize_suffix = WFUtility::makeSafe($resize_suffix);

                // check for individual resize values
                foreach (array_merge($file_resize_width, $file_resize_height) as $item) {
                    // at least one value set, so resize
                    if (!empty($item)) {
                        $file_resize = true;

                        break;
                    }
                }

                // transfer values
                if ($file_resize) {
                    $resize_width = $file_resize_width;
                    $resize_height = $file_resize_height;
                    $resize_crop = $file_resize_crop;

                    // get file resize suffix
                    $file_resize_suffix = $app->input->get('upload_file_resize_suffix', array(), 'array');

                    // clean suffix
                    $file_resize_suffix = WFUtility::makeSafe($file_resize_suffix);

                    // transfer values
                    $resize_suffix = $file_resize_suffix;

                    // set global resize option
                    $resize = true;
                }
            }
        }

        // no resizing, return empty array
        if (!$resize) {
            return false;
        }

        // create resize options array
        $options = compact(array('resize_width', 'resize_height', 'resize_crop', 'resize_suffix', 'resize_quality'));

        $this->resizeImage($file, $options, $cache);

        return true;
    }

    protected function watermarkImage($file, &$cache)
    {
        $app = Factory::getApplication();
        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        // get imagelab instance
        $instance = $this->getImageLab($file);

        // no instance was created, perhaps due to memory error?
        if (!$instance) {
            return false;
        }

        // get extension
        $extension = WFUtility::getExtension($file);

        // map of options and default values
        $vars = array(
            'type' => 'text',
            'text' => '',
            'font_style' => 'LiberationSans-Regular.ttf',
            'font_size' => '32',
            'font_color' => '#FFFFFF',
            'opacity' => 50,
            'position' => 'center',
            'margin' => 10,
            'angle' => 0,
            'image' => '',
        );

        // process options with passed in values or parameters
        foreach ($vars as $key => $value) {
            $value = $app->input->get('watermark_' . $key, $this->getParam('editor.watermark_' . $key, $value));

            if ($key == 'font_style') {
                // default LiberationSans fonts
                if (preg_match('#^LiberationSans-(Regular|Bold|BoldItalic|Italic)\.ttf$#', $value)) {
                    $value = WFUtility::makePath(WF_EDITOR_PRO_MEDIA, '/fonts/' . $value);
                    // custom font
                } else {
                    $value = WFUtility::makePath(JPATH_SITE, $value);
                }
            }

            if ($key == 'image') {
                if (strpos($value, '://') !== false) {
                    $value = '';
                } else {
                    $value = WFUtility::makePath(JPATH_SITE, $value);
                }
            }

            $options[$key] = $value;
        }

        // should image quality be set?
        $quality = (int) $this->getParam('editor.upload_quality', 100);

        // watermark
        foreach ($cache as $destination => $data) {
            // load processed data if available
            if ($data) {
                $instance->loadString($data);
            }

            $instance->watermark($options);

            $data = $instance->toString($extension, array('quality' => $quality));

            // valid data string
            if ($data) {
                // write to file and update cache
                if ($filesystem->write($destination, $data)) {
                    $cache[$destination] = $data;
                } else {
                    $browser->setResult(Text::_('WF_MANAGER_WATERMARK_ERROR'), 'error');
                }
            }

            // restore backup resource
            $instance->restore();
        }

        return true;
    }

    public function watermarkImages($files)
    {
        $files = (array) $files;

        $cache = array();

        foreach ($files as $file) {
            $cache[$file] = '';

            $this->watermarkImage($file, $cache);
        }

        return true;
    }

    protected function watermarkUploadImage($file, &$cache)
    {
        $app = Factory::getApplication();

        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        // watermark state
        $watermark = (int) $browser->get('upload_watermark_state');

        if ($app->input->getInt('inline', 0) == 0) {
            // option visible so allow user set value
            if ((bool) $browser->get('upload_watermark')) {
                $watermark = $app->input->getInt('upload_watermark_state', 0);
            }
        }

        // no watermark, return false
        if (!$watermark) {
            return false;
        }

        // if the files array is empty, no resizing was done, create a new one for further processing
        if (empty($cache)) {
            $cache = array(
                $file => '',
            );
        }

        $this->watermarkImage($file, $cache);

        return true;
    }

    protected function thumbnailUploadImage($file, &$cache)
    {
        $app = Factory::getApplication();

        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        // get extension
        $extension = WFUtility::getExtension($file);

        $thumbnail = (int) $browser->get('upload_thumbnail_state');

        // get parameter values, allow empty but fallback to system default
        $tw = $browser->get('upload_thumbnail_width');
        $th = $browser->get('upload_thumbnail_height');

        // both values cannot be empty
        if (empty($tw) && empty($th)) {
            $tw = 120;
            $th = 90;
        }

        $crop = $browser->get('upload_thumbnail_crop');

        if ($app->input->getInt('inline', 0) == 0) {
            // Thumbnail options visible
            if ((bool) $browser->get('upload_thumbnail')) {
                $thumbnail = $app->input->getInt('upload_thumbnail_state', 0);

                $tw = $app->input->getInt('upload_thumbnail_width');
                $th = $app->input->getInt('upload_thumbnail_height');

                // Crop Thumbnail
                $crop = $app->input->getInt('upload_thumbnail_crop', 0);
            }
        }

        // not activated
        if (!$thumbnail) {
            return false;
        }

        $tq = $browser->get('upload_thumbnail_quality');

        // cast values to integer
        $tw = (int) $tw;
        $th = (int) $th;

        // need at least one value
        if ($tw || $th) {

            // get imagelab instance
            $instance = $this->getImageLab($file);

            // no instance was created, perhaps due to memory error?
            if (!$instance) {
                $browser->setResult(Text::_('WF_IMGMANAGER_EXT_THUMBNAIL_ERROR'), 'error');
                return false;
            }

            // if the files array is empty, no other processing was done, create a new one for further processing
            if (empty($cache)) {
                $cache = array(
                    $file => '',
                );
            }

            foreach ($cache as $destination => $data) {
                // if image data is available, load it
                if ($data) {
                    $instance->loadString($data);
                }

                $thumb = WFUtility::makePath($this->getThumbDir($destination, true), $this->getThumbName($destination));

                $w = $instance->getWidth();
                $h = $instance->getHeight();

                // calculate width if not set
                if (!$tw) {
                    $tw = round($th / $h * $w, 0);
                }

                // calculate height if not set
                if (!$th) {
                    $th = round($tw / $w * $h, 0);
                }

                if ($crop) {
                    $instance->fit($tw, $th);
                } else {
                    $instance->resize($tw, $th);
                }

                $data = $instance->toString($extension, array('quality' => $tq));

                if ($data) {
                    // write to file
                    if (!$filesystem->write($thumb, $data)) {
                        $browser->setResult(Text::_('WF_IMGMANAGER_EXT_THUMBNAIL_ERROR'), 'error');
                    }
                }

                // restore backup resource
                $instance->restore();
            }
        }

        return true;
    }

    /**
     * Special function to determine whether an image can be resampled, as this required Imagick support
     *
     * @return boolean
     */
    protected function canResampleImage()
    {
        $resample = (bool) $this->getParam('editor.resample_image', false);
        $imagick = (bool) $this->getParam('editor.prefer_imagick', true);

        return $resample && $imagick && extension_loaded('imagick');
    }

    public function onUpload($file, $relative = '')
    {
        $app = Factory::getApplication();

        // get file extension
        $ext = WFUtility::getExtension($file, true);

        // must be an image
        if (!in_array(strtolower($ext), ['jpg', 'jpeg', 'png', 'apng', 'webp'])) {
            return array();
        }

        // get filesystem reference
        $filesystem = $this->getFileBrowser()->getFileSystem();

        // make file path relative
        $file = $filesystem->toRelative($file);

        // a cache of processed files. This includes the original file, and any others created by resizing
        $cache = array();

        // process image resize
        $this->resizeUploadImage($file, $cache);

        // process thumbnails
        $this->thumbnailUploadImage($file, $cache);

        // process image watermark
        $this->watermarkUploadImage($file, $cache);

        // should image quality be set?
        $upload_quality = (int) $this->getParam('editor.upload_quality', 100);

        // get from upload value
        $upload_quality = $app->input->getInt('quality', $upload_quality);

        // should the image be resampled?
        $upload_resample = $this->canResampleImage();

        if (!isset($cache[$file])) {
            // are we resampling or setting upload quality?
            if ($upload_resample || $upload_quality < 100) {
                // get filesystem reference
                $filesystem = $this->getFileBrowser()->getFileSystem();

                // get imagelab instance
                $instance = $this->getImageLab($file);

                if ($instance) {
                    $cache = array(
                        $file => '',
                    );

                    foreach ($cache as $destination => $data) {
                        if ($data) {
                            $instance->loadString($data);
                        }

                        $options = array();

                        if ($upload_quality < 100) {
                            $options['quality'] = $upload_quality;
                        }

                        $data = $instance->toString($ext, $options);

                        if ($data) {
                            $filesystem->write($destination, $data);
                        }
                    }

                    $instance->destroy();
                }

            }
        } else {
            $instance = $this->getImageLab($file);

            if ($instance) {
                $instance->destroy();
            }
        }

        return array();
    }

    private function toRelative($file)
    {
        return WFUtility::makePath(str_replace(JPATH_ROOT . '/', '', WFUtility::mb_dirname(Path::clean($file))), WFUtility::mb_basename($file));
    }

    private function cleanTempDirectory()
    {
        $files = Folder::files($this->getCacheDirectory(), '^(wf_ie_)([a-z0-9]+)\.(jpg|jpeg|gif|png|webp)$');

        if (!empty($files)) {
            $time = strtotime('24 hours ago');
            clearstatcache();
            foreach ($files as $file) {
                // delete files older than 24 hours
                if (@filemtime($file) >= $time) {
                    @File::delete($file);
                }
            }
        }
    }

    public function cleanEditorTmp($file = null, $exit = true)
    {
        // Check for request forgeries
        Session::checkToken('request') or jexit(Text::_('JINVALID_TOKEN'));

        // check for image editor access
        if ($this->checkAccess('image_editor', 1) === false) {
            throw new Exception(Text::_('JERROR_ALERTNOAUTHOR'));
        }

        if ($file) {
            $ext = WFUtility::getExtension($file, true);

            // create temp file
            $tmp = 'wf_ie_' . md5($file) . '.' . $ext;
            $path = WFUtility::makePath($this->getCacheDirectory(), $tmp);

            self::validateFilePath($file);

            $result = false;

            if (is_file($path)) {
                $result = @File::delete($path);
            }

            if ($exit) {
                return (bool) $result;
            }
        } else {
            $this->cleanTempDirectory();
        }

        return true;
    }

    /**
     * Apply an image edit to a file and return a url to a temp version of that file
     *
     * @param [string] $file The name of the file being edited
     * @param [string] $task The edit type to apply, eg: resize
     * @param [object] $value The edit value to apply
     * @return WFFileSystemResult
     */
    public function applyImageEdit($file, $task, $value)
    {
        // Check for request forgeries
        Session::checkToken('request') or jexit(Text::_('JINVALID_TOKEN'));

        // check for image editor access
        if ($this->checkAccess('image_editor', 1) === false) {
            throw new Exception(Text::_('JERROR_ALERTNOAUTHOR'));
        }

        $app = Factory::getApplication();

        $browser = $this->getFileBrowser();

        // check file
        self::validateFilePath($file);

        $upload = $app->input->files->get('file', array(), 'array');

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

        if (isset($upload) && isset($upload['tmp_name']) && is_uploaded_file($upload['tmp_name'])) {
            self::validateFile($upload);

            $ext = WFUtility::getExtension($file, true);

            // create temp file
            $tmp = 'wf_ie_' . md5($file) . '.' . $ext;
            $tmp = WFUtility::makePath($this->getCacheDirectory(), $tmp);

            // delete existing tmp file
            if (is_file($tmp)) {
                @File::delete($tmp);
            }

            $image = new WFImage(null, array(
                'preferImagick' => (bool) $this->getParam('editor.prefer_imagick', true),
            ));

            $image->loadFile($upload['tmp_name']);
            $image->setType($ext);

            switch ($task) {
                case 'resize':
                    $image->resize($value->width, $value->height);
                    break;
                case 'crop':
                    $image->crop($value->width, $value->height, $value->x, $value->y, false, 1);
                    break;
            }

            // get image data
            $data = $image->toString($ext);

            // write to file
            if ($data) {
                $result->state = (bool) @File::write($tmp, $data);
            }

            if ($result->state === true) {
                $tmp = str_replace(WFUtility::cleanPath(JPATH_SITE), '', $tmp);
                $browser->setResult(WFUtility::cleanPath($tmp, '/'), 'files');
            } else {
                $browser->setResult(Text::_('WF_IMAGE_EDIT_APPLY_ERROR'), 'error');
            }

            @unlink($upload['tmp_name']);

            return $browser->getResult();
        }
    }

    public function saveImageEdit($file, $name, $options = array(), $quality = 100)
    {
        // Check for request forgeries
        Session::checkToken('request') or jexit(Text::_('JINVALID_TOKEN'));

        // check for image editor access
        if ($this->checkAccess('image_editor', 1) === false) {
            throw new Exception(Text::_('JERROR_ALERTNOAUTHOR'));
        }

        $app = Factory::getApplication();

        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        // check file
        self::validateFilePath($file);

        // clean temp
        $this->cleanEditorTmp($file, false);

        // check new name
        self::validateFilePath($name);

        $upload = $app->input->files->get('file', '', 'files', 'array');

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

        if (isset($upload) && isset($upload['tmp_name']) && is_uploaded_file($upload['tmp_name'])) {
            $tmp = $upload['tmp_name'];

            self::validateFile($upload);
            $result = $filesystem->upload('multipart', trim($tmp), WFUtility::mb_dirname($file), $name);

            @unlink($tmp);
        } else {
            // set upload as false - JSON request
            $upload = false;

            $file = WFUtility::makePath($filesystem->getBaseDir(), $file);
            $dest = WFUtility::mb_dirname($file) . '/' . WFUtility::mb_basename($name);

            // get extension
            $ext = WFUtility::getExtension($dest);

            // create image
            $image = $this->getImageLab($file);

            foreach ($options as $filter) {
                if (isset($filter->task)) {
                    $args = isset($filter->args) ? (array) $filter->args : array();

                    switch ($filter->task) {
                        case 'resize':
                            $w = $args[0];
                            $h = $args[1];

                            $image->resize($w, $h);
                            break;
                        case 'crop':
                            $w = $args[0];
                            $h = $args[1];

                            $x = $args[2];
                            $y = $args[3];

                            $image->crop($w, $h, $x, $y);
                            break;
                        case 'rotate':
                            $image->rotate(array_shift($args));
                            break;
                        case 'flip':
                            $image->flip(array_shift($args));
                            break;
                    }
                }
            }

            // get image data
            $data = $image->toString($ext);

            // make path relative
            $dest = $filesystem->toRelative($dest);

            // write to file
            if ($data) {
                $result->state = (bool) $filesystem->write($dest, $data);
            }

            // set path
            $result->path = $dest;
        }

        if ($result->state === true) {
            // check if its a valid image
            if (@getimagesize($result->path) === false) {
                File::delete($result->path);
                throw new InvalidArgumentException('Invalid image file');
            } else {
                $result->path = str_replace(WFUtility::cleanPath(JPATH_SITE), '', $result->path);
                $browser->setResult(WFUtility::cleanPath($result->path, '/'), 'files');
            }
        } else {
            $browser->setResult($result->message || Text::_('WF_MANAGER_EDIT_SAVE_ERROR'), 'error');
        }

        // return to WFRequest
        return $browser->getResult();
    }

    public function saveTextFile($file, $name)
    {
        // Check for request forgeries
        Session::checkToken('request') or jexit(Text::_('JINVALID_TOKEN'));

        // check for text editor access
        if ($this->checkAccess('text_editor', 0) === false) {
            throw new Exception(Text::_('JERROR_ALERTNOAUTHOR'));
        }

        // check file
        self::validateFilePath($file);

        // check new name
        self::validateFilePath($name);

        $allowedFiles = array('txt', 'html', 'htm', 'xml', 'md', 'csv', 'json', 'css', 'less', 'scss', 'js', 'ts');

        $nameExt = WFUtility::getExtension($name, true);

        // check for allowed file types
        if (!in_array($nameExt, $allowedFiles)) {
            throw new Exception(Text::_('WF_MANAGER_EDIT_TEXT_SAVE_INVALID'));
        }

        $fileExt = WFUtility::getExtension($file, true);

        // get permitted file types
        $types = $this->getFileTypes();

        // validate extension against file types
        if (!in_array($fileExt, $types)) {
            throw new Exception(Text::_('WF_MANAGER_EDIT_TEXT_SAVE_INVALID'));
        }

        $app = Factory::getApplication();

        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

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

        $file = WFUtility::makePath($filesystem->getBaseDir(), $file);
        $dest = WFUtility::mb_dirname($file) . '/' . WFUtility::mb_basename($name);

        // make path relative
        $dest = $filesystem->toRelative($dest);

        $data = $app->input->post->get('data', '', 'RAW');
        $data = rawurldecode($data);

        // write to file
        if ($data) {
            $result->state = (bool) $filesystem->write($dest, $data);
        }

        // set path
        $result->path = $dest;

        if ($result->state === true) {
            $result->path = str_replace(WFUtility::cleanPath(JPATH_SITE), '', $result->path);
            $browser->setResult(WFUtility::cleanPath($result->path, '/'), 'files');
        } else {
            $browser->setResult($result->message || Text::_('WF_MANAGER_EDIT_TEXT_SAVE_ERROR'), 'error');
        }

        // return to WFRequest
        return $browser->getResult();
    }

    private function getCacheDirectory()
    {
        $app = Factory::getApplication();

        $cache = $app->getCfg('tmp_path');
        $dir = $this->getParam('editor.cache', $cache);

        // make sure a value is set
        if (empty($dir)) {
            $dir = 'tmp';
        }

        // check for and create absolute path
        if (strpos($dir, JPATH_SITE) === false) {
            $dir = WFUtility::makePath(JPATH_SITE, Path::clean($dir));
        }

        if (!is_dir($dir)) {
            if (@Folder::create($dir)) {
                return $dir;
            }
        }

        return $dir;
    }

    private function cleanCacheDir()
    {

        $cache_max_size = intval($this->getParam('editor.cache_size', 10, 0)) * 1024 * 1024;
        $cache_max_age = intval($this->getParam('editor.cache_age', 30, 0)) * 86400;
        $cache_max_files = intval($this->getParam('editor.cache_files', 0, 0));

        if ($cache_max_age > 0 || $cache_max_size > 0 || $cache_max_files > 0) {
            $path = $this->getCacheDirectory();
            $files = Folder::files($path, '^(wf_thumb_cache_)([a-z0-9]+)\.(jpg|jpeg|gif|png|webp)$');
            $num = count($files);
            $size = 0;
            $cutofftime = time() - 3600;

            if ($num) {
                foreach ($files as $file) {
                    $file = WFUtility::makePath($path, $file);
                    if (is_file($file)) {
                        $ftime = @fileatime($file);
                        $fsize = @filesize($file);
                        if ($fsize == 0 && $ftime < $cutofftime) {
                            @File::delete($file);
                        }
                        if ($cache_max_files > 0) {
                            if ($num > $cache_max_files) {
                                @File::delete($file);
                                --$num;
                            }
                        }
                        if ($cache_max_age > 0) {
                            if ($ftime < (time() - $cache_max_age)) {
                                @File::delete($file);
                            }
                        }
                        if ($cache_max_files > 0) {
                            if (($size + $fsize) > $cache_max_size) {
                                @File::delete($file);
                            }
                        }
                    }
                }
            }
        }

        return true;
    }

    private function redirectThumb($file, $mime)
    {
        if (is_file($file)) {
            header('Content-length: ' . filesize($file));
            header('Content-type: ' . $mime);
            header('Location: ' . $this->toRelative($file));
        }
    }

    private function outputImage($file, $mime)
    {
        if (is_file($file)) {
            header('Content-length: ' . filesize($file));
            header('Content-type: ' . $mime);
            ob_clean();
            flush();

            @readfile($file);
        }

        exit();
    }

    private function getCacheThumbPath($file, $width, $height)
    {

        $mtime = @filemtime($file);
        $thumb = 'wf_thumb_cache_' . md5(WFUtility::mb_basename(WFUtility::stripExtension($file)) . $mtime . $width . $height) . '.' . WFUtility::getExtension($file, true);

        return WFUtility::makePath($this->getCacheDirectory(), $thumb);
    }

    private function createCacheThumb($file)
    {

        $browser = $this->getFileBrowser();

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

        $extension = WFUtility::getExtension($file, true);

        // not an image
        if (!in_array($extension, array('jpeg', 'jpeg', 'png', 'tiff', 'gif', 'webp'))) {
            exit();
        }

        $file = WFUtility::makePath($browser->getBaseDir(), $file);

        // default for list thumbnails
        $width = 100;
        $height = 100;
        $quality = 75;

        $info = @getimagesize($file);

        // not a valid image?
        if (!$info) {
            exit();
        }

        list($w, $h, $type, $text, $mime) = $info;

        // smaller than thumbnail so output file instead
        if (($w < $width && $h < $height)) {
            return $this->outputImage($file, $mime);
        }

        $exif_types = array('jpg', 'jpeg', 'tiff');

        // try exif thumbnail
        if (in_array($extension, $exif_types)) {
            $exif = exif_thumbnail($file, $width, $height, $mime);

            if ($exif !== false) {
                header('Content-type: ' . $mime);
                die($exif);
            }
        }

        $thumb = $this->getCacheThumbPath($file, $width, $height);

        if (is_file($thumb)) {
            return $this->outputImage($thumb, $mime);
        }

        // create thumbnail file
        $image = new WFImage($file, array(
            'preferImagick' => (bool) $this->getParam('editor.prefer_imagick', true),
        ));

        $image->fit($width, $height);

        if ($image->toFile($thumb, $extension, array('quality' => $quality))) {
            if (is_file($thumb)) {
                return $this->outputImage($thumb, $mime);
            }
        }

        // exit with no data
        exit();
    }

    public function getThumbnails($files)
    {
        $browser = $this->getFileBrowser();

        $thumbnails = array();
        foreach ($files as $file) {
            $thumbnails[$file['name']] = $this->getCacheThumb(WFUtility::makePath($browser->getBaseDir(), $file['url']), true, 50, 50, WFUtility::getExtension($file['name']), 50);
        }

        return $thumbnails;
    }

    protected static function validateFile($file)
    {
        return WFUtility::isSafeFile($file);
    }

    /**
     * Validate an image path and extension.
     *
     * @param type $path Image path
     *
     * @throws InvalidArgumentException
     */
    protected static function validateFilePath($path)
    {
        // nothing to validate
        if (empty($path)) {
            return false;
        }

        // clean path
        $path = WFUtility::cleanPath($path);

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

        // check file name and contents
        if (WFUtility::validateFileName($path) === false) {
            throw new InvalidArgumentException('Action failed: Invalid file name');
        }
    }

    /**
     * Get an image's thumbnail file name.
     *
     * @param string $file the full path to the image file
     *
     * @return string of the thumbnail file
     */
    protected function getThumbName($file)
    {
        $prefix = $this->getParam('thumbnail_prefix', 'thumb_$');

        $ext = WFUtility::getExtension($file);

        if (strpos($prefix, '$') !== false) {
            return str_replace('$', WFUtility::mb_basename($file, '.' . $ext), $prefix) . '.' . $ext;
        }

        return (string) $prefix . WFUtility::mb_basename($file);
    }

    protected function getThumbDir($file, $create)
    {
        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        // get base directory from editor parameter
        $baseDir = $this->getParam('editor.thumbnail_folder', '', 'thumbnails');

        // get directory from plugin parameter, if any (Image Manager Extended)
        $folder = $this->getParam($this->getName() . '.thumbnail_folder', '', '$$');

        // ugly workaround for parameter issues - a $ or $$ value denotes un unset value, so fallback to global
        // a user can "unset" the value, if it has been stored as an empty string, by setting the value to $
        if ($folder === "$" || $folder === "$$") {
            $folder = $baseDir;
        }

        // make path relative to source file
        $dir = WFUtility::makePath(WFUtility::mb_dirname($file), $folder);

        // create the folder if it does not exist
        if ($create && !$filesystem->exists($dir)) {
            $filesystem->createFolder(WFUtility::mb_dirname($dir), WFUtility::mb_basename($dir));
        }

        return $dir;
    }

    /**
     * Create a thumbnail.
     *
     * @param string $file    relative path of the image
     * @param string $width   thumbnail width
     * @param string $height  thumbnail height
     * @param string $quality thumbnail quality (%)
     * @param string $mode    thumbnail mode
     */
    public function createThumbnail($file, $width = null, $height = null, $quality = 100, $box = null)
    {
        // check path
        self::validateFilePath($file);

        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        $thumb = WFUtility::makePath($this->getThumbDir($file, true), $this->getThumbName($file));

        $extension = WFUtility::getExtension($file);

        $instance = $this->getImageLab($file);

        if ($instance) {
            if ($box) {
                $box = (array) $box;
                $instance->crop($box['sw'], $box['sh'], $box['sx'], $box['sy']);
            }

            $instance->resize($width, $height);

            $data = $instance->toString($extension, array('quality' => $quality));

            if ($data) {
                // write to file
                if (!$filesystem->write($thumb, $data)) {
                    $browser->setResult(Text::_('WF_IMGMANAGER_EXT_THUMBNAIL_ERROR'), 'error');
                }
            }
        }

        return $browser->getResult();
    }

    /**
     * Creates thumbnails for an array of files.
     *
     * @param array $files  relative path of the image
     */
    public function createThumbnails($files)
    {
        $files = (array) $files;

        $app = Factory::getApplication();
        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        $tw = $app->input->getInt('thumbnail_width');
        $th = $app->input->getInt('thumbnail_height');

        // Crop Thumbnail
        $crop = $app->input->getInt('thumbnail_crop', 0);

        $tq = $browser->get('upload_thumbnail_quality');

        // need at least one value
        if ($tw || $th) {

            foreach ($files as $file) {

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

                // get extension
                $extension = WFUtility::getExtension($file);

                // get imagelab instance
                $instance = $this->getImageLab($file);

                // no instance was created, perhaps due to memory error?
                if (!$instance) {
                    $browser->setResult(Text::_('WF_IMGMANAGER_EXT_THUMBNAIL_ERROR'), 'error');
                    return false;
                }

                $thumb = WFUtility::makePath($this->getThumbDir($file, true), $this->getThumbName($file));

                $w = $instance->getWidth();
                $h = $instance->getHeight();

                // calculate width if not set
                if (!$tw) {
                    $tw = round($th / $h * $w, 0);
                }

                // calculate height if not set
                if (!$th) {
                    $th = round($tw / $w * $h, 0);
                }

                if ($crop) {
                    $instance->fit($tw, $th);
                } else {
                    $instance->resize($tw, $th);
                }

                $data = $instance->toString($extension, array('quality' => $tq));

                if ($data) {
                    // write to file
                    if (!$filesystem->write($thumb, $data)) {
                        $browser->setResult(Text::_('WF_IMGMANAGER_EXT_THUMBNAIL_ERROR'), 'error');
                    }
                }
            }
        }

        return $browser->getResult();
    }

    /**
     * Remove exif data from an image by rewriting it. This will also rotate images to correct orientation.
     *
     * @param $file Absolute path to the image file
     *
     * @return bool
     */
    private function removeExifData($file)
    {
        $exif = null;

        // check if exif_read_data disabled...
        if (function_exists('exif_read_data')) {

            // get exif data
            $exif = @exif_read_data($file, 'EXIF');
            $rotate = 0;

            if ($exif && !empty($exif['Orientation'])) {
                $orientation = (int) $exif['Orientation'];

                // Fix Orientation
                switch ($orientation) {
                    case 3:
                        $rotate = 180;
                        break;
                    case 6:
                        $rotate = 90;
                        break;
                    case 8:
                        $rotate = 270;
                        break;
                }
            }
        }

        if (extension_loaded('imagick')) {
            try {
                $img = new Imagick($file);

                if ($rotate) {
                    $img->rotateImage(new ImagickPixel(), $rotate);
                }

                $img->stripImage();

                $img->writeImage($file);
                $img->clear();
                $img->destroy();

                return true;
            } catch (Exception $e) {
            }
        } elseif (extension_loaded('gd')) {
            try {

                $handle = imagecreatefromjpeg($file);

                // extended resource check
                if (!((is_object($handle) && get_class($handle) == 'GdImage') || (is_resource($handle) && get_resource_type($handle) == 'gd'))) {
                    return false;
                }

                if ($rotate) {
                    $rotation = imagerotate($handle, -$rotate, 0);

                    if ($rotation) {
                        $handle = $rotation;
                    }
                }

                imagejpeg($handle, $file);
                @imagedestroy($handle);

                return true;

            } catch (Exception $e) {
            }
        }

        return false;
    }

    protected function getFileBrowserConfig($config = array())
    {
        $resize_width = $this->getParam('editor.resize_width', '', 640);

        if (!is_array($resize_width)) {
            $resize_width = explode(',', (string) $resize_width);
        }

        $resize_height = $this->getParam('editor.resize_height', '', 480);

        if (!is_array($resize_height)) {
            $resize_height = explode(',', (string) $resize_height);
        }

        $resize_label = $this->getParam('editor.resize_label', '');

        if (!is_array($resize_label)) {
            $resize_label = explode(',', (string) $resize_label);
        }

        $resize_suffix = $this->getParam('editor.resize_suffix', '');

        if (!is_array($resize_suffix)) {
            $resize_suffix = explode(',', (string) $resize_suffix);
        }

        $resize_crop = $this->getParam('editor.upload_resize_crop', 0);

        if (!is_array($resize_crop)) {
            $resize_crop = explode(',', (string) $resize_crop);
        }

        $resize_quality = $this->getParam('editor.resize_quality', 100);

        if (!is_array($resize_quality)) {
            $resize_quality = explode(',', (string) $resize_quality);
        }

        $data = array(
            'view_mode' => $this->getParam('editor.mode', 'list'),
            'can_edit_images' => $this->get('can_edit_images'),
            'cache_enable' => $this->getParam('editor.cache_enable', 0),
            // Upload
            'upload_resize' => $this->getParam('editor.upload_resize', 1),
            'upload_resize_state' => $this->getParam('editor.upload_resize_state', 0),
            // value must be cast as string for javascript processing
            'upload_resize_label' => $resize_label,
            // value must be cast as string for javascript processing
            'upload_resize_width' => $resize_width,
            // value must be cast as string for javascript processing
            'upload_resize_height' => $resize_height,
            // value must be cast as string for javascript processing
            'upload_resize_suffix' => $resize_suffix,
            'upload_resize_crop' => $resize_crop,
            'upload_resize_quality' => $resize_quality,
            'upload_resize_enlarge' => $this->getParam('editor.upload_resize_enlarge', 0),
            // watermark
            'upload_watermark' => $this->getParam('editor.upload_watermark', 0),
            'upload_watermark_state' => $this->getParam('editor.upload_watermark_state', 0),
            // thumbnail
            'upload_thumbnail' => $this->getParam('editor.upload_thumbnail', 1),
            'upload_thumbnail_state' => $this->getParam('editor.upload_thumbnail_state', 0),
            'upload_thumbnail_crop' => $this->getParam('editor.upload_thumbnail_crop', 0),
            // value must be cast as string for javascript processing
            'upload_thumbnail_width' => (string) $this->getParam('editor.upload_thumbnail_width', '', 120),
            // value must be cast as string for javascript processing
            'upload_thumbnail_height' => (string) $this->getParam('editor.upload_thumbnail_height', '', 90),
            'upload_thumbnail_quality' => $this->getParam('editor.upload_thumbnail_quality', 80),
        );

        $config = WFUtility::array_merge_recursive_distinct($data, $config);

        return parent::getFileBrowserConfig($config);
    }

    /**
     * Check for the thumbnail for a given file.
     *
     * @param string $relative The relative path of the file
     *
     * @return The thumbnail URL or false if none
     */
    private function getThumbnail($relative)
    {
        // get browser
        $browser = $this->getFileBrowser();
        $filesystem = $browser->getFileSystem();

        $path = WFUtility::makePath($browser->getBaseDir(), $relative);
        $dim = @getimagesize($path);

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

        /*$thumbfolder = $this->getParam('thumbnail_folder', '', 'thumbnails');

        $dir = WFUtility::makePath(str_replace('\\', '/', dirname($relative)), $thumbfolder);
        $thumbnail = WFUtility::makePath($dir, $this->getThumbName($relative));*/

        $thumbnail = $this->getThumbPath($relative);

        // Image is a thumbnail
        if ($relative === $thumbnail) {
            return $relative;
        }

        // The original image is smaller than a thumbnail so just return the url to the original image.
        if ($dim[0] <= $this->getParam('thumbnail_size', 120) && $dim[1] <= $this->getParam('thumbnail_size', 90)) {
            return $relative;
        }

        //check for thumbnails, if exists return the thumbnail url
        if (file_exists(WFUtility::makePath($browser->getBaseDir(), $thumbnail))) {
            return $thumbnail;
        }

        return false;
    }

    private function getThumbPath($file)
    {
        return WFUtility::makePath($this->getThumbDir($file, false), $this->getThumbName($file));
    }

    public function onFilesDelete($file)
    {
        $browser = $this->getFileBrowser();

        if (file_exists(WFUtility::makePath($browser->getBaseDir(), $this->getThumbPath($file)))) {
            $this->deleteThumbnail($file);
        }

        return array();
    }

    public function getThumbnailDimensions($file)
    {
        return $this->getDimensions($this->getThumbPath($file));
    }

    public function deleteThumbnail($files)
    {
        if (!$this->checkAccess('thumbnail_editor', 1)) {
            throw new Exception(Text::_('JERROR_ALERTNOAUTHOR'));
        }

        $files = (array) $files;

        for ($i = 0; $i < count($files); $i++) {
            $file = $files[$i];

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

            $browser = $this->getFileBrowser();
            $filesystem = $browser->getFileSystem();
            $dir = $this->getThumbDir($file, false);

            $thumb = $this->getThumbPath($file);

            if ($browser->deleteItem($thumb)) {
                if ($i == count($files) - 1) {
                    if ($filesystem->countFiles($dir) == 0 && $filesystem->countFolders($dir) == 0) {
                        if (!$browser->deleteItem($dir)) {
                            $browser->setResult(Text::_('WF_IMGMANAGER_EXT_THUMBNAIL_FOLDER_DELETE_ERROR'), 'error');
                        }
                    }
                }
            }
        }

        return $browser->getResult();
    }
}