<?php
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with TYPO3 source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace Causal\ImageAutoresize\Service;

use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\Index\Indexer;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Resource\ResourceStorage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use Causal\ImageAutoresize\Utility\ImageUtility;

/**
 * This is a utility class to resize pictures based on rules.
 *
 * @category    Service
 * @package     TYPO3
 * @subpackage  tx_imageautoresize
 * @author      Xavier Perseguers <xavier@causal.ch>
 * @license     http://www.gnu.org/copyleft/gpl.html
 */
class ImageResizer
{

    /**
     * @var array
     */
    protected $rulesets = [];

    /**
     * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
     */
    protected $signalSlotDispatcher;

    /**
     * @var array|null
     */
    protected $lastMetadata = null;

    /**
     * Default constructor
     */
    public function __construct()
    {
        $this->signalSlotDispatcher = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
    }

    /**
     * Initializes the hook configuration as a meaningful ordered list
     * of rule sets.
     *
     * @param array $configuration
     * @return void
     */
    public function initializeRulesets(array $configuration)
    {
        $general = $configuration;
        $general['usergroup'] = '';
        unset($general['rulesets']);
        $general = $this->expandValuesInRuleset($general);
        if ($general['conversion_mapping'] === '') {
            $general['conversion_mapping'] = [];
        }

        if (isset($configuration['rulesets'])) {
            $rulesets = $this->compileRuleSets($configuration['rulesets']);
        } else {
            $rulesets = [];
        }

        // Inherit values from general configuration in rule sets if needed
        foreach ($rulesets as $k => &$ruleset) {
            foreach ($general as $key => $value) {
                if (!isset($ruleset[$key])) {
                    $ruleset[$key] = $value;
                } elseif ($ruleset[$key] === '') {
                    $ruleset[$key] = $value;
                }
            }
            if (count($ruleset['usergroup']) == 0) {
                // Make sure not to try to override general configuration
                // => only keep directories not present in general configuration
                $ruleset['directories'] = array_diff($ruleset['directories'], $general['directories']);
                if (count($ruleset['directories']) == 0) {
                    unset($rulesets[$k]);
                }
            }
        }

        // Use general configuration as very last rule set
        $rulesets[] = $general;
        $this->rulesets = $rulesets;
    }

    /**
     * Returns the resized/converted file name (no actual processing).
     *
     * @param string $fileName
     * @param \TYPO3\CMS\Core\Authentication\BackendUserAuthentication|null $backendUser
     * @param array $ruleset The optional ruleset to use
     * @return string|null Eiter null if no resize/conversion should take place or the resized/converted file name
     */
    public function getProcessedFileName($fileName, \TYPO3\CMS\Core\Authentication\BackendUserAuthentication $backendUser = null, array $ruleset = null)
    {
        if ($ruleset === null) {
            $ruleset = $this->getRuleset($fileName, $fileName, $backendUser);
        }

        if (count($ruleset) === 0) {
            // File does not match any rule set
            return null;
        }

        if ($backendUser === null && count($ruleset['usergroup']) > 0) {
            // Rule set is targeting some user group but we have no backend user (scheduler task)
            // so we should skip this file altogether
            return null;
        }

        // Extract the extension
        if (($dotPosition = strrpos($fileName, '.')) === false) {
            // File has no extension
            return null;
        }
        $fileExtension = strtolower(substr($fileName, $dotPosition + 1));

        if ($fileExtension === 'png' && !$ruleset['resize_png_with_alpha']) {
            if (file_exists($fileName) && ImageUtility::isTransparentPng($fileName)) {
                return null;
            }
        }

        if ($fileExtension === 'gif' && file_exists($fileName) && ImageUtility::isAnimatedGif($fileName)) {
            return null;
        }

        if (isset($ruleset['conversion_mapping'][$fileExtension])) {
            // File format will be converted
            $destExtension = $ruleset['conversion_mapping'][$fileExtension];
            $destDirectory = PathUtility::dirname($fileName);
            $destFileName = PathUtility::basename(substr($fileName, 0, strlen($fileName) - strlen($fileExtension)) . $destExtension);

            $fileName = $destDirectory . '/' . $destFileName;
        }

        return $fileName;
    }

    /**
     * Processes upload of a file.
     *
     * @param string $fileName Full path to the file to be processed
     * @param string $targetFileName Expected target file name, if not converted (only file name, no path)
     * @param string $targetDirectory
     * @param \TYPO3\CMS\Core\Resource\File $file
     * @param \TYPO3\CMS\Core\Authentication\BackendUserAuthentication $backendUser
     * @param callback $callbackNotification Callback to send notification
     * @return string File name that was finally written
     */
    public function processFile($fileName, $targetFileName = '', $targetDirectory = '', \TYPO3\CMS\Core\Resource\File $file = null, \TYPO3\CMS\Core\Authentication\BackendUserAuthentication $backendUser = null, $callbackNotification = null)
    {
        $this->lastMetadata = null;

        if (!(empty($targetFileName) && empty($targetDirectory))) {
            $targetDirectory = rtrim($targetDirectory, '/') . '/';
            $ruleset = $this->getRuleset($fileName, $targetDirectory . $targetFileName, $backendUser);
        } else {
            $ruleset = $this->getRuleset($fileName, $fileName, $backendUser);
        }

        if (count($ruleset) === 0) {
            // File does not match any rule set
            return $fileName;
        }

        // Make file name relative, store as $targetFileName
        if (empty($targetFileName)) {
            $targetFileName = PathUtility::stripPathSitePrefix($fileName);
        }

        // Extract the extension
        if (($dotPosition = strrpos($fileName, '.')) === false) {
            // File has no extension
            return $fileName;
        }
        $fileExtension = strtolower(substr($fileName, $dotPosition + 1));

        if ($fileExtension === 'png' && !$ruleset['resize_png_with_alpha']) {
            if (ImageUtility::isTransparentPng($fileName)) {
                $message = sprintf(
                    $this->localize('LLL:EXT:image_autoresize/Resources/Private/Language/locallang.xlf:message.imageTransparent'),
                    $targetFileName
                );
                $this->notify($callbackNotification, $message, \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING);
                return $fileName;
            }
        }
        if ($fileExtension === 'gif' && ImageUtility::isAnimatedGif($fileName)) {
            $message = sprintf(
                $this->localize('LLL:EXT:image_autoresize/Resources/Private/Language/locallang.xlf:message.imageAnimated'),
                $targetFileName
            );
            $this->notify($callbackNotification, $message, \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING);
            return $fileName;
        }

        $processedFileName = $this->getProcessedFileName($fileName, $backendUser, $ruleset);
        if ($processedFileName === null) {
            // No processing to do
            return $fileName;
        }

        if (!is_writable($fileName)) {
            $message = sprintf(
                $this->localize('LLL:EXT:image_autoresize/Resources/Private/Language/locallang.xlf:message.imageNotWritable'),
                $targetFileName
            );
            $this->notify($callbackNotification, $message, \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
            return $fileName;
        }

        $targetDestFileName = $fileName;
        if (isset($ruleset['conversion_mapping'][$fileExtension])) {
            // File format will be converted
            $destExtension = $ruleset['conversion_mapping'][$fileExtension];
            $destDirectory = PathUtility::dirname($fileName);
            $destFileName = PathUtility::basename(substr($fileName, 0, strlen($fileName) - strlen($fileExtension)) . $destExtension);

            if (empty($targetDirectory)) {
                // Ensures $destFileName does not yet exist, otherwise make it unique!
                /* @var $fileFunc \TYPO3\CMS\Core\Utility\File\BasicFileUtility */
                $fileFunc = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Utility\File\BasicFileUtility::class);
                $destFileName = $fileFunc->getUniqueName($destFileName, $destDirectory);
                $targetDestFileName = $destFileName;
            } else {
                $destFileName = $destDirectory . '/' . $destFileName;
                $targetDestFileName = $targetDirectory . PathUtility::basename(substr($targetFileName, 0, strlen($targetFileName) - strlen($fileExtension)) . $destExtension);
            }
        } else {
            // File format stays the same
            $destExtension = $fileExtension;
            $destFileName = $fileName;
        }

        // Image is bigger than allowed, will now resize it to (hopefully) make it lighter
        /** @var $gifCreator \TYPO3\CMS\Frontend\Imaging\GifBuilder */
        $gifCreator = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Imaging\GifBuilder::class);
        $gifCreator->init();
        $gifCreator->absPrefix = PATH_site;

        $imParams = isset($gifCreator->cmds[$destExtension]) ? $gifCreator->cmds[$destExtension] : '';
        $imParams .= (bool)$ruleset['keep_metadata'] === true ? ' ###SkipStripProfile###' : '';
        $metadata = ImageUtility::getMetadata($fileName, true);
        $this->lastMetadata = $metadata;
        $isRotated = false;

        if ((bool)$ruleset['auto_orient'] === true) {
            $orientation = ImageUtility::getOrientation($fileName);
            $isRotated = ImageUtility::isRotated($orientation);
            $transformation = ImageUtility::getTransformation($orientation);
            if ($transformation !== '') {
                $imParams .= ' ' . $transformation;
            }
        }

        if (
            isset($ruleset['max_size'])
            && $ruleset['max_size'] > 0
            && isset($metadata['width'])
            && $metadata['width'] > 0
            && isset($metadata['height'])
            && $metadata['height'] > 0
            && $metadata['width'] * $metadata['height'] > $ruleset['max_size']
        ) {
            $factor = sqrt($ruleset['max_size'] / ($metadata['width'] * $metadata['height']));

            $ruleset['max_width'] = min($ruleset['max_width'], floor($metadata['width'] * $factor));
            $ruleset['max_height'] = min($ruleset['max_height'], floor($metadata['height'] * $factor));
        }

        if ($isRotated) {
            // Invert max_width and max_height as the picture
            // will be automatically rotated
            $options = [
                'maxW' => $ruleset['max_height'],
                'maxH' => $ruleset['max_width'],
            ];
        } else {
            $options = [
                'maxW' => $ruleset['max_width'],
                'maxH' => $ruleset['max_height'],
            ];
        }

