mirror of
https://github.com/getgrav/grav.git
synced 2025-02-20 19:56:53 +01:00
Merge branch 'develop' of github.com:getgrav/grav into feature/media
Conflicts: CHANGELOG.md composer.lock system/src/Grav/Common/Media/Interfaces/MediaObjectInterface.php
This commit is contained in:
commit
d1b565f4a7
|
|
@ -21,6 +21,13 @@
|
|||
* Fixed remote URLs in markdown if using subfolder setup
|
||||
* Fixed calls to undefined `Media` methods, they will now return `null` in order to fix twig templates with undefined variables
|
||||
|
||||
# v1.7.35
|
||||
## mm/dd/2022
|
||||
|
||||
1. [](#new)
|
||||
* Added support for `multipart/form-data` content type in PUT and PATCH requests
|
||||
* Added support for object relationships
|
||||
|
||||
# v1.7.34
|
||||
## 06/14/2022
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^4.1",
|
||||
"phpstan/phpstan": "^1.2",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.0",
|
||||
"phpunit/php-code-coverage": "~9.2",
|
||||
"getgrav/markdowndocs": "^2.0",
|
||||
|
|
|
|||
|
|
@ -1156,6 +1156,13 @@ form:
|
|||
local6: local6
|
||||
local7: local7
|
||||
|
||||
log.syslog.tag:
|
||||
type: text
|
||||
size: small
|
||||
label: PLUGIN_ADMIN.SYSLOG_TAG
|
||||
help: PLUGIN_ADMIN.SYSLOG_TAG_HELP
|
||||
placeholder: "grav"
|
||||
|
||||
debugger:
|
||||
type: tab
|
||||
title: PLUGIN_ADMIN.DEBUGGER
|
||||
|
|
|
|||
|
|
@ -125,6 +125,17 @@ config:
|
|||
- username
|
||||
- fullname
|
||||
|
||||
relationships:
|
||||
media:
|
||||
type: media
|
||||
cardinality: to-many
|
||||
avatar:
|
||||
type: media
|
||||
cardinality: to-one
|
||||
# roles:
|
||||
# type: user-groups
|
||||
# cardinality: to-many
|
||||
|
||||
blueprints:
|
||||
configure:
|
||||
fields:
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ log:
|
|||
handler: file # Log handler. Currently supported: file | syslog
|
||||
syslog:
|
||||
facility: local6 # Syslog facilities output
|
||||
tag: grav # Syslog tag. Default: "grav".
|
||||
|
||||
debugger:
|
||||
enabled: false # Enable Grav debugger and following settings
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ use Grav\Common\Flex\Types\UserGroups\UserGroupIndex;
|
|||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\User\Traits\UserTrait;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Contracts\Relationships\ToOneRelationshipInterface;
|
||||
use Grav\Framework\File\Formatter\JsonFormatter;
|
||||
use Grav\Framework\File\Formatter\YamlFormatter;
|
||||
use Grav\Framework\Filesystem\Filesystem;
|
||||
|
|
@ -38,7 +39,10 @@ use Grav\Framework\Flex\Flex;
|
|||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\Storage\FileStorage;
|
||||
use Grav\Framework\Flex\Traits\FlexMediaTrait;
|
||||
use Grav\Framework\Flex\Traits\FlexRelationshipsTrait;
|
||||
use Grav\Framework\Form\FormFlashFile;
|
||||
use Grav\Framework\Media\MediaIdentifier;
|
||||
use Grav\Framework\Media\UploadedMediaObject;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
|
|
@ -77,6 +81,7 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
|||
}
|
||||
use UserTrait;
|
||||
use UserObjectLegacyTrait;
|
||||
use FlexRelationshipsTrait;
|
||||
|
||||
/** @var Closure|null */
|
||||
static public $authorizeCallable;
|
||||
|
|
@ -682,6 +687,81 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
|||
return $folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return array|object|null
|
||||
* @internal
|
||||
*/
|
||||
public function initRelationship(string $name)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'media':
|
||||
$list = [];
|
||||
foreach ($this->getMedia()->all() as $filename => $object) {
|
||||
$list[] = $this->buildMediaObject(null, $filename, $object);
|
||||
}
|
||||
|
||||
return $list;
|
||||
case 'avatar':
|
||||
return $this->buildMediaObject('avatar', basename($this->getAvatarUrl()), $this->getAvatarImage());
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('%s: Relationship %s does not exist', $this->getFlexType(), $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool Return true if relationships were updated.
|
||||
*/
|
||||
protected function updateRelationships(): bool
|
||||
{
|
||||
$modified = $this->getRelationships()->getModified();
|
||||
if ($modified) {
|
||||
foreach ($modified as $relationship) {
|
||||
$name = $relationship->getName();
|
||||
switch ($name) {
|
||||
case 'avatar':
|
||||
\assert($relationship instanceof ToOneRelationshipInterface);
|
||||
$this->updateAvatarRelationship($relationship);
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('%s: Relationship %s cannot be modified', $this->getFlexType(), $name), 400);
|
||||
}
|
||||
}
|
||||
|
||||
$this->resetRelationships();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ToOneRelationshipInterface $relationship
|
||||
*/
|
||||
protected function updateAvatarRelationship(ToOneRelationshipInterface $relationship): void
|
||||
{
|
||||
$files = [];
|
||||
$avatar = $this->getAvatarImage();
|
||||
if ($avatar) {
|
||||
$files['avatar'][$avatar->filename] = null;
|
||||
}
|
||||
|
||||
$identifier = $relationship->getIdentifier();
|
||||
if ($identifier) {
|
||||
\assert($identifier instanceof MediaIdentifier);
|
||||
$object = $identifier->getObject();
|
||||
if ($object instanceof UploadedMediaObject) {
|
||||
$uploadedFile = $object->getUploadedFile();
|
||||
if ($uploadedFile) {
|
||||
$files['avatar'][$uploadedFile->getClientFilename()] = $uploadedFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->update([], $files);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Blueprint
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ use Grav\Common\Service\TaskServiceProvider;
|
|||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Framework\DI\Container;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Grav\Framework\RequestHandler\Middlewares\MultipartRequestSupport;
|
||||
use Grav\Framework\RequestHandler\RequestHandler;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Grav\Framework\Session\Messages;
|
||||
|
|
@ -117,6 +118,7 @@ class Grav extends Container
|
|||
* @var array All middleware processors that are processed in $this->process()
|
||||
*/
|
||||
protected $middleware = [
|
||||
'multipartRequestSupport',
|
||||
'initializeProcessor',
|
||||
'pluginsProcessor',
|
||||
'themesProcessor',
|
||||
|
|
@ -259,6 +261,9 @@ class Grav extends Container
|
|||
|
||||
$container = new Container(
|
||||
[
|
||||
'multipartRequestSupport' => function () {
|
||||
return new MultipartRequestSupport();
|
||||
},
|
||||
'initializeProcessor' => function () {
|
||||
return new InitializeProcessor($this);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -261,7 +261,8 @@ class InitializeProcessor extends ProcessorBase
|
|||
$log->popHandler();
|
||||
|
||||
$facility = $config->get('system.log.syslog.facility', 'local6');
|
||||
$logHandler = new SyslogHandler('grav', $facility);
|
||||
$tag = $config->get('system.log.syslog.tag', 'grav');
|
||||
$logHandler = new SyslogHandler($tag, $facility);
|
||||
$formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
|
||||
$logHandler->setFormatter($formatter);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Media;
|
||||
|
||||
use Grav\Framework\Contracts\Object\IdentifierInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Media Object Interface
|
||||
*/
|
||||
interface MediaObjectInterface extends IdentifierInterface
|
||||
{
|
||||
/**
|
||||
* Returns true if the object exists.
|
||||
*
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function exists(): bool;
|
||||
|
||||
/**
|
||||
* Get metadata associated to the media object.
|
||||
*
|
||||
* @return array
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getMeta(): array;
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @return mixed
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function get(string $field);
|
||||
|
||||
/**
|
||||
* Return URL pointing to the media object.
|
||||
*
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getUrl(): string;
|
||||
|
||||
/**
|
||||
* Create media response.
|
||||
*
|
||||
* @param array $actions
|
||||
* @return ResponseInterface
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function createResponse(array $actions): ResponseInterface;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Object;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* Interface IdentifierInterface
|
||||
*/
|
||||
interface IdentifierInterface extends JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Get identifier's ID.
|
||||
*
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getId(): string;
|
||||
|
||||
/**
|
||||
* Get identifier's type.
|
||||
*
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getType(): string;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Relationships;
|
||||
|
||||
use ArrayAccess;
|
||||
use Grav\Framework\Contracts\Object\IdentifierInterface;
|
||||
|
||||
/**
|
||||
* Interface RelationshipIdentifierInterface
|
||||
*/
|
||||
interface RelationshipIdentifierInterface extends IdentifierInterface
|
||||
{
|
||||
/**
|
||||
* If identifier has meta.
|
||||
*
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function hasIdentifierMeta(): bool;
|
||||
|
||||
/**
|
||||
* Get identifier meta.
|
||||
*
|
||||
* @return array<string,mixed>|ArrayAccess<string,mixed>
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getIdentifierMeta();
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Relationships;
|
||||
|
||||
use Countable;
|
||||
use Grav\Framework\Contracts\Object\IdentifierInterface;
|
||||
use IteratorAggregate;
|
||||
use JsonSerializable;
|
||||
use Serializable;
|
||||
|
||||
/**
|
||||
* Interface Relationship
|
||||
*
|
||||
* @template T of IdentifierInterface
|
||||
* @template P of IdentifierInterface
|
||||
* @extends IteratorAggregate<string, T>
|
||||
*/
|
||||
interface RelationshipInterface extends Countable, IteratorAggregate, JsonSerializable, Serializable
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getType(): string;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function isModified(): bool;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getCardinality(): string;
|
||||
|
||||
/**
|
||||
* @return P
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getParent(): IdentifierInterface;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string|null $type
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function has(string $id, string $type = null): bool;
|
||||
|
||||
/**
|
||||
* @param T $identifier
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function hasIdentifier(IdentifierInterface $identifier): bool;
|
||||
|
||||
/**
|
||||
* @param T $identifier
|
||||
* @return bool
|
||||
*/
|
||||
public function addIdentifier(IdentifierInterface $identifier): bool;
|
||||
|
||||
/**
|
||||
* @param T|null $identifier
|
||||
* @return bool
|
||||
*/
|
||||
public function removeIdentifier(IdentifierInterface $identifier = null): bool;
|
||||
|
||||
/**
|
||||
* @return iterable<T>
|
||||
*/
|
||||
public function getIterator(): iterable;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Relationships;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* Interface RelationshipsInterface
|
||||
*
|
||||
* @template T of \Grav\Framework\Contracts\Object\IdentifierInterface
|
||||
* @template P of \Grav\Framework\Contracts\Object\IdentifierInterface
|
||||
* @extends ArrayAccess<string,RelationshipInterface<T,P>>
|
||||
* @extends Iterator<string,RelationshipInterface<T,P>>
|
||||
*/
|
||||
interface RelationshipsInterface extends Countable, ArrayAccess, Iterator, JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function isModified(): bool;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getModified(): array;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function count(): int;
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return RelationshipInterface<T,P>|null
|
||||
*/
|
||||
public function offsetGet($offset): ?RelationshipInterface;
|
||||
|
||||
/**
|
||||
* @return RelationshipInterface<T,P>|null
|
||||
*/
|
||||
public function current(): ?RelationshipInterface;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function key(): string;
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Relationships;
|
||||
|
||||
use Grav\Framework\Contracts\Object\IdentifierInterface;
|
||||
|
||||
/**
|
||||
* Interface ToManyRelationshipInterface
|
||||
*
|
||||
* @template T of IdentifierInterface
|
||||
* @template P of IdentifierInterface
|
||||
* @template-extends RelationshipInterface<T,P>
|
||||
*/
|
||||
interface ToManyRelationshipInterface extends RelationshipInterface
|
||||
{
|
||||
/**
|
||||
* @param positive-int $pos
|
||||
* @return IdentifierInterface|null
|
||||
*/
|
||||
public function getNthIdentifier(int $pos): ?IdentifierInterface;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string|null $type
|
||||
* @return T|null
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getIdentifier(string $id, string $type = null): ?IdentifierInterface;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string|null $type
|
||||
* @return T|null
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getObject(string $id, string $type = null): ?object;
|
||||
|
||||
/**
|
||||
* @param iterable<T> $identifiers
|
||||
* @return bool
|
||||
*/
|
||||
public function addIdentifiers(iterable $identifiers): bool;
|
||||
|
||||
/**
|
||||
* @param iterable<T> $identifiers
|
||||
* @return bool
|
||||
*/
|
||||
public function replaceIdentifiers(iterable $identifiers): bool;
|
||||
|
||||
/**
|
||||
* @param iterable<T> $identifiers
|
||||
* @return bool
|
||||
*/
|
||||
public function removeIdentifiers(iterable $identifiers): bool;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Relationships;
|
||||
|
||||
use Grav\Framework\Contracts\Object\IdentifierInterface;
|
||||
|
||||
/**
|
||||
* Interface ToOneRelationshipInterface
|
||||
*
|
||||
* @template T of IdentifierInterface
|
||||
* @template P of IdentifierInterface
|
||||
* @template-extends RelationshipInterface<T,P>
|
||||
*/
|
||||
interface ToOneRelationshipInterface extends RelationshipInterface
|
||||
{
|
||||
/**
|
||||
* @param string|null $id
|
||||
* @param string|null $type
|
||||
* @return T|null
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getIdentifier(string $id = null, string $type = null): ?IdentifierInterface;
|
||||
|
||||
/**
|
||||
* @param string|null $id
|
||||
* @param string|null $type
|
||||
* @return T|null
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getObject(string $id = null, string $type = null): ?object;
|
||||
|
||||
/**
|
||||
* @param T|null $identifier
|
||||
* @return bool
|
||||
*/
|
||||
public function replaceIdentifier(IdentifierInterface $identifier = null): bool;
|
||||
}
|
||||
|
|
@ -275,6 +275,7 @@ class FlexDirectoryForm implements FlexDirectoryFormInterface, JsonSerializable
|
|||
'unique_id' => $this->getUniqueId(),
|
||||
'form_name' => $this->getName(),
|
||||
'folder' => $this->getFlashFolder(),
|
||||
'id' => $this->getFlashId(),
|
||||
'directory' => $this->getDirectory()
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ class FlexForm implements FlexObjectFormInterface, JsonSerializable
|
|||
'unique_id' => $this->getUniqueId(),
|
||||
'form_name' => $this->getName(),
|
||||
'folder' => $this->getFlashFolder(),
|
||||
'id' => $this->getFlashId(),
|
||||
'object' => $this->getObject()
|
||||
];
|
||||
|
||||
|
|
|
|||
75
system/src/Grav/Framework/Flex/FlexIdentifier.php
Normal file
75
system/src/Grav/Framework/Flex/FlexIdentifier.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Flex;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Object\Identifiers\Identifier;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Interface IdentifierInterface
|
||||
*
|
||||
* @template T of FlexObjectInterface
|
||||
* @extends Identifier<T>
|
||||
*/
|
||||
class FlexIdentifier extends Identifier
|
||||
{
|
||||
/** @var string */
|
||||
private $keyField;
|
||||
/** @var FlexObjectInterface|null */
|
||||
private $object = null;
|
||||
|
||||
/**
|
||||
* @param FlexObjectInterface $object
|
||||
* @return FlexIdentifier<T>
|
||||
*/
|
||||
public static function createFromObject(FlexObjectInterface $object): FlexIdentifier
|
||||
{
|
||||
$instance = new static($object->getKey(), $object->getFlexType(), 'key');
|
||||
$instance->setObject($object);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* IdentifierInterface constructor.
|
||||
* @param string $id
|
||||
* @param string $type
|
||||
* @param string $keyField
|
||||
*/
|
||||
public function __construct(string $id, string $type, string $keyField = 'key')
|
||||
{
|
||||
parent::__construct($id, $type);
|
||||
|
||||
$this->keyField = $keyField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public function getObject(): ?FlexObjectInterface
|
||||
{
|
||||
if (!isset($this->object)) {
|
||||
/** @var Flex $flex */
|
||||
$flex = Grav::instance()['flex'];
|
||||
|
||||
$this->object = $flex->getObject($this->getId(), $this->getType(), $this->keyField);
|
||||
}
|
||||
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param T $object
|
||||
*/
|
||||
public function setObject(FlexObjectInterface $object): void
|
||||
{
|
||||
$type = $this->getType();
|
||||
if ($type !== $object->getFlexType()) {
|
||||
throw new RuntimeException(sprintf('Object has to be type %s, %s given', $type, $object->getFlexType()));
|
||||
}
|
||||
|
||||
$this->object = $object;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,9 @@ use Grav\Framework\Cache\CacheInterface;
|
|||
use Grav\Framework\Filesystem\Filesystem;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Form\FormFlashFile;
|
||||
use Grav\Framework\Media\Interfaces\MediaObjectInterface;
|
||||
use Grav\Framework\Media\MediaObject;
|
||||
use Grav\Framework\Media\UploadedMediaObject;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
|
|
@ -232,6 +235,55 @@ trait FlexMediaTrait
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $field
|
||||
* @param string $filename
|
||||
* @param MediaObjectInterface|null $image
|
||||
* @return MediaObject|UploadedMediaObject
|
||||
*/
|
||||
protected function buildMediaObject(?string $field, string $filename, MediaObjectInterface $image = null)
|
||||
{
|
||||
if (!$image) {
|
||||
$media = $field ? $this->getMediaField($field) : null;
|
||||
if ($media) {
|
||||
$image = $media[$filename];
|
||||
}
|
||||
}
|
||||
|
||||
return new MediaObject($field, $filename, $image, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $field
|
||||
* @return array
|
||||
*/
|
||||
protected function buildMediaList(?string $field): array
|
||||
{
|
||||
$names = $field ? (array)$this->getNestedProperty($field) : [];
|
||||
$media = $field ? $this->getMediaField($field) : null;
|
||||
if (null === $media) {
|
||||
$media = $this->getMedia();
|
||||
}
|
||||
|
||||
$list = [];
|
||||
foreach ($names as $key => $val) {
|
||||
$name = is_string($val) ? $val : $key;
|
||||
$medium = $media[$name];
|
||||
if ($medium) {
|
||||
if ($medium->uploaded_file) {
|
||||
$upload = $medium->uploaded_file;
|
||||
$id = $upload instanceof FormFlashFile ? $upload->getId() : "{$field}-{$name}";
|
||||
|
||||
$list[] = new UploadedMediaObject($id, $field, $name, $upload);
|
||||
} else {
|
||||
$list[] = $this->buildMediaObject($field, $name, $medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
* @return void
|
||||
|
|
@ -310,7 +362,7 @@ trait FlexMediaTrait
|
|||
$updated = false;
|
||||
foreach ($this->getUpdatedMedia() as $filename => $upload) {
|
||||
if (is_array($upload)) {
|
||||
// Uses new format with [UploadedFileInterface, array].
|
||||
/** @var array{UploadedFileInterface,array} $upload */
|
||||
$settings = $upload[1];
|
||||
if (isset($settings['destination']) && $settings['destination'] === $media->getPath()) {
|
||||
$upload = $upload[0];
|
||||
|
|
@ -323,6 +375,7 @@ trait FlexMediaTrait
|
|||
$updated = true;
|
||||
if ($medium) {
|
||||
$medium->uploaded = true;
|
||||
$medium->uploaded_file = $upload;
|
||||
$media->add($filename, $medium);
|
||||
} elseif (is_callable([$media, 'hide'])) {
|
||||
$media->hide($filename);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Flex\Traits;
|
||||
|
||||
use Grav\Framework\Contracts\Relationships\RelationshipInterface;
|
||||
use Grav\Framework\Contracts\Relationships\RelationshipsInterface;
|
||||
use Grav\Framework\Flex\FlexIdentifier;
|
||||
use Grav\Framework\Relationships\Relationships;
|
||||
|
||||
/**
|
||||
* Trait FlexRelationshipsTrait
|
||||
*/
|
||||
trait FlexRelationshipsTrait
|
||||
{
|
||||
/** @var RelationshipsInterface|null */
|
||||
private $_relationships = null;
|
||||
|
||||
/**
|
||||
* @return Relationships
|
||||
*/
|
||||
public function getRelationships(): Relationships
|
||||
{
|
||||
if (!isset($this->_relationships)) {
|
||||
$blueprint = $this->getBlueprint();
|
||||
$options = $blueprint->get('config/relationships', []);
|
||||
$parent = FlexIdentifier::createFromObject($this);
|
||||
|
||||
$this->_relationships = new Relationships($parent, $options);
|
||||
}
|
||||
|
||||
return $this->_relationships;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return RelationshipInterface|null
|
||||
*/
|
||||
public function getRelationship(string $name): ?RelationshipInterface
|
||||
{
|
||||
return $this->getRelationships()[$name];
|
||||
}
|
||||
|
||||
protected function resetRelationships(): void
|
||||
{
|
||||
$this->_relationships = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable $collection
|
||||
* @return array
|
||||
*/
|
||||
protected function buildFlexIdentifierList(iterable $collection): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($collection as $object) {
|
||||
$list[] = FlexIdentifier::createFromObject($object);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,8 @@ class FormFlash implements FormFlashInterface
|
|||
/** @var bool */
|
||||
protected $exists;
|
||||
/** @var string */
|
||||
protected $id;
|
||||
/** @var string */
|
||||
protected $sessionId;
|
||||
/** @var string */
|
||||
protected $uniqueId;
|
||||
|
|
@ -75,9 +77,12 @@ class FormFlash implements FormFlashInterface
|
|||
});
|
||||
}
|
||||
|
||||
$this->sessionId = $config['session_id'] ?? 'no-session';
|
||||
$this->id = $config['id'] ?? '';
|
||||
$this->sessionId = $config['session_id'] ?? '';
|
||||
$this->uniqueId = $config['unique_id'] ?? '';
|
||||
|
||||
$this->setUser($config['user'] ?? null);
|
||||
|
||||
$folder = $config['folder'] ?? ($this->sessionId ? 'tmp://forms/' . $this->sessionId : '');
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
|
|
@ -133,6 +138,14 @@ class FormFlash implements FormFlashInterface
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id && $this->uniqueId ? $this->id . '/' . $this->uniqueId : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
|
@ -390,8 +403,8 @@ class FormFlash implements FormFlashInterface
|
|||
*/
|
||||
public function clearFiles()
|
||||
{
|
||||
foreach ($this->files as $field => $files) {
|
||||
foreach ($files as $name => $upload) {
|
||||
foreach ($this->files as $files) {
|
||||
foreach ($files as $upload) {
|
||||
$this->removeTmpFile($upload['tmp_name'] ?? '');
|
||||
}
|
||||
}
|
||||
|
|
@ -406,6 +419,7 @@ class FormFlash implements FormFlashInterface
|
|||
{
|
||||
return [
|
||||
'form' => $this->formName,
|
||||
'id' => $this->getId(),
|
||||
'unique_id' => $this->uniqueId,
|
||||
'url' => $this->url,
|
||||
'user' => $this->user,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ use function sprintf;
|
|||
*/
|
||||
class FormFlashFile implements UploadedFileInterface, JsonSerializable
|
||||
{
|
||||
/** @var string */
|
||||
private $id;
|
||||
/** @var string */
|
||||
private $field;
|
||||
/** @var bool */
|
||||
|
|
@ -45,6 +47,7 @@ class FormFlashFile implements UploadedFileInterface, JsonSerializable
|
|||
*/
|
||||
public function __construct(string $field, array $upload, FormFlash $flash)
|
||||
{
|
||||
$this->id = $flash->getId() ?: $flash->getUniqueId();
|
||||
$this->field = $field;
|
||||
$this->upload = $upload;
|
||||
$this->flash = $flash;
|
||||
|
|
@ -107,6 +110,11 @@ class FormFlashFile implements UploadedFileInterface, JsonSerializable
|
|||
}
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
|
@ -222,6 +230,7 @@ class FormFlashFile implements UploadedFileInterface, JsonSerializable
|
|||
public function __debugInfo()
|
||||
{
|
||||
return [
|
||||
'id:private' => $this->id,
|
||||
'field:private' => $this->field,
|
||||
'moved:private' => $this->moved,
|
||||
'upload:private' => $this->upload,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,13 @@ interface FormFlashInterface extends \JsonSerializable
|
|||
*/
|
||||
public function __construct($config);
|
||||
|
||||
/**
|
||||
* Get unique form flash id if set.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string;
|
||||
|
||||
/**
|
||||
* Get session Id associated to this form instance.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -453,10 +453,10 @@ trait FormTrait
|
|||
'session_id' => $this->getSessionId(),
|
||||
'unique_id' => $this->getUniqueId(),
|
||||
'form_name' => $this->getName(),
|
||||
'folder' => $this->getFlashFolder()
|
||||
'folder' => $this->getFlashFolder(),
|
||||
'id' => $this->getFlashId()
|
||||
];
|
||||
|
||||
|
||||
$this->flash = new FormFlash($config);
|
||||
$this->flash->setUrl($grav['uri']->url)->setUser($grav['user'] ?? null);
|
||||
}
|
||||
|
|
@ -486,7 +486,8 @@ trait FormTrait
|
|||
'session_id' => $this->getSessionId(),
|
||||
'unique_id' => $uniqueId,
|
||||
'form_name' => $name,
|
||||
'folder' => $this->getFlashFolder()
|
||||
'folder' => $this->getFlashFolder(),
|
||||
'id' => $this->getFlashId()
|
||||
];
|
||||
$flash = new FormFlash($config);
|
||||
if ($flash->exists() && $flash->getFormName() === $name) {
|
||||
|
|
@ -614,6 +615,28 @@ trait FormTrait
|
|||
return strpos($path, '!!') === false ? rtrim($path, '/') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getFlashId(): ?string
|
||||
{
|
||||
// Fill template token keys/value pairs.
|
||||
$dataMap = [
|
||||
'[FORM_NAME]' => $this->getName(),
|
||||
'[SESSIONID]' => 'session',
|
||||
'[USERNAME]' => '!!',
|
||||
'[USERNAME_OR_SESSIONID]' => '!!',
|
||||
'[ACCOUNT]' => 'account'
|
||||
];
|
||||
|
||||
$flashLookupFolder = $this->getFlashLookupFolder();
|
||||
|
||||
$path = str_replace(array_keys($dataMap), array_values($dataMap), $flashLookupFolder);
|
||||
|
||||
// Make sure we only return valid paths.
|
||||
return strpos($path, '!!') === false ? rtrim($path, '/') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -9,9 +9,39 @@
|
|||
|
||||
namespace Grav\Framework\Media\Interfaces;
|
||||
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
/**
|
||||
* Class implements media object interface.
|
||||
*
|
||||
* @property UploadedFileInterface|null $uploaded_file
|
||||
*/
|
||||
interface MediaObjectInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array containing the file metadata
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMeta();
|
||||
|
||||
/**
|
||||
* Return URL to file.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function url($reset = true);
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @example $value = $this->get('this.is.my.nested.variable');
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $default Default value (or null).
|
||||
* @param string|null $separator Separator, defaults to '.'
|
||||
* @return mixed Value.
|
||||
*/
|
||||
public function get($name, $default = null, $separator = null);
|
||||
}
|
||||
|
|
|
|||
150
system/src/Grav/Framework/Media/MediaIdentifier.php
Normal file
150
system/src/Grav/Framework/Media/MediaIdentifier.php
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Media;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Framework\Contracts\Media\MediaObjectInterface;
|
||||
use Grav\Framework\Flex\Flex;
|
||||
use Grav\Framework\Flex\FlexFormFlash;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Object\Identifiers\Identifier;
|
||||
|
||||
/**
|
||||
* Interface IdentifierInterface
|
||||
*
|
||||
* @template T of MediaObjectInterface
|
||||
* @extends Identifier<T>
|
||||
*/
|
||||
class MediaIdentifier extends Identifier
|
||||
{
|
||||
/** @var MediaObjectInterface|null */
|
||||
private $object = null;
|
||||
|
||||
/**
|
||||
* @param MediaObjectInterface $object
|
||||
* @return MediaIdentifier<T>
|
||||
*/
|
||||
public static function createFromObject(MediaObjectInterface $object): MediaIdentifier
|
||||
{
|
||||
$instance = new static($object->getId());
|
||||
$instance->setObject($object);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*/
|
||||
public function __construct(string $id)
|
||||
{
|
||||
parent::__construct($id, 'media');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public function getObject(): ?MediaObjectInterface
|
||||
{
|
||||
if (!isset($this->object)) {
|
||||
$type = $this->getType();
|
||||
$id = $this->getId();
|
||||
|
||||
$parts = explode('/', $id);
|
||||
if ($type === 'media' && str_starts_with($id, 'uploads/')) {
|
||||
array_shift($parts);
|
||||
[, $folder, $uniqueId, $field, $filename] = $this->findFlash($parts);
|
||||
|
||||
$flash = $this->getFlash($folder, $uniqueId);
|
||||
if ($flash->exists()) {
|
||||
|
||||
$uploadedFile = $flash->getFilesByField($field)[$filename] ?? null;
|
||||
|
||||
$this->object = UploadedMediaObject::createFromFlash($flash, $field, $filename, $uploadedFile);
|
||||
}
|
||||
} else {
|
||||
$type = array_shift($parts);
|
||||
$key = array_shift($parts);
|
||||
$field = array_shift($parts);
|
||||
$filename = implode('/', $parts);
|
||||
|
||||
$flexObject = $this->getFlexObject($type, $key);
|
||||
if ($flexObject && method_exists($flexObject, 'getMediaField') && method_exists($flexObject, 'getMedia')) {
|
||||
$media = $field !== 'media' ? $flexObject->getMediaField($field) : $flexObject->getMedia();
|
||||
$image = null;
|
||||
if ($media) {
|
||||
$image = $media[$filename];
|
||||
}
|
||||
|
||||
$this->object = new MediaObject($field, $filename, $image, $flexObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($this->object)) {
|
||||
throw new \RuntimeException(sprintf('Object not found for identifier {type: "%s", id: "%s"}', $type, $id));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param T $object
|
||||
*/
|
||||
public function setObject(MediaObjectInterface $object): void
|
||||
{
|
||||
$type = $this->getType();
|
||||
$objectType = $object->getType();
|
||||
|
||||
if ($type !== $objectType) {
|
||||
throw new \RuntimeException(sprintf('Object has to be type %s, %s given', $type, $objectType));
|
||||
}
|
||||
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
protected function findFlash(array $parts): ?array
|
||||
{
|
||||
$type = array_shift($parts);
|
||||
if ($type === 'account') {
|
||||
/** @var UserInterface|null $user */
|
||||
$user = Grav::instance()['user'] ?? null;
|
||||
$folder = $user->getMediaFolder();
|
||||
} else {
|
||||
$folder = 'tmp://';
|
||||
}
|
||||
|
||||
if (!$folder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
do {
|
||||
$part = array_shift($parts);
|
||||
$folder .= "/{$part}";
|
||||
} while (!str_starts_with($part, 'flex-'));
|
||||
|
||||
$uniqueId = array_shift($parts);
|
||||
$field = array_shift($parts);
|
||||
$filename = implode('/', $parts);
|
||||
|
||||
return [$type, $folder, $uniqueId, $field, $filename];
|
||||
}
|
||||
|
||||
protected function getFlash(string $folder, string $uniqueId): FlexFormFlash
|
||||
{
|
||||
$config = [
|
||||
'unique_id' => $uniqueId,
|
||||
'folder' => $folder
|
||||
];
|
||||
|
||||
return new FlexFormFlash($config);
|
||||
}
|
||||
|
||||
protected function getFlexObject(string $type, string $key): ?FlexObjectInterface
|
||||
{
|
||||
/** @var Flex $flex */
|
||||
$flex = Grav::instance()['flex'];
|
||||
|
||||
return $flex->getObject($key, $type);
|
||||
}
|
||||
}
|
||||
215
system/src/Grav/Framework/Media/MediaObject.php
Normal file
215
system/src/Grav/Framework/Media/MediaObject.php
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Media;
|
||||
|
||||
use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Framework\Contracts\Media\MediaObjectInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Media\Interfaces\MediaObjectInterface as GravMediaObjectInterface;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class MediaObject
|
||||
*/
|
||||
class MediaObject implements MediaObjectInterface
|
||||
{
|
||||
/** @var string */
|
||||
static public $placeholderImage = 'image://media/thumb.png';
|
||||
|
||||
/** @var FlexObjectInterface */
|
||||
public $object;
|
||||
/** @var GravMediaObjectInterface|null */
|
||||
public $media;
|
||||
|
||||
/** @var string|null */
|
||||
private $field;
|
||||
/** @var string */
|
||||
private $filename;
|
||||
|
||||
/**
|
||||
* MediaObject constructor.
|
||||
* @param string|null $field
|
||||
* @param string $filename
|
||||
* @param GravMediaObjectInterface|null $media
|
||||
* @param FlexObjectInterface $object
|
||||
*/
|
||||
public function __construct(?string $field, string $filename, ?GravMediaObjectInterface $media, FlexObjectInterface $object)
|
||||
{
|
||||
$this->field = $field;
|
||||
$this->filename = $filename;
|
||||
$this->media = $media;
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return 'media';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
$field = $this->field;
|
||||
$object = $this->object;
|
||||
$path = $field ? "/{$field}/" : '/media/';
|
||||
|
||||
return $object->getType() . '/' . $object->getKey() . $path . basename($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return $this->media !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMeta(): array
|
||||
{
|
||||
if (!isset($this->media)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->media->getMeta();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get(string $field)
|
||||
{
|
||||
if (!isset($this->media)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->media->get($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl(): string
|
||||
{
|
||||
if (!isset($this->media)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->media->url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create media response.
|
||||
*
|
||||
* @param array $actions
|
||||
* @return Response
|
||||
*/
|
||||
public function createResponse(array $actions): ResponseInterface
|
||||
{
|
||||
if (!isset($this->media)) {
|
||||
return $this->create404Response($actions);
|
||||
}
|
||||
|
||||
$media = $this->media;
|
||||
|
||||
if ($actions) {
|
||||
$media = $this->processMediaActions($media, $actions);
|
||||
}
|
||||
|
||||
// FIXME: This only works for images
|
||||
if (!$media instanceof ImageMedium) {
|
||||
throw new \RuntimeException('Not Implemented', 500);
|
||||
}
|
||||
|
||||
$filename = $media->path(false);
|
||||
$time = filemtime($filename);
|
||||
$size = filesize($filename);
|
||||
$body = fopen($filename, 'rb');
|
||||
$headers = [
|
||||
'Content-Type' => $media->get('mime'),
|
||||
'Last-Modified' => gmdate('D, d M Y H:i:s', $time) . ' GMT',
|
||||
'ETag' => sprintf('%x-%x', $size, $time)
|
||||
];
|
||||
|
||||
return new Response(200, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process media actions
|
||||
*
|
||||
* @param GravMediaObjectInterface $medium
|
||||
* @param array $actions
|
||||
* @return GravMediaObjectInterface
|
||||
*/
|
||||
protected function processMediaActions(GravMediaObjectInterface $medium, array $actions): GravMediaObjectInterface
|
||||
{
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $method => $params) {
|
||||
$matches = [];
|
||||
|
||||
if (preg_match('/\[(.*)]/', $params, $matches)) {
|
||||
$args = [explode(',', $matches[1])];
|
||||
} else {
|
||||
$args = explode(',', $params);
|
||||
}
|
||||
|
||||
try {
|
||||
$medium->{$method}(...$args);
|
||||
} catch (Throwable $e) {
|
||||
// Ignore all errors for now and just skip the action.
|
||||
}
|
||||
}
|
||||
|
||||
return $medium;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $actions
|
||||
* @return Response
|
||||
*/
|
||||
protected function create404Response(array $actions): Response
|
||||
{
|
||||
// Display placeholder image.
|
||||
$filename = static::$placeholderImage;
|
||||
|
||||
$time = filemtime($filename);
|
||||
$size = filesize($filename);
|
||||
$body = fopen($filename, 'rb');
|
||||
$headers = [
|
||||
'Content-Type' => 'image/svg',
|
||||
'Last-Modified' => gmdate('D, d M Y H:i:s', $time) . ' GMT',
|
||||
'ETag' => sprintf('%x-%x', $size, $time)
|
||||
];
|
||||
|
||||
return new Response(404, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->getType(),
|
||||
'id' => $this->getId()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->jsonSerialize();
|
||||
}
|
||||
}
|
||||
172
system/src/Grav/Framework/Media/UploadedMediaObject.php
Normal file
172
system/src/Grav/Framework/Media/UploadedMediaObject.php
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Media;
|
||||
|
||||
use Grav\Framework\Contracts\Media\MediaObjectInterface;
|
||||
use Grav\Framework\Flex\FlexFormFlash;
|
||||
use Grav\Framework\Form\Interfaces\FormFlashInterface;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
/**
|
||||
* Class UploadedMediaObject
|
||||
*/
|
||||
class UploadedMediaObject implements MediaObjectInterface
|
||||
{
|
||||
/** @var string */
|
||||
static public $placeholderImage = 'image://media/thumb.png';
|
||||
|
||||
/** @var FormFlashInterface */
|
||||
public $object;
|
||||
|
||||
/** @var string */
|
||||
private $id;
|
||||
/** @var string|null */
|
||||
private $field;
|
||||
/** @var string */
|
||||
private $filename;
|
||||
/** @var array */
|
||||
private $meta;
|
||||
/** @var UploadedFileInterface|null */
|
||||
private $uploadedFile;
|
||||
|
||||
/**
|
||||
* @param FlexFormFlash $flash
|
||||
* @param string|null $field
|
||||
* @param string $filename
|
||||
* @param UploadedFileInterface|null $uploadedFile
|
||||
* @return static
|
||||
*/
|
||||
public static function createFromFlash(FlexFormFlash $flash, ?string $field, string $filename, ?UploadedFileInterface $uploadedFile = null)
|
||||
{
|
||||
$id = $flash->getId();
|
||||
|
||||
return new static($id, $field, $filename, $uploadedFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string|null $field
|
||||
* @param string $filename
|
||||
* @param UploadedFileInterface|null $uploadedFile
|
||||
*/
|
||||
public function __construct(string $id, ?string $field, string $filename, ?UploadedFileInterface $uploadedFile = null)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->field = $field;
|
||||
$this->filename = $filename;
|
||||
$this->uploadedFile = $uploadedFile;
|
||||
if ($uploadedFile) {
|
||||
$this->meta = [
|
||||
'filename' => $uploadedFile->getClientFilename(),
|
||||
'mime' => $uploadedFile->getClientMediaType(),
|
||||
'size' => $uploadedFile->getSize()
|
||||
];
|
||||
} else {
|
||||
$this->meta = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return 'media';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
$id = $this->id;
|
||||
$field = $this->field;
|
||||
$path = $field ? "/{$field}/" : '';
|
||||
|
||||
return 'uploads/' . $id . $path . basename($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
//return $this->uploadedFile !== null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMeta(): array
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get(string $field)
|
||||
{
|
||||
return $this->meta[$field] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UploadedFileInterface|null
|
||||
*/
|
||||
public function getUploadedFile(): ?UploadedFileInterface
|
||||
{
|
||||
return $this->uploadedFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $actions
|
||||
* @return Response
|
||||
*/
|
||||
public function createResponse(array $actions): ResponseInterface
|
||||
{
|
||||
// Display placeholder image.
|
||||
$filename = static::$placeholderImage;
|
||||
|
||||
$time = filemtime($filename);
|
||||
$size = filesize($filename);
|
||||
$body = fopen($filename, 'rb');
|
||||
$headers = [
|
||||
'Content-Type' => 'image/svg',
|
||||
'Last-Modified' => gmdate('D, d M Y H:i:s', $time) . ' GMT',
|
||||
'ETag' => sprintf('%x-%x', $size, $time)
|
||||
];
|
||||
|
||||
return new Response(404, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->getType(),
|
||||
'id' => $this->getId()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->jsonSerialize();
|
||||
}
|
||||
}
|
||||
66
system/src/Grav/Framework/Object/Identifiers/Identifier.php
Normal file
66
system/src/Grav/Framework/Object/Identifiers/Identifier.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Object\Identifiers;
|
||||
|
||||
use Grav\Framework\Contracts\Object\IdentifierInterface;
|
||||
|
||||
/**
|
||||
* Interface IdentifierInterface
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
class Identifier implements IdentifierInterface
|
||||
{
|
||||
/** @var string */
|
||||
private $id;
|
||||
/** @var string */
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* IdentifierInterface constructor.
|
||||
* @param string $id
|
||||
* @param string $type
|
||||
*/
|
||||
public function __construct(string $id, string $type)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'id' => $this->id
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->jsonSerialize();
|
||||
}
|
||||
}
|
||||
217
system/src/Grav/Framework/Relationships/Relationships.php
Normal file
217
system/src/Grav/Framework/Relationships/Relationships.php
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Relationships;
|
||||
|
||||
use Grav\Framework\Contracts\Object\IdentifierInterface;
|
||||
use Grav\Framework\Contracts\Relationships\RelationshipInterface;
|
||||
use Grav\Framework\Contracts\Relationships\RelationshipsInterface;
|
||||
use Grav\Framework\Flex\FlexIdentifier;
|
||||
use RuntimeException;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Class Relationships
|
||||
*
|
||||
* @template T of \Grav\Framework\Contracts\Object\IdentifierInterface
|
||||
* @template P of \Grav\Framework\Contracts\Object\IdentifierInterface
|
||||
* @implements RelationshipsInterface<T,P>
|
||||
*/
|
||||
class Relationships implements RelationshipsInterface
|
||||
{
|
||||
/** @var P */
|
||||
protected $parent;
|
||||
/** @var array */
|
||||
protected $options;
|
||||
|
||||
/** @var RelationshipInterface<T,P>[] */
|
||||
protected $relationships;
|
||||
|
||||
/**
|
||||
* Relationships constructor.
|
||||
* @param P $parent
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(IdentifierInterface $parent, array $options)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->options = $options;
|
||||
$this->relationships = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function isModified(): bool
|
||||
{
|
||||
return !empty($this->getModified());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RelationshipInterface<T,P>[]
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getModified(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->relationships as $name => $relationship) {
|
||||
if ($relationship->isModified()) {
|
||||
$list[$name] = $relationship;
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->options[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return RelationshipInterface<T,P>|null
|
||||
*/
|
||||
public function offsetGet($offset): ?RelationshipInterface
|
||||
{
|
||||
if (!isset($this->relationships[$offset])) {
|
||||
$options = $this->options[$offset] ?? null;
|
||||
if (null === $options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->relationships[$offset] = $this->createRelationship($offset, $options);
|
||||
}
|
||||
|
||||
return $this->relationships[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @param mixed $value
|
||||
* @return never-return
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
throw new RuntimeException('Setting relationship is not supported', 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return never-return
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
throw new RuntimeException('Removing relationship is not allowed', 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RelationshipInterface<T,P>|null
|
||||
*/
|
||||
public function current(): ?RelationshipInterface
|
||||
{
|
||||
$name = key($this->options);
|
||||
if ($name === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->offsetGet($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function key(): string
|
||||
{
|
||||
return key($this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
next($this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
reset($this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return key($this->options) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this as $name => $relationship) {
|
||||
$list[$name] = $relationship->jsonSerialize();
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $options
|
||||
* @return ToOneRelationship|ToManyRelationship
|
||||
*/
|
||||
private function createRelationship(string $name, array $options): RelationshipInterface
|
||||
{
|
||||
$data = null;
|
||||
|
||||
$parent = $this->parent;
|
||||
if ($parent instanceof FlexIdentifier) {
|
||||
$object = $parent->getObject();
|
||||
if (!method_exists($object, 'initRelationship')) {
|
||||
throw new RuntimeException(sprintf('Bad relationship %s', $name), 500);
|
||||
}
|
||||
|
||||
$data = $object->initRelationship($name);
|
||||
}
|
||||
|
||||
$cardinality = $options['cardinality'] ?? '';
|
||||
switch ($cardinality) {
|
||||
case 'to-one':
|
||||
$relationship = new ToOneRelationship($parent, $name, $options, $data);
|
||||
break;
|
||||
case 'to-many':
|
||||
$relationship = new ToManyRelationship($parent, $name, $options, $data ?? []);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException(sprintf('Bad relationship cardinality %s', $cardinality), 500);
|
||||
}
|
||||
|
||||
return $relationship;
|
||||
}
|
||||
}
|
||||
259
system/src/Grav/Framework/Relationships/ToManyRelationship.php
Normal file
259
system/src/Grav/Framework/Relationships/ToManyRelationship.php
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Relationships;
|
||||
|
||||
use ArrayIterator;
|
||||
use Grav\Framework\Compat\Serializable;
|
||||
use Grav\Framework\Contracts\Object\IdentifierInterface;
|
||||
use Grav\Framework\Contracts\Relationships\ToManyRelationshipInterface;
|
||||
use Grav\Framework\Relationships\Traits\RelationshipTrait;
|
||||
use function count;
|
||||
use function is_callable;
|
||||
|
||||
/**
|
||||
* Class ToManyRelationship
|
||||
*
|
||||
* @template T of IdentifierInterface
|
||||
* @template P of IdentifierInterface
|
||||
* @template-implements ToManyRelationshipInterface<T,P>
|
||||
*/
|
||||
class ToManyRelationship implements ToManyRelationshipInterface
|
||||
{
|
||||
/** @template-use RelationshipTrait<T> */
|
||||
use RelationshipTrait;
|
||||
use Serializable;
|
||||
|
||||
/** @var IdentifierInterface[] */
|
||||
protected $identifiers = [];
|
||||
|
||||
/**
|
||||
* ToManyRelationship constructor.
|
||||
* @param string $name
|
||||
* @param IdentifierInterface $parent
|
||||
* @param iterable<IdentifierInterface> $identifiers
|
||||
*/
|
||||
public function __construct(IdentifierInterface $parent, string $name, array $options, iterable $identifiers = [])
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->name = $name;
|
||||
|
||||
$this->parseOptions($options);
|
||||
$this->addIdentifiers($identifiers);
|
||||
|
||||
$this->modified = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getCardinality(): string
|
||||
{
|
||||
return 'to-many';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->identifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function fetch(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->identifiers as $identifier) {
|
||||
if (is_callable([$identifier, 'getObject'])) {
|
||||
$identifier = $identifier->getObject();
|
||||
}
|
||||
$list[] = $identifier;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string|null $type
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function has(string $id, string $type = null): bool
|
||||
{
|
||||
return $this->getIdentifier($id, $type) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param positive-int $pos
|
||||
* @return IdentifierInterface|null
|
||||
*/
|
||||
public function getNthIdentifier(int $pos): ?IdentifierInterface
|
||||
{
|
||||
$items = array_keys($this->identifiers);
|
||||
$key = $items[$pos - 1] ?? null;
|
||||
if (null === $key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->identifiers[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string|null $type
|
||||
* @return IdentifierInterface|null
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getIdentifier(string $id, string $type = null): ?IdentifierInterface
|
||||
{
|
||||
if (null === $type) {
|
||||
$type = $this->getType();
|
||||
}
|
||||
|
||||
if ($type === 'media' && !str_contains($id, '/')) {
|
||||
$name = $this->name;
|
||||
$id = $this->parent->getType() . '/' . $this->parent->getId() . '/'. $name . '/' . $id;
|
||||
}
|
||||
|
||||
$key = "{$type}/{$id}";
|
||||
|
||||
return $this->identifiers[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string|null $type
|
||||
* @return T|null
|
||||
*/
|
||||
public function getObject(string $id, string $type = null): ?object
|
||||
{
|
||||
$identifier = $this->getIdentifier($id, $type);
|
||||
if ($identifier && is_callable([$identifier, 'getObject'])) {
|
||||
$identifier = $identifier->getObject();
|
||||
}
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IdentifierInterface $identifier
|
||||
* @return bool
|
||||
*/
|
||||
public function addIdentifier(IdentifierInterface $identifier): bool
|
||||
{
|
||||
return $this->addIdentifiers([$identifier]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IdentifierInterface|null $identifier
|
||||
* @return bool
|
||||
*/
|
||||
public function removeIdentifier(IdentifierInterface $identifier = null): bool
|
||||
{
|
||||
return !$identifier || $this->removeIdentifiers([$identifier]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<IdentifierInterface> $identifiers
|
||||
* @return bool
|
||||
*/
|
||||
public function addIdentifiers(iterable $identifiers): bool
|
||||
{
|
||||
foreach ($identifiers as $identifier) {
|
||||
$type = $identifier->getType();
|
||||
$id = $identifier->getId();
|
||||
$key = "{$type}/{$id}";
|
||||
|
||||
$this->identifiers[$key] = $this->checkIdentifier($identifier);
|
||||
$this->modified = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<IdentifierInterface> $identifiers
|
||||
* @return bool
|
||||
*/
|
||||
public function replaceIdentifiers(iterable $identifiers): bool
|
||||
{
|
||||
$this->identifiers = [];
|
||||
$this->modified = true;
|
||||
|
||||
return $this->addIdentifiers($identifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<IdentifierInterface> $identifiers
|
||||
* @return bool
|
||||
*/
|
||||
public function removeIdentifiers(iterable $identifiers): bool
|
||||
{
|
||||
foreach ($identifiers as $identifier) {
|
||||
$type = $identifier->getType();
|
||||
$id = $identifier->getId();
|
||||
$key = "{$type}/{$id}";
|
||||
|
||||
unset($this->identifiers[$key]);
|
||||
$this->modified = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<IdentifierInterface>
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getIterator(): iterable
|
||||
{
|
||||
return new ArrayIterator($this->identifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->getIterator() as $item) {
|
||||
$list[] = $item->jsonSerialize();
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [
|
||||
'parent' => $this->parent,
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'options' => $this->options,
|
||||
'modified' => $this->modified,
|
||||
'identifiers' => $this->identifiers,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$this->parent = $data['parent'];
|
||||
$this->name = $data['name'];
|
||||
$this->type = $data['type'];
|
||||
$this->options = $data['options'];
|
||||
$this->modified = $data['modified'];
|
||||
$this->identifiers = $data['identifiers'];
|
||||
}
|
||||
}
|
||||
207
system/src/Grav/Framework/Relationships/ToOneRelationship.php
Normal file
207
system/src/Grav/Framework/Relationships/ToOneRelationship.php
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Relationships;
|
||||
|
||||
use ArrayIterator;
|
||||
use Grav\Framework\Compat\Serializable;
|
||||
use Grav\Framework\Contracts\Object\IdentifierInterface;
|
||||
use Grav\Framework\Contracts\Relationships\ToOneRelationshipInterface;
|
||||
use Grav\Framework\Relationships\Traits\RelationshipTrait;
|
||||
use function is_callable;
|
||||
|
||||
/**
|
||||
* Class ToOneRelationship
|
||||
*
|
||||
* @template T of IdentifierInterface
|
||||
* @template P of IdentifierInterface
|
||||
* @template-implements ToOneRelationshipInterface<T,P>
|
||||
*/
|
||||
class ToOneRelationship implements ToOneRelationshipInterface
|
||||
{
|
||||
/** @template-use RelationshipTrait<T> */
|
||||
use RelationshipTrait;
|
||||
use Serializable;
|
||||
|
||||
/** @var IdentifierInterface|null */
|
||||
protected $identifier = null;
|
||||
|
||||
public function __construct(IdentifierInterface $parent, string $name, array $options, IdentifierInterface $identifier = null)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->name = $name;
|
||||
|
||||
$this->parseOptions($options);
|
||||
$this->replaceIdentifier($identifier);
|
||||
|
||||
$this->modified = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getCardinality(): string
|
||||
{
|
||||
return 'to-one';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->identifier ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object|null
|
||||
*/
|
||||
public function fetch(): ?object
|
||||
{
|
||||
$identifier = $this->identifier;
|
||||
if (is_callable([$identifier, 'getObject'])) {
|
||||
$identifier = $identifier->getObject();
|
||||
}
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|null $id
|
||||
* @param string|null $type
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function has(string $id = null, string $type = null): bool
|
||||
{
|
||||
return $this->getIdentifier($id, $type) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $id
|
||||
* @param string|null $type
|
||||
* @return IdentifierInterface|null
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getIdentifier(string $id = null, string $type = null): ?IdentifierInterface
|
||||
{
|
||||
if ($id && $this->getType() === 'media' && !str_contains($id, '/')) {
|
||||
$name = $this->name;
|
||||
$id = $this->parent->getType() . '/' . $this->parent->getId() . '/'. $name . '/' . $id;
|
||||
}
|
||||
|
||||
$identifier = $this->identifier ?? null;
|
||||
if (null === $identifier || ($type && $type !== $identifier->getType()) || ($id && $id !== $identifier->getId())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $id
|
||||
* @param string|null $type
|
||||
* @return T|null
|
||||
*/
|
||||
public function getObject(string $id = null, string $type = null): ?object
|
||||
{
|
||||
$identifier = $this->getIdentifier($id, $type);
|
||||
if ($identifier && is_callable([$identifier, 'getObject'])) {
|
||||
$identifier = $identifier->getObject();
|
||||
}
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IdentifierInterface $identifier
|
||||
* @return bool
|
||||
*/
|
||||
public function addIdentifier(IdentifierInterface $identifier): bool
|
||||
{
|
||||
$this->identifier = $this->checkIdentifier($identifier);
|
||||
$this->modified = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IdentifierInterface|null $identifier
|
||||
* @return bool
|
||||
*/
|
||||
public function replaceIdentifier(IdentifierInterface $identifier = null): bool
|
||||
{
|
||||
if ($identifier === null) {
|
||||
$this->identifier = null;
|
||||
$this->modified = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->addIdentifier($identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IdentifierInterface|null $identifier
|
||||
* @return bool
|
||||
*/
|
||||
public function removeIdentifier(IdentifierInterface $identifier = null): bool
|
||||
{
|
||||
if (null === $identifier || $this->has($identifier->getId(), $identifier->getType())) {
|
||||
$this->identifier = null;
|
||||
$this->modified = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<IdentifierInterface>
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getIterator(): iterable
|
||||
{
|
||||
return new ArrayIterator((array)$this->identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function jsonSerialize(): ?array
|
||||
{
|
||||
return $this->identifier ? $this->identifier->jsonSerialize() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [
|
||||
'parent' => $this->parent,
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'options' => $this->options,
|
||||
'modified' => $this->modified,
|
||||
'identifier' => $this->identifier,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$this->parent = $data['parent'];
|
||||
$this->name = $data['name'];
|
||||
$this->type = $data['type'];
|
||||
$this->options = $data['options'];
|
||||
$this->modified = $data['modified'];
|
||||
$this->identifier = $data['identifier'];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Relationships\Traits;
|
||||
|
||||
use Grav\Framework\Contracts\Object\IdentifierInterface;
|
||||
use Grav\Framework\Flex\FlexIdentifier;
|
||||
use Grav\Framework\Media\MediaIdentifier;
|
||||
use Grav\Framework\Object\Identifiers\Identifier;
|
||||
use RuntimeException;
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
* Trait RelationshipTrait
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
trait RelationshipTrait
|
||||
{
|
||||
/** @var IdentifierInterface */
|
||||
protected $parent;
|
||||
/** @var string */
|
||||
protected $name;
|
||||
/** @var string */
|
||||
protected $type;
|
||||
/** @var array */
|
||||
protected $options;
|
||||
/** @var bool */
|
||||
protected $modified = false;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function isModified(): bool
|
||||
{
|
||||
return $this->modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IdentifierInterface
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function getParent(): IdentifierInterface
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IdentifierInterface $identifier
|
||||
* @return bool
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function hasIdentifier(IdentifierInterface $identifier): bool
|
||||
{
|
||||
return $this->getIdentifier($identifier->getId(), $identifier->getType()) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @phpstan-pure
|
||||
*/
|
||||
abstract public function count(): int;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @phpstan-pure
|
||||
*/
|
||||
public function check(): void
|
||||
{
|
||||
$min = $this->options['min'] ?? 0;
|
||||
$max = $this->options['max'] ?? 0;
|
||||
|
||||
if ($min || $max) {
|
||||
$count = $this->count();
|
||||
if ($min && $count < $min) {
|
||||
throw new RuntimeException(sprintf('%s relationship has too few objects in it', $this->name));
|
||||
}
|
||||
if ($max && $count > $max) {
|
||||
throw new RuntimeException(sprintf('%s relationship has too many objects in it', $this->name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IdentifierInterface $identifier
|
||||
* @return IdentifierInterface
|
||||
*/
|
||||
private function checkIdentifier(IdentifierInterface $identifier): IdentifierInterface
|
||||
{
|
||||
if ($this->type !== $identifier->getType()) {
|
||||
throw new RuntimeException(sprintf('Bad identifier type %s', $identifier->getType()));
|
||||
}
|
||||
|
||||
if (get_class($identifier) !== Identifier::class) {
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
if ($this->type === 'media') {
|
||||
return new MediaIdentifier($identifier->getId());
|
||||
}
|
||||
|
||||
return new FlexIdentifier($identifier->getId(), $identifier->getType());
|
||||
}
|
||||
|
||||
private function parseOptions(array $options): void
|
||||
{
|
||||
$this->type = $options['type'];
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\RequestHandler
|
||||
|
|
@ -7,8 +7,6 @@
|
|||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\RequestHandler\Middlewares;
|
||||
|
||||
use Grav\Common\Data\ValidationException;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\RequestHandler
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\RequestHandler\Middlewares;
|
||||
|
||||
use Grav\Framework\Psr7\UploadedFile;
|
||||
use Nyholm\Psr7\Stream;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use function array_slice;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Multipart request support for PUT and PATCH.
|
||||
*/
|
||||
class MultipartRequestSupport implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$contentType = $request->getHeaderLine('content-type');
|
||||
$method = $request->getMethod();
|
||||
if (!str_starts_with($contentType, 'multipart/form-data') || !in_array($method, ['PUT', 'PATH'], true)) {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
$boundary = explode('; boundary=', $contentType, 2)[1] ?? '';
|
||||
$parts = explode("--{$boundary}", $request->getBody()->getContents());
|
||||
$parts = array_slice($parts, 1, count($parts) - 2);
|
||||
|
||||
$params = [];
|
||||
$files = [];
|
||||
foreach ($parts as $part) {
|
||||
$this->processPart($params, $files, $part);
|
||||
}
|
||||
|
||||
return $handler->handle($request->withParsedBody($params)->withUploadedFiles($files));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @param array $files
|
||||
* @param string $part
|
||||
* @return void
|
||||
*/
|
||||
protected function processPart(array &$params, array &$files, string $part): void
|
||||
{
|
||||
$part = ltrim($part, "\r\n");
|
||||
[$rawHeaders, $body] = explode("\r\n\r\n", $part, 2);
|
||||
|
||||
// Parse headers.
|
||||
$rawHeaders = explode("\r\n", $rawHeaders);
|
||||
$headers = array_reduce(
|
||||
$rawHeaders,
|
||||
static function (array $headers, $header) {
|
||||
[$name, $value] = explode(':', $header);
|
||||
$headers[strtolower($name)] = ltrim($value, ' ');
|
||||
|
||||
return $headers;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
if (!isset($headers['content-disposition'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse content disposition header.
|
||||
$contentDisposition = $headers['content-disposition'];
|
||||
preg_match('/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/', $contentDisposition, $matches);
|
||||
$name = $matches[2];
|
||||
$filename = $matches[4] ?? null;
|
||||
|
||||
if ($filename !== null) {
|
||||
$stream = Stream::create($body);
|
||||
$this->addFile($files, $name, new UploadedFile($stream, strlen($body), UPLOAD_ERR_OK, $filename, $headers['content-type'] ?? null));
|
||||
} elseif (strpos($contentDisposition, 'filename') !== false) {
|
||||
// Not uploaded file.
|
||||
$stream = Stream::create('');
|
||||
$this->addFile($files, $name, new UploadedFile($stream, 0, UPLOAD_ERR_NO_FILE));
|
||||
} else {
|
||||
// Regular field.
|
||||
$params[$name] = substr($body, 0, -2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
* @param string $name
|
||||
* @param UploadedFileInterface $file
|
||||
* @return void
|
||||
*/
|
||||
protected function addFile(array &$files, string $name, UploadedFileInterface $file): void
|
||||
{
|
||||
if (strpos($name, '[]') === strlen($name) - 2) {
|
||||
$name = substr($name, 0, -2);
|
||||
|
||||
if (isset($files[$name]) && is_array($files[$name])) {
|
||||
$files[$name][] = $file;
|
||||
} else {
|
||||
$files[$name] = [$file];
|
||||
}
|
||||
} else {
|
||||
$files[$name] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,10 +28,10 @@ trait RequestHandlerTrait
|
|||
protected $middleware;
|
||||
|
||||
/** @var callable */
|
||||
private $handler;
|
||||
protected $handler;
|
||||
|
||||
/** @var ContainerInterface|null */
|
||||
private $container;
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user