luyadev/luya-module-admin

View on GitHub
src/helpers/Storage.php

Summary

Maintainability
B
4 hrs
Test Coverage
F
42%
<?php

namespace luya\admin\helpers;

use InvalidArgumentException;
use luya\admin\file\Item;
use luya\admin\models\StorageFile;
use luya\admin\models\StorageImage;
use luya\admin\Module;
use luya\Exception;
use luya\helpers\FileHelper;
use Yii;

/**
 * Helper class to handle remove, upload and moving of storage files.
 *
 * The class provides common functions in order to work with the Storage component. This helper method will only work
 * if the {{luya\admin\storage\BaseFileSystemStorage}} component is registered which is by default the case when the LUYA
 * admin module is provided.
 *
 * @author Basil Suter <basil@nadar.io>
 * @since 1.0.0
 */
class Storage
{
    /**
     * Get the file upload error messages.
     *
     * @return array All possible error codes when uploading files with its given message and meaning.
     */
    public static function getUploadErrorMessages()
    {
        return [
            UPLOAD_ERR_OK => Module::t('upload_err_message_0'),
            UPLOAD_ERR_INI_SIZE => Module::t('upload_err_message_1'),
            UPLOAD_ERR_FORM_SIZE =>  Module::t('upload_err_message_2'),
            UPLOAD_ERR_PARTIAL =>  Module::t('upload_err_message_3'),
            UPLOAD_ERR_NO_FILE =>  Module::t('upload_err_message_4'),
            UPLOAD_ERR_NO_TMP_DIR =>  Module::t('upload_err_message_6'),
            UPLOAD_ERR_CANT_WRITE =>  Module::t('upload_err_message_7'),
            UPLOAD_ERR_EXTENSION =>  Module::t('upload_err_message_8'),
        ];
    }

    /**
     * Get the upload error message from a given $_FILES error code id.
     *
     * @param integer $errorId
     * @return string
     */
    public static function getUploadErrorMessage($errorId)
    {
        $messagesArray = self::getUploadErrorMessages();
        return $messagesArray[$errorId] ?? 'Unknown upload error.';
    }

    /**
     * Remove a file from the storage system.
     *
     * @param integer $fileId The file id to delete
     * @param boolean $cleanup If cleanup is enabled, also all images will be deleted, this is by default turned off because
     * casual you want to remove the large source file but not the images where used in several tables and situations.
     * @return boolean
     */
    public static function removeFile($fileId, $cleanup = false)
    {
        $model = StorageFile::find()->where(['id' => $fileId, 'is_deleted' => false])->one();
        if ($model) {
            if ($cleanup) {
                foreach (Yii::$app->storage->findImages(['file_id' => $fileId]) as $imageItem) {
                    StorageImage::findOne($imageItem->id)->delete();
                }
            }
            $response = $model->delete();
            Yii::$app->storage->flushArrays();
            return $response;
        }

        return true;
    }

    /**
     * Remove an image from the storage system and database.
     *
     * @param integer $imageId The corresponding imageId for the {{\luya\admin\models\StorageImage}} Model to remove.
     * @param boolean $cleanup If cleanup is enabled, all other images will be deleted. Even the {{\luya\admin\models\StorageFile}} will be removed
     * from the database and filesystem. By default cleanup is disabled and will only remove the provided $imageId itself from {{\luya\admin\models\StorageImage}}.
     * @return boolean
     */
    public static function removeImage($imageId, $cleanup = false)
    {
        Yii::$app->storage->flushArrays();
        $image = Yii::$app->storage->getImage($imageId);
        if ($cleanup && $image) {
            $fileId = $image->fileId;
            foreach (Yii::$app->storage->findImages(['file_id' => $fileId]) as $imageItem) {
                $storageImage = StorageImage::findOne($imageItem->id);
                if ($storageImage) {
                    $storageImage->delete();
                }
            }
        }

        $file = StorageImage::findOne($imageId);
        if ($file) {
            return $file->delete();
        }

        return false;
    }

    /**
     * Get the image resolution of a given file path.
     *
     * @param string $filePath
     * @param bool $throwException
     * @return array
     * @throws Exception
     */
    public static function getImageResolution($filePath, $throwException = false)
    {
        $dimensions = @getimagesize(Yii::getAlias($filePath));

        $width = 0;
        $height = 0;

        if (isset($dimensions[0]) && isset($dimensions[1])) {
            $width = (int)$dimensions[0];
            $height = (int)$dimensions[1];
        } elseif ($throwException) {
            throw new Exception("Unable to determine the resolutions of the file $filePath.");
        }

        return [
            'width' => $width,
            'height' => $height,
        ];
    }

    /**
     * Move an array of storage fileIds to another folder.
     *
     * @param array $fileIds
     * @param integer $folderId
     */
    public static function moveFilesToFolder(array $fileIds, $folderId)
    {
        foreach ($fileIds as $fileId) {
            static::moveFileToFolder($fileId, $folderId);
        }
    }