        $originalFileSize = filesize($fileName);
        $tempFileInfo = null;
        $tempFileInfo = $gifCreator->imageMagickConvert($fileName, $destExtension, '', '', $imParams, '', $options, true);
        if (filesize($tempFileInfo[3]) >= $originalFileSize - 10240 && $destExtension === $fileExtension) {
            // Conversion leads to same or bigger file (rounded to 10KB to accomodate tiny variations in compression) => skip!
            $tempFileInfo = null;
        }
        if ($tempFileInfo) {
            // Signal to post-process the image
            $this->signalSlotDispatcher->dispatch(
                __CLASS__,
                'afterImageResize',
                [
                    'operation' => ($fileName === $destFileName) ? 'RESIZE' : 'RESIZE_CONVERT',
                    'source' => $fileName,
                    'destination' => $tempFileInfo[3],
                    'newWidth' => &$tempFileInfo[0],
                    'newHeight' => &$tempFileInfo[1],
                ]
            );

            $newFileSize = filesize($tempFileInfo[3]);
            $this->reportAdditionalStorageClaimed($originalFileSize - $newFileSize);

            // Replace original file
            @unlink($fileName);
            @rename($tempFileInfo[3], $destFileName);

            if ($fileName === $destFileName) {
                $message = sprintf(
                    $this->localize('LLL:EXT:image_autoresize/Resources/Private/Language/locallang.xlf:message.imageResized'),
                    $targetFileName, $tempFileInfo[0], $tempFileInfo[1]
                );
            } else {
                $message = sprintf(
                    $this->localize('LLL:EXT:image_autoresize/Resources/Private/Language/locallang.xlf:message.imageResizedAndRenamed'),
                    $targetFileName, $tempFileInfo[0], $tempFileInfo[1], PathUtility::basename($targetDestFileName)
                );
            }

            // Indexation in TYPO3 6.2 is using another signal, after the file
            // has been actually uploaded
            $this->lastMetadata['COMPUTED']['Width'] = $tempFileInfo[0];
            $this->lastMetadata['COMPUTED']['Height'] = $tempFileInfo[1];

            if ($isRotated && (bool)$ruleset['keep_metadata'] === true && $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_version_5'] === 'gm') {
                ImageUtility::resetOrientation($destFileName);
            }

            // Inform FAL about new image size and dimensions
            try {
                $destinationFile = ResourceFactory::getInstance()->retrieveFileOrFolderObject($destFileName);
                if ($destinationFile instanceof File) {
                    $indexer = $this->getIndexer($destinationFile->getStorage());
                    $indexer->updateIndexEntry($destinationFile);
                }
            } catch (FolderDoesNotExistException $e) {
                // We are in upload process. Do nothing
            }

            $this->notify($callbackNotification, $message, \TYPO3\CMS\Core\Messaging\FlashMessage::INFO);
        } else {
            // Destination file was not written
            $destFileName = $fileName;
        }
        return $destFileName;
    }

    /**
     * Returns the last extracted metadata.
     *
     * @return array|null
     */
    public function getLastMetadata()
    {
        return $this->lastMetadata;
    }

    /**
     * Localizes a label.
     *
     * @param string $input
     * @return string
     */
    protected function localize($input)
    {
        if (TYPO3_MODE === 'FE') {
            $output = is_object($GLOBALS['TSFE']) ? $GLOBALS['TSFE']->sL($input) : $input;
        } else {
            $output = $GLOBALS['LANG']->sL($input);
        }
        return $output;
    }

    /**
     * Sends a notification.
     *
     * @param callback $callbackNotification Callback to send notification
     * @param string $message
     * @param integer $severity
     */
    protected function notify($callbackNotification, $message, $severity)
    {
        $callableName = '';
        if (is_callable($callbackNotification, false, $callableName)) {
            call_user_func($callbackNotification, $message, $severity);
        }
    }

    /**
     * Returns the rule set that applies to a given file for a given backend user (or null
     * if using scheduler task).
     *
     * @param string $sourceFileName
     * @param string $targetFileName
     * @param \TYPO3\CMS\Core\Authentication\BackendUserAuthentication $backendUser
     * @return array
     */
    protected function getRuleset($sourceFileName, $targetFileName, \TYPO3\CMS\Core\Authentication\BackendUserAuthentication $backendUser = null)
    {
        $ret = [];

        // Make file name relative and extract the extension
        $relTargetFileName = substr($targetFileName, strlen(PATH_site));
        $fileExtension = strtolower(substr($targetFileName, strrpos($targetFileName, '.') + 1));

        $beGroups = $backendUser !== null ? array_keys($GLOBALS['BE_USER']->userGroups) : [];
        $fileSize = is_file($sourceFileName)
            ? filesize($sourceFileName)
            : -1;    // -1 is a special value so that file size is not taken into account (yet)

        // Try to find a matching ruleset
        foreach ($this->rulesets as $ruleset) {
            if (!is_array($ruleset['file_types'])) {
                // Default general settings do not include any watched image types
                continue;
            }
            if (count($ruleset['usergroup']) > 0 && (
                    $backendUser === null ||
                    count(array_intersect($ruleset['usergroup'], $beGroups)) == 0)
            ) {
                // Backend user is not member of a group configured for the current rule set
                continue;
            }
            $processFile = false;
            foreach ($ruleset['directories'] as $directoryPattern) {
                $processFile |= preg_match($directoryPattern, $relTargetFileName);
            }
            $processFile &= in_array($fileExtension, $ruleset['file_types']);
            $processFile &= $fileSize === -1 || ($fileSize > $ruleset['threshold']);
            if ($processFile) {
                $ret = $ruleset;
                break;
            }
        }
        return $ret;
    }

    /**
     * Returns all directories found in the various rulesets.
     *
     * @return array
     */
    public function getAllDirectories()
    {
        $directories = [];
        foreach ($this->rulesets as $ruleset) {
            $dirs = GeneralUtility::trimExplode(',', $ruleset['directories_config'], true);
            $directories = array_merge($directories, $dirs);
        }
        $directories = array_unique($directories);
        asort($directories);
        return $directories;
    }

    /**
     * Returns all file types found in the various rulesets.
     *
     * @return array
     */
    public function getAllFileTypes()
    {
        $fileTypes = [];
        foreach ($this->rulesets as $ruleset) {
            if (is_array($ruleset['file_types'])) {
                $fileTypes = array_merge($fileTypes, $ruleset['file_types']);
            }
        }
        $fileTypes = array_unique($fileTypes);
        return $fileTypes;
    }

    /**
     * Compiles all FlexForm rule sets.
     *
     * @param array $rulesets
     * @return array
     */
    protected function compileRulesets(array $rulesets)
    {
        $out = [];

        $elements = $rulesets['data']['sDEF']['lDEF']['ruleset']['el'];
        foreach ($elements as $container) {
            if (isset($container['container']['el'])) {
                $values = [];
                foreach ($container['container']['el'] as $key => $value) {
                    if ($key === 'title') {
                        continue;
                    }
                    $values[$key] = $value['vDEF'];
                }
                $out[] = $this->expandValuesInRuleset($values);
            }
        }

        return $out;
    }

    /**
     * Expands values of a rule set.
     *
     * @param array $ruleset
     * @return array
     */
    protected function expandValuesInRuleset(array $ruleset)
    {
        $values = [];
        foreach ($ruleset as $key => $value) {
            switch ($key) {
                case 'usergroup':
                    $value = GeneralUtility::trimExplode(',', $value, true);
                    break;
                case 'directories':
                    $values['directories_config'] = '';
                    $value = GeneralUtility::trimExplode(',', $value, true);
                    // Sanitize name of the directories
                    foreach ($value as &$directory) {
                        $directory = rtrim($directory, '/') . '/';
                        if (!empty($values['directories_config'])) {
                            $values['directories_config'] .= ',';
                        }
                        $values['directories_config'] .= $directory;
                        $directory = $this->getDirectoryPattern($directory);
                    }
                    if (count($value) == 0) {
                        // Inherit configuration
                        $value = '';
                    }
                    break;
                case 'file_types':
                    $value = GeneralUtility::trimExplode(',', $value, true);
                    if (count($value) == 0) {
                        // Inherit configuration
                        $value = '';
                    }
                    break;
                case 'threshold':
                    if (!is_numeric($value)) {
                        $unit = strtoupper(substr($value, -1));
                        $factor = 1 * ($unit === 'K' ? 1024 : ($unit === 'M' ? 1024 * 1024 : 0));
                        $value = intval(trim(substr($value, 0, strlen($value) - 1))) * $factor;
                    }
                // Beware: fall-back to next value processing
                case 'max_width':
                case 'max_height':
                    if ($value <= 0) {
                        // Inherit configuration
                        $value = '';
                    }
                    break;
                case 'max_size':
                    if (!is_numeric($value)) {
                        $unit = strtoupper(substr($value, -1));
                        $factor = 1 * ($unit === 'M' ? 1000000 : 1);
                        $value = intval(trim(substr($value, 0, strlen($value) - 1))) * $factor;
                    }
                    break;
                case 'conversion_mapping':
                    if (strpos($value, CRLF) !== false) {
                        $mapping = GeneralUtility::trimExplode(CRLF, $value, true);
                    } else {
                        $mapping = GeneralUtility::trimExplode(',', $value, true);
                    }
                    if (count($mapping) > 0) {
                        $value = $this->expandConversionMapping($mapping);
                    } else {
                        // Inherit configuration
                        $value = '';
                    }
                    break;
            }
            $values[$key] = $value;
        }

        return $values;
    }

    /**
     * Expands the image type conversion mapping.
     *
     * @param array $mapping Array of lines similar to "bmp => jpg", "tif => jpg"
     * @return array Key/Value pairs of mapping: array('bmp' => 'jpg', 'tif' => 'jpg')
     */
    protected function expandConversionMapping(array $mapping)
    {
        $ret = [];
        $matches = [];
        foreach ($mapping as $m) {
            if (preg_match('/^(.*)\s*=>\s*(.*)/', $m, $matches)) {
                $ret[trim($matches[1])] = trim($matches[2]);
            }
        }
        return $ret;
    }

    /**
     * Returns a regular expression pattern to match directories.
     *
     * @param string $directory
     * @return string
     */
    public function getDirectoryPattern($directory)
    {
        $pattern = '/^' . str_replace('/', '\\/', $directory) . '/';
        $pattern = str_replace('\\/**\\/', '\\/([^\/]+\\/)*', $pattern);
        $pattern = str_replace('\\/*\\/', '\\/[^\/]+\\/', $pattern);

        return $pattern;
    }

    /**
     * Stores how many extra bytes have been freed.
     *
     * @param integer $bytes
     * @return void
     */
    protected function reportAdditionalStorageClaimed($bytes)
    {
        $legacyFileName = PATH_site . 'typo3conf/.tx_imageautoresize';
        $fileName = PATH_site . 'typo3temp/.tx_imageautoresize';

        // Note: transfer of legacy filename should be removed after some time
        if (file_exists($legacyFileName) && !file_exists($fileName)) {
            @rename($legacyFileName, $fileName);
        }

        $data = [];
        if (file_exists($fileName)) {
            $data = json_decode(file_get_contents($fileName), true);
            if (!is_array($data)) {
                $data = [];
            }
        }

        $data['bytes'] = $bytes + (isset($data['bytes']) ? (int)$data['bytes'] : 0);
        $data['images'] = 1 + (isset($data['images']) ? (int)$data['images'] : 0);

        GeneralUtility::writeFile($fileName, json_encode($data));
    }

    /**
     * Gets the indexer
     *
     * @param ResourceStorage $storage
     * @return Indexer
     */
    protected function getIndexer(\TYPO3\CMS\Core\Resource\ResourceStorage $storage)
    {
        return GeneralUtility::makeInstance(Indexer::class, $storage);
    }
}


