vendor/terminal42/contao-fineuploader/widgets/FineUploaderBase.php line 37

Open in your IDE?
  1. <?php
  2. /**
  3.  * fineuploader extension for Contao Open Source CMS
  4.  *
  5.  * @copyright  Copyright (c) 2008-2015, terminal42 gmbh
  6.  * @author     terminal42 gmbh <info@terminal42.ch>
  7.  * @license    http://opensource.org/licenses/lgpl-3.0.html LGPL
  8.  * @link       http://github.com/terminal42/contao-fineuploader
  9.  */
  10. /**
  11.  * Class FineUploaderBase
  12.  *
  13.  * Parent class for "fine uploader" widgets.
  14.  */
  15. abstract class FineUploaderBase extends \Widget
  16. {
  17.     /**
  18.      * Temporary upload path
  19.      * @var string
  20.      */
  21.     protected $strTemporaryPath 'system/tmp';
  22.     /**
  23.      * Initialize the object
  24.      * @param array
  25.      */
  26.     public function __construct($arrAttributes=null)
  27.     {
  28.         $arrAttributes['decodeEntities'] = true;
  29.         parent::__construct($arrAttributes);
  30.         // Clean the chunks session when the widget is initialized
  31.         if (!\Environment::get('isAjaxRequest')) {
  32.             unset($_SESSION[$this->strName '_FINEUPLOADER_CHUNKS']);
  33.         }
  34.     }
  35.     /**
  36.      * Add the labels and messages.
  37.      *
  38.      * @param null $arrAttributes
  39.      */
  40.     public function parse($arrAttributes null)
  41.     {
  42.         // Messages (passed on to fineuploader JS)
  43.         $basicTextOptions = array(
  44.             'text'  => array(
  45.                 'formatProgress',
  46.                 'failUpload',
  47.                 'waitingForResponse',
  48.                 'paused',
  49.             ),
  50.             'messages'  => array(
  51.                 'typeError',
  52.                 'sizeError',
  53.                 'minSizeError',
  54.                 'emptyError',
  55.                 'noFilesError',
  56.                 'tooManyItemsError',
  57.                 'maxHeightImageError',
  58.                 'maxWidthImageError',
  59.                 'minHeightImageError',
  60.                 'minWidthImageError',
  61.                 'retryFailTooManyItems',
  62.                 'onLeave',
  63.                 'unsupportedBrowserIos8Safari',
  64.             ),
  65.             'retry' => array(
  66.                 'autoRetryNote',
  67.             ),
  68.             'deleteFile' => array(
  69.                 'confirmMessage',
  70.                 'deletingStatusText',
  71.                 'deletingFailedText',
  72.             ),
  73.             'paste' => array(
  74.                 'namePromptMessage',
  75.             )
  76.         );
  77.         $config = array();
  78.         foreach ($basicTextOptions as $category => $messages) {
  79.             foreach ($messages as $message) {
  80.                 // Only translate if available, otherwise fall back to default (EN)
  81.                 if (isset($GLOBALS['TL_LANG']['MSC']['fineuploader_trans'][$category][$message])) {
  82.                     $config[$category][$message] = $GLOBALS['TL_LANG']['MSC']['fineuploader_trans'][$category][$message];
  83.                 }
  84.             }
  85.         }
  86.         // BC (used to be a JSON string)
  87.         if (isset($this->arrConfiguration['uploaderConfig'])
  88.             && $this->arrConfiguration['uploaderConfig'] !== ''
  89.         ) {
  90.             $this->arrConfiguration['uploaderConfig'] = json_decode('{' $this->arrConfiguration['uploaderConfig'] . '}'true);
  91.         }
  92.         // Merge with custom options
  93.         $this->config json_encode(array_merge($config, (array) $this->arrConfiguration['uploaderConfig']));
  94.         // Labels (in HTML)
  95.         $labels = array(
  96.             'drop',
  97.             'upload',
  98.             'processing',
  99.             'cancel',
  100.             'retry',
  101.             'delete',
  102.             'close',
  103.             'yes',
  104.             'no',
  105.         );
  106.         $preparedLabels = array();
  107.         foreach ($labels as $label) {
  108.             $preparedLabels[$label] = $GLOBALS['TL_LANG']['MSC']['fineuploader_' $label];
  109.         }
  110.         // Set the upload button label
  111.         if ($this->uploadButtonLabel) {
  112.             $preparedLabels['upload'] = $this->uploadButtonLabel;
  113.         }
  114.         $this->labels $preparedLabels;
  115.         return parent::parse($arrAttributes);
  116.     }
  117.     /**
  118.      * Add the required attribute if mandatory
  119.      *
  120.      * @param string
  121.      * @param mixed
  122.      */
  123.     public function __set($strKey$varValue)
  124.     {
  125.         switch ($strKey) {
  126.             case 'mandatory':
  127.                 if ($varValue) {
  128.                     $this->arrAttributes['required'] = 'required';
  129.                 } else {
  130.                     unset($this->arrAttributes['required']);
  131.                 }
  132.                 // DO NOT BREAK HERE
  133.         }
  134.         parent::__set($strKey$varValue);
  135.     }
  136.     /**
  137.      * Validate the upload
  138.      * @return string
  139.      */
  140.     public function validateUpload()
  141.     {
  142.         \Message::reset();
  143.         $strTempName $this->strName '_fineuploader';
  144.         $objUploader = new \Haste\Util\FileUpload($this->strName);
  145.         $blnIsChunk = isset($_POST['qqpartindex']);
  146.         // Convert the $_FILES array to Contao format
  147.         if (!empty($_FILES[$strTempName])) {
  148.             $arrFile = array
  149.             (
  150.                 'name' => array($_FILES[$strTempName]['name']),
  151.                 'type' => array($_FILES[$strTempName]['type']),
  152.                 'tmp_name' => array($_FILES[$strTempName]['tmp_name']),
  153.                 'error' => array($_FILES[$strTempName]['error']),
  154.                 'size' => array($_FILES[$strTempName]['size']),
  155.             );
  156.             // Replace the comma character (#22)
  157.             $arrFile['name'] = str_replace(',''_'$arrFile['name']);
  158.             // Set the UUID as the filename
  159.             if ($blnIsChunk) {
  160.                 $arrFile['name'][0] = \Input::post('qquuid') . '.chunk';
  161.             }
  162.             // Check if the file exists
  163.             if (file_exists(TL_ROOT '/' $this->strTemporaryPath '/' $arrFile['name'][0])) {
  164.                 $arrFile['name'][0] = $this->getFileName($arrFile['name'][0], $this->strTemporaryPath);
  165.             }
  166.             $_FILES[$this->strName] = $arrFile;
  167.             unset($_FILES[$strTempName]); // Unset the temporary file
  168.         }
  169.         $varInput '';
  170.         // Add the "chunk" extension to upload types
  171.         if ($blnIsChunk) {
  172.             $extensions   trimsplit(','$GLOBALS['TL_CONFIG']['uploadTypes']);
  173.             $extensions[] = 'chunk';
  174.             $objUploader->setExtensions($extensions);
  175.         }
  176.         // Validate the minlength
  177.         if ($this->arrConfiguration['minlength'] > && !$blnIsChunk) {
  178.             $objUploader->setMinFileSize($this->arrConfiguration['minlength']);
  179.         }
  180.         // Validate the maxlength
  181.         if ($this->arrConfiguration['maxlength'] > || $blnIsChunk) {
  182.             $objUploader->setMaxFileSize($blnIsChunk $this->arrConfiguration['chunkSize'] : $this->arrConfiguration['maxlength']);
  183.         }
  184.         // Set the maximum width
  185.         if ($this->arrConfiguration['maxWidth']) {
  186.             $objUploader->setImageWidth($this->arrConfiguration['maxWidth']);
  187.         }
  188.         // Set the maximum height
  189.         if ($this->arrConfiguration['maxHeight']) {
  190.             $objUploader->setImageHeight($this->arrConfiguration['maxHeight']);
  191.         }
  192.         try {
  193.             $varInput $objUploader->uploadTo($this->strTemporaryPath);
  194.             if ($objUploader->hasError()) {
  195.                 if (version_compare(VERSION'4.2''>=')) {
  196.                     $session = \System::getContainer()->get('session');
  197.                     foreach ($session->getFlashBag()->peek('contao.'.TL_MODE.'.error') as $strError) {
  198.                         $this->addError($strError);
  199.                     }
  200.                 } else {
  201.                     foreach ((array) $_SESSION['TL_ERROR'] as $strError) {
  202.                         $this->addError($strError);
  203.                     }
  204.                 }
  205.             }
  206.             \Message::reset();
  207.         } catch (\Exception $e) {
  208.             $this->addError($e->getMessage());
  209.         }
  210.         if (!is_array($varInput) || empty($varInput)) {
  211.             $this->addError($GLOBALS['TL_LANG']['MSC']['fineuploader_error']);
  212.         }
  213.         $varInput $varInput[0];
  214.         // Store the chunk in the session for further merge
  215.         if ($blnIsChunk) {
  216.             $_SESSION[$this->strName '_FINEUPLOADER_CHUNKS'][\Input::post('qqfilename')][] = $varInput;
  217.             // This is the last chunking request, merge the chunks and create the final file
  218.             if (\Input::post('qqpartindex') == \Input::post('qqtotalparts') - 1) {
  219.                 $strFileName = \Input::post('qqfilename');
  220.                 // Get the new file name
  221.                 if (file_exists(TL_ROOT '/' $this->strTemporaryPath '/' $strFileName)) {
  222.                     $strFileName $this->getFileName($strFileName$this->strTemporaryPath);
  223.                 }
  224.                 $objFile = new \File($this->strTemporaryPath '/' $strFileName);
  225.                 // Merge the chunks
  226.                 foreach ($_SESSION[$this->strName '_FINEUPLOADER_CHUNKS'][\Input::post('qqfilename')] as $strChunk) {
  227.                     $objFile->append(file_get_contents(TL_ROOT '/' $strChunk), '');
  228.                     // Delete the file
  229.                     \Files::getInstance()->delete($strChunk);
  230.                 }
  231.                 $objFile->close();
  232.                 $varInput $objFile->path;
  233.                 // Validate the minlength
  234.                 if ($this->arrConfiguration['minlength'] > && $objFile->size $this->arrConfiguration['minlength']) {
  235.                     $readableSize = \System::getReadableSize($this->arrConfiguration['minlength']);
  236.                     $this->addError(sprintf($GLOBALS['TL_LANG']['ERR']['minFileSize'], $readableSize));
  237.                     \System::log('File "'.$objFile->name.'" is smaller than the minimum file size of '.$readableSize__METHOD__TL_ERROR);
  238.                 }
  239.                 // Validate the maxlength
  240.                 if ($this->arrConfiguration['maxlength'] > && $objFile->size $this->arrConfiguration['maxlength']) {
  241.                     $readableSize = \System::getReadableSize($this->arrConfiguration['maxlength']);
  242.                     $this->addError(sprintf($GLOBALS['TL_LANG']['ERR']['maxFileSize'], $readableSize));
  243.                     \System::log('File "'.$objFile->name.'" exceeds the maximum file size of '.$readableSize__METHOD__TL_ERROR);
  244.                 }
  245.                 // Reset the chunk flag
  246.                 $blnIsChunk false;
  247.                 // Unset the file session after merging the chunks
  248.                 unset($_SESSION[$this->strName '_FINEUPLOADER_CHUNKS'][\Input::post('qqfilename')]);
  249.             }
  250.         }
  251.         // Validate and move the file immediately
  252.         if ($this->arrConfiguration['directUpload'] && !$blnIsChunk) {
  253.             $varInput $this->validatorSingle($varInput$this->getDestinationFolder());
  254.         }
  255.         return $varInput;
  256.     }
  257.     /**
  258.      * Return an array if the "multiple" attribute is set
  259.      * @param mixed
  260.      * @return mixed
  261.      */
  262.     protected function validator($varInput)
  263.     {
  264.         $varReturn $this->blnIsMultiple ? array() : '';
  265.         $strDestination $this->getDestinationFolder();
  266.         // Check if mandatory
  267.         if ($varInput == '' && $this->mandatory) {
  268.             $this->addError(sprintf($GLOBALS['TL_LANG']['ERR']['mandatory'], $this->strLabel));
  269.         }
  270.         // Single file
  271.         elseif (strpos($varInput',') === false) {
  272.             $varInput $this->validatorSingle($varInput$strDestination);
  273.             $varReturn $this->blnIsMultiple ? array($varInput) : $varInput;
  274.         }
  275.         // Multiple files
  276.         else {
  277.             $arrValues array_filter(explode(','$varInput));
  278.             // Limit the number of uploads
  279.             if ($this->arrConfiguration['uploaderLimit'] > 0) {
  280.                 $arrValues array_slice($arrValues0$this->arrConfiguration['uploaderLimit']);
  281.             }
  282.             foreach ($arrValues as $k => $v) {
  283.                 $arrValues[$k] = $this->validatorSingle($v$strDestination);
  284.             }
  285.             $varReturn $this->blnIsMultiple $arrValues $arrValues[0];
  286.         }
  287.         return $varReturn;
  288.     }
  289.     /**
  290.      * Get the destination folder
  291.      *
  292.      * @return mixed
  293.      */
  294.     protected function getDestinationFolder()
  295.     {
  296.         $destination = \Config::get('uploadPath');
  297.         $folder null;
  298.         // Specify the target folder in the DCA (eval)
  299.         if (isset($this->arrConfiguration['uploadFolder'])) {
  300.             $folder $this->arrConfiguration['uploadFolder'];
  301.         }
  302.         // Use the user's home directory
  303.         if ($this->arrConfiguration['useHomeDir'] && FE_USER_LOGGED_IN) {
  304.             $user FrontendUser::getInstance();
  305.             if ($user->assignDir && $user->homeDir) {
  306.                 $folder $user->homeDir;
  307.             }
  308.         }
  309.         if ($folder !== null && \Validator::isUuid($folder)) {
  310.             $folderModel = \FilesModel::findByUuid($folder);
  311.             if ($folderModel !== null) {
  312.                 $destination $folderModel->path;
  313.             }
  314.         } else {
  315.             $destination $folder;
  316.         }
  317.         return $destination;
  318.     }
  319.     /**
  320.      * Validate a single file.
  321.      *
  322.      * @param mixed
  323.      * @param string
  324.      * @return mixed
  325.      */
  326.     protected function validatorSingle($varFile$strDestination)
  327.     {
  328.         // Move the temporary file
  329.         if (!\Validator::isStringUuid($varFile) && is_file(TL_ROOT '/' $varFile)) {
  330.             $varFile $this->moveTemporaryFile($varFile$strDestination);
  331.         }
  332.         // Convert uuid to binary format
  333.         if (\Validator::isStringUuid($varFile)) {
  334.             $varFile = \StringUtil::uuidToBin($varFile);
  335.         }
  336.         return $varFile;
  337.     }
  338.     /**
  339.      * Move the temporary file to its destination
  340.      * @param string
  341.      * @param string
  342.      * @return string
  343.      */
  344.     protected function moveTemporaryFile($strFile$strDestination)
  345.     {
  346.         if (!is_file(TL_ROOT '/' $strFile)) {
  347.             return '';
  348.         }
  349.         // Do not store the file
  350.         if (!$this->arrConfiguration['storeFile']) {
  351.             return $strFile;
  352.         }
  353.         // The file is not temporary
  354.         if (stripos($strFile$this->strTemporaryPath) === false) {
  355.             return $strFile;
  356.         }
  357.         $strNew $strDestination '/' basename($strFile);
  358.         // Do not overwrite existing files
  359.         if ($this->arrConfiguration['doNotOverwrite']) {
  360.             $strNew $strDestination '/' $this->getFileName(basename($strFile), $strDestination);
  361.         }
  362.         $blnRename = \Files::getInstance()->rename($strFile$strNew);
  363.         \Files::getInstance()->chmod($strNew, \Config::get('defaultFileChmod'));
  364.         // Add the file to Dbafs
  365.         if ($this->arrConfiguration['addToDbafs'] && $blnRename) {
  366.             $objModel = \Dbafs::addResource($strNew);
  367.             if ($objModel !== null) {
  368.                 $strNew $objModel->uuid;
  369.             }
  370.         }
  371.         return $strNew;
  372.     }
  373.     /**
  374.      * Get the new file name if it already exists in the folder
  375.      * @param string
  376.      * @param string
  377.      * @return string
  378.      */
  379.     protected function getFileName($strFile$strFolder)
  380.     {
  381.         if (!file_exists(TL_ROOT '/' $strFolder '/' $strFile)) {
  382.             return $strFile;
  383.         }
  384.         $offset 1;
  385.         $pathinfo pathinfo($strFile);
  386.         $name $pathinfo['filename'];
  387.         $arrAll scan(TL_ROOT '/' $strFolder);
  388.         $arrFiles preg_grep('/^' preg_quote($name'/') . '.*\.' preg_quote($pathinfo['extension'], '/') . '/'$arrAll);
  389.         foreach ($arrFiles as $file) {
  390.             if (preg_match('/__[0-9]+\.' preg_quote($pathinfo['extension'], '/') . '$/'$file)) {
  391.                 $file str_replace('.' $pathinfo['extension'], ''$file);
  392.                 $intValue intval(substr($file, (strrpos($file'_') + 1)));
  393.                 $offset max($offset$intValue);
  394.             }
  395.         }
  396.         return str_replace($name$name '__' . ++$offset$strFile);
  397.     }
  398.     /**
  399.      * Generate a file item and return it as HTML string
  400.      * @param string
  401.      * @return string
  402.      */
  403.     protected function generateFileItem($strPath)
  404.     {
  405.         if (!is_file(TL_ROOT '/' $strPath)) {
  406.             return '';
  407.         }
  408.         $imageSize $this->getImageSize();
  409.         $objFile = new \File($strPathtrue);
  410.         $strInfo $strPath ' <span class="tl_gray">(' . \System::getReadableSize($objFile->size) . ($objFile->isGdImage ', ' $objFile->width 'x' $objFile->height ' px' '') . ')</span>';
  411.         $allowedDownload trimsplit(','strtolower($GLOBALS['TL_CONFIG']['allowedDownload']));
  412.         $strReturn '';
  413.         // Show files and folders
  414.         if (!$this->blnIsGallery && !$this->blnIsDownloads) {
  415.             if ($objFile->isGdImage) {
  416.                 $strReturn = \Image::getHtml(\Image::get($strPath$imageSize[0], $imageSize[1], $imageSize[2]), '''class="gimage" title="' specialchars($strInfo) . '"');
  417.             } else {
  418.                 $strReturn = \Image::getHtml($objFile->icon) . ' ' $strInfo;
  419.             }
  420.         }
  421.         // Show a sortable list of files only
  422.         else {
  423.             if ($this->blnIsGallery) {
  424.                 // Only show images
  425.                 if ($objFile->isGdImage) {
  426.                     $strReturn = \Image::getHtml(\Image::get($strPath$imageSize[0], $imageSize[1], $imageSize[2]), '''class="gimage" title="' specialchars($strInfo) . '"');
  427.                 }
  428.             } else {
  429.                 // Only show allowed download types
  430.                 if (in_array($objFile->extension$allowedDownload) && !preg_match('/^meta(_[a-z]{2})?\.txt$/'$objFile->basename)) {
  431.                     $strReturn = \Image::getHtml($objFile->icon) . ' ' $strPath;
  432.                 }
  433.             }
  434.         }
  435.         return $strReturn;
  436.     }
  437.     /**
  438.      * Get the image size
  439.      *
  440.      * @return array
  441.      */
  442.     protected function getImageSize()
  443.     {
  444.         if (is_array($this->imageSize)) {
  445.             return $this->imageSize;
  446.         }
  447.         return [8060'center_center'];
  448.     }
  449. }