    /**
     * Move a storage file to another folder.
     *
     * @param string|int $fileId
     * @param string|int $folderId
     * @return boolean
     */
    public static function moveFileToFolder($fileId, $folderId)
    {
        $file = StorageFile::findOne($fileId);

        if ($file) {
            $file->updateAttributes(['folder_id' => $folderId]);
            Yii::$app->storage->flushArrays();
            return true;
        }

        return false;
    }

    /**
     * Replace the source of a file on the webeserver based on new and old source path informations.
     *
     * The replaced file will have the name of the $oldFileSource but the file will be the content of the $newFileSource.
     *
     * @param string $fileName The filename identifier key in order to find the file based on the locale files system.
     * @param string $newFileSource The path to the new file which is going to have the same name as the old file e.g. `path/of/new.jpg`.  $_FILES['tmp_name']
     * @param string $newFileName The new name of the file which is uploaded, mostly given from $_FILES['name']
     * @return boolean Whether moving was successful or not.
     */
    public static function replaceFile($fileName, $newFileSource, $newFileName)
    {
        try {
            Yii::$app->storage->ensureFileUpload($newFileSource, $newFileName);
        } catch (\Exception $e) {
            return false;
        }

        return Yii::$app->storage->fileSystemReplaceFile($fileName, $newFileSource);
    }

    /**
     * Update the hash file sum, file size and remove image filter version from this file.
     *
     * @param integer $fileId
     * @param string $fileContent
     * @return boolean
     */
    public static function refreshFile($fileId, $filePath)
    {
        foreach (StorageImage::find()->where(['file_id' => $fileId])->all() as $img) {
            // remove the source
            if ($img->deleteSource()) {
                // recreate image filters
                $img->imageFilter($img->filter_id, false);
            }
        }

        $file = StorageFile::findOne($fileId);

        if (!$file) {
            throw new InvalidArgumentException("Unable to find the given file.");
        }

        $fileHash = FileHelper::md5sum($filePath);
        $fileSize = @filesize($filePath);
        $file->updateAttributes([
            'hash_file' => $fileHash,
            'file_size' => $fileSize,
            'upload_timestamp' => time(),
        ]);
        return true;
    }

    /**
     * Replace the current file based on image data
     *
     * @param string $fileName
     * @param string $newFileContent
     * @return boolean
     * @since 3.1.0
     */
    public static function replaceFileFromContent($fileName, $newFileContent)
    {
        $newFileSource = @tempnam(sys_get_temp_dir(), 'replaceFromFromContent');
        FileHelper::writeFile($newFileSource, $newFileContent);

        try {
            Yii::$app->storage->ensureFileUpload($newFileSource, $fileName);
        } catch (\Exception $e) {
            return false;
        }

        return Yii::$app->storage->fileSystemReplaceFile($fileName, $newFileSource);
    }

    /**
     * Upload a file with content
     *
     * @param string $content
     * @param string $fileName
     * @return Item
     * @since 3.1.0
     */
    public static function uploadFromContent($content, $fileName, $folderId = 0, $isHidden = false)
    {
        $fromTempFile = @tempnam(sys_get_temp_dir(), 'uploadFromContent');
        FileHelper::writeFile($fromTempFile, $content);

        return Yii::$app->storage->addFile($fromTempFile, $fileName, $folderId, $isHidden);
    }

    /**
     * Add File to the storage container by providing the $_FILES array name.
     *
     * Example usage:
     *
     * ```php
     * $return = Storage::uploadFromFileArray($_FILES['image'], 0, true);
     * ``
     *
     * Example response
     *
     * ```php
     * ['upload' => false, 'message' => 'file uploaded succesfully', 'file_id' => 123], // success response example
     * ['upload' => true, 'message' => 'No file was uploaded.', 'file_id' => 0], // error response example
     * ```
     *
     * @param array $fileArray Its an entry of the files array like $_FILES['logo_image'].
     * @param integer $toFolder The id of the folder the file should be uploaded to, see {{luya\admin\storage\BaseFileSystemStorage::findFolders}}
     * @param string $isHidden Whether the file should be hidden or not.
     * @return array An array with key `upload`, `message` and `file_id`. When upload is false, an error occured otherwise true. The message key contains the error messages. If no error happend `file_id` will contain the new uploaded file id.
     */
    public static function uploadFromFileArray(array $fileArray, $toFolder = 0, $isHidden = false)
    {
        $files = self::extractFilesDataFromFilesArray($fileArray);

        if (count($files) !== 1) {
            return ['upload' => false, 'message' => 'no image found', 'file_id' => 0];
        }

        return self::verifyAndSaveFile($files[0], $toFolder, $isHidden);
    }