<?php
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with TYPO3 source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace Causal\ImageAutoresize\Utility;

use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;

/**
 * This is a generic image utility.
 *
 * @category    Utility
 * @package     TYPO3
 * @subpackage  tx_imageautoresize
 * @author      Xavier Perseguers <xavier@causal.ch>
 * @license     http://www.gnu.org/copyleft/gpl.html
 */
class ImageUtility
{

    /**
     * Returns the EXIF orientation of a given picture.
     *
     * @param string $fileName
     * @return integer
     */
    public static function getOrientation($fileName)
    {
        $orientation = 1; // Fallback to "straight"
        $metadata = static::getMetadata($fileName);
        if (isset($metadata['Orientation'])) {
            $orientation = $metadata['Orientation'];
        }
        return $orientation;
    }

    /**
     * Returns metadata from a given file.
     *
     * @param string $fileName
     * @param bool $fullExtract
     * @return array
     */
    public static function getMetadata($fileName, $fullExtract = false)
    {
        $metadata = static::getBasicMetadata($fileName);

        if ($fullExtract && !empty($metadata)) {
            $virtualFileObject = static::getVirtualFileObject($fileName, $metadata);
            $extractorRegistry = \TYPO3\CMS\Core\Resource\Index\ExtractorRegistry::getInstance();
            $extractionServices = $extractorRegistry->getExtractorsWithDriverSupport('Local');

            $newMetadata = [
                0 => $metadata,
            ];
            foreach ($extractionServices as $service) {
                if ($service->canProcess($virtualFileObject)) {
                    $newMetadata[$service->getPriority()] = $service->extractMetaData($virtualFileObject, $newMetadata);
                }
            }
            ksort($newMetadata);
            foreach ($newMetadata as $data) {
                $metadata = array_merge($metadata, $data);
            }
        }

        return $metadata;
    }

    /**
     * Creates a virtual File object to be used transparently by external
     * metadata extraction services as if it would come from standard FAL.
     *
     * @param string $fileName
     * @param array $metadata
     * @return \TYPO3\CMS\Core\Resource\File
     */
    protected static function getVirtualFileObject($fileName, array $metadata)
    {
        /** @var \TYPO3\CMS\Core\Resource\ResourceFactory $resourceFactory */
        $resourceFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);

        $recordData = [
            'uid' => 0,
            'pid' => 0,
            'name' => 'Temporary Upload Storage',
            'description' => 'Internal storage, mounting the temporary PHP upload directory.',
            'driver' => 'Local',
            'processingfolder' => '',
            // legacy code
            'configuration' => '',
            'is_online' => true,
            'is_browsable' => false,
            'is_public' => false,
            'is_writable' => false,
            'is_default' => false,
        ];
        $storageConfiguration = [
            'basePath' => PathUtility::dirname($fileName),
            'pathType' => 'absolute'
        ];

        $virtualStorage = $resourceFactory->createStorageObject($recordData, $storageConfiguration);
        $name = PathUtility::basename($fileName);
        $extension = strtolower(substr($name, strrpos($name, '.') + 1));

        /** @var \TYPO3\CMS\Core\Resource\File $virtualFileObject */
        $virtualFileObject = GeneralUtility::makeInstance(
            \TYPO3\CMS\Core\Resource\File::class,
            [
                'identifier' => '/' . $name,
                'name' => $name,
                'extension' => $extension,
            ],
            $virtualStorage,
            $metadata
        );

        return $virtualFileObject;
    }

    /**
     * Returns metadata from a given file using basic, built-in, PHP-based extractor.
     *
     * @param string $fileName
     * @return array
     */
    protected static function getBasicMetadata($fileName)
    {
        $extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1));
        $metadata = [];
        if (GeneralUtility::inList('jpg,jpeg,tif,tiff', $extension) && function_exists('exif_read_data')) {
            $exif = @exif_read_data($fileName);
            if ($exif) {
                $metadata = $exif;
                // Fix description coming from EXIF
                $metadata['ImageDescription'] = static::safeUtf8Encode($metadata['ImageDescription']);

                // Process the longitude/latitude/altitude
                if (isset($metadata['GPSLatitude']) && is_array($metadata['GPSLatitude'])) {
                    $reference = isset($metadata['GPSLatitudeRef']) ? $metadata['GPSLatitudeRef'] : 'N';
                    $decimal = static::rationalToDecimal($metadata['GPSLatitude']);
                    $decimal *= $reference === 'N' ? 1 : -1;
                    $metadata['GPSLatitudeDecimal'] = $decimal;
                }
                if (isset($metadata['GPSLongitude']) && is_array($metadata['GPSLongitude'])) {
                    $reference = isset($metadata['GPSLongitudeRef']) ? $metadata['GPSLongitudeRef'] : 'E';
                    $decimal = static::rationalToDecimal($metadata['GPSLongitude']);
                    $decimal *= $reference === 'E' ? 1 : -1;
                    $metadata['GPSLongitudeDecimal'] = $decimal;
                }
                if (isset($metadata['GPSAltitude'])) {
                    $rationalParts = explode('/', $metadata['GPSAltitude']);
                    if (!empty($rationalParts[1])) {
                        $metadata['GPSAltitudeDecimal'] = $rationalParts[0] / $rationalParts[1];
                    } else {
                        $metadata['GPSAltitudeDecimal'] = 0;
                    }
                }
            }
            // Try to extract IPTC data
            $imageinfo = [];
            if (function_exists('iptcparse') && getimagesize($fileName, $imageinfo)) {
                if (isset($imageinfo['APP13'])) {
                    $data = iptcparse($imageinfo['APP13']);
                    $mapping = [
                        '2#005' => 'Title',
                        '2#025' => 'Keywords',
                        '2#040' => 'Instructions',
                        '2#080' => 'Creator',
                        '2#085' => 'CreatorFunction',
                        '2#090' => 'City',
                        '2#092' => 'Location',
                        '2#095' => 'Region',
                        '2#100' => 'CountryCode',
                        '2#101' => 'Country',
                        '2#103' => 'IdentifierWork',
                        '2#105' => 'CreatorTitle',
                        '2#110' => 'Credit',
                        '2#115' => 'Source',
                        '2#116' => 'Copyright',
                        '2#120' => 'Description',
                        '2#122' => 'DescriptionAuthor',
                    ];
                    foreach ($mapping as $iptcKey => $metadataKey) {
                        if (isset($data[$iptcKey])) {
                            $metadata['IPTC' . $metadataKey] = static::safeUtf8Encode($data[$iptcKey][0]);
                        }
                    }
                }
            }
        }
        return $metadata;
    }

    /**
     * Safely converts some text to UTF-8.
     *
     * @param string $text
     * @return string
     */
    protected static function safeUtf8Encode($text)
    {
        if (function_exists('mb_detect_encoding')) {
            if (mb_detect_encoding($text, 'UTF-8', true) !== 'UTF-8') {
                $text = utf8_encode($text);
            }
        } else {
            // Fall back to hack
            $encodedText = utf8_encode($text);
            if (strpos($encodedText, 'Ã') === false) {
                $text = $encodedText;
            }
        }
        return $text;
    }

    /**
     * Converts an EXIF rational into its decimal representation.
     *
     * @param array $components
     * @return float
     */
    protected static function rationalToDecimal(array $components)
    {
        foreach ($components as $key => $value) {
            $rationalParts = explode('/', $value);
            if (!empty($rationalParts[1])) {
                $components[$key] = $rationalParts[0] / $rationalParts[1];
            } else {
                $components[$key] = 0;
            }
        }
        list($hours, $minutes, $seconds) = $components;

        return $hours + ($minutes / 60) + ($seconds / 3600);
    }

    /**
     * Returns true if the given picture is rotated.
     *
     * @param integer $orientation EXIF orientation
     * @return integer
     * @see http://www.impulseadventure.com/photo/exif-orientation.html
     */
    public static function isRotated($orientation)
    {
        $ret = false;
        switch ($orientation) {
            case 2: // horizontal flip
            case 3: // 180°
            case 4: // vertical flip
            case 5: // vertical flip + 90 rotate right
            case 6: // 90° rotate right
            case 7: // horizontal flip + 90 rotate right
            case 8: // 90° rotate left
                $ret = true;
                break;
        }
        return $ret;
    }

    /**
     * Returns a command line parameter to fix the orientation of a rotated picture.
     *
     * @param integer $orientation
     * @return string
     */
    public static function getTransformation($orientation)
    {
        $transformation = '';
        if (
            (isset($GLOBALS['TYPO3_CONF_VARS']['GFX']['processor']) && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor'] === 'ImageMagick')
            ||
            (isset($GLOBALS['TYPO3_CONF_VARS']['GFX']['im_version_5']) && $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_version_5'] !== 'gm')
        ) {
            // ImageMagick
            if ($orientation >= 2 && $orientation <= 8) {
                $transformation = '-auto-orient';
            }
        } else {
            // GraphicsMagick
            switch ($orientation) {
                case 2: // horizontal flip
                    $transformation = '-flip horizontal';
                    break;
                case 3: // 180°
                    $transformation = '-rotate 180';
                    break;
                case 4: // vertical flip
                    $transformation = '-flip vertical';
                    break;
                case 5: // vertical flip + 90 rotate right
                    $transformation = '-transpose';
                    break;
                case 6: // 90° rotate right
                    $transformation = '-rotate 90';
                    break;
                case 7: // horizontal flip + 90 rotate right
                    $transformation = '-transverse';
                    break;
                case 8: // 90° rotate left
                    $transformation = '-rotate 270';
                    break;
            }
        }
        return $transformation;
    }

    /**
     * Resets the EXIF orientation flag of a picture.
     *
     * @param string $fileName
     * @return void
     * @see http://sylvana.net/jpegcrop/exif_orientation.html
     */
    public static function resetOrientation($fileName)
    {
        \Causal\ImageAutoresize\Utility\JpegExifOrient::setOrientation($fileName, 1);
    }

    /**
     * Returns true if the given PNG file contains transparency information.
     *
     * @param string $fileName
     * @return bool
     */
    public static function isTransparentPng($fileName)
    {
        $bytes = file_get_contents($fileName, false, null, 24, 2);    // read 24th and 25th bytes
        $byte24 = ord($bytes{0});
        $byte25 = ord($bytes{1});
        if ($byte24 === 16 || $byte25 === 6 || $byte25 === 4) {
            return true;
        } else {
            $content = file_get_contents($fileName);
            return strpos($content, 'tRNS') !== false;
        }
    }

    /**
     * Returns true if the given GIF file is animated.
     *
     * @param string $fileName
     * @return bool
     */
    public static function isAnimatedGif($fileName)
    {
        if (($fh = fopen($fileName, 'rb')) === false) {
            throw new \RuntimeException('Can\'t open ' . $fileName, 1454678600);
        }
        $count = 0;
        // An animated gif contains multiple "frames", with each frame having a
        // header made up of:
        // - a static 4-byte sequence (\x00\x21\xF9\x04)
        // - 4 variable bytes
        // - a static 2-byte sequence (\x00\x2C)

        // We read through the file til we reach the end of the file, or we've found
        // at least 2 frame headers
        while (!feof($fh) && $count < 2) {
            $chunk = fread($fh, 1024 * 100); // read 100kb at a time
            $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00[\x2C\x21]#s', $chunk, $matches);
        }

        fclose($fh);
        return $count > 1;
    }

}


<?php
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with TYPO3 source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace Causal\ImageAutoresize\Controller;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;

/**
 * THIS CLASS IS USED IN TYPO3 v8 only
 *
 * @package Causal\ImageAutoresize\Controller
 */
class FormFlexAjaxControllerV8 extends \TYPO3\CMS\Backend\Controller\FormFlexAjaxController
{

    /**
     * Render a single flex form section container to add it to the DOM
     *
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @return ResponseInterface
     */
    public function containerAdd(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        $GLOBALS['TCA']['tx_imageautoresize'] = include(ExtensionManagementUtility::extPath('image_autoresize') . 'Configuration/TCA/Module/Options.php');
        $GLOBALS['TCA']['tx_imageautoresize']['ajax'] = true;

        // Trick to use a virtual record
        $dataProviders =& $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['tcaDatabaseRecord'];
        $dataProviders[\Causal\ImageAutoresize\Backend\Form\FormDataProvider\VirtualDatabaseEditRow::class] = [
            'before' => [
                \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseEditRow::class,
            ]
        ];

        $record = [
            'uid' => \Causal\ImageAutoresize\Controller\ConfigurationController::virtualRecordId,
            'pid' => 0,
        ];

        // Initialize record in our virtual provider
        \Causal\ImageAutoresize\Backend\Form\FormDataProvider\VirtualDatabaseEditRow::initialize($record);

        $response = parent::containerAdd($request, $response);
        return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
    }

}


<?php
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with TYPO3 source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace Causal\ImageAutoresize\Slots;

use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * Slot implementation to extend the list of actions in Extension Manager.
 *
 * @category    Slots
 * @package     TYPO3
 * @subpackage  tx_imageautoresize
 * @author      Xavier Perseguers <xavier@causal.ch>
 * @license     http://www.gnu.org/copyleft/gpl.html
 */
class ExtensionManager
{

    const SIGNAL_ProcessActions = 'processActions';

    /**
     * Extends the list of actions for EXT:image_autoresize to link to
     * the configuration module.
     *
     * @param array $extension
     * @param array $actions
     */
    public function processActions(array $extension, array &$actions)
    {
        if ($extension['key'] === 'image_autoresize') {
            $moduleUrl = BackendUtility::getModuleUrl('xMOD_tximageautoresize');

            $extensionName = 'extensionmanager';
            $titleKey = 'extensionList.configure';
            $title = \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate($titleKey, $extensionName);

            $icon = 'actions-system-extension-configure';
            /** @var \TYPO3\CMS\Core\Imaging\IconFactory $iconFactory */
            $iconFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconFactory::class);
            $icon = (string)$iconFactory->getIcon($icon, \TYPO3\CMS\Core\Imaging\Icon::SIZE_SMALL);

            // Configure action comes as first icon
            $configureAction = sprintf('%s', htmlspecialchars($title), htmlspecialchars($moduleUrl), $icon);
            $actions[0] = $configureAction;
            unset($actions[1], $actions[2], $actions[3], $actions[4]);

            $title = htmlspecialchars($extension['title']);
            $titleAction = htmlspecialchars($moduleUrl);
            $pattern = "/>$title</";
            $replacement = "'>$title<'";
            $actions[] = "";
        }
    }

}


