Laravel version update

Laravel version update
This commit is contained in:
Manish Verma
2018-08-06 18:48:58 +05:30
parent d143048413
commit 126fbb0255
13678 changed files with 1031482 additions and 778530 deletions

View File

@@ -0,0 +1,67 @@
<?php
namespace Flow;
class Autoloader
{
/**
* Directory path
*
* @var string
*/
private $dir;
/**
* Constructor
*
* @param string|null $dir
*/
public function __construct($dir = null)
{
if (is_null($dir)) {
$dir = __DIR__.'/..';
}
$this->dir = $dir;
}
/**
* Return directory path
*
* @return string
*/
public function getDir()
{
return $this->dir;
}
/**
* Register
*
* @codeCoverageIgnore
* @param string|null $dir
*/
public static function register($dir = null)
{
ini_set('unserialize_callback_func', 'spl_autoload_call');
spl_autoload_register(array(new self($dir), 'autoload'));
}
/**
* Handles autoloading of classes
*
* @param string $class A class name
*
* @return boolean Returns true if the class has been loaded
*/
public function autoload($class)
{
if (0 !== strpos($class, 'Flow')) {
return;
}
if (file_exists($file = $this->dir.'/'.str_replace('\\', '/', $class).'.php')) {
require $file;
}
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Flow;
/**
* Class Basic
*
* Example for handling basic uploads
*
* @package Flow
*/
class Basic
{
/**
* @param string $destination where to save file
* @param string|ConfigInterface $config
* @param RequestInterface $request optional
* @return bool
*/
public static function save($destination, $config, RequestInterface $request = null)
{
if (!$config instanceof ConfigInterface) {
$config = new Config(array(
'tempDir' => $config,
));
}
$file = new File($config, $request);
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if ($file->checkChunk()) {
header("HTTP/1.1 200 Ok");
} else {
// The 204 response MUST NOT include a message-body, and thus is always terminated by the first empty line after the header fields.
header("HTTP/1.1 204 No Content");
return false;
}
} else {
if ($file->validateChunk()) {
$file->saveChunk();
} else {
// error, invalid chunk upload request, retry
header("HTTP/1.1 400 Bad Request");
return false;
}
}
if ($file->validateFile() && $file->save($destination)) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace Flow;
class Config implements ConfigInterface
{
/**
* Config
*
* @var array
*/
private $config;
/**
* Controller
*
* @param array $config
*/
public function __construct($config = array())
{
$this->config = $config;
}
/**
* Set path to temporary directory for chunks storage
*
* @param $path
*/
public function setTempDir($path)
{
$this->config['tempDir'] = $path;
}
/**
* Get path to temporary directory for chunks storage
*
* @return string
*/
public function getTempDir()
{
return isset($this->config['tempDir']) ? $this->config['tempDir'] : '';
}
/**
* Set chunk identifier
*
* @param callable $callback
*/
public function setHashNameCallback($callback)
{
$this->config['hashNameCallback'] = $callback;
}
/**
* Generate chunk identifier
*
* @return callable
*/
public function getHashNameCallback()
{
return isset($this->config['hashNameCallback']) ? $this->config['hashNameCallback'] : '\Flow\Config::hashNameCallback';
}
/**
* Callback to pre-process chunk
*
* @param callable $callback
*/
public function setPreprocessCallback($callback)
{
$this->config['preprocessCallback'] = $callback;
}
/**
* Callback to pre-process chunk
*
* @return callable|null
*/
public function getPreprocessCallback()
{
return isset($this->config['preprocessCallback']) ? $this->config['preprocessCallback'] : null;
}
/**
* Delete chunks on save
*
* @param bool $delete
*/
public function setDeleteChunksOnSave($delete)
{
$this->config['deleteChunksOnSave'] = $delete;
}
/**
* Delete chunks on save
*
* @return bool
*/
public function getDeleteChunksOnSave()
{
return isset($this->config['deleteChunksOnSave']) ? $this->config['deleteChunksOnSave'] : true;
}
/**
* Generate chunk identifier
*
* @param RequestInterface $request
*
* @return string
*/
public static function hashNameCallback(RequestInterface $request)
{
return sha1($request->getIdentifier());
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Flow;
interface ConfigInterface
{
/**
* Get path to temporary directory for chunks storage
*
* @return string
*/
public function getTempDir();
/**
* Generate chunk identifier
*
* @return callable
*/
public function getHashNameCallback();
/**
* Callback to pre-process chunk
*
* @param callable $callback
*/
public function setPreprocessCallback($callback);
/**
* Callback to preprocess chunk
*
* @return callable|null
*/
public function getPreprocessCallback();
/**
* Delete chunks on save
*
* @param bool $delete
*/
public function setDeleteChunksOnSave($delete);
/**
* Delete chunks on save
*
* @return bool
*/
public function getDeleteChunksOnSave();
}

View File

@@ -0,0 +1,233 @@
<?php
namespace Flow;
class File
{
/**
* @var RequestInterface
*/
protected $request;
/**
* @var ConfigInterface
*/
private $config;
/**
* File hashed unique identifier
*
* @var string
*/
private $identifier;
/**
* Constructor
*
* @param ConfigInterface $config
* @param RequestInterface $request
*/
public function __construct(ConfigInterface $config, RequestInterface $request = null)
{
$this->config = $config;
if ($request === null) {
$request = new Request();
}
$this->request = $request;
$this->identifier = call_user_func($this->config->getHashNameCallback(), $request);
}
/**
* Get file identifier
*
* @return string
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* Return chunk path
*
* @param int $index
*
* @return string
*/
public function getChunkPath($index)
{
return $this->config->getTempDir().DIRECTORY_SEPARATOR.basename($this->identifier).'_'. (int) $index;
}
/**
* Check if chunk exist
*
* @return bool
*/
public function checkChunk()
{
return file_exists($this->getChunkPath($this->request->getCurrentChunkNumber()));
}
/**
* Validate file request
*
* @return bool
*/
public function validateChunk()
{
$file = $this->request->getFile();
if (!$file) {
return false;
}
if (!isset($file['tmp_name']) || !isset($file['size']) || !isset($file['error'])) {
return false;
}
if ($this->request->getCurrentChunkSize() != $file['size']) {
return false;
}
if ($file['error'] !== UPLOAD_ERR_OK) {
return false;
}
return true;
}
/**
* Save chunk
*
* @return bool
*/
public function saveChunk()
{
$file = $this->request->getFile();
return $this->_move_uploaded_file($file['tmp_name'], $this->getChunkPath($this->request->getCurrentChunkNumber()));
}
/**
* Check if file upload is complete
*
* @return bool
*/
public function validateFile()
{
$totalChunks = $this->request->getTotalChunks();
$totalChunksSize = 0;
for ($i = $totalChunks; $i >= 1; $i--) {
$file = $this->getChunkPath($i);
if (!file_exists($file)) {
return false;
}
$totalChunksSize += filesize($file);
}
return $this->request->getTotalSize() == $totalChunksSize;
}
/**
* Merge all chunks to single file
*
* @param string $destination final file location
*
*
* @throws FileLockException
* @throws FileOpenException
* @throws \Exception
*
* @return bool indicates if file was saved
*/
public function save($destination)
{
$fh = fopen($destination, 'wb');
if (!$fh) {
throw new FileOpenException('failed to open destination file: '.$destination);
}
if (!flock($fh, LOCK_EX | LOCK_NB, $blocked)) {
// @codeCoverageIgnoreStart
if ($blocked) {
// Concurrent request has requested a lock.
// File is being processed at the moment.
// Warning: lock is not checked in windows.
return false;
}
// @codeCoverageIgnoreEnd
throw new FileLockException('failed to lock file: '.$destination);
}
$totalChunks = $this->request->getTotalChunks();
try {
$preProcessChunk = $this->config->getPreprocessCallback();
for ($i = 1; $i <= $totalChunks; $i++) {
$file = $this->getChunkPath($i);
$chunk = fopen($file, "rb");
if (!$chunk) {
throw new FileOpenException('failed to open chunk: '.$file);
}
if ($preProcessChunk !== null) {
call_user_func($preProcessChunk, $chunk);
}
stream_copy_to_stream($chunk, $fh);
fclose($chunk);
}
} catch (\Exception $e) {
flock($fh, LOCK_UN);
fclose($fh);
throw $e;
}
if ($this->config->getDeleteChunksOnSave()) {
$this->deleteChunks();
}
flock($fh, LOCK_UN);
fclose($fh);
return true;
}
/**
* Delete chunks dir
*/
public function deleteChunks()
{
$totalChunks = $this->request->getTotalChunks();
for ($i = 1; $i <= $totalChunks; $i++) {
$path = $this->getChunkPath($i);
if (file_exists($path)) {
unlink($path);
}
}
}
/**
* This method is used only for testing
*
* @private
* @codeCoverageIgnore
*
* @param string $filePath
* @param string $destinationPath
*
* @return bool
*/
public function _move_uploaded_file($filePath, $destinationPath)
{
return move_uploaded_file($filePath, $destinationPath);
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Flow;
class FileLockException extends \Exception
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Flow;
class FileOpenException extends \Exception
{
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Flow;
/**
* Class FustyRequest
*
* Imitates single file request as a single chunk file upload
*
* @package Flow
*/
class FustyRequest extends Request
{
private $isFusty = false;
public function __construct($params = null, $file = null)
{
parent::__construct($params, $file);
$this->isFusty = $this->getTotalSize() === null && $this->getFileName() && $this->getFile();
if ($this->isFusty) {
$this->params['flowTotalSize'] = isset($this->file['size']) ? $this->file['size'] : 0;
$this->params['flowTotalChunks'] = 1;
$this->params['flowChunkNumber'] = 1;
$this->params['flowChunkSize'] = $this->params['flowTotalSize'];
$this->params['flowCurrentChunkSize'] = $this->params['flowTotalSize'];
}
}
/**
* Checks if request is formed by fusty flow
* @return bool
*/
public function isFustyFlowRequest()
{
return $this->isFusty;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Flow\Mongo;
use Flow\Config;
/**
* @codeCoverageIgnore
*/
class MongoConfig extends Config implements MongoConfigInterface
{
private $gridFs;
/**
* @param \MongoGridFS $gridFS storage of the upload (and chunks)
*/
function __construct(\MongoGridFS $gridFS)
{
parent::__construct();
$this->gridFs = $gridFS;
}
/**
* @return \MongoGridFS
*/
public function getGridFs()
{
return $this->gridFs;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Flow\Mongo;
use Flow\ConfigInterface;
/**
* @codeCoverageIgnore
*/
interface MongoConfigInterface extends ConfigInterface
{
/**
* @return \MongoGridFS
*/
public function getGridFs();
}

View File

@@ -0,0 +1,181 @@
<?php
namespace Flow\Mongo;
use Flow\File;
use Flow\Request;
use Flow\RequestInterface;
/**
* Notes:
*
* - One should ensure indices on the gridfs collection on the property 'flowIdentifier'.
* - Chunk preprocessor not supported (must not modify chunks size)!
* - Must use 'forceChunkSize=true' on client side.
*
* @codeCoverageIgnore
*/
class MongoFile extends File
{
private $uploadGridFsFile;
/**
* @var MongoConfigInterface
*/
private $config;
function __construct(MongoConfigInterface $config, RequestInterface $request = null)
{
if ($request === null) {
$request = new Request();
}
parent::__construct($config, $request);
$this->config = $config;
}
/**
* return array
*/
protected function getGridFsFile()
{
if (!$this->uploadGridFsFile) {
$gridFsFileQuery = $this->getGridFsFileQuery();
$changed = $gridFsFileQuery;
$changed['flowUpdated'] = new \MongoDate();
$this->uploadGridFsFile = $this->config->getGridFs()->findAndModify($gridFsFileQuery, $changed, null,
['upsert' => true, 'new' => true]);
}
return $this->uploadGridFsFile;
}
/**
* @param $index int|string 1-based
* @return bool
*/
public function chunkExists($index)
{
return $this->config->getGridFs()->chunks->find([
'files_id' => $this->getGridFsFile()['_id'],
'n' => (intval($index) - 1)
])->limit(1)->hasNext();
}
public function checkChunk()
{
return $this->chunkExists($this->request->getCurrentChunkNumber());
}
/**
* Save chunk
* @return bool
* @throws \Exception if upload size is invalid or some other unexpected error occurred.
*/
public function saveChunk()
{
try {
$file = $this->request->getFile();
$chunkQuery = [
'files_id' => $this->getGridFsFile()['_id'],
'n' => intval($this->request->getCurrentChunkNumber()) - 1,
];
$chunk = $chunkQuery;
$data = file_get_contents($file['tmp_name']);
$actualChunkSize = strlen($data);
if ($actualChunkSize > $this->request->getDefaultChunkSize() ||
($actualChunkSize < $this->request->getDefaultChunkSize() &&
$this->request->getCurrentChunkNumber() != $this->request->getTotalChunks())
) {
throw new \Exception("Invalid upload! (size: {$actualChunkSize})");
}
$chunk['data'] = new \MongoBinData($data, 0); // \MongoBinData::GENERIC is not defined for older mongo drivers
$this->config->getGridFs()->chunks->findAndModify($chunkQuery, $chunk, [], ['upsert' => true]);
unlink($file['tmp_name']);
$this->ensureIndices();
return true;
} catch (\Exception $e) {
// try to remove a possibly (partly) stored chunk:
if (isset($chunkQuery)) {
$this->config->getGridFs()->chunks->remove($chunkQuery);
}
throw $e;
}
}
/**
* @return bool
*/
public function validateFile()
{
$totalChunks = $this->request->getTotalChunks();
for ($i = 1; $i <= $totalChunks; $i++) {
if (!$this->chunkExists($i)) {
return false;
}
}
return true;
}
/**
* Merge all chunks to single file
* @param $metadata array additional metadata for final file
* @return \MongoId|bool of saved file or false if file was already saved
* @throws \Exception
*/
public function saveToGridFs($metadata = null)
{
$file = $this->getGridFsFile();
$file['flowStatus'] = 'finished';
$file['metadata'] = $metadata;
$result = $this->config->getGridFs()->findAndModify($this->getGridFsFileQuery(), $file);
// on second invocation no more file can be found, as the flowStatus changed:
if (is_null($result)) {
return false;
} else {
return $file['_id'];
}
}
public function save($destination)
{
throw new \Exception("Must not use 'save' on MongoFile - use 'saveToGridFs'!");
}
public function deleteChunks()
{
// nothing to do, as chunks are directly part of the final file
}
public function ensureIndices()
{
$chunksCollection = $this->config->getGridFs()->chunks;
$indexKeys = ['files_id' => 1, 'n' => 1];
$indexOptions = ['unique' => true, 'background' => true];
if(method_exists($chunksCollection, 'createIndex')) { // only available for PECL mongo >= 1.5.0
$chunksCollection->createIndex($indexKeys, $indexOptions);
} else {
$chunksCollection->ensureIndex($indexKeys, $indexOptions);
}
}
/**
* @return array
*/
protected function getGridFsFileQuery()
{
return [
'flowIdentifier' => $this->request->getIdentifier(),
'flowStatus' => 'uploading',
'filename' => $this->request->getFileName(),
'chunkSize' => intval($this->request->getDefaultChunkSize()),
'length' => intval($this->request->getTotalSize())
];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Flow\Mongo;
use Flow\FileOpenException;
/**
* @codeCoverageIgnore
*/
class MongoUploader
{
/**
* Delete chunks older than expiration time.
*
* @param \MongoGridFS $gridFs
* @param int $expirationTime seconds
*
* @throws FileOpenException
*/
public static function pruneChunks($gridFs, $expirationTime = 172800)
{
$result = $gridFs->remove([
'flowUpdated' => ['$lt' => new \MongoDate(time() - $expirationTime)],
'flowStatus' => 'uploading'
]);
if (!$result) {
throw new FileOpenException("Could not remove chunks!");
}
}
}

View File

@@ -0,0 +1,50 @@
Usage
--------------
* Must use 'forceChunkSize=true' on client side.
* Chunk preprocessor not supported.
* One should ensure indices on the gridfs collection on the property 'flowIdentifier'.
Besides the points above, the usage is analogous to the 'normal' flow-php:
```php
$config = new \Flow\Mongo\MongoConfig($yourGridFs);
$file = new \Flow\Mongo\MongoFile($config);
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if ($file->checkChunk()) {
header("HTTP/1.1 200 Ok");
} else {
header("HTTP/1.1 204 No Content");
return ;
}
} else {
if ($file->validateChunk()) {
$file->saveChunk();
} else {
// error, invalid chunk upload request, retry
header("HTTP/1.1 400 Bad Request");
return ;
}
}
if ($file->validateFile()) {
// File upload was completed
$id = $file->saveToGridFs(['your metadata'=>'value']);
if($id) {
//do custom post processing here, $id is the MongoId of the gridfs file
}
} else {
// This is not a final chunk, continue to upload
}
```
Delete unfinished files
-----------------------
For this you should setup cron, which would check each chunk upload time.
If chunk is uploaded long time ago, then chunk should be deleted.
Helper method for checking this:
```php
\Flow\Mongo\MongoUploader::pruneChunks($yourGridFs);
```

View File

@@ -0,0 +1,152 @@
<?php
namespace Flow;
class Request implements RequestInterface
{
/**
* Request parameters
*
* @var array
*/
protected $params;
/**
* File
*
* @var array
*/
protected $file;
/**
* Constructor
*
* @param array|null $params
* @param array|null $file
*/
public function __construct($params = null, $file = null)
{
if ($params === null) {
$params = $_REQUEST;
}
if ($file === null && isset($_FILES['file'])) {
$file = $_FILES['file'];
}
$this->params = $params;
$this->file = $file;
}
/**
* Get parameter value
*
* @param string $name
*
* @return string|int|null
*/
public function getParam($name)
{
return isset($this->params[$name]) ? $this->params[$name] : null;
}
/**
* Get uploaded file name
*
* @return string|null
*/
public function getFileName()
{
return $this->getParam('flowFilename');
}
/**
* Get total file size in bytes
*
* @return int|null
*/
public function getTotalSize()
{
return $this->getParam('flowTotalSize');
}
/**
* Get file unique identifier
*
* @return string|null
*/
public function getIdentifier()
{
return $this->getParam('flowIdentifier');
}
/**
* Get file relative path
*
* @return string|null
*/
public function getRelativePath()
{
return $this->getParam('flowRelativePath');
}
/**
* Get total chunks number
*
* @return int|null
*/
public function getTotalChunks()
{
return $this->getParam('flowTotalChunks');
}
/**
* Get default chunk size
*
* @return int|null
*/
public function getDefaultChunkSize()
{
return $this->getParam('flowChunkSize');
}
/**
* Get current uploaded chunk number, starts with 1
*
* @return int|null
*/
public function getCurrentChunkNumber()
{
return $this->getParam('flowChunkNumber');
}
/**
* Get current uploaded chunk size
*
* @return int|null
*/
public function getCurrentChunkSize()
{
return $this->getParam('flowCurrentChunkSize');
}
/**
* Return $_FILES request
*
* @return array|null
*/
public function getFile()
{
return $this->file;
}
/**
* Checks if request is formed by fusty flow
*
* @return bool
*/
public function isFustyFlowRequest()
{
return false;
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Flow;
interface RequestInterface
{
/**
* Get uploaded file name
*
* @return string
*/
public function getFileName();
/**
* Get total file size in bytes
*
* @return int
*/
public function getTotalSize();
/**
* Get file unique identifier
*
* @return string
*/
public function getIdentifier();
/**
* Get file relative path
*
* @return string
*/
public function getRelativePath();
/**
* Get total chunks number
*
* @return int
*/
public function getTotalChunks();
/**
* Get default chunk size
*
* @return int
*/
public function getDefaultChunkSize();
/**
* Get current uploaded chunk number, starts with 1
*
* @return int
*/
public function getCurrentChunkNumber();
/**
* Get current uploaded chunk size
*
* @return int
*/
public function getCurrentChunkSize();
/**
* Return $_FILES request
*
* @return array|null
*/
public function getFile();
/**
* Checks if request is formed by fusty flow
*
* @return bool
*/
public function isFustyFlowRequest();
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Flow;
class Uploader
{
/**
* Delete chunks older than expiration time.
*
* @param string $chunksFolder
* @param int $expirationTime seconds
*
* @throws FileOpenException
*/
public static function pruneChunks($chunksFolder, $expirationTime = 172800)
{
$handle = opendir($chunksFolder);
if (!$handle) {
throw new FileOpenException('failed to open folder: '.$chunksFolder);
}
while (false !== ($entry = readdir($handle))) {
if ($entry == "." || $entry == ".." || $entry == ".gitignore") {
continue;
}
$path = $chunksFolder.DIRECTORY_SEPARATOR.$entry;
if (is_dir($path)) {
continue;
}
if (time() - filemtime($path) > $expirationTime) {
unlink($path);
}
}
closedir($handle);
}
}