    /**
     * Add Files to storage component by just providing $_FILES array, used for multi file storage.
     *
     * Example usage:
     *
     * ```php
     * $return = Storage::uploadFromFiles($_FILES, 0, true);
     * ```
     *
     * Example response
     *
     * ```php
     * ['upload' => false, 'message' => 'file uploaded succesfully', 'file_id' => 123], // success response example
     * ['upload' => true, 'message' => 'No file was uploaded.', 'file_id' => 0], // error response example
     * ```
     *
     * @param array $filesArray Use $_FILES array.
     * @param integer $toFolder The id of the folder the file should be uploaded to, see {{luya\admin\storage\BaseFileSystemStorage::findFolders}}
     * @param string $isHidden Whether the file should be hidden or not.
     * @return array An array with key `upload`, `message` and `file_id`. When upload is false, an error occured otherwise true. The message key contains the error messages. If no error happend `file_id` will contain the new uploaded file id.
     */
    public static function uploadFromFiles(array $filesArray, $toFolder = 0, $isHidden = false)
    {
        $files = [];
        foreach ($filesArray as $fileArrayKey => $file) {
            $files = array_merge($files, self::extractFilesDataFromFilesArray($file));
        }

        foreach ($files as $file) {
            return self::verifyAndSaveFile($file, $toFolder, $isHidden);
        }

        return ['upload' => false, 'message' => 'no files selected or empty files list.', 'file_id' => 0];
    }

    /**
     * Example Input unform:
     *
     * ```php
     *   Array
     *   (
     *       [name] => Array
     *           (
     *               [attachment] => Array
     *                   (
     *                       [0] => Altersfragen-Leimental (4).pdf
     *                       [1] => Altersfragen-Leimental (2).pdf
     *                   )
     *
     *           )
     *
     *       [type] => Array
     *           (
     *               [attachment] => Array
     *                   (
     *                       [0] => application/pdf
     *                       [1] => application/pdf
     *                   )
     *           )
     *       [tmp_name] => Array
     *           (
     *               [attachment] => Array
     *                   (
     *                       [0] => /tmp/phpNhqnwR
     *                       [1] => /tmp/phpbZ8XSn
     *                   )
     *           )
     *       [error] => Array
     *           (
     *               [attachment] => Array
     *                   (
     *                       [0] => 0
     *                       [1] => 0
     *                   )
     *           )
     *       [size] => Array
     *           (
     *               [attachment] => Array
     *                   (
     *                       [0] => 261726
     *                       [1] => 255335
     *                   )
     *           )
     *   )
     * ```
     *
     * to:
     *
     * ```php
     *
     *   Array
     *   (
     *       [0] => Array
     *           (
     *               [name] => Altersfragen-Leimental (4).pdf
     *               [type] => application/pdf
     *               [tmp_name] => /tmp/phpNhqnwR
     *               [error] => 0
     *               [size] => 261726
     *           )
     *       [1] => Array
     *           (
     *               [name] => Altersfragen-Leimental (2).pdf
     *               [type] => application/pdf
     *               [tmp_name] => /tmp/phpbZ8XSn
     *               [error] => 0
     *               [size] => 255335
     *           )
     *   )
     * ```
     * @param array $files
     * @return array
     */
    public static function extractFilesDataFromMultipleFiles(array $files)
    {
        $data = [];
        $i = 0;
        foreach ($files as $type => $field) {
            foreach ($field as $fieldName => $values) {
                if (is_array($values)) {
                    foreach ($values as $key => $value) {
                        $data[$key][$type] = $value;
                    }
                } else {
                    $data[$i][$type] = $values;
                }
            }
        }

        return $data;
    }

    /**
     * Extract $_FILES array.
     *
     * @param array $file
     * @return array
     */
    public static function extractFilesDataFromFilesArray(array $file)
    {
        if (!isset($file['tmp_name'])) {
            return [];
        }

        $files = [];
        if (is_array($file['tmp_name'])) {
            foreach ($file['tmp_name'] as $index => $value) {
                // skip empty or none exsting tmp file names
                if (!isset($file['tmp_name'][$index]) || empty($file['tmp_name'][$index])) {
                    continue;
                }
                // create files structure array
                $files[] = [
                    'name' => $file['name'][$index],
                    'type' => $file['type'][$index],
                    'tmp_name' => $file['tmp_name'][$index],
                    'error' => $file['error'][$index],
                    'size' => $file['size'][$index],
                ];
            }
        } elseif (isset($file['tmp_name']) && !empty($file['tmp_name'])) {
            $files[] = [
                'name' => $file['name'],
                'type' => $file['type'],
                'tmp_name' => $file['tmp_name'],
                'error' => $file['error'],
                'size' => $file['size'],
            ];
        }

        return $files;
    }

    /**
     *
     * @param array $file An array with the following keys available:
     * - name:
     * - type:
     * - tmp_name:
     * - error:
     * - size:
     * @param number $toFolder
     * @param string $isHidden
     * @return array
     */
    protected static function verifyAndSaveFile(array $file, $toFolder = 0, $isHidden = false)
    {
        try {
            if ($file['error'] !== UPLOAD_ERR_OK) {
                return ['upload' => false, 'message' => static::getUploadErrorMessage($file['error']), 'file_id' => 0];
            }

            $file = Yii::$app->storage->addFile($file['tmp_name'], $file['name'], $toFolder, $isHidden);
            if ($file) {
                return ['upload' => true, 'message' => 'file uploaded succesfully', 'file_id' => $file->id];
            }
        } catch (Exception $err) {
            return ['upload' => false, 'message' => $err->getMessage(), 'file_id' => 0];
        }

        return ['upload' => false, 'message' => 'no files selected or empty files list.', 'file_id' => 0];
    }
}