<?php
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with TYPO3 source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace Causal\ImageAutoresize\Tca;

use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * TCA Helper class.
 *
 * @category    TCA
 * @package     TYPO3
 * @subpackage  tx_imageautoresize
 * @author      Xavier Perseguers <xavier@causal.ch>
 * @license     http://www.gnu.org/copyleft/gpl.html
 */
class Graphics
{

    /**
     * Prepares a list of image file extensions supported by the
     * current TYPO3 install.
     *
     * @param array $settings content element configuration
     * @return void
     */
    public function getImageFileExtensions(array $settings)
    {
        $languageService = $this->getLanguageService();

        $extensions = GeneralUtility::trimExplode(',', strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']), true);
        // We don't consider PDF being an image...
        if ($key = array_search('pdf', $extensions)) {
            unset($extensions[$key]);
        }
        // ... neither SVG since vectorial
        if ($key = array_search('svg', $extensions)) {
            unset($extensions[$key]);
        }
        asort($extensions);

        $elements = [];
        foreach ($extensions as $extension) {
            $label = $languageService->sL('LLL:EXT:image_autoresize/Resources/Private/Language/locallang.xlf:extension.' . $extension);
            $label = $label ? $label : '.' . $extension;
            $elements[] = [$label, $extension];
        }

        $settings['items'] = array_merge($settings['items'], $elements);
    }

    /**
     * Returns the language service.
     *
     * @return \TYPO3\CMS\Lang\LanguageService
     */
    protected function getLanguageService()
    {
        return $GLOBALS['LANG'];
    }

}


<?php
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with TYPO3 source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace Causal\ImageAutoresize\Utility;

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Utility\PathUtility;

/**
 * This is a FAL-manipulation utility.
 *
 * @category    Utility
 * @package     TYPO3
 * @subpackage  tx_imageautoresize
 * @author      Xavier Perseguers <xavier@causal.ch>
 * @license     http://www.gnu.org/copyleft/gpl.html
 */
class FAL
{

    /**
     * @var array
     */
    protected static $reflectedClasses = [];

    /**
     * Creates/updates the index entry for a given file.
     *
     * @param \TYPO3\CMS\Core\Resource\File $file
     * @param string $origFileName
     * @param string $newFileName
     * @param integer $width
     * @param integer $height
     * @param array $metadata EXIF metadata
     * @return void
     */
    public static function indexFile(\TYPO3\CMS\Core\Resource\File $file = null, $origFileName, $newFileName, $width, $height, array $metadata = [])
    {
        if ($file === null) {
            $file = static::findExistingFile($origFileName);
        }
        if ($file !== null) {
            static::updateIndex($file, $width, $height, $metadata);
        } else {
            static::createIndex($newFileName, $width, $height);
        }
    }

    /**
     * Finds an existing file.
     *
     * @param string $fileName
     * @return \TYPO3\CMS\Core\Resource\AbstractFile|null
     */
    protected static function findExistingFile($fileName)
    {
        $file = null;
        $relativePath = substr(PathUtility::dirname($fileName), strlen(PATH_site));
        $resourceFactory = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
        $targetFolder = $resourceFactory->retrieveFileOrFolderObject($relativePath);

        $storageConfiguration = $targetFolder->getStorage()->getConfiguration();
        if (isset($storageConfiguration['basePath'])) {
            $basePath = rtrim($storageConfiguration['basePath'], '/') . '/';
            $basePath = GeneralUtility::getFileAbsFileName($basePath);
            $identifier = substr($fileName, strlen($basePath) - 1);

            $row = GeneralUtility::makeInstance(ConnectionPool::class)
                ->getConnectionForTable('sys_file')
                ->select(
                    ['uid'],
                    'sys_file',
                    [
                        'storage' => $targetFolder->getStorage()->getUid(),
                        'identifier' => $identifier,
                    ]
                )
                ->fetch();

            if (!empty($row['uid'])) {
                /** @var \TYPO3\CMS\Core\Resource\FileRepository $fileRepository */
                $fileRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\FileRepository::class);
                $file = $fileRepository->findByUid($row['uid']);
            }
        }

        return $file;
    }

    /**
     * Updates the index entry for a given file.
     *
     * @param \TYPO3\CMS\Core\Resource\File $file
     * @param integer $width
     * @param integer $height
     * @param array $metadata EXIF metadata
     * @return void
     */
    protected static function updateIndex(\TYPO3\CMS\Core\Resource\File $file = null, $width, $height, array $metadata = [])
    {
        if (count($metadata) > 0) {
            /** @var \TYPO3\CMS\Core\Resource\Index\MetaDataRepository $metadataRepository */
            $metadataRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Index\MetaDataRepository::class);
            // Will take care of creating the record if it does not exist yet
            $currentMetadata = $metadataRepository->findByFile($file);

            $newMetadata = [];
            // Pre-populate with metadata coming from external extractors
            foreach ($currentMetadata as $key => $value) {
                if (!empty($metadata[$key])) {
                    // Known issue with "creator_tool" having a software version sometimes
                    if ($key === 'creator_tool' && MathUtility::canBeInterpretedAsFloat($metadata[$key])) {
                        continue;
                    }
                    $newMetadata[$key] = $metadata[$key];
                }
            }
            // Width and height are always wrong since we resized the image
            unset($newMetadata['width']);
            unset($newMetadata['height']);
            // We deal with resized images so unit is always pixels
            $newMetadata['unit'] = 'px';

            // Mapping for the built-in PHP-based metadata extractor
            $mapping = [
                'color_space' => 'ColorSpace',
                'content_creation_date' => 'DateTimeOriginal',
                'creator' => 'IPTCCreator|Company',
                'creator_tool' => 'Model|Make|Software',
                'description' => 'ImageDescription',
                'keywords' => 'IPTCKeywords',
                'latitude' => 'GPSLatitudeDecimal',
                'longitude' => 'GPSLongitudeDecimal',
                'location_city' => 'IPTCCity',
                'location_country' => 'IPTCCountry',
                'location_region' => 'IPTCRegion',
                'note' => 'IPTCLocation',
                'publisher' => 'IPTCCredit',
                'source' => 'IPTCSource',
                'title' => 'IPTCTitle',
            ];
            foreach ($mapping as $falKey => $metadataKeyMapping) {
                if (!empty($newMetadata[$falKey])) {
                    // We already have a known-to-be-valid metadata for this FAL property
                    continue;
                }
                $metatadaKeys = explode('|', $metadataKeyMapping);
                foreach ($metatadaKeys as $metadataKey) {
                    $value = null;
                    if (isset($metadata[$metadataKey])) {
                        $value = trim($metadata[$metadataKey]);
                        if (ord($value) === 1) $value = null;
                        switch ($metadataKey) {
                            case 'ColorSpace':
                                if ($value == 1) {
                                    $value = 'RGB';
                                } else {
                                    // Unknown
                                    $value = null;
                                }
                                break;
                            case 'DateTimeOriginal':
                                $value = strtotime($value);
                                break;
                        }
                    }
                    if (!empty($value)) {
                        $newMetadata[$falKey] = $value;
                        break;
                    }
                }
            }
            $metadataRepository->update($file->getUid(), $newMetadata);
        }
    }

    /**
     * Creates the index entry for a given file.
     *
     * @param string $fileName
     * @param integer $width
     * @param integer $height
     * @return void
     */
    protected static function createIndex($fileName, $width, $height)
    {
        $relativePath = substr(PathUtility::dirname($fileName), strlen(PATH_site));
        $resourceFactory = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
        $targetFolder = $resourceFactory->retrieveFileOrFolderObject($relativePath);
        $targetFilename = PathUtility::basename($fileName);

        $storageConfiguration = $targetFolder->getStorage()->getConfiguration();
        if (!isset($storageConfiguration['basePath'])) {
            // Probably a file found in uploads/ or similar
            return;
        }
        $basePath = rtrim($storageConfiguration['basePath'], '/') . '/';
        $basePath = GeneralUtility::getFileAbsFileName($basePath);
        $identifier = substr($fileName, strlen($basePath) - 1);

        // TODO: possibly create file with nearly no info and populate them with
        // a call to $file->getStorage()->getFileInfo($file) instead of using $driver
        /** @var \TYPO3\CMS\Core\Resource\Driver\AbstractDriver $driver */
        $driver = static::accessProtectedProperty($targetFolder->getStorage(), 'driver');
        $fileInfo = $driver->getFileInfoByIdentifier($identifier);
        $file = $resourceFactory->createFileObject($fileInfo);

        /** @var \TYPO3\CMS\Core\Resource\FileRepository $fileRepository */
        $fileRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\FileRepository::class);
        $fileRepository->addToIndex($file);
    }

    /**
     * Returns the value of a protected property.
     *
     * @param object $object
     * @param string $propertyName
     * @return mixed
     */
    protected static function accessProtectedProperty($object, $propertyName)
    {
        $className = get_class($object);
        if (!isset(static::$reflectedClasses[$className])) {
            static::$reflectedClasses[$className] = new \ReflectionClass($className);
        }
        $class = static::$reflectedClasses[$className];
        $property = $class->getProperty($propertyName);
        $property->setAccessible(true);

        return $property->getValue($object);
    }

}


<?php
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with TYPO3 source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace Causal\ImageAutoresize\Hooks;

use Causal\ImageAutoresize\Controller\ConfigurationController;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\DataHandling\DataHandlerProcessUploadHookInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use Causal\ImageAutoresize\Service\ImageResizer;

/**
 * This class extends \TYPO3\CMS\Core\DataHandling\DataHandler and hooks into the
 * upload of old, non-FAL files to uploads/ directory.
 *
 * @category    Hooks
 * @package     TYPO3
 * @subpackage  tx_imageautoresize
 * @author      Xavier Perseguers <xavier@causal.ch>
 * @license     http://www.gnu.org/copyleft/gpl.html
 */
class FileUploadHook implements DataHandlerProcessUploadHookInterface
{

    /**
     * @var ImageResizer
     */
    protected static $imageResizer;

    /**
     * Default constructor.
     */
    public function __construct()
    {
        if (static::$imageResizer === null) {
            static::$imageResizer = GeneralUtility::makeInstance(ImageResizer::class);

            $configuration = ConfigurationController::readConfiguration();
            static::$imageResizer->initializeRulesets($configuration);
        }
    }

    /**
     * Post-processes a file upload.
     *
     * @param string $filename The uploaded file
     * @param DataHandler $parentObject
     * @return void
     */
    public function processUpload_postProcessAction(&$filename, DataHandler $pObj)
    {
        $filename = static::$imageResizer->processFile(
            $filename,
            '', // Target file name
            '', // Target directory
            null,
            $GLOBALS['BE_USER'],
            [$this, 'notify']
        );
    }

    /**
     * Notifies the user using a Flash message.
     *
     * @param string $message The message
     * @param integer $severity Optional severity, must be either of \TYPO3\CMS\Core\Messaging\FlashMessage::INFO,
     *                          \TYPO3\CMS\Core\Messaging\FlashMessage::OK, \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING
     *                          or \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR.
     *                          Default is \TYPO3\CMS\Core\Messaging\FlashMessage::OK.
     * @return void
     * @internal This method is public only to be callable from a callback
     */
    public function notify($message, $severity = \TYPO3\CMS\Core\Messaging\FlashMessage::OK)
    {
        if (TYPO3_MODE !== 'BE') {
            return;
        }
        $flashMessage = GeneralUtility::makeInstance(
            \TYPO3\CMS\Core\Messaging\FlashMessage::class,
            $message,
            '',
            $severity,
            true
        );
        /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
        $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
        /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
        $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
        $defaultFlashMessageQueue->enqueue($flashMessage);
    }

}


<?php
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is based on work http://sylvana.net/jpegcrop/jpegexiforient.c
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with TYPO3 source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace Causal\ImageAutoresize\Utility;

/**
 * This is a utility class to set the Exif Orientation Tag.
 * It can be used for automatic orientation correction of digital camera pictures.
 *
 * The Exif orientation value gives the orientation of the camera
 * relative to the scene when the image was captured.  The relation
 * of the '0th row' and '0th column' to visual position is shown as
 * below.
 *
 * Value | 0th Row     | 0th Column
 * ------+-------------+-----------
 *   1   | top         | left side
 *   2   | top         | right side
 *   3   | bottom      | right side
 *   4   | bottom      | left side
 *   5   | left side   | top
 *   6   | right side  | top
 *   7   | right side  | bottom
 *   8   | left side   | bottom
 *
 * For convenience, here is what the letter F would look like if it were
 * tagged correctly and displayed by a program that ignores the orientation
 * tag:
 *
 *   1        2       3      4         5            6           7          8
 *
 * 888888  888888      88  88      8888888888  88                  88  8888888888
 * 88          88      88  88      88  88      88  88          88  88      88  88
 * 8888      8888    8888  8888    88          8888888888  8888888888          88
 * 88          88      88  88
 * 88          88  888888  888888
 *
 *
 * @category    Utility
 * @package     TYPO3
 * @subpackage  tx_imageautoresize
 * @author      Xavier Perseguers <xavier@causal.ch>
 * @license     http://www.gnu.org/copyleft/gpl.html
 */
class JpegExifOrient
{

