Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 375
ResourceService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 16
6162.00
0.00% covered (danger)
0.00%
0 / 374
 __construct
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 3
 __clone
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 2
 getInstance
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 5
 addResources
0.00% covered (danger)
0.00%
0 / 1
30.00
0.00% covered (danger)
0.00%
0 / 23
 removeResource
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 13
 removeResourceFromBackendStorage
0.00% covered (danger)
0.00%
0 / 1
72.00
0.00% covered (danger)
0.00%
0 / 24
 removeFilesFromS3
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 21
 getResourceById
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 12
 getPreSignedUrlByResourceId
0.00% covered (danger)
0.00%
0 / 1
90.00
0.00% covered (danger)
0.00%
0 / 29
 getPreSignedS3URL
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 30
 validateS3Tags
0.00% covered (danger)
0.00%
0 / 1
42.00
0.00% covered (danger)
0.00%
0 / 14
 uploadResource
0.00% covered (danger)
0.00%
0 / 1
30.00
0.00% covered (danger)
0.00%
0 / 36
 migrationUploadResource
0.00% covered (danger)
0.00%
0 / 1
20.00
0.00% covered (danger)
0.00%
0 / 34
 copyResource
0.00% covered (danger)
0.00%
0 / 1
110.00
0.00% covered (danger)
0.00%
0 / 32
 cloneFilesInS3
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 40
 cloneFilesInLocalStorage
