mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Added FormFlash and FormFlashFile classes, allow custom FlexForm class, improvements
This commit is contained in:
@@ -6,8 +6,10 @@
|
||||
* Added `orderBy()` and `limit()` methods to `ObjectCollectionInterface` and its base classes
|
||||
* Flex: Added support for custom object index classes (API compatibility break)
|
||||
* Added `user-data://` which is a writable stream (`user://data` is not and should be avoided)
|
||||
* Added support for `/action:{$action}` (like task but works without nonce, used only for getting data)
|
||||
* Added support for `/action:{$action}` (like task but used without nonce when only receiving data)
|
||||
* Added `onAction.{$action}` event
|
||||
* Added `FormFlash` class to contain AJAX uploaded files in more reliable way
|
||||
* Added `FormFlashFile` class which implements `UploadedFileInterface` from PSR-7
|
||||
1. [](#improved)
|
||||
* Improve Flex storage
|
||||
1. [](#bugfix)
|
||||
|
||||
@@ -16,6 +16,7 @@ use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\Interfaces\FlexFormInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Form\FormFlash;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
@@ -36,31 +37,33 @@ class FlexForm implements FlexFormInterface
|
||||
private $submitted;
|
||||
/** @var string[] */
|
||||
private $errors;
|
||||
/** @var Data */
|
||||
/** @var Data|FlexObjectInterface */
|
||||
private $data;
|
||||
/** @var UploadedFileInterface[] */
|
||||
/** @var array|UploadedFileInterface[] */
|
||||
private $files;
|
||||
/** @var FlexObjectInterface */
|
||||
private $object;
|
||||
/** @var FormFlash */
|
||||
private $flash;
|
||||
|
||||
/**
|
||||
* FlexForm constructor.
|
||||
* @param string $name
|
||||
* @param FlexObjectInterface|null $object
|
||||
* @param FlexObjectInterface $object
|
||||
*/
|
||||
public function __construct(string $name = '', FlexObjectInterface $object = null)
|
||||
public function __construct(string $name, FlexObjectInterface $object)
|
||||
{
|
||||
$this->reset();
|
||||
|
||||
if ($object) {
|
||||
$this->setObject($object);
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
$this->id = $this->getName();
|
||||
$this->setObject($object);
|
||||
$this->setId($this->getName());
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML id="..." attribute.
|
||||
*
|
||||
* Defaults to 'flex-[type]-[name]', where 'type' is object type and 'name' is the first parameter given in constructor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
@@ -69,6 +72,8 @@ class FlexForm implements FlexFormInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets HTML id="" attribute.
|
||||
*
|
||||
* @param string $id
|
||||
*/
|
||||
public function setId(string $id): void
|
||||
@@ -77,34 +82,10 @@ class FlexForm implements FlexFormInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
$object = $this->object;
|
||||
$name = $this->name ?: 'object';
|
||||
|
||||
return "flex-{$object->getType(false)}-{$name}";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNonceName(): string
|
||||
{
|
||||
return 'nonce';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNonceAction(): string
|
||||
{
|
||||
return 'flex-object';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique id for the current form instance. By default regenerated on every page reload.
|
||||
*
|
||||
* This id is used to load the saved form state, if available.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueId(): string
|
||||
@@ -116,6 +97,51 @@ class FlexForm implements FlexFormInterface
|
||||
return $this->uniqueid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets unique form id allowing you to attach the form state to the object for example.
|
||||
*
|
||||
* @param string $uniqueId
|
||||
*/
|
||||
public function setUniqueId(string $uniqueId): void
|
||||
{
|
||||
$this->uniqueid = $uniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
$object = $this->getObject();
|
||||
$name = $this->name ?: 'object';
|
||||
|
||||
return "flex-{$object->getType(false)}-{$name}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFormName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNonceName(): string
|
||||
{
|
||||
return 'form-nonce';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNonceAction(): string
|
||||
{
|
||||
return 'form';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -125,29 +151,20 @@ class FlexForm implements FlexFormInterface
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getButtons(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'type' => 'submit',
|
||||
'value' => 'Save'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data|FlexObjectInterface
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
if (null === $this->data) {
|
||||
$this->data = $this->getObject();
|
||||
}
|
||||
return $this->data ?? $this->getObject();
|
||||
}
|
||||
|
||||
return $this->data;
|
||||
/**
|
||||
* @return array|UploadedFileInterface[]
|
||||
*/
|
||||
public function getFiles(): array
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,57 +177,11 @@ class FlexForm implements FlexFormInterface
|
||||
*/
|
||||
public function getValue(string $name)
|
||||
{
|
||||
$data = $this->getData();
|
||||
return $data instanceof FlexObject ? $data->getNestedProperty($name) : $data->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UploadedFileInterface[]
|
||||
*/
|
||||
public function getFiles(): array
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Route|null
|
||||
*/
|
||||
public function getFileUploadAjaxRoute(): ?Route
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!method_exists($object, 'route')) {
|
||||
return null;
|
||||
if (null === $this->data) {
|
||||
return $this->getObject()->getNestedProperty($name);
|
||||
}
|
||||
|
||||
return $object->route('/edit.json/task:media.upload');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $field
|
||||
* @param $filename
|
||||
* @return Route|null
|
||||
*/
|
||||
public function getFileDeleteAjaxRoute($field, $filename): ?Route
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!method_exists($object, 'route')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $object->route('/edit.json/task:media.delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this method clones the object.
|
||||
*
|
||||
* @param FlexObjectInterface $object
|
||||
* @return $this
|
||||
*/
|
||||
public function setObject(FlexObjectInterface $object): FlexFormInterface
|
||||
{
|
||||
$this->object = clone $object;
|
||||
|
||||
return $this;
|
||||
return $this->data->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,10 +189,6 @@ class FlexForm implements FlexFormInterface
|
||||
*/
|
||||
public function getObject(): FlexObjectInterface
|
||||
{
|
||||
if (!$this->object) {
|
||||
throw new \RuntimeException('FlexForm: Object is not defined');
|
||||
}
|
||||
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
@@ -285,26 +252,14 @@ class FlexForm implements FlexFormInterface
|
||||
}
|
||||
|
||||
$this->files = $files ?? [];
|
||||
$this->data = new Data($this->decodeData($data['data'] ?? []));
|
||||
$this->data = new Data($this->decodeData($data['data'] ?? []), $this->getBlueprint());
|
||||
if ($this->getErrors()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->validate();
|
||||
$this->doSubmit($this->data->toArray(), $this->files);
|
||||
|
||||
$this->submitted = true;
|
||||
|
||||
$object = clone $this->object;
|
||||
$object->update($this->data->toArray());
|
||||
$object->triggerEvent('onSave');
|
||||
|
||||
if (method_exists($object, 'upload')) {
|
||||
$object->upload($this->files);
|
||||
}
|
||||
$object->save();
|
||||
|
||||
$this->object = $object;
|
||||
$this->valid = true;
|
||||
} catch (ValidationException $e) {
|
||||
$list = [];
|
||||
foreach ($e->getMessages() as $field => $errors) {
|
||||
@@ -386,6 +341,33 @@ class FlexForm implements FlexFormInterface
|
||||
$this->object = $data['object'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Route|null
|
||||
*/
|
||||
public function getFileUploadAjaxRoute(): ?Route
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!method_exists($object, 'route')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $object->route('/edit.json/task:media.upload');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $field
|
||||
* @param $filename
|
||||
* @return Route|null
|
||||
*/
|
||||
public function getFileDeleteAjaxRoute($field, $filename): ?Route
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!method_exists($object, 'route')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $object->route('/edit.json/task:media.delete');
|
||||
}
|
||||
|
||||
public function getMediaTaskRoute(): string
|
||||
{
|
||||
@@ -405,6 +387,33 @@ class FlexForm implements FlexFormInterface
|
||||
return '/' . $this->object->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this method clones the object.
|
||||
*
|
||||
* @param FlexObjectInterface $object
|
||||
* @return $this
|
||||
*/
|
||||
protected function setObject(FlexObjectInterface $object): FlexFormInterface
|
||||
{
|
||||
$this->object = clone $object;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get flash object
|
||||
*
|
||||
* @return FormFlash
|
||||
*/
|
||||
protected function getFlash()
|
||||
{
|
||||
if (null === $this->flash) {
|
||||
$this->flash = new FormFlash($this->getName(), $this->getUniqueId());
|
||||
}
|
||||
|
||||
return $this->flash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -415,6 +424,41 @@ class FlexForm implements FlexFormInterface
|
||||
$this->checkUploads($this->files);
|
||||
}
|
||||
|
||||
protected function setErrors(array $errors): void
|
||||
{
|
||||
$this->errors = array_merge($this->errors, $errors);
|
||||
}
|
||||
|
||||
protected function setError(string $error): void
|
||||
{
|
||||
$this->errors[] = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function doSubmit(array $data, array $files)
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$object = clone $this->object;
|
||||
$object->update($data);
|
||||
|
||||
if (method_exists($object, 'triggerEvent')) {
|
||||
$object->triggerEvent('onSave');
|
||||
}
|
||||
|
||||
if (method_exists($object, 'upload')) {
|
||||
$object->upload($files);
|
||||
}
|
||||
|
||||
$object->save();
|
||||
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
protected function checkUploads(array $files): void
|
||||
{
|
||||
foreach ($files as $file) {
|
||||
|
||||
@@ -18,6 +18,7 @@ use Grav\Common\Page\Medium\MediumFactory;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Framework\ContentBlock\HtmlBlock;
|
||||
use Grav\Framework\Flex\Interfaces\FlexAuthorizeInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexFormInterface;
|
||||
use Grav\Framework\Flex\Traits\FlexAuthorizeTrait;
|
||||
use Grav\Framework\Object\Access\NestedArrayAccessTrait;
|
||||
use Grav\Framework\Object\Access\NestedPropertyTrait;
|
||||
@@ -170,7 +171,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
public function getForm(string $name = '')
|
||||
{
|
||||
if (!isset($this->_forms[$name])) {
|
||||
$this->_forms[$name] = new FlexForm($name, $this);
|
||||
$this->_forms[$name] = $this->createFormObject($name);
|
||||
}
|
||||
|
||||
return $this->_forms[$name];
|
||||
@@ -662,4 +663,15 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
|
||||
unset ($elements['storage_key'], $elements['storage_timestamp']);
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods allows you to override form objects in child classes.
|
||||
*
|
||||
* @param string $name Form name
|
||||
* @return FlexFormInterface
|
||||
*/
|
||||
protected function createFormObject(string $name): FlexFormInterface
|
||||
{
|
||||
return new FlexForm($name, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,16 @@ interface FlexFormInterface extends \Serializable
|
||||
*/
|
||||
public function setId(string $id): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueId(): string;
|
||||
|
||||
/**
|
||||
* @param string $uniqueId
|
||||
*/
|
||||
public function setUniqueId(string $uniqueId): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -47,21 +57,11 @@ interface FlexFormInterface extends \Serializable
|
||||
*/
|
||||
public function getNonceAction(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueId(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAction(): string;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getButtons() : array;
|
||||
|
||||
/**
|
||||
* @return Data|FlexObjectInterface
|
||||
*/
|
||||
@@ -94,14 +94,6 @@ interface FlexFormInterface extends \Serializable
|
||||
*/
|
||||
public function getFileDeleteAjaxRoute($field, $filename): ?Route;
|
||||
|
||||
/**
|
||||
* Note: this method clones the object.
|
||||
*
|
||||
* @param FlexObjectInterface $object
|
||||
* @return $this
|
||||
*/
|
||||
public function setObject(FlexObjectInterface $object): self;
|
||||
|
||||
/**
|
||||
* @return FlexObjectInterface
|
||||
*/
|
||||
@@ -152,20 +144,6 @@ interface FlexFormInterface extends \Serializable
|
||||
*/
|
||||
public function getBlueprint(): Blueprint;
|
||||
|
||||
/**
|
||||
* Implements \Serializable::serialize().
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize(): string;
|
||||
|
||||
/**
|
||||
* Implements \Serializable::unserialize().
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
public function unserialize($data): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -80,7 +80,7 @@ trait FlexMediaTrait
|
||||
}
|
||||
}
|
||||
|
||||
public function uploadMediaFile(UploadedFileInterface $uploadedFile, string $filename = null) : void
|
||||
public function uploadMediaFile(UploadedFileInterface $uploadedFile, string $filename = null, string $field = null) : void
|
||||
{
|
||||
$this->checkUploadedMediaFile($uploadedFile);
|
||||
|
||||
@@ -121,7 +121,7 @@ trait FlexMediaTrait
|
||||
$this->clearMediaCache();
|
||||
}
|
||||
|
||||
public function deleteMediaFile(string $filename) : void
|
||||
public function deleteMediaFile(string $filename, string $field = null) : void
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$language = $grav['language'];
|
||||
|
||||
400
system/src/Grav/Framework/Form/FormFlash.php
Normal file
400
system/src/Grav/Framework/Form/FormFlash.php
Normal file
@@ -0,0 +1,400 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Form
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Form;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Session;
|
||||
use Grav\Common\User\User;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
|
||||
class FormFlash implements \JsonSerializable
|
||||
{
|
||||
/** @var string */
|
||||
protected $form;
|
||||
/** @var string */
|
||||
protected $uniqueId;
|
||||
/** @var string */
|
||||
protected $url;
|
||||
/** @var array */
|
||||
protected $user;
|
||||
/** @var array */
|
||||
protected $uploads;
|
||||
/** @var array */
|
||||
protected $uploadObjects;
|
||||
/** @var bool */
|
||||
protected $exists;
|
||||
|
||||
/**
|
||||
* FormFlashObject constructor.
|
||||
* @param string $form
|
||||
* @param string $uniqueId
|
||||
*/
|
||||
public function __construct(string $form, $uniqueId = null)
|
||||
{
|
||||
$this->form = $form;
|
||||
$this->uniqueId = $uniqueId;
|
||||
|
||||
$file = $this->getTmpIndex();
|
||||
$this->exists = $file->exists();
|
||||
|
||||
$data = $this->exists ? (array)$file->content() : [];
|
||||
$this->url = $data['url'] ?? null;
|
||||
$this->user = $data['user'] ?? null;
|
||||
$this->uploads = $data['uploads'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFormName() : string
|
||||
{
|
||||
return $this->form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqieId() : string
|
||||
{
|
||||
return $this->uniqueId ?? $this->getFormName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function exists() : bool
|
||||
{
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function save() : self
|
||||
{
|
||||
$file = $this->getTmpIndex();
|
||||
$file->save($this->jsonSerialize());
|
||||
$this->exists = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete() : self
|
||||
{
|
||||
$this->removeTmpDir();
|
||||
$this->uploads = [];
|
||||
$this->exists = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl() : string
|
||||
{
|
||||
return $this->url ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return $this
|
||||
*/
|
||||
public function setUrl(string $url) : self
|
||||
{
|
||||
$this->url = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUsername() : string
|
||||
{
|
||||
return $this->user['username'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail() : string
|
||||
{
|
||||
return $this->user['email'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User|null $user
|
||||
* @return $this
|
||||
*/
|
||||
public function setUser(?User $user = null) : self
|
||||
{
|
||||
if ($user && $user->username) {
|
||||
$this->user = [
|
||||
'username' => $user->username,
|
||||
'email' => $user->email
|
||||
];
|
||||
} else {
|
||||
$this->user = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @return array
|
||||
*/
|
||||
public function getFilesByField(string $field) : array
|
||||
{
|
||||
if (!isset($this->uploadObjects[$field])) {
|
||||
$objects = [];
|
||||
foreach ($this->uploads[$field] ?? [] as $filename => $upload) {
|
||||
$objects[$filename] = new FormFlashFile($field, $upload, $this);
|
||||
}
|
||||
$this->uploadObjects[$field] = $objects;
|
||||
}
|
||||
|
||||
return $this->uploadObjects[$field];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFilesByFields() : array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->uploads as $field => $values) {
|
||||
if (strpos($field, '/')) {
|
||||
continue;
|
||||
}
|
||||
$list[$field] = $this->getFilesByField($field);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @deprecated 1.6 For backwards compatibility only, do not use.
|
||||
*/
|
||||
public function getLegacyFiles() : array
|
||||
{
|
||||
$fields = [];
|
||||
foreach ($this->uploads as $field => $files) {
|
||||
if (strpos($field, '/')) {
|
||||
continue;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
$file['tmp_name'] = $this->getTmpDir() . '/' . $file['tmp_name'];
|
||||
$fields[$field][$file['path'] ?? $file['name']] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @param string $filename
|
||||
* @param array $upload
|
||||
* @return bool
|
||||
*/
|
||||
public function uploadFile(string $field, string $filename, array $upload) : bool
|
||||
{
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
|
||||
Folder::create($tmp_dir);
|
||||
|
||||
$tmp_file = $upload['file']['tmp_name'];
|
||||
$basename = basename($tmp_file);
|
||||
|
||||
if (!move_uploaded_file($tmp_file, $tmp_dir . '/' . $basename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$upload['file']['tmp_name'] = $basename;
|
||||
|
||||
if (!isset($this->uploads[$field])) {
|
||||
$this->uploads[$field] = [];
|
||||
}
|
||||
|
||||
// Prepare object for later save
|
||||
$upload['file']['name'] = $filename;
|
||||
|
||||
// Replace old file, including original
|
||||
$oldUpload = $this->uploads[$field][$filename] ?? null;
|
||||
if (isset($oldUpload['tmp_name'])) {
|
||||
$this->removeTmpFile($oldUpload['tmp_name']);
|
||||
}
|
||||
|
||||
$originalUpload = $this->uploads[$field . '/original'][$filename] ?? null;
|
||||
if (isset($originalUpload['tmp_name'])) {
|
||||
$this->removeTmpFile($originalUpload['tmp_name']);
|
||||
unset($this->uploads[$field . '/original'][$filename]);
|
||||
}
|
||||
|
||||
// Prepare data to be saved later
|
||||
$this->uploads[$field][$filename] = $upload['file'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @param string $filename
|
||||
* @param array $upload
|
||||
* @param array $crop
|
||||
* @return bool
|
||||
*/
|
||||
public function cropFile(string $field, string $filename, array $upload, array $crop) : bool
|
||||
{
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
|
||||
Folder::create($tmp_dir);
|
||||
|
||||
$tmp_file = $upload['file']['tmp_name'];
|
||||
$basename = basename($tmp_file);
|
||||
|
||||
if (!move_uploaded_file($tmp_file, $tmp_dir . '/' . $basename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$upload['file']['tmp_name'] = $basename;
|
||||
|
||||
if (!isset($this->uploads[$field])) {
|
||||
$this->uploads[$field] = [];
|
||||
}
|
||||
|
||||
// Prepare object for later save
|
||||
$upload['file']['name'] = $filename;
|
||||
|
||||
$oldUpload = $this->uploads[$field][$filename] ?? null;
|
||||
if ($oldUpload) {
|
||||
$originalUpload = $this->uploads[$field . '/original'][$filename] ?? null;
|
||||
if ($originalUpload) {
|
||||
$this->removeTmpFile($oldUpload['tmp_name']);
|
||||
} else {
|
||||
$oldUpload['crop'] = $crop;
|
||||
$this->uploads[$field . '/original'][$filename] = $oldUpload;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare data to be saved later
|
||||
$this->uploads[$field][$filename] = $upload['file'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public function removeFile(string $field, string $filename) : bool
|
||||
{
|
||||
if (!$field || !$filename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = $this->getTmpIndex();
|
||||
if (!$file->exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$upload = $this->uploads[$field][$filename] ?? null;
|
||||
if (null !== $upload) {
|
||||
$this->removeTmpFile($upload['tmp_name'] ?? '');
|
||||
}
|
||||
$upload = $this->uploads[$field . '/original'][$filename] ?? null;
|
||||
if (null !== $upload) {
|
||||
$this->removeTmpFile($upload['tmp_name'] ?? '');
|
||||
}
|
||||
|
||||
// Walk backward to cleanup any empty field that's left
|
||||
unset(
|
||||
$this->uploadObjects[$field][$filename],
|
||||
$this->uploads[$field][$filename],
|
||||
$this->uploadObjects[$field . '/original'][$filename],
|
||||
$this->uploads[$field . '/original'][$filename]
|
||||
);
|
||||
if (empty($this->uploads[$field])) {
|
||||
unset($this->uploads[$field]);
|
||||
}
|
||||
if (empty($this->uploads[$field . '/original'])) {
|
||||
unset($this->uploads[$field . '/original']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize() : array
|
||||
{
|
||||
return [
|
||||
'form' => $this->form,
|
||||
'unique_id' => $this->uniqueId,
|
||||
'url' => $this->url,
|
||||
'user' => $this->user,
|
||||
'uploads' => $this->uploads
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTmpDir() : string
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Session $session */
|
||||
$session = $grav['session'];
|
||||
|
||||
$location = [
|
||||
'forms',
|
||||
$session->getId(),
|
||||
$this->uniqueId ?: $this->form
|
||||
];
|
||||
|
||||
return $grav['locator']->findResource('tmp://', true, true) . '/' . implode('/', $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return YamlFile
|
||||
*/
|
||||
protected function getTmpIndex() : YamlFile
|
||||
{
|
||||
// Do not use CompiledYamlFile as the file can change multiple times per second.
|
||||
return YamlFile::instance($this->getTmpDir() . '/index.yaml');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
protected function removeTmpFile(string $name) : void
|
||||
{
|
||||
$filename = $this->getTmpDir() . '/' . $name;
|
||||
if ($name && is_file($filename)) {
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
|
||||
protected function removeTmpDir() : void
|
||||
{
|
||||
$tmpDir = $this->getTmpDir();
|
||||
if (file_exists($tmpDir)) {
|
||||
Folder::delete($tmpDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
system/src/Grav/Framework/Form/FormFlashFile.php
Normal file
146
system/src/Grav/Framework/Form/FormFlashFile.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Form
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Form;
|
||||
|
||||
use Grav\Framework\Psr7\Stream;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
class FormFlashFile implements UploadedFileInterface, \JsonSerializable
|
||||
{
|
||||
private $field;
|
||||
private $moved = false;
|
||||
private $upload;
|
||||
private $flash;
|
||||
|
||||
public function __construct(string $field, array $upload, FormFlash $flash)
|
||||
{
|
||||
$this->field = $field;
|
||||
$this->upload = $upload;
|
||||
$this->flash = $flash;
|
||||
|
||||
if ($this->isOk() && (empty($this->upload['tmp_name']) || !file_exists($this->getTmpFile()))) {
|
||||
$this->upload['error'] = \UPLOAD_ERR_NO_FILE;
|
||||
}
|
||||
|
||||
if (!isset($this->upload['size'])) {
|
||||
$this->upload['size'] = $this->isOk() ? filesize($this->getTmpFile()) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StreamInterface
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
$this->validateActive();
|
||||
|
||||
$resource = \fopen($this->getTmpFile(), 'rb');
|
||||
|
||||
return Stream::create($resource);
|
||||
}
|
||||
|
||||
public function moveTo($targetPath)
|
||||
{
|
||||
$this->validateActive();
|
||||
|
||||
if (!\is_string($targetPath) || empty($targetPath)) {
|
||||
throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
|
||||
}
|
||||
|
||||
$this->moved = \copy($this->getTmpFile(), $targetPath);
|
||||
|
||||
if (false === $this->moved) {
|
||||
throw new \RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath));
|
||||
}
|
||||
|
||||
$this->flash->removeFile($this->field, $this->upload['tmp_name']);
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->upload['size'];
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->upload['error'] ?? \UPLOAD_ERR_OK;
|
||||
}
|
||||
|
||||
public function getClientFilename()
|
||||
{
|
||||
return $this->upload['name'] ?? 'unknown';
|
||||
}
|
||||
|
||||
public function getClientMediaType()
|
||||
{
|
||||
return $this->upload['type'] ?? 'application/octet-stream';
|
||||
}
|
||||
|
||||
public function isMoved() : bool
|
||||
{
|
||||
return $this->moved;
|
||||
}
|
||||
|
||||
public function getMetaData() : array
|
||||
{
|
||||
if (isset($this->upload['crop'])) {
|
||||
return ['crop' => $this->upload['crop']];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getDestination()
|
||||
{
|
||||
return $this->upload['path'];
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->upload;
|
||||
}
|
||||
|
||||
public function __debugInfo()
|
||||
{
|
||||
return [
|
||||
'field:private' => $this->field,
|
||||
'moved:private' => $this->moved,
|
||||
'upload:private' => $this->upload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException if is moved or not ok
|
||||
*/
|
||||
private function validateActive(): void
|
||||
{
|
||||
if (!$this->isOk()) {
|
||||
throw new \RuntimeException('Cannot retrieve stream due to upload error');
|
||||
}
|
||||
|
||||
if ($this->moved) {
|
||||
throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool return true if there is no upload error
|
||||
*/
|
||||
private function isOk(): bool
|
||||
{
|
||||
return \UPLOAD_ERR_OK === $this->getError();
|
||||
}
|
||||
|
||||
private function getTmpFile() : string
|
||||
{
|
||||
return $this->flash->getTmpDir() . '/' . $this->upload['tmp_name'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user