    /**
     * Sets the Exif Orientation for a given JPG file.
     *
     * @param string $fileName
     * @param integer $orientation
     * @return void
     * @throws \RuntimeException
     */
    public static function setOrientation($fileName, $orientation)
    {
        $exif_data = [];    // Buffer
        $offsetJfif = 0;

        if (($fh = fopen($fileName, 'rb+')) === false) {
            throw new \RuntimeException('Can\'t open ' . $fileName, 1363533724);
        }

        // Read file head, check for JPEG SOI + JFIF/Exif APP1
        for ($i = 0; $i < 4; $i++) {
            $exif_data[$i] = self::read_1_byte($fh);
        }
        if ($exif_data[0] !== 0xFF ||
            $exif_data[1] !== 0xD8
        ) {
            // Do nothing?
        }

        // JFIF segment: http://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#JFIF_segment_format
        if ($exif_data[2] === 0xFF && $exif_data[3] === 0xE0) {
            // Get the marker parameter length count
            $length = self::read_2_bytes($fh);
            $offsetJfif = $length + 2;    // "+ 2" to skip the 2 bytes introducing this additional segment
            // Length includes itself, so must be at least 2
            // Following JFIF data length must be at least 6
            if ($length < 8) {
                return;
            }
            $length -= 8;
            // Read JFIF head, check for "JFIF"
            for ($i = 0; $i < 5; $i++) {
                $exif_data[$i] = self::read_1_byte($fh);
            }
            if ($exif_data[0] !== 0x4A ||
                $exif_data[1] !== 0x46 ||
                $exif_data[2] !== 0x49 ||
                $exif_data[3] !== 0x46 ||
                $exif_data[4] !== 0
            ) {
                return;
            }
            // Read JFIF body
            for ($i = 0; $i < $length; $i++) {
                $exif_data[$i] = self::read_1_byte($fh);
            }

            if (self::read_1_byte($fh) !== 0) {
                // Seems there is a 0 byte to separate segments...
                return;
            }
            // Read next 2 bytes in $exif_data[2] and $exif_data[3] as Exif APP1 segment
            // is now expected
            $exif_data[2] = self::read_1_byte($fh);
            $exif_data[3] = self::read_1_byte($fh);
        }

        // Exif APP1
        if ($exif_data[2] !== 0xFF || $exif_data[3] !== 0xE1) {
            return;
        }

        // Get the marker parameter length count
        $length = self::read_2_bytes($fh);
        // Length includes itself, so must be at least 2
        // Following Exif data length must be at least 6
        if ($length < 8) {
            return;
        }
        $length -= 8;
        // Read Exif head, check for "Exif"
        for ($i = 0; $i < 6; $i++) {
            $exif_data[$i] = self::read_1_byte($fh);
        }
        if ($exif_data[0] !== 0x45 ||
            $exif_data[1] !== 0x78 ||
            $exif_data[2] !== 0x69 ||
            $exif_data[3] !== 0x66 ||
            $exif_data[4] !== 0 ||
            $exif_data[5] !== 0
        ) {
            return;
        }
        // Read Exif body
        for ($i = 0; $i < $length; $i++) {
            $exif_data[$i] = self::read_1_byte($fh);
        }

        if ($length < 12) {    // Length of an IFD entry
            return;
        }

        // Discover byte order
        if ($exif_data[0] === 0x49 && $exif_data[1] === 0x49) {
            $is_motorola = false;
        } elseif ($exif_data[0] === 0x4D && $exif_data[1] === 0x4D) {
            $is_motorola = true;
        } else {
            return;
        }

        // Check Tag mark
        if ($is_motorola) {
            if ($exif_data[2] !== 0) return;
            if ($exif_data[3] !== 0x2A) return;
        } else {
            if ($exif_data[3] !== 0) return;
            if ($exif_data[2] !== 0x2A) return;
        }

        // Get first IFD offset (offset to IFD0)
        if ($is_motorola) {
            if ($exif_data[4] !== 0) return;
            if ($exif_data[5] !== 0) return;
            $offset = $exif_data[6];
            $offset <<= 8;
            $offset += $exif_data[7];
        } else {
            if ($exif_data[7] !== 0) return;
            if ($exif_data[6] !== 0) return;
            $offset = $exif_data[5];
            $offset <<= 8;
            $offset += $exif_data[4];
        }
        // Check end of data segment
        if ($offset > $length - 2) return;

        // Get the number of directory entries contained in this IFD
        if ($is_motorola) {
            $number_of_tags = $exif_data[$offset];
            $number_of_tags <<= 8;
            $number_of_tags += $exif_data[$offset + 1];
        } else {
            $number_of_tags = $exif_data[$offset + 1];
            $number_of_tags <<= 8;
            $number_of_tags += $exif_data[$offset];
        }
        if ($number_of_tags === 0) return;
        $offset += 2;

        // Search for Orientation Tag in IFD0
        while (true) {
            // Check end of data segment
            if ($offset > $length - 12) return;
            // Get Tag number
            if ($is_motorola) {
                $tagnum = $exif_data[$offset];
                $tagnum <<= 8;
                $tagnum += $exif_data[$offset + 1];
            } else {
                $tagnum = $exif_data[$offset + 1];
                $tagnum <<= 8;
                $tagnum += $exif_data[$offset];
            }
            // Found Orientation Tag
            if ($tagnum === 0x0112) break;
            if (--$number_of_tags === 0) return;
            $offset += 12;
        }

        // Set the Orientation value
        if ($is_motorola) {
            $exif_data[$offset + 2] = 0;    // Format = unsigned short (2 octets)
            $exif_data[$offset + 3] = 3;
            $exif_data[$offset + 4] = 0;    // Number of Components = 1
            $exif_data[$offset + 5] = 0;
            $exif_data[$offset + 6] = 0;
            $exif_data[$offset + 7] = 1;
            $exif_data[$offset + 8] = 0;
            $exif_data[$offset + 9] = $orientation;
            $exif_data[$offset + 10] = 0;
            $exif_data[$offset + 11] = 0;
        } else {
            $exif_data[$offset + 2] = 3;    // Format = unsigned short (2 octets)
            $exif_data[$offset + 3] = 0;
            $exif_data[$offset + 4] = 1;    // Number of Components = 1
            $exif_data[$offset + 5] = 0;
            $exif_data[$offset + 6] = 0;
            $exif_data[$offset + 7] = 0;
            $exif_data[$offset + 8] = $orientation;
            $exif_data[$offset + 9] = 0;
            $exif_data[$offset + 10] = 0;
            $exif_data[$offset + 11] = 0;
        }
        fseek($fh, (4 + 2 + 6 + 2) + $offsetJfif + $offset, SEEK_SET);
        $data = '';
        for ($i = 0; $i < 10; $i++) {
            $data .= chr($exif_data[$i + $offset + 2]);
        }
        fwrite($fh, $data);
        fclose($fh);
    }

    /**
     * Reads one byte, testing for EOF.
     *
     * @param resource $handle
     * @return integer
     * @throws \RuntimeException
     */
    protected static function read_1_byte($handle)
    {
        $c = fgetc($handle);
        if ($c === false) {
            throw new \RuntimeException('Premature EOF in JPEG file', 1363533326);
        }
        return ord($c);
    }

    /**
     * Reads 2 bytes, converts them to unsigned int
     * Remark: All 2-byte quantities in JPEG markers are MSB first.
     *
     * @param resource $handle
     * @return integer
     * @throws \RuntimeException
     */
    protected static function read_2_bytes($handle)
    {
        $c1 = fgetc($handle);
        if ($c1 === false) {
            throw new \RuntimeException('Premature EOF in JPEG file', 1363533326);
        }
        $c2 = fgetc($handle);
        if ($c2 === false) {
            throw new \RuntimeException('Premature EOF in JPEG file', 1363533326);
        }
        return (ord($c1) << 8) + (ord($c2));
    }

}


<?php
namespace Muffin\Orderly\Model\Behavior;

use ArrayObject;
use Cake\Event\Event;
use Cake\ORM\Behavior;
use Cake\ORM\Query;