0.00% covered (danger)
0.00%
0 / 1
182.00
0.00% covered (danger)
0.00%
0 / 56
<?php
namespace com\linways\core\ams\professional\service;
use Aws\S3\S3Client;
use Aws\Credentials\Credentials;
use com\linways\base\util\SecurityUtils;
use com\linways\core\ams\professional\dto\Resource;
use com\linways\core\ams\professional\request\CopyResourceRequest;
use com\linways\core\ams\professional\util\S3Utils;
use com\linways\core\ams\professional\constant\BackendTypes;
use com\linways\core\ams\professional\constant\resource\S3TagConstant;
use com\linways\core\ams\professional\mapper\ResourceMapper;
use com\linways\core\ams\professional\response\GetResourceUrl;
use com\linways\core\ams\professional\request\AddResourceRequest;
use com\linways\core\ams\professional\request\RemoveResourceRequest;
use com\linways\core\ams\professional\request\UploadResourceRequest;
use com\linways\core\ams\professional\request\GetPreSignedUrlRequest;
use com\linways\core\ams\professional\exception\ProfessionalException;
use com\linways\core\ams\professional\util\UUID;
use function strings\lowerCase;
use com\linways\core\ams\professional\util\CommonUtil;
class ResourceService extends BaseService
{
    /**
     * Presence of a static member variable
     *
     * @var null
     */
    private static $_instance = null;
    /**
     * @var array
     */
    private $mapper = [];
    /**
     * Locked down the constructor or Prevent any outside instantiation of this class
     *
     * ResourceService constructor.
     */
    private function __construct()
    {
        $this->mapper = ResourceMapper::getInstance()->getMapper();
    }
    /**
     * Prevent any object or instance of that class to be cloned or Prevent any copy of this object
     */
    private function __clone()
    {
    }
    /**
     * Have a single globally accessible static method
     *
     * @return ResourceService|null
     */
    public static function getInstance()
    {
        if (!is_object(self::$_instance))
            self::$_instance = new self();
        return self::$_instance;
    }
    /**
     * @param AddResourceRequest $request
     * @return Object|integer
     * @throws ProfessionalException
     */
    public function addResources(AddResourceRequest $request)
    {
        $request->backendType = $this->realEscapeString($request->backendType);
        $request->path = $this->realEscapeString($request->path);
        $request->context = $this->realEscapeString($request->context);
        $request->isConfirmed = $this->realEscapeString($request->isConfirmed);
        if (empty($request->path) &&  $request->backendType != 'VIEWWAY') {
            throw new ProfessionalException(ProfessionalException::INVALID_RESOURCE_PATH, "Invalid resource path given");
        }
        if (empty($request->context)) {
            throw new ProfessionalException(ProfessionalException::INVALID_RESOURCE_CONTEXT, "Invalid resource context given");
        }
        $request->storageObject = addslashes($request->storageObject);
        $request->id = SecurityUtils::getRandomString();
        $sql = "INSERT INTO lin_resource (id,path, context, backend_type,storage_object, created_by, updated_by, created_date, updated_date) 
                VALUES ('$request->id','$request->path','$request->context','$request->backendType','$request->storageObject',
                        $request->createdBy,$request->updatedBy,UTC_TIMESTAMP(),UTC_TIMESTAMP())";
        try {
            $this->executeQuery($sql);
            return $request->id;
        } catch (\Exception $e) {
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
    /**
     * Caution: This will remove the file history completely from the lin_resource table and backend storage.
     * If backend type is local storage, then this will remove file from the database only.
     * @param RemoveResourceRequest $request
     * @throws ProfessionalException
     */
    public function removeResource(RemoveResourceRequest $request)
    {
        $request = $this->realEscapeObject($request);
        if (empty($request->resourceId)) {
            throw new ProfessionalException(ProfessionalException::INVALID_RESOURCE_ID, "Invalid resource details given");
        }
        try {
            $this->removeResourceFromBackendStorage($request);
            $sql = "DELETE FROM lin_resource WHERE  id = '$request->resourceId'";
            $this->executeQueryForObject($sql);
        } catch (\Exception $e) {
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
    /**my
     * @param RemoveResourceRequest $request
     * @throws ProfessionalException
     */
    private function removeResourceFromBackendStorage(RemoveResourceRequest $request)
    {
        $resource = $this->getResourceById($request->resourceId);
        if (empty($resource)) {
            throw new ProfessionalException(ProfessionalException::RESOURCE_NOT_FOUND, "Resource not found in the storage");
        }
        try {
            switch ($resource->backendType) {
                case BackendTypes::S3:
                    $this->removeFilesFromS3($resource->storageObject, $request);
                    break;
                case BackendTypes::LOCAL_STORAGE:
                    //in future, if the service part and web part in different servers this code won't work
                    unlink(DOCUMENT_ROOT . $resource->path);
                    break;
                case BackendTypes::GOOGLE_DRIVE:
                case BackendTypes::YOUTUBE:
                case BackendTypes::EXTERNAL_URL:
                    //No need to remove file from user google account
                    break;
                default:
                    throw new ProfessionalException(ProfessionalException::INVALID_RESOURCE_STORAGE_TYPE, "Invalid resource storage type");
            }
        } catch (\Exception $e) {
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
    /**
     * @param $storageObject
     * @param RemoveResourceRequest $request
     * @throws ProfessionalException
     */
    private function removeFilesFromS3($storageObject, RemoveResourceRequest $request)
    {
        try {
            $tempStorageObject = CommonUtil::convertObjectToUTF8Format($storageObject); //remove unwanted Char from json content
            $storageObject = json_decode($tempStorageObject);
            $bucket = $storageObject->bucket;
            $key = $storageObject->key;
            $credentials = new Credentials($request->accessKey, $request->secretKey);
            $s3 = new S3Client([
                'version' => '2006-03-01',
                'region' => 'ap-south-1',
                'credentials' => $credentials,
            ]);
            $s3->deleteObject([
                'Bucket' => $bucket,
                'Key' => $key
            ]);
        } catch (\Exception $e) {
            error_log("CODE:" . $e->getCode() . " Message:" . $e->getMessage());
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
    /**
     * @param $resourceId
     * @return Object|Resource
     * @throws ProfessionalException
     */
    public function getResourceById($resourceId)
    {
        $resourceId = $this->realEscapeString($resourceId);
        if (empty($resourceId)) {
            throw new ProfessionalException(ProfessionalException::INVALID_RESOURCE_ID, "Invalid resource id");
        }
        try {
            $sql = "SELECT id,path,context,storage_object,backend_type FROM lin_resource WHERE id = '$resourceId'";
            return $this->executeQueryForObject($sql, false, $this->mapper[ResourceMapper::GET_RESOURCE]);
        } catch (\Exception $e) {
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
    /**
     * @param GetPreSignedUrlRequest $request
     * @return GetResourceUrl
     * @throws ProfessionalException
     */
    public function getPreSignedUrlByResourceId(GetPreSignedUrlRequest $request)
    {
        if (empty($request->resourceId)) {
            throw new ProfessionalException(ProfessionalException::INVALID_RESOURCE_ID, "Invalid resource id");
        }
        try {
            $resource = $this->getResourceById($request->resourceId);
            switch ($resource->backendType) {
                case BackendTypes::S3:
                    return $this->getPreSignedS3URL($resource, $request);
                case BackendTypes::LOCAL_STORAGE:
                case BackendTypes::EXTERNAL_URL:
                    $response = new GetResourceUrl();
                    $response->url = $resource->path;
                case BackendTypes::GOOGLE_DRIVE:
                    $response = new GetResourceUrl();
                    $response->url = $resource->path;
                    $storageObject = json_decode($resource->storageObject);
                    $response->name = "";
                    if (!empty($storageObject) && !empty($storageObject->name)) {
                        $response->name = $storageObject->name;
                    }
                    return $response;
                default:
                    throw new ProfessionalException(ProfessionalException::RESOURCE_BACKEND_TYPE_NOT_FOUND, "Resource type not found");
            }
        } catch (\Exception $e) {
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
    /**
     * @param Resource $resource
     * @param GetPreSignedUrlRequest $request
     * @return GetResourceUrl
     * @throws ProfessionalException
     */
    private function getPreSignedS3URL(Resource $resource, GetPreSignedUrlRequest $request)
    {
        $response = new GetResourceUrl();
        $tempStorageObject = CommonUtil::convertObjectToUTF8Format($resource->storageObject);
        $storageObject = json_decode($tempStorageObject);
        $storageObject->key = htmlspecialchars_decode($storageObject->key);
        $storageObject->name = htmlspecialchars_decode($storageObject->name);
        if($request->utfConversion){
            $storageObject->key = utf8_decode($storageObject->key);
            $storageObject->name = utf8_decode($storageObject->name);
        }
        if (empty($storageObject)) {
            throw new ProfessionalException(ProfessionalException::INVALID_RESOURCE_ID, "Resource not found. Contact your system administrator");
        }
        $key = $storageObject->key;
        $bucket = $storageObject->bucket;
        
        $credentials = new Credentials($request->accessKey, $request->secretKey);
        $s3Client = new S3Client([
            'version' => '2006-03-01',
            'region' => 'ap-south-1',
            'credentials' => $credentials,
        ]);
        $cmd = $s3Client->getCommand('GetObject', [
            'Bucket' => $bucket,
            'Key' => $key
        ]);
        // $test = $s3Client->getObject('GetObject', [
        //     'Bucket' => $bucket,
        //     'Key' => $key
        // ]);
        $request = $s3Client->createPresignedRequest($cmd, '+30 minutes');
        $response->url = (string) $request->getUri();
        $response->name = $storageObject->name;
        return $response;
    }
    /**
    * Validates the tags added the object
    * returns string if valid else throws errors
    * @param array $tags
    * @throws ProfessionalException
    * @return string
    */
    public function validateS3Tags($tags){
        if(empty($tags)){
            return "";
        }
        $responseTag = [];
        foreach($tags as $key => $value){
            
            $key = strtolower(trim($key));
            
            if(!array_key_exists($key, S3TagConstant::S3TAGS))
                throw new ProfessionalException( ProfessionalException::INVALID_S3_TAGS, "The tag key added to this object is invalid" );
            if(!in_array($value, S3TagConstant::S3TAGS[$key]) && count(S3TagConstant::S3TAGS[$key]) > 0)
                throw new ProfessionalException( ProfessionalException::INVALID_S3_TAGS, "The tag value added to this object is invalid" );
            $responseTag [] = "$key=$value";
        }     
        
        return implode('&',$responseTag);
    }
    /**
     * @param UploadResourceRequest $request
     * @return int|Object
     * @return AddResourceRequest object if returnResourceObjectBeforeAddingToLinResource is true. In case if you only need to upload to s3 and use the object. Adding to linresource table can be skipped.
     * @throws ProfessionalException
     */
    public function uploadResource(UploadResourceRequest $request)
    {
        $request = $this->realEscapeObject($request);
        $addResourceRequest = new AddResourceRequest();
        $addResourceRequest->context = $request->context;
        $addResourceRequest->backendType = $request->backendType;
        try {
            if ($request->backendType === BackendTypes::S3) {
                $credentials = new Credentials($request->accessKey, $request->secretKey);
                $s3Client = new S3Client([
                    'version' => '2006-03-01',
                    'region' => 'ap-south-1',
                    'credentials' => $credentials,
                ]);
                $tags = $this->validateS3Tags($request->putObjectTags);
                $key = $request->s3FolderPath . ($request->key ? $request->key : md5(uniqid(rand())) . "-" . time() . "-" . $request->fileName);
                $result = $s3Client->putObject([
                    'Bucket' => $request->bucket,
                    'Key' => $key,
                    'SourceFile' => $request->filePath,
                    'Tagging' => $tags
                ]);
                $s3Object = (object) ["bucket" => $request->bucket, "key" => $key, "name" => $request->fileName];
                unlink($request->filePath);
                $addResourceRequest->storageObject = json_encode($s3Object);
                $addResourceRequest->path = $s3Object->key;
            }
            $addResourceRequest->updatedBy = $request->createdBy;
            $addResourceRequest->createdBy = $request->createdBy;
            if ($request->returnResourceObjectBeforeAddingToLinResource == true) {
                return $addResourceRequest;
            }
            return $this->addResources($addResourceRequest);
        } catch (\Exception $e) {
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
    public function migrationUploadResource(UploadResourceRequest $request)
    {
        $request = $this->realEscapeObject($request);
        $addResourceRequest = new AddResourceRequest();
        $addResourceRequest->context = $request->context;
        $addResourceRequest->backendType = $request->backendType;
        try {
            if ($request->backendType === BackendTypes::S3) {
                $credentials = new Credentials($request->accessKey, $request->secretKey);
                $s3Client = new S3Client([
                    'version' => '2006-03-01',
                    'region' => 'ap-south-1',
                    'credentials' => $credentials,
                ]);
                $key = $request->s3FolderPath . ($request->key ? $request->key : md5(uniqid(rand())) . "-" . time() . "-" . $request->fileName);
                $result = $s3Client->putObject([
                    'Bucket' => $request->bucket,
                    'Key' => $key,
                    'SourceFile' => $request->filePath
                ]);
                $s3Object = (object) ["bucket" => $request->bucket, "key" => $key, "name" => $request->fileName];
                //unlink($request->filePath);
                $addResourceRequest->storageObject = json_encode($s3Object);
                $addResourceRequest->path = $s3Object->key;
            }
            $addResourceRequest->updatedBy = $request->createdBy;
            $addResourceRequest->createdBy = $request->createdBy;
            $addResourceRequest->backendType = $this->realEscapeString($addResourceRequest->backendType);
            $addResourceRequest->path = $this->realEscapeString($addResourceRequest->path);
            $addResourceRequest->context = $this->realEscapeString($addResourceRequest->context);
            $addResourceRequest->isConfirmed = $this->realEscapeString($addResourceRequest->isConfirmed);
            return $addResourceRequest;
        } catch (\Exception $e) {
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
    /**
     * @param CopyResourceRequest $request
     * @return int|Object
     * @throws ProfessionalException
     */
    public function copyResource(CopyResourceRequest $request)
    {
        if (empty($request->resourceId)) {
            throw new ProfessionalException(ProfessionalException::INVALID_RESOURCE_ID, "Invalid resource details given");
        }
        if (!$request->isCopyToSameDirectory) {
            if (empty($request->directoryName)) {
                throw new ProfessionalException(ProfessionalException::INVALID_DIRECTORY_NAME, "Invalid directory name");
            }
            if (empty($request->userType)) {
                throw new ProfessionalException(ProfessionalException::INVALID_USER_TYPE, "Invalid user type");
            }
            if (empty($request->userName)) {
                throw new ProfessionalException(ProfessionalException::INVALID_USER_NAME, "Invalid user name");
            }
        }
        try {
            $resource = $this->getResourceById($request->resourceId);
            if (empty($resource)) {
                throw new ProfessionalException(ProfessionalException::RESOURCE_NOT_FOUND, "The given resource doesn't exist");
            }
            switch ($resource->backendType) {
                case BackendTypes::S3:
                    return $this->cloneFilesInS3($request, $resource);
                case BackendTypes::LOCAL_STORAGE:
                    return $this->cloneFilesInLocalStorage($request, $resource);
                default:
                    throw new ProfessionalException(ProfessionalException::INVALID_RESOURCE_STORAGE_TYPE, "Invalid resource storage type given");
            }
        } catch (\Exception $e) {
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
    /**
     * Cloning S3 Object
     *
     * @param CopyResourceRequest $request
     * @param $resource
     * @return int|Object
     * @throws ProfessionalException
     */
    private function cloneFilesInS3(CopyResourceRequest $request, $resource)
    {
        try {
            $credentials = new Credentials($request->accessKey, $request->secretKey);
            $s3Client = new S3Client([
                'version' => '2006-03-01',
                'region' => 'ap-south-1',
                'credentials' => $credentials,
            ]);
            $storageObject = json_decode($resource->storageObject);
            $uuid = UUID::v4();
            $fileName = $storageObject->fileName;
            $extension = ltrim(strstr($fileName, '.'), '.');
            /**
             * Copying to different directory
             */
            if (!$request->isCopyToSameDirectory) {
                $key = $request->collegeCode . "/" . $request->directoryName . "/" . date('Y') . "/" . $uuid . "-" . $request->userId . "." . $extension;
            } else {
                $path = $storageObject->key;
                $directoryName = dirname($path);
                $key = $directoryName . "/" . $uuid . "." . $extension;
            }
            $s3Client->copyObject([
                'Bucket' => $storageObject->bucket,
                'CopySource' => $storageObject->key,
                'Key' => $key
            ]);
            $newStorageObject = new \stdClass();
            $newStorageObject->key = $key;
            $newStorageObject->bucket = $storageObject->bucket;
            $newStorageObject->name = $fileName;
            $addResourceRequest = new AddResourceRequest();
            $addResourceRequest->context = $request->context;
            $addResourceRequest->backendType = $resource->backendType;
            $addResourceRequest->path = $key;
            $addResourceRequest->storageObject = json_encode($newStorageObject);
            $addResourceRequest->updatedBy = $request->createdBy;
            $addResourceRequest->createdBy = $request->createdBy;
            return $this->addResources($addResourceRequest);
        } catch (\Exception $e) {
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
    /**
     * Cloning file in the local storage
     *
     * @param CopyResourceRequest $request
     * @param Resource $resource
     * @return int|Object
     * @throws ProfessionalException
     */
    private function cloneFilesInLocalStorage(CopyResourceRequest $request, $resource)
    {
        $path = $resource->path;
        $pathParts = pathinfo($path);
        $fileName = $pathParts['filename'];
        $fileExtension = $pathParts['extension'];
        $newFileName = $fileName . "-copy-" . time() . "." . $fileExtension;
        $newPath = "";
        try {
            /**
             * Checking developers assigned exact path to copy.
             * Here file with same name will be overwrite
             */
            if (!empty($request->destinationPath)) {
                if (mkdir(dirname($request->destinationPath), 0777, true)) {
                    if (!copy($path, $request->destinationPath)) {
                        throw new ProfessionalException(ProfessionalException::FAILED_TO_COPY_THE_FILE, "Failed to copy the file");
                    }
                    $newPath = $request->destinationPath;
                } else {
                    throw new ProfessionalException(ProfessionalException::FAILED_TO_CREATE_THE_DIRECTORY, "Failed to copy the file");
                }
            } elseif (!$request->isCopyToSameDirectory) {
                /**
                 * Copying to the same directory flag is set, then only file name will change
                 */
                $directoryName = dirname($path);
                $newPath = $directoryName . "/" . $newFileName;
                if (!copy($path, $newPath)) {
                    throw new ProfessionalException(ProfessionalException::FAILED_TO_CREATE_THE_DIRECTORY, "Failed to copy the file");
                }
            } else {
                /**
                 * Dynamic file name and path directory creation is set here
                 */
                if (empty($request->userType) || empty($request->userCode)) {
                    throw new ProfessionalException(ProfessionalException::INVALID_USER_TYPE, "Invalid user details given");
                }
                if (empty($request->directoryName) || empty($request->parentDirectory)) {
                    throw new ProfessionalException(ProfessionalException::INVALID_DIRECTORY_NAME, "Invalid target directory details given");
                }
                $userType = lowerCase($request->userType);
                $newPath = DOCUMENT_ROOT . $userType . "/" . $request->parentDirectory . "/" . $request->collegeCode . "/" . $request->userCode . "/" . $request->directoryName . "/" . $newFileName;
                if (mkdir(dirname($newPath), 0777, true)) {
                    if (!copy($path, $newPath)) {
                        throw new ProfessionalException(ProfessionalException::FAILED_TO_COPY_THE_FILE, "Failed to copy the file");
                    }
                } else {
                    throw new ProfessionalException(ProfessionalException::FAILED_TO_CREATE_THE_DIRECTORY, "Failed to copy the file");
                }
            }
            $newStorageObject = new \stdClass();
            $newStorageObject->key = $newPath;
            $newStorageObject->bucket = "";
            $newStorageObject->name = $newFileName;
            $addResourceRequest = new AddResourceRequest();
            $addResourceRequest->context = $request->context;
            $addResourceRequest->backendType = $resource->backendType;
            $addResourceRequest->path = $newPath;
            $addResourceRequest->storageObject = json_encode($newStorageObject);
            $addResourceRequest->updatedBy = $request->createdBy;
            $addResourceRequest->createdBy = $request->createdBy;
            return $this->addResources($addResourceRequest);
        } catch (\Exception $e) {
            throw new ProfessionalException($e->getCode(), $e->getMessage());
        }
    }
}