class OrderlyBehavior extends Behavior
{
    /**
     * Initialize behavior
     *
     * @param array $config Config
     * @return void
     */
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->_normalizeConfig($config);
    }

    /**
     * Add default order clause to query as necessary.
     *
     * @param \Cake\Event\Event $event Event
     * @param \Cake\ORM\Query $query Query
     * @param \ArrayObject $options Options
     * @param bool $primary Boolean indicating whether it's primary query.
     * @return void
     */
    public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
    {
        $orders = $this->_config['orders'];

        $args = [$query, $options, $primary];

        foreach ($orders as $config) {
            if ((!empty($config['callback'])
                    && call_user_func_array($config['callback'], $args)
                ) || !$query->clause('order')
            ) {
                $query->order($config['order']);
                break;
            }
        }
    }

    /**
     * Normalize configuration.
     *
     * @param mixed $orders Orders config
     * @return void
     */
    protected function _normalizeConfig($orders)
    {
        if (empty($orders)) {
            $orders = [[]];
        } elseif (isset($orders['order']) || isset($orders['callback'])) {
            $orders = [$orders];
        }

        $default = [
            'order' => $this->_table->aliasField($this->_table->getDisplayField()),
            'callback' => null,
        ];

        foreach ($orders as $key => $value) {
            $orders[$key] = $orders[$key] + $default;
        }

        $this->_config = [
            'orders' => $orders,
        ];
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\adminUi\widget;
use yii;
use yii\helpers\Html;

/**
 * ButtonDropdown renders a group or split button dropdown bootstrap component.
 *
 * For example,
 *
 * ```php
 * // a button group using Dropdown widget
 * echo ButtonDropdown::widget([
 *     'label' => 'Action',
 *     'dropdown' => [
 *         'items' => [
 *             ['label' => 'DropdownA', 'url' => '/'],
 *             ['label' => 'DropdownB', 'url' => '#'],
 *         ],
 *     ],
 * ]);
 * ```
 * @see http://getbootstrap.com/javascript/#buttons
 * @see http://getbootstrap.com/components/#btn-dropdowns
 * @author Antonio Ramirez <amigo.cobos@gmail.com>
 * @since 2.0
 */
class ButtonDropdown extends Widget
{
    /**
     * @var string the button label
     */
    public $label = 'Button';
    /**
     * @var array the HTML attributes of the button.
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $options = [];
    /**
     * @var array the configuration array for [[Dropdown]].
     */
    public $dropdown = [];
    /**
     * @var boolean whether to display a group of split-styled button group.
     */
    public $split = false;

    /**
     * @var boolean whether to display a group of split-styled button group.
     */
    public $group = false;

    /**
     * @var string the tag to use to render the button
     */
    public $tagName = 'button';
    /**
     * @var boolean whether the label should be HTML-encoded.
     */
    public $encodeLabel = true;

    /**
     * Renders the widget.
     */
    public function run()
    {
        if ($this->split) {
            echo $this->renderGroup($this->renderButton() . "\n" . $this->renderDropdown());
        }else if($this->group){
            echo $this->renderGroup($this->renderButton() . "\n" . $this->renderDropdown());
        }else{
            echo $this->renderButton() . "\n" . $this->renderDropdown();
        }
        $this->registerPlugin('button');
    }

    protected function renderGroup($content) {
        echo Html::tag('div', $content, ['class' =>'btn-group']);
    }

    /**
     * Generates the button dropdown.
     * @return string the rendering result.
     */
    protected function renderButton()
    {
        Html::addCssClass($this->options, 'btn');
        $label = $this->label;
        if ($this->encodeLabel) {
            $label = Html::encode($label);
        }
        if ($this->split) {
            $options = $this->options;
            $this->options['data-toggle'] = 'dropdown';
            Html::addCssClass($this->options, 'dropdown-toggle');
            $splitButton = Button::widget([
                'label' => '',
                'encodeLabel' => false,
                'options' => $this->options,
                'view' => $this->getView(),
            ]);
        } else {
            $label .= ' ';
            $options = $this->options;
            if (!isset($options['href'])) {
                $options['href'] = '#';
            }
            Html::addCssClass($options, 'dropdown-toggle');
            $options['data-toggle'] = 'dropdown';
            $splitButton = '';
        }

        return Button::widget([
            'tagName' => $this->tagName,
            'label' => $label,
            'options' => $options,
            'encodeLabel' => false,
            'view' => $this->getView(),
        ]) . "\n" . $splitButton;
    }

    /**
     * Generates the dropdown menu.
     * @return string the rendering result.
     */
    protected function renderDropdown()
    {
        $config = $this->dropdown;
        $config['clientOptions'] = false;
        $config['view'] = $this->getView();

        return Dropdown::widget($config);
    }
}


<?php

/**
 * @file
 * TeamSpeak 3 PHP Framework
 *
 * $Id: TSDNS.php 10/11/2013 11:35:21 scp@orilla $
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package   TeamSpeak3
 * @version   1.1.23
 * @author    Sven 'ScP' Paulsen
 * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved.
 */

namespace TeamSpeak3\Adapter;

use TeamSpeak3\Helper\Profiler;
use TeamSpeak3\Helper\Signal;
use TeamSpeak3\Transport\AbstractTransport;
use TeamSpeak3\Ts3Exception;
use TeamSpeak3\Helper\StringHelper;

/**
 * @class TSDNS
 * @brief Provides methods to query a TSDNS server.
 */
class TSDNS extends AbstractAdapter
{
    /**
     * The TCP port number used by any TSDNS server.
     *
     * @var integer
     */
    protected $default_port = 41144;

    /**
     * Connects the AbstractTransport object and performs initial actions on the remote
     * server.
     *
     * @throws Ts3Exception
     * @return void
     */
    public function syn()
    {
        if (!isset($this->options["port"]) || empty($this->options["port"])) {
            $this->options["port"] = $this->default_port;
        }

        $this->initTransport($this->options);
        $this->transport->setAdapter($this);

        Profiler::init(spl_object_hash($this));

        Signal::getInstance()->emit("tsdnsConnected", $this);
    }

    /**
     * The FileTransfer destructor.
     *
     * @return void
     */
    public function __destruct()
    {
        if ($this->getTransport() instanceof AbstractTransport && $this->getTransport()->isConnected()) {
            $this->getTransport()->disconnect();
        }
    }

    /**
     * Queries the TSDNS server for a specified virtual hostname and returns the result.
     *
     * @param  StringHelper|string $tsdns
     * @throws Ts3Exception
     * @return StringHelper
     */
    public function resolve($tsdns)
    {
        $this->getTransport()->sendLine($tsdns);
        $repl = $this->getTransport()->readLine();
        $this->getTransport()->disconnect();

        if ($repl->section(":", 0)->toInt() == 404) {
            throw new Ts3Exception("unable to resolve TSDNS hostname (" . $tsdns . ")");
        }

        Signal::getInstance()->emit("tsdnsResolved", $tsdns, $repl);

        return $repl;
    }
}


<?php

/**
 * @file
 * TeamSpeak 3 PHP Framework
 *
 * $Id: AbstractAdapter.php 10/11/2013 11:35:21 scp@orilla $
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package   TeamSpeak3
 * @version   1.1.23
 * @author    Sven 'ScP' Paulsen
 * @copyright Copyright (c) 2010 by Planet TeamSpeak. All rights reserved.
 */

namespace TeamSpeak3\Node;

use \RecursiveIterator;
use \ArrayAccess;
use \Countable;
use TeamSpeak3\Adapter\ServerQuery\Reply;
use TeamSpeak3\Adapter\ServerQuery;
use TeamSpeak3\Helper\Convert;
use TeamSpeak3\Helper\StringHelper;
use TeamSpeak3\Ts3Exception;
use TeamSpeak3\Viewer\IViewer;

/**
 * @class AbstractNode
 * @brief Abstract class describing a TeamSpeak 3 node and all it's parameters.
 */
abstract class AbstractNode implements RecursiveIterator, ArrayAccess, Countable
{
    /**
     * @ignore
     */
    protected $parent = null;

    /**
     * @ignore
     */
    protected $server = null;

    /**
     * @ignore
     */
    protected $nodeId = 0x00;

    /**
     * @ignore
     */
    protected $nodeList = null;

    /**
     * @ignore
     */
    protected $nodeInfo = array();

    /**
     * @ignore
     */
    protected $storage = array();

    /**
     * Sends a prepared command to the server and returns the result.
     *
     * @param  string $cmd
     * @param  boolean $throw
     * @return Reply
     */
    public function request($cmd, $throw = true)
    {
        return $this->getParent()->request($cmd, $throw);
    }

    /**
     * Uses given parameters and returns a prepared ServerQuery command.
     *
     * @param  string $cmd
     * @param  array $params
     * @return StringHelper
     */
    public function prepare($cmd, array $params = array())
    {
        return $this->getParent()->prepare($cmd, $params);
    }

    /**
     * Prepares and executes a ServerQuery command and returns the result.
     *
     * @param  string $cmd
     * @param  array $params
     * @return Reply
     */
    public function execute($cmd, array $params = array())
    {
        return $this->request($this->prepare($cmd, $params));
    }

    /**
     * Returns the parent object of the current node.
     *
     * @return ServerQuery
     * @return AbstractNode
     */
    public function getParent()
    {
        return $this->parent;
    }

    /**
     * Returns the primary ID of the current node.
     *
     * @return integer
     */
    public function getId()
    {
        return $this->nodeId;
    }

    /**
     * Returns TRUE if the node icon has a local source.
     *
     * @param  string $key
     * @return boolean
     */
    public function iconIsLocal($key)
    {
        return ($this[$key] > 0 && $this[$key] < 1000) ? true : false;
    }

    /**
     * Returns the internal path of the node icon.
     *
     * @param  string $key
     * @return StringHelper
     */
    public function iconGetName($key)
    {
        $iconid = ($this[$key] < 0) ? (pow(2, 32)) - ($this[$key] * -1) : $this[$key];

        return new StringHelper("/icon_" . $iconid);
    }

    /**
     * Returns a possible classname for the node which can be used as a HTML property.
     *
     * @param  string $prefix
     * @return string
     */
    public function getClass($prefix = "ts3_")
    {
        if ($this instanceof Channel && $this->isSpacer()) {
            return $prefix . "spacer";
        } elseif ($this instanceof Client && $this["client_type"]) {
            return $prefix . "query";
        }

        return $prefix . StringHelper::factory(get_class($this))->toLower();
    }

    /**
     * Returns a unique identifier for the node which can be used as a HTML property.
     *
     * @return string
     */
    abstract public function getUniqueId();

    /**
     * Returns the name of a possible icon to display the node object.
     *
     * @return string
     */
    abstract public function getIcon();

    /**
     * Returns a symbol representing the node.
     *
     * @return string
     */
    abstract public function getSymbol();

    /**
     * Returns the HTML code to display a TeamSpeak 3 viewer.
     *
     * @param  IViewer $viewer
     * @return string
     */
    public function getViewer(IViewer $viewer)
    {
        $html = $viewer->fetchObject($this);

        $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);

        foreach ($iterator as $node) {
            $siblings = array();

            for ($level = 0; $level < $iterator->getDepth(); $level++) {
                $siblings[] = ($iterator->getSubIterator($level)->hasNext()) ? 1 : 0;
            }

            $siblings[] = (!$iterator->getSubIterator($level)->hasNext()) ? 1 : 0;

            $html .= $viewer->fetchObject($node, $siblings);
        }

        return $html;
    }

    /**
     * Filters given node list array using specified filter rules.
     *
     * @param  array $nodes
     * @param  array $rules
     * @return array
     */
    protected function filterList(array $nodes = array(), array $rules = array())
    {
        if (!empty($rules)) {
            foreach ($nodes as $node) {
                if (!$node instanceof AbstractNode) {
                    continue;
                }

                $props = $node->getInfo(false);
                $props = array_intersect_key($props, $rules);
                $match = true;

                foreach ($props as $key => $val) {
                    if ($val instanceof StringHelper) {
                        $match = $val->contains($rules[$key], true);
                    } else {
                        $match = $val == $rules[$key];
                    }

                    if ($match === false) {
                        unset($nodes[$node->getId()]);
                    }
                }
            }
        }

        return $nodes;
    }

    /**
     * Returns all information available on this node. If $convert is enabled, some property
     * values will be converted to human-readable values.
     *
     * @param  boolean $extend
     * @param  boolean $convert
     * @return array
     */
    public function getInfo($extend = true, $convert = false)
    {
        if ($extend) {
            $this->fetchNodeInfo();
        }

        if ($convert) {
            $info = $this->nodeInfo;

            foreach ($info as $key => $val) {
                $key = StringHelper::factory($key);

                if ($key->contains("_bytes_")) {
                    $info[$key->toString()] = Convert::bytes($val);
                } elseif ($key->contains("_bandwidth_")) {
                    $info[$key->toString()] = Convert::bytes($val) . "/s";
                } elseif ($key->contains("_packets_")) {
                    $info[$key->toString()] = number_format($val, null, null, ".");
                } elseif ($key->contains("_packetloss_")) {
                    $info[$key->toString()] = sprintf("%01.2f", floatval($val->toString()) * 100) . "%";
                } elseif ($key->endsWith("_uptime")) {
                    $info[$key->toString()] = Convert::seconds($val);
                } elseif ($key->endsWith("_version")) {
                    $info[$key->toString()] = Convert::version($val);
                } elseif ($key->endsWith("_icon_id")) {
                    $info[$key->toString()] = $this->iconGetName($key)->filterDigits();
                }
            }

            return $info;
        }

        return $this->nodeInfo;
    }

    /**
     * Returns the specified property or a pre-defined default value from the node info array.
     *
     * @param  string $property
     * @param  mixed $default
     * @return mixed
     */
    public function getProperty($property, $default = null)
    {
        if (!$this->offsetExists($property)) {
            $this->fetchNodeInfo();
        }

        if (!$this->offsetExists($property)) {
            return $default;
        }

        return $this->nodeInfo[(string)$property];
    }

    /**
     * Returns a string representation of this node.
     *
     * @return string
     */
    public function __toString()
    {
        return get_class($this);
    }

    /**
     * Returns a string representation of this node.
     *
     * @return string
     */
    public function toString()
    {
        return $this->__toString();
    }

    /**
     * Returns an assoc array filled with current node info properties.
     *
     * @return array
     */
    public function toArray()
    {
        return $this->nodeList;
    }

    /**
     * Called whenever we're using an unknown method.
     *
     * @param  string $name
     * @param  array $args
     * @throws Ts3Exception
     * @return mixed
     */
    public function __call($name, array $args)
    {
        if ($this->getParent() instanceof AbstractNode) {
            return call_user_func_array(array($this->getParent(), $name), $args);
        }

        throw new Ts3Exception("node method '" . $name . "()' does not exist");
    }

    /**
     * Writes data to the internal storage array.
     *
     * @param  string $key
     * @param  mixed $val
     * @return void
     */
    protected function setStorage($key, $val)
    {
        $this->storage[$key] = $val;
    }

    /**
     * Returns data from the internal storage array.
     *
     * @param  string $key
     * @param  mixed $default
     * @return mixed
     */
    protected function getStorage($key, $default = null)
    {
        return (array_key_exists(
                $key,
                $this->storage
            ) && !empty($this->storage[$key])) ? $this->storage[$key] : $default;
    }

    /**
     * Deletes data from the internal storage array.
     *
     * @param  string $key
     * @return void
     */
    protected function delStorage($key)
    {
        unset($this->storage[$key]);
    }

    /**
     * Commit pending data.
     *
     * @return array
     */
    public function __sleep()
    {
        return array("parent", "storage", "nodeId");
    }

    /**
     * @ignore
     */
    protected function fetchNodeList()
    {
        $this->nodeList = array();
    }

    /**
     * @ignore
     */
    protected function fetchNodeInfo()
    {
        return;
    }

    /**
     * @ignore
     */
    protected function resetNodeInfo()
    {
        $this->nodeInfo = array();
    }

    /**
     * @ignore
     */
    protected function verifyNodeList()
    {
        if ($this->nodeList === null) {
            $this->fetchNodeList();
        }
    }

    /**
     * @ignore
     */
    protected function resetNodeList()
    {
        $this->nodeList = null;
    }

    /**
     * @ignore
     */
    public function count()
    {
        $this->verifyNodeList();

        return count($this->nodeList);
    }

    /**
     * @ignore
     */
    public function current()
    {
        $this->verifyNodeList();

        return current($this->nodeList);
    }

    /**
     * @ignore
     */
    public function getChildren()
    {
        $this->verifyNodeList();

        return $this->current();
    }

    /**
     * @ignore
     */
    public function hasChildren()
    {
        $this->verifyNodeList();

        return $this->current()->count() > 0;
    }

    /**
     * @ignore
     */
    public function hasNext()
    {
        $this->verifyNodeList();

        return $this->key() + 1 < $this->count();
    }

    /**
     * @ignore
     */
    public function key()
    {
        $this->verifyNodeList();

        return key($this->nodeList);
    }

    /**
     * @ignore
     */
    public function valid()
    {
        $this->verifyNodeList();

        return $this->key() !== null;
    }

    /**
     * @ignore
     */
    public function next()
    {
        $this->verifyNodeList();

        return next($this->nodeList);
    }

    /**
     * @ignore
     */
    public function rewind()
    {
        $this->verifyNodeList();

        return reset($this->nodeList);
    }

    /**
     * @ignore
     */
    public function offsetExists($offset)
    {
        return array_key_exists((string)$offset, $this->nodeInfo) ? true : false;
    }

    /**
     * @ignore
     */
    public function offsetGet($offset)
    {
        if (!$this->offsetExists($offset)) {
            $this->fetchNodeInfo();
        }

        if (!$this->offsetExists($offset)) {
            throw new Ts3Exception("invalid parameter", 0x602);
        }

        return $this->nodeInfo[(string)$offset];
    }

    /**
     * @ignore
     */
    public function offsetSet($offset, $value)
    {
        if (method_exists($this, "modify")) {
            return $this->modify(array((string)$offset => $value));
        }

        throw new Ts3Exception("node '" . get_class($this) . "' is read only");
    }

    /**
     * @ignore
     */
    public function offsetUnset($offset)
    {
        unset($this->nodeInfo[(string)$offset]);
    }

    /**
     * @ignore
     */
    public function __get($offset)
    {
        return $this->offsetGet($offset);
    }

    /**
     * @ignore
     */
    public function __set($offset, $value)
    {
        $this->offsetSet($offset, $value);
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\adminUi\widget;

use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\adminUi\assetsBundle\AdminUiAsset;
/**
 * Progress renders a bootstrap progress bar component.
 *
 * For example,
 *
 * ```php
 * // default with label
 * echo Progress::widget([
 *     'percent' => 60,
 *     'label' => 'test',
 * ]);
 *
 * // styled
 * echo Progress::widget([
 *     'percent' => 65,
 *     'barOptions' => ['class' => 'progress-bar-danger']
 * ]);
 *
 * // striped
 * echo Progress::widget([
 *     'percent' => 70,
 *     'barOptions' => ['class' => 'progress-bar-warning'],
 *     'options' => ['class' => 'progress-striped']
 * ]);
 *
 * // striped animated
 * echo Progress::widget([
 *     'percent' => 70,
 *     'barOptions' => ['class' => 'progress-bar-success'],
 *     'options' => ['class' => 'active progress-striped']
 * ]);
 *
 * // stacked bars
 * echo Progress::widget([
 *     'bars' => [
 *         ['percent' => 30, 'options' => ['class' => 'progress-bar-danger']],
 *         ['percent' => 30, 'label' => 'test', 'options' => ['class' => 'progress-bar-success']],
 *         ['percent' => 35, 'options' => ['class' => 'progress-bar-warning']],
 *     ]
 * ]);
 * ```
 * @see http://getbootstrap.com/components/#progress
 * @author Antonio Ramirez <amigo.cobos@gmail.com>
 * @author Alexander Makarov <sam@rmcreative.ru>
 * @since 2.0
 */
class Progress extends Widget
{
    const HORIZONTAL = 1;
    const VERTICLE = 2;

    const DEFAULT_ORIENTATION = 1;

    /**
     * @var string the button label.
     */
    public $orientation = self::DEFAULT_ORIENTATION;

    /**
     * @var string the button label.
     */
    public $label;
    /**
     * @var integer the amount of progress as a percentage.
     */
    public $percent = 0;
    /**
     * @var array the HTML attributes of the bar.
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $barOptions = [];
    /**
     * @var array a set of bars that are stacked together to form a single progress bar.
     * Each bar is an array of the following structure:
     *
     * ```php
     * [
     *     // required, the amount of progress as a percentage.
     *     'percent' => 30,
     *     // optional, the label to be displayed on the bar
     *     'label' => '30%',
     *     // optional, array, additional HTML attributes for the bar tag
     *     'options' => [],
     * ]
     * ```
     */
    public $bars;

    /**
     * Initializes the widget.
     * If you override this method, make sure you call the parent implementation first.
     */
    public function init()
    {
        parent::init();
        Html::addCssClass($this->options, 'progress');
        if($this->orientation == self::VERTICLE){
            Html::addCssClass($this->options, 'vertical');
        }
    }

    /**
     * Renders the widget.
     */
    public function run()
    {
        echo Html::beginTag('div', $this->options) . "\n";
        echo $this->renderProgress() . "\n";
        echo Html::endTag('div') . "\n";
        AdminUiAsset::register($this->getView());
    }

    /**
     * Renders the progress.
     * @return string the rendering result.
     * @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar.
     */
    protected function renderProgress()
    {
        if (empty($this->bars)) {
            return $this->renderBar($this->percent, $this->label, $this->barOptions);
        }
        $bars = [];
        foreach ($this->bars as $bar) {
            $label = ArrayHelper::getValue($bar, 'label', '');
            if (!isset($bar['percent'])) {
                throw new InvalidConfigException("The 'percent' option is required.");
            }
            $options = ArrayHelper::getValue($bar, 'options', []);
            $bars[] = $this->renderBar($bar['percent'], $label, $options);
        }

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

    /**
     * Generates a bar
     * @param integer $percent the percentage of the bar
     * @param string $label, optional, the label to display at the bar
     * @param array $options the HTML attributes of the bar
     * @return string the rendering result.
     */
    protected function renderBar($percent, $label = '', $options = [])
    {
        $defaultOptions = [
            'role' => 'progressbar',
            'aria-valuenow' => $percent,
            'aria-valuemin' => 0,
            'aria-valuemax' => 100,
            'style' => ($this->orientation == self::HORIZONTAL)? "width:{$percent}%" : "height:{$percent}%",
        ];

        $options = array_merge($defaultOptions, $options);
        Html::addCssClass($options, 'progress-bar');

        $out = Html::beginTag('div', $options);
        $out .= $label;
        $out .= Html::tag('span', \Yii::t('yii', '{percent}% Complete', ['percent' => $percent]), [
            'class' => 'sr-only'
        ]);
        $out .= Html::endTag('div');

        return $out;
    }
}


<?php

/*
 * @author Mithun Mandal <mithun12000@gmail.com>
 * @project AdminUi
 * @projecturl https://github.com/mithun12000/adminUI
 * @country India
 */

namespace yii\adminUi\widget;

use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;

/**
 * Callout renders an callout bootstrap element.
 *
 * For example,
 *
 * ```php
 * echo Callout::widget([
 *     'options' => [
 *         'class' => 'callout-danger',
 *     ],
 *     'header' => 'hi callout',
 *     'body' => 'Say hello...',
 * ]);
 * ```
 *
 * The following example will show the content enclosed between the [[begin()]]
 * and [[end()]] calls within the alert box:
 *
 * ```php
 * Callout::begin([
 *     'options' => [
 *         'class' => 'alert-warning',
 *     ],
 *     'header' => 'hi callout'
 * ]);
 *
 * echo 'Say hello...';
 *
 * Callout::end();
 * ```
 *
 * @see http://getbootstrap.com/components/#alerts
 * @author Mithun Mandal <mithun12000@gmail.com>
 * @since 2.0
 */
class Callout extends Widget
{
    /**
     * @var string the body content in the alert component. Note that anything between
     * the [[begin()]] and [[end()]] calls of the Alert widget will also be treated
     * as the body content, and will be rendered before this.
     */
    public $body;
    /**
     * @var string the header content in the callout component.
     */
    public $header;

    /**
     * Initializes the widget.
     */
    public function init()
    {
        parent::init();

        $this->initOptions();

        echo Html::beginTag('div', $this->options) . "\n";
        echo $this->renderHeaderBegin() . "\n";
    }

    /**
     * Renders the widget.
     */
    public function run()
    {
        echo "\n" . $this->renderBodyEnd();
        echo "\n" . Html::endTag('div');
    }

    /**
     * Renders the close button if any before rendering the content.
     * @return string the rendering result
     */
    protected function renderHeaderBegin()
    {
        if($this->header){
            return Html::tag('h4',  $this->header);
        }else{
            return null;
        }
    }

    /**
     * Renders the alert body (if any).
     * @return string the rendering result
     */
    protected function renderBodyEnd()
    {
        return $this->body . "\n";
    }

    /**
     * Initializes the widget options.
     * This method sets the default values for various options.
     */
    protected function initOptions()
    {
        Html::addCssClass($this->options, 'callout');
    }
}