(Grav GitSync) Automatic Commit from smokephil
This commit is contained in:
commit
4267db646d
2765 changed files with 462171 additions and 0 deletions
1913
plugins/flex-objects/classes/Admin/AdminController.php
Normal file
1913
plugins/flex-objects/classes/Admin/AdminController.php
Normal file
File diff suppressed because it is too large
Load diff
361
plugins/flex-objects/classes/Controllers/AbstractController.php
Normal file
361
plugins/flex-objects/classes/Controllers/AbstractController.php
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\FlexObjects\Controllers;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Session;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Controller\Traits\ControllerResponseTrait;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\FlexForm;
|
||||
use Grav\Framework\Flex\FlexFormFlash;
|
||||
use Grav\Framework\Flex\Interfaces\FlexFormInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Grav\Framework\RequestHandler\Exception\NotFoundException;
|
||||
use Grav\Framework\RequestHandler\Exception\PageExpiredException;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Grav\Plugin\FlexObjects\Flex;
|
||||
use Grav\Plugin\Form\Forms;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\Session\Message;
|
||||
use function in_array;
|
||||
use function is_callable;
|
||||
|
||||
/**
|
||||
* Class AbstractController
|
||||
* @package Grav\Plugin\FlexObjects\Controllers
|
||||
*/
|
||||
abstract class AbstractController implements RequestHandlerInterface
|
||||
{
|
||||
use ControllerResponseTrait;
|
||||
|
||||
/** @var string */
|
||||
protected $nonce_action = 'flex-object';
|
||||
/** @var string */
|
||||
protected $nonce_name = 'nonce';
|
||||
/** @var ServerRequestInterface */
|
||||
protected $request;
|
||||
/** @var Grav */
|
||||
protected $grav;
|
||||
/** @var UserInterface|null */
|
||||
protected $user;
|
||||
/** @var string */
|
||||
protected $type;
|
||||
/** @var string */
|
||||
protected $key;
|
||||
/** @var FlexDirectory */
|
||||
protected $directory;
|
||||
/** @var FlexObjectInterface */
|
||||
protected $object;
|
||||
|
||||
/**
|
||||
* Handle request.
|
||||
*
|
||||
* Fires event: flex.[directory].[task|action].[command]
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return Response
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$attributes = $request->getAttributes();
|
||||
$this->request = $request;
|
||||
$this->grav = $attributes['grav'] ?? Grav::instance();
|
||||
$this->type = $attributes['type'] ?? null;
|
||||
$this->key = $attributes['key'] ?? null;
|
||||
if ($this->type) {
|
||||
$this->directory = $this->getFlex()->getDirectory($this->type);
|
||||
$this->object = $attributes['object'] ?? null;
|
||||
if (!$this->object && $this->key && $this->directory) {
|
||||
$this->object = $this->directory->getObject($this->key) ?? $this->directory->createObject([], $this->key ?? '');
|
||||
if (is_callable([$this->object, 'refresh'])) {
|
||||
$this->object->refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Route $route */
|
||||
$route = $attributes['route'];
|
||||
$post = $this->getPost();
|
||||
|
||||
if ($this->isFormSubmit()) {
|
||||
$form = $this->getForm();
|
||||
$this->nonce_name = $attributes['nonce_name'] ?? $form->getNonceName();
|
||||
$this->nonce_action = $attributes['nonce_action'] ?? $form->getNonceAction();
|
||||
}
|
||||
|
||||
try {
|
||||
$task = $request->getAttribute('task') ?? $post['task'] ?? $route->getParam('task');
|
||||
if ($task) {
|
||||
if (empty($attributes['forwarded'])) {
|
||||
$this->checkNonce($task);
|
||||
}
|
||||
$type = 'task';
|
||||
$command = $task;
|
||||
} else {
|
||||
$type = 'action';
|
||||
$command = $request->getAttribute('action') ?? $post['action'] ?? $route->getParam('action') ?? 'display';
|
||||
}
|
||||
$command = strtolower($command);
|
||||
|
||||
$event = new Event(
|
||||
[
|
||||
'controller' => $this,
|
||||
'response' => null
|
||||
]
|
||||
);
|
||||
|
||||
$this->grav->fireEvent("flex.{$this->type}.{$type}.{$command}", $event);
|
||||
|
||||
$response = $event['response'];
|
||||
if (!$response) {
|
||||
/** @var Inflector $inflector */
|
||||
$inflector = $this->grav['inflector'];
|
||||
$method = $type . $inflector::camelize($command);
|
||||
if ($method && method_exists($this, $method)) {
|
||||
$response = $this->{$method}($request);
|
||||
} else {
|
||||
throw new NotFoundException($request);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$response = $this->createErrorResponse($e);
|
||||
}
|
||||
|
||||
if ($response instanceof Response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $this->createJsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServerRequestInterface
|
||||
*/
|
||||
public function getRequest(): ServerRequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPost(string $name = null, $default = null)
|
||||
{
|
||||
$body = $this->request->getParsedBody();
|
||||
|
||||
if ($name) {
|
||||
return $body[$name] ?? $default;
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isFormSubmit(): bool
|
||||
{
|
||||
return (bool)$this->getPost('__form-name__');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $type
|
||||
* @return FlexForm
|
||||
*/
|
||||
public function getForm(string $type = null): FlexFormInterface
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!$object) {
|
||||
throw new \RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
$formName = $this->getPost('__form-name__');
|
||||
if ($formName) {
|
||||
/** @var Forms $forms */
|
||||
$forms = $this->getGrav()['forms'];
|
||||
$form = $forms->getActiveForm();
|
||||
if ($form instanceof FlexForm && $form->getName() === $formName && $form->getObject()->getFlexKey() === $object->getFlexKey()) {
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
|
||||
return $object->getForm($type ?? 'edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FlexObjectInterface $object
|
||||
* @param string $type
|
||||
* @return FlexFormFlash
|
||||
*/
|
||||
protected function getFormFlash(FlexObjectInterface $object, string $type = '')
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
$url = $uri->url;
|
||||
|
||||
$formName = $this->getPost('__form-name__');
|
||||
if (!$formName) {
|
||||
$form = $object->getForm($type);
|
||||
$formName = $form->getName();
|
||||
$uniqueId = $form->getUniqueId();
|
||||
} else {
|
||||
$uniqueId = $this->getPost('__unique_form_id__') ?: $formName ?: sha1($url);
|
||||
}
|
||||
|
||||
/** @var Session $session */
|
||||
$session = $this->grav['session'];
|
||||
|
||||
$config = [
|
||||
'session_id' => $session->getId(),
|
||||
'unique_id' => $uniqueId,
|
||||
'form_name' => $formName,
|
||||
];
|
||||
$flash = new FlexFormFlash($config);
|
||||
if (!$flash->exists()) {
|
||||
$flash->setUrl($url)->setUser($this->grav['user']);
|
||||
}
|
||||
|
||||
return $flash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Grav
|
||||
*/
|
||||
public function getGrav(): Grav
|
||||
{
|
||||
return $this->grav;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Session
|
||||
*/
|
||||
public function getSession(): Session
|
||||
{
|
||||
return $this->grav['session'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Flex
|
||||
*/
|
||||
public function getFlex(): Flex
|
||||
{
|
||||
return $this->grav['flex_objects'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDirectoryType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getObjectKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FlexDirectory|null
|
||||
*/
|
||||
public function getDirectory(): ?FlexDirectory
|
||||
{
|
||||
return $this->directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FlexObjectInterface|null
|
||||
*/
|
||||
public function getObject(): ?FlexObjectInterface
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param array $args
|
||||
* @return string
|
||||
*/
|
||||
public function translate(string $string, ...$args): string
|
||||
{
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
array_unshift($args, $string);
|
||||
|
||||
return $language->translate($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param string $type
|
||||
* @return $this
|
||||
*/
|
||||
public function setMessage(string $message, string $type = 'info'): self
|
||||
{
|
||||
/** @var Message $messages */
|
||||
$messages = $this->grav['messages'];
|
||||
$messages->add($message, $type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserInterface $user
|
||||
* @return void
|
||||
*/
|
||||
public function setUser(UserInterface $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
protected function getConfig(): Config
|
||||
{
|
||||
return $this->grav['config'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $task
|
||||
* @return void
|
||||
* @throws PageExpiredException
|
||||
*/
|
||||
protected function checkNonce(string $task): void
|
||||
{
|
||||
$nonce = null;
|
||||
|
||||
if (in_array(strtoupper($this->request->getMethod()), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
|
||||
$nonce = $this->getPost($this->nonce_name);
|
||||
}
|
||||
|
||||
if (!$nonce) {
|
||||
$nonce = $this->grav['uri']->param($this->nonce_name);
|
||||
}
|
||||
|
||||
if (!$nonce) {
|
||||
$nonce = $this->grav['uri']->query($this->nonce_name);
|
||||
}
|
||||
|
||||
if (!$nonce || !Utils::verifyNonce($nonce, $this->nonce_action)) {
|
||||
throw new PageExpiredException($this->request);
|
||||
}
|
||||
}
|
||||
}
|
||||
675
plugins/flex-objects/classes/Controllers/MediaController.php
Normal file
675
plugins/flex-objects/classes/Controllers/MediaController.php
Normal file
|
|
@ -0,0 +1,675 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\FlexObjects\Controllers;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Page\Medium\MediumFactory;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\FlexObject;
|
||||
use Grav\Framework\Flex\Interfaces\FlexAuthorizeInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Media\Interfaces\MediaInterface;
|
||||
use LogicException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RuntimeException;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class MediaController
|
||||
* @package Grav\Plugin\FlexObjects\Controllers
|
||||
*/
|
||||
class MediaController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaUpload(): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('media.create');
|
||||
|
||||
$object = $this->getObject();
|
||||
if (null === $object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
if (!method_exists($object, 'checkUploadedMediaFile')) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
// Get updated object from Form Flash.
|
||||
$flash = $this->getFormFlash($object);
|
||||
if ($flash->exists()) {
|
||||
$object = $flash->getObject() ?? $object;
|
||||
$object->update([], $flash->getFilesByFields());
|
||||
}
|
||||
|
||||
// Get field for the uploaded media.
|
||||
$field = $this->getPost('name', 'undefined');
|
||||
if ($field === 'undefined') {
|
||||
$field = null;
|
||||
}
|
||||
|
||||
$request = $this->getRequest();
|
||||
$files = $request->getUploadedFiles();
|
||||
if ($field && isset($files['data'])) {
|
||||
$files = $files['data'];
|
||||
$parts = explode('.', $field);
|
||||
$last = array_pop($parts);
|
||||
foreach ($parts as $name) {
|
||||
if (!is_array($files[$name])) {
|
||||
throw new RuntimeException($this->translate('PLUGIN_ADMIN.INVALID_PARAMETERS'), 400);
|
||||
}
|
||||
$files = $files[$name];
|
||||
}
|
||||
$file = $files[$last] ?? null;
|
||||
|
||||
} else {
|
||||
// Legacy call with name being the filename instead of field name.
|
||||
$file = $files['file'] ?? null;
|
||||
$field = null;
|
||||
}
|
||||
|
||||
/** @var UploadedFileInterface $file */
|
||||
if (is_array($file)) {
|
||||
$file = reset($file);
|
||||
}
|
||||
|
||||
if (!$file instanceof UploadedFileInterface) {
|
||||
throw new RuntimeException($this->translate('PLUGIN_ADMIN.INVALID_PARAMETERS'), 400);
|
||||
}
|
||||
|
||||
$filename = $file->getClientFilename();
|
||||
|
||||
$object->checkUploadedMediaFile($file, $filename, $field);
|
||||
|
||||
try {
|
||||
// TODO: This only merges main level data, but is good for ordering (for now).
|
||||
$data = $flash->getData() ?? [];
|
||||
$data = array_replace($data, (array)$this->getPost('data'));
|
||||
|
||||
$crop = $this->getPost('crop');
|
||||
if (is_string($crop)) {
|
||||
$crop = json_decode($crop, true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
$flash->setData($data);
|
||||
$flash->addUploadedFile($file, $field, $crop);
|
||||
$flash->save();
|
||||
} catch (Exception $e) {
|
||||
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
// Include exif metadata into the response if configured to do so
|
||||
$metadata = [];
|
||||
$include_metadata = $this->grav['config']->get('system.media.auto_metadata_exif', false);
|
||||
if ($include_metadata) {
|
||||
$medium = MediumFactory::fromUploadedFile($file);
|
||||
|
||||
$media = $object->getMedia();
|
||||
$media->add($filename, $medium);
|
||||
|
||||
$basename = str_replace(['@3x', '@2x'], '', Utils::pathinfo($filename, PATHINFO_BASENAME));
|
||||
if (isset($media[$basename])) {
|
||||
$metadata = $media[$basename]->metadata() ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
'code' => 200,
|
||||
'status' => 'success',
|
||||
'message' => $this->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY'),
|
||||
'filename' => htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'metadata' => $metadata
|
||||
];
|
||||
|
||||
return $this->createJsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaUploadMeta(): ResponseInterface
|
||||
{
|
||||
try {
|
||||
$this->checkAuthorization('media.create');
|
||||
|
||||
$object = $this->getObject();
|
||||
if (null === $object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
if (!method_exists($object, 'getMediaField')) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
$object->refresh();
|
||||
|
||||
// Get updated object from Form Flash.
|
||||
$flash = $this->getFormFlash($object);
|
||||
if ($flash->exists()) {
|
||||
$object = $flash->getObject() ?? $object;
|
||||
$object->update([], $flash->getFilesByFields());
|
||||
}
|
||||
|
||||
// Get field and data for the uploaded media.
|
||||
$field = (string)$this->getPost('field');
|
||||
$media = $object->getMediaField($field);
|
||||
if (!$media) {
|
||||
throw new RuntimeException('Media field not found: ' . $field, 404);
|
||||
}
|
||||
|
||||
$data = $this->getPost('data');
|
||||
if (is_string($data)) {
|
||||
$data = json_decode($data, true);
|
||||
}
|
||||
|
||||
$filename = Utils::basename($data['name'] ?? '');
|
||||
|
||||
// Update field.
|
||||
$files = $object->getNestedProperty($field, []);
|
||||
// FIXME: Do we want to save something into the field as well?
|
||||
$files[$filename] = [];
|
||||
$object->setNestedProperty($field, $files);
|
||||
|
||||
$info = [
|
||||
'modified' => $data['modified'] ?? null,
|
||||
'size' => $data['size'] ?? null,
|
||||
'mime' => $data['mime'] ?? null,
|
||||
'width' => $data['width'] ?? null,
|
||||
'height' => $data['height'] ?? null,
|
||||
'duration' => $data['duration'] ?? null,
|
||||
'orientation' => $data['orientation'] ?? null,
|
||||
'meta' => array_filter($data, static function ($val) { return $val !== null; })
|
||||
];
|
||||
$info = array_filter($info, static function ($val) { return $val !== null; });
|
||||
|
||||
// As the file may not be saved locally, we need to update the index.
|
||||
$media->updateIndex([$filename => $info]);
|
||||
|
||||
$object->save();
|
||||
$flash->save();
|
||||
|
||||
$response = [
|
||||
'code' => 200,
|
||||
'status' => 'success',
|
||||
'message' => $this->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY'),
|
||||
'field' => $field,
|
||||
'filename' => $filename,
|
||||
'metadata' => $data
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addException($e);
|
||||
|
||||
return $this->createJsonErrorResponse($e);
|
||||
}
|
||||
|
||||
return $this->createJsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaReorder(): ResponseInterface
|
||||
{
|
||||
try {
|
||||
$this->checkAuthorization('media.update');
|
||||
|
||||
$object = $this->getObject();
|
||||
if (null === $object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
if (!method_exists($object, 'getMediaField')) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
$object->refresh();
|
||||
|
||||
// Get updated object from Form Flash.
|
||||
$flash = $this->getFormFlash($object);
|
||||
if ($flash->exists()) {
|
||||
$object = $flash->getObject() ?? $object;
|
||||
$object->update([], $flash->getFilesByFields());
|
||||
}
|
||||
|
||||
// Get field and data for the uploaded media.
|
||||
$field = (string)$this->getPost('field');
|
||||
$media = $object->getMediaField($field);
|
||||
if (!$media) {
|
||||
throw new RuntimeException('Media field not found: ' . $field, 404);
|
||||
}
|
||||
|
||||
// Create id => filename map from all files in the media.
|
||||
$map = [];
|
||||
foreach ($media as $name => $medium) {
|
||||
$id = $medium->get('meta.id');
|
||||
if ($id) {
|
||||
$map[$id] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
// Get reorder list and reorder the map.
|
||||
$data = $this->getPost('data');
|
||||
if (is_string($data)) {
|
||||
$data = json_decode($data, true);
|
||||
}
|
||||
$data = array_fill_keys($data, null);
|
||||
$map = array_filter(array_merge($data, $map), static function($val) { return $val !== null; });
|
||||
|
||||
// Reorder the files.
|
||||
$files = $object->getNestedProperty($field, []);
|
||||
$map = array_fill_keys($map, null);
|
||||
$files = array_filter(array_merge($map, $files), static function($val) { return $val !== null; });
|
||||
|
||||
// Update field.
|
||||
$object->setNestedProperty($field, $files);
|
||||
$object->save();
|
||||
$flash->save();
|
||||
|
||||
$response = [
|
||||
'code' => 200,
|
||||
'status' => 'success',
|
||||
'message' => $this->translate('PLUGIN_ADMIN.FIELD_REORDER_SUCCESSFUL'),
|
||||
'field' => $field,
|
||||
'ordering' => array_keys($files)
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addException($e);
|
||||
|
||||
$ex = new RuntimeException($this->translate('PLUGIN_ADMIN.FIELD_REORDER_FAILED', $field), $e->getCode(), $e);
|
||||
return $this->createJsonErrorResponse($ex);
|
||||
}
|
||||
|
||||
return $this->createJsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaDelete(): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('media.delete');
|
||||
|
||||
/** @var FlexObjectInterface|null $object */
|
||||
$object = $this->getObject();
|
||||
if (!$object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
$filename = $this->getPost('filename');
|
||||
|
||||
// Handle bad filenames.
|
||||
if (!Utils::checkFilename($filename)) {
|
||||
throw new RuntimeException($this->translate('PLUGIN_ADMIN.NO_FILE_FOUND'), 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$field = $this->getPost('name');
|
||||
$flash = $this->getFormFlash($object);
|
||||
$flash->removeFile($filename, $field);
|
||||
$flash->save();
|
||||
} catch (Exception $e) {
|
||||
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$response = [
|
||||
'code' => 200,
|
||||
'status' => 'success',
|
||||
'message' => $this->translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8')
|
||||
];
|
||||
|
||||
return $this->createJsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in pagemedia field.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaCopy(): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('media.create');
|
||||
|
||||
/** @var FlexObjectInterface|null $object */
|
||||
$object = $this->getObject();
|
||||
if (!$object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
if (!method_exists($object, 'uploadMediaFile')) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
$request = $this->getRequest();
|
||||
$files = $request->getUploadedFiles();
|
||||
|
||||
$file = $files['file'] ?? null;
|
||||
if (!$file instanceof UploadedFileInterface) {
|
||||
throw new RuntimeException($this->translate('PLUGIN_ADMIN.INVALID_PARAMETERS'), 400);
|
||||
}
|
||||
|
||||
$post = $request->getParsedBody();
|
||||
$filename = $post['name'] ?? $file->getClientFilename();
|
||||
|
||||
// Upload media right away.
|
||||
$object->uploadMediaFile($file, $filename);
|
||||
|
||||
// Include exif metadata into the response if configured to do so
|
||||
$metadata = [];
|
||||
$include_metadata = $this->grav['config']->get('system.media.auto_metadata_exif', false);
|
||||
if ($include_metadata) {
|
||||
$basename = str_replace(['@3x', '@2x'], '', Utils::pathinfo($filename, PATHINFO_BASENAME));
|
||||
$media = $object->getMedia();
|
||||
if (isset($media[$basename])) {
|
||||
$metadata = $media[$basename]->metadata() ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
if ($object instanceof PageInterface) {
|
||||
// Backwards compatibility to existing plugins.
|
||||
// DEPRECATED: page
|
||||
$this->grav->fireEvent('onAdminAfterAddMedia', new Event(['object' => $object, 'page' => $object]));
|
||||
}
|
||||
|
||||
$response = [
|
||||
'code' => 200,
|
||||
'status' => 'success',
|
||||
'message' => $this->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY'),
|
||||
'filename' => htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'metadata' => $metadata
|
||||
];
|
||||
|
||||
return $this->createJsonResponse($response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used in pagemedia field.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaRemove(): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('media.delete');
|
||||
|
||||
/** @var FlexObjectInterface|null $object */
|
||||
$object = $this->getObject();
|
||||
if (!$object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
if (!method_exists($object, 'deleteMediaFile')) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
$field = $this->getPost('field');
|
||||
$filename = $this->getPost('filename');
|
||||
|
||||
// Handle bad filenames.
|
||||
if (!Utils::checkFilename($filename)) {
|
||||
throw new RuntimeException($this->translate('PLUGIN_ADMIN.NO_FILE_FOUND'), 400);
|
||||
}
|
||||
|
||||
$object->deleteMediaFile($filename, $field);
|
||||
if ($field) {
|
||||
$order = $object->getNestedProperty($field);
|
||||
unset($order[$filename]);
|
||||
$object->setNestedProperty($field, $order);
|
||||
$object->save();
|
||||
}
|
||||
|
||||
if ($object instanceof PageInterface) {
|
||||
// Backwards compatibility to existing plugins.
|
||||
// DEPRECATED: page
|
||||
$this->grav->fireEvent('onAdminAfterDelMedia', new Event(['object' => $object, 'page' => $object, 'media' => $object->getMedia(), 'filename' => $filename]));
|
||||
}
|
||||
|
||||
$response = [
|
||||
'code' => 200,
|
||||
'status' => 'success',
|
||||
'message' => $this->translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8')
|
||||
];
|
||||
|
||||
return $this->createJsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function actionMediaList(): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('media.list');
|
||||
|
||||
/** @var MediaInterface|FlexObjectInterface $object */
|
||||
$object = $this->getObject();
|
||||
if (!$object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
// Get updated object from Form Flash.
|
||||
$flash = $this->getFormFlash($object);
|
||||
if ($flash->exists()) {
|
||||
$object = $flash->getObject() ?? $object;
|
||||
$object->update([], $flash->getFilesByFields());
|
||||
}
|
||||
|
||||
$media = $object->getMedia();
|
||||
$media_list = [];
|
||||
|
||||
/**
|
||||
* @var string $name
|
||||
* @var Medium $medium
|
||||
*/
|
||||
foreach ($media->all() as $name => $medium) {
|
||||
$media_list[$name] = [
|
||||
'url' => $medium->display($medium->get('extension') === 'svg' ? 'source' : 'thumbnail')->cropZoom(400, 300)->url(),
|
||||
'size' => $medium->get('size'),
|
||||
'metadata' => $medium->metadata() ?: [],
|
||||
'original' => $medium->higherQualityAlternative()->get('filename')
|
||||
];
|
||||
}
|
||||
|
||||
$response = [
|
||||
'code' => 200,
|
||||
'status' => 'success',
|
||||
'results' => $media_list
|
||||
];
|
||||
|
||||
return $this->createJsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the filepicker field to get a list of files in a folder.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function actionMediaPicker(): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('media.list');
|
||||
|
||||
/** @var FlexObject $object */
|
||||
$object = $this->getObject();
|
||||
if (!$object || !\is_callable([$object, 'getFieldSettings'])) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
// Get updated object from Form Flash.
|
||||
$flash = $this->getFormFlash($object);
|
||||
if ($flash->exists()) {
|
||||
$object = $flash->getObject() ?? $object;
|
||||
$object->update([], $flash->getFilesByFields());
|
||||
}
|
||||
|
||||
$name = $this->getPost('name');
|
||||
$settings = $name ? $object->getFieldSettings($name) : null;
|
||||
if (empty($settings['media_picker_field'])) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
$media = $object->getMediaField($name);
|
||||
|
||||
$available_files = [];
|
||||
$metadata = [];
|
||||
$thumbs = [];
|
||||
|
||||
/**
|
||||
* @var string $name
|
||||
* @var Medium $medium
|
||||
*/
|
||||
foreach ($media->all() as $name => $medium) {
|
||||
$available_files[] = $name;
|
||||
|
||||
if (isset($settings['include_metadata'])) {
|
||||
$img_metadata = $medium->metadata();
|
||||
if ($img_metadata) {
|
||||
$metadata[$name] = $img_metadata;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Peak in the flashObject for optimistic filepicker updates
|
||||
$pending_files = [];
|
||||
$sessionField = base64_encode($this->grav['uri']->url());
|
||||
$flash = $this->getSession()->getFlashObject('files-upload');
|
||||
$folder = $media->getPath() ?: null;
|
||||
|
||||
if ($flash && isset($flash[$sessionField])) {
|
||||
foreach ($flash[$sessionField] as $field => $data) {
|
||||
foreach ($data as $file) {
|
||||
$test = \dirname($file['path']);
|
||||
if ($test === $folder) {
|
||||
$pending_files[] = $file['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getSession()->setFlashObject('files-upload', $flash);
|
||||
|
||||
// Handle Accepted file types
|
||||
// Accept can only be file extensions (.pdf|.jpg)
|
||||
if (isset($settings['accept'])) {
|
||||
$available_files = array_filter($available_files, function ($file) use ($settings) {
|
||||
return $this->filterAcceptedFiles($file, $settings);
|
||||
});
|
||||
|
||||
$pending_files = array_filter($pending_files, function ($file) use ($settings) {
|
||||
return $this->filterAcceptedFiles($file, $settings);
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($settings['deny'])) {
|
||||
$available_files = array_filter($available_files, function ($file) use ($settings) {
|
||||
return $this->filterDeniedFiles($file, $settings);
|
||||
});
|
||||
|
||||
$pending_files = array_filter($pending_files, function ($file) use ($settings) {
|
||||
return $this->filterDeniedFiles($file, $settings);
|
||||
});
|
||||
}
|
||||
|
||||
// Generate thumbs if needed
|
||||
if (isset($settings['preview_images']) && $settings['preview_images'] === true) {
|
||||
foreach ($available_files as $filename) {
|
||||
$thumbs[$filename] = $media[$filename]->zoomCrop(100,100)->url();
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
'code' => 200,
|
||||
'status' => 'success',
|
||||
'files' => array_values($available_files),
|
||||
'pending' => array_values($pending_files),
|
||||
'folder' => $folder,
|
||||
'metadata' => $metadata,
|
||||
'thumbs' => $thumbs
|
||||
];
|
||||
|
||||
return $this->createJsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param array $settings
|
||||
* @return false|int
|
||||
*/
|
||||
protected function filterAcceptedFiles(string $file, array $settings)
|
||||
{
|
||||
$valid = false;
|
||||
|
||||
foreach ((array)$settings['accept'] as $type) {
|
||||
$find = str_replace('*', '.*', $type);
|
||||
$valid |= preg_match('#' . $find . '$#i', $file);
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param array $settings
|
||||
* @return false|int
|
||||
*/
|
||||
protected function filterDeniedFiles(string $file, array $settings)
|
||||
{
|
||||
$valid = true;
|
||||
|
||||
foreach ((array)$settings['deny'] as $type) {
|
||||
$find = str_replace('*', '.*', $type);
|
||||
$valid = !preg_match('#' . $find . '$#i', $file);
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action
|
||||
* @return void
|
||||
* @throws LogicException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function checkAuthorization(string $action): void
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!$object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
// If object does not have ACL support ignore ACL checks.
|
||||
if (!$object instanceof FlexAuthorizeInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'media.list':
|
||||
$action = 'read';
|
||||
break;
|
||||
|
||||
case 'media.create':
|
||||
case 'media.update':
|
||||
case 'media.delete':
|
||||
$action = $object->exists() ? 'update' : 'create';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new LogicException(sprintf('Unsupported authorize action %s', $action), 500);
|
||||
}
|
||||
|
||||
if (!$object->isAuthorized($action, null, $this->user)) {
|
||||
throw new RuntimeException('Forbidden', 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
543
plugins/flex-objects/classes/Controllers/ObjectController.php
Normal file
543
plugins/flex-objects/classes/Controllers/ObjectController.php
Normal file
|
|
@ -0,0 +1,543 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\FlexObjects\Controllers;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Framework\Flex\FlexForm;
|
||||
use Grav\Framework\Flex\FlexObject;
|
||||
use Grav\Framework\Flex\Interfaces\FlexAuthorizeInterface;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Grav\Plugin\FlexObjects\Events\FlexTaskEvent;
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Object controller is for the frontend.
|
||||
*
|
||||
* Currently following tasks are supported:
|
||||
*
|
||||
* - save (create or update)
|
||||
* - create
|
||||
* - update
|
||||
* - delete
|
||||
* - reset
|
||||
* - preview
|
||||
*/
|
||||
class ObjectController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* Save object.
|
||||
*
|
||||
* Forwards call to either create or update task.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskSave(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$form = $this->getForm();
|
||||
$object = $form->getObject();
|
||||
|
||||
return $object->exists() ? $this->taskUpdate($request) : $this->taskCreate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create object.
|
||||
*
|
||||
* Task fails if object exists.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskCreate(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('create');
|
||||
|
||||
$form = $this->getForm();
|
||||
$callable = function (array $data, array $files, FlexObject $object) {
|
||||
if (method_exists($object, 'storeOriginal')) {
|
||||
$object->storeOriginal();
|
||||
}
|
||||
$object->update($data, $files);
|
||||
if (\is_callable([$object, 'check'])) {
|
||||
$object->check($this->user);
|
||||
}
|
||||
|
||||
$event = new FlexTaskEvent($this, $object, 'create');
|
||||
$this->grav->dispatchEvent($event);
|
||||
|
||||
$object->save();
|
||||
};
|
||||
|
||||
$form->setSubmitMethod($callable);
|
||||
$form->handleRequest($request);
|
||||
if (!$form->isValid()) {
|
||||
$error = $form->getError();
|
||||
if ($error) {
|
||||
$this->setMessage($error, 'error');
|
||||
}
|
||||
$errors = $form->getErrors();
|
||||
foreach ($errors as $field) {
|
||||
foreach ($field as $error) {
|
||||
$this->setMessage($error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
$data = $form->getData();
|
||||
if (null !== $data) {
|
||||
$object = $form->getObject();
|
||||
$flash = $form->getFlash();
|
||||
$flash->setObject($object);
|
||||
$flash->setData($data->toArray());
|
||||
$flash->save();
|
||||
}
|
||||
|
||||
return $this->createDisplayResponse();
|
||||
}
|
||||
|
||||
// FIXME: make it conditional
|
||||
$grav = $this->grav;
|
||||
$grav->fireEvent('gitsync');
|
||||
|
||||
$this->object = $form->getObject();
|
||||
$event = new Event(
|
||||
[
|
||||
'task' => 'create',
|
||||
'controller' => $this,
|
||||
'object' => $this->object,
|
||||
'response' => null,
|
||||
'message' => null,
|
||||
]
|
||||
);
|
||||
|
||||
$this->grav->fireEvent("flex.{$this->type}.task.create.after", $event);
|
||||
|
||||
$this->setMessage($event['message'] ?? $this->translate('PLUGIN_FLEX_OBJECTS.STATE.CREATED_SUCCESSFULLY'), 'info');
|
||||
|
||||
if ($event['response']) {
|
||||
return $event['response'];
|
||||
}
|
||||
|
||||
|
||||
$redirect = $request->getAttribute('redirect', (string)$request->getUri());
|
||||
|
||||
return $this->createRedirectResponse($redirect, 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update object.
|
||||
*
|
||||
* Task fails if object does not exist.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskUpdate(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('update');
|
||||
|
||||
$form = $this->getForm();
|
||||
$callable = function (array $data, array $files, FlexObject $object) {
|
||||
if (method_exists($object, 'storeOriginal')) {
|
||||
$object->storeOriginal();
|
||||
}
|
||||
$object->update($data, $files);
|
||||
if (\is_callable([$object, 'check'])) {
|
||||
$object->check($this->user);
|
||||
}
|
||||
|
||||
$event = new FlexTaskEvent($this, $object, 'update');
|
||||
$this->grav->dispatchEvent($event);
|
||||
|
||||
$object->save();
|
||||
};
|
||||
|
||||
$form->setSubmitMethod($callable);
|
||||
$form->handleRequest($request);
|
||||
if (!$form->isValid()) {
|
||||
$error = $form->getError();
|
||||
if ($error) {
|
||||
$this->setMessage($error, 'error');
|
||||
}
|
||||
$errors = $form->getErrors();
|
||||
foreach ($errors as $field) {
|
||||
foreach ($field as $error) {
|
||||
$this->setMessage($error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
$data = $form->getData();
|
||||
if (null !== $data) {
|
||||
$object = $form->getObject();
|
||||
$flash = $form->getFlash();
|
||||
$flash->setObject($object);
|
||||
$flash->setData($data->toArray());
|
||||
$flash->save();
|
||||
}
|
||||
|
||||
return $this->createDisplayResponse();
|
||||
}
|
||||
|
||||
// FIXME: make it conditional
|
||||
$grav = $this->grav;
|
||||
$grav->fireEvent('gitsync');
|
||||
|
||||
$this->object = $form->getObject();
|
||||
$event = new Event(
|
||||
[
|
||||
'task' => 'update',
|
||||
'controller' => $this,
|
||||
'object' => $this->object,
|
||||
'response' => null,
|
||||
'message' => null,
|
||||
]
|
||||
);
|
||||
|
||||
$this->grav->fireEvent("flex.{$this->type}.task.update.after", $event);
|
||||
|
||||
$this->setMessage($event['message'] ?? $this->translate('PLUGIN_FLEX_OBJECTS.STATE.UPDATED_SUCCESSFULLY'), 'info');
|
||||
|
||||
if ($event['response']) {
|
||||
return $event['response'];
|
||||
}
|
||||
|
||||
$redirect = $request->getAttribute('redirect', (string)$request->getUri()->getPath());
|
||||
|
||||
return $this->createRedirectResponse($redirect, 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete object.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskDelete(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('delete');
|
||||
|
||||
$object = $this->getObject();
|
||||
if (!$object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
$event = new FlexTaskEvent($this, $object, 'delete');
|
||||
$this->grav->dispatchEvent($event);
|
||||
|
||||
$object->delete();
|
||||
|
||||
// FIXME: make it conditional
|
||||
$grav = $this->grav;
|
||||
$grav->fireEvent('gitsync');
|
||||
|
||||
$event = new Event(
|
||||
[
|
||||
'task' => 'delete',
|
||||
'controller' => $this,
|
||||
'object' => $object,
|
||||
'response' => null,
|
||||
'message' => null,
|
||||
]
|
||||
);
|
||||
|
||||
$this->grav->fireEvent("flex.{$this->type}.task.delete.after", $event);
|
||||
|
||||
$this->setMessage($this->translate($event['message'] ?? 'PLUGIN_FLEX_OBJECTS.STATE.DELETED_SUCCESSFULLY'), 'info');
|
||||
|
||||
if ($event['response']) {
|
||||
return $event['response'];
|
||||
}
|
||||
|
||||
$redirect = $request->getAttribute('redirect', (string)$request->getUri()->getPath());
|
||||
|
||||
return $this->createRedirectResponse($redirect, 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset form to original values.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskReset(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('save');
|
||||
|
||||
$flash = $this->getForm()->getFlash();
|
||||
$flash->delete();
|
||||
|
||||
$redirect = $request->getAttribute('redirect', (string)$request->getUri()->getPath());
|
||||
|
||||
return $this->createRedirectResponse($redirect, 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview object.
|
||||
*
|
||||
* Takes a form input and converts it to visible presentation of the object.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskPreview(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('save');
|
||||
|
||||
/** @var FlexForm $form */
|
||||
$form = $this->getForm('edit');
|
||||
$form->setRequest($request);
|
||||
if (!$form->validate()) {
|
||||
$error = $form->getError();
|
||||
if ($error) {
|
||||
$this->setMessage($error, 'error');
|
||||
}
|
||||
$errors = $form->getErrors();
|
||||
foreach ($errors as $field) {
|
||||
foreach ($field as $error) {
|
||||
$this->setMessage($error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createRedirectResponse((string)$request->getUri(), 303);
|
||||
}
|
||||
|
||||
$this->object = $form->updateObject();
|
||||
|
||||
return $this->actionDisplayPreview();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaList(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$directory = $this->getDirectory();
|
||||
if (!$directory) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
return $this->forwardMediaTask('action', 'media.list');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaUpload(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$directory = $this->getDirectory();
|
||||
if (!$directory) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
return $this->forwardMediaTask('task', 'media.upload');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaUploadMeta(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$directory = $this->getDirectory();
|
||||
if (!$directory) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
return $this->forwardMediaTask('task', 'media.upload.meta');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaReorder(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$directory = $this->getDirectory();
|
||||
if (!$directory) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
return $this->forwardMediaTask('task', 'media.reorder');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskMediaDelete(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$directory = $this->getDirectory();
|
||||
if (!$directory) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
return $this->forwardMediaTask('task', 'media.delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function taskGetFilesInFolder(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$directory = $this->getDirectory();
|
||||
if (!$directory) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
return $this->forwardMediaTask('action', 'media.picker');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @deprecated Do not use
|
||||
*/
|
||||
public function taskFilesUpload(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
/** @var Route $route */
|
||||
$route = $this->grav['route'];
|
||||
if ($route->getParam('task') === 'media.upload') {
|
||||
return $this->taskMediaUpload($request);
|
||||
}
|
||||
|
||||
throw new RuntimeException('Task filesUpload should not be called, please update form plugin!', 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @deprecated Do not use
|
||||
*/
|
||||
public function taskRemoveMedia(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
/** @var Route $route */
|
||||
$route = $this->grav['route'];
|
||||
if ($route->getParam('task') === 'media.delete') {
|
||||
return $this->taskMediaDelete($request);
|
||||
}
|
||||
|
||||
throw new RuntimeException('Task removeMedia should not be called, please update form plugin!', 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display object preview.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function actionDisplayPreview(): ResponseInterface
|
||||
{
|
||||
$this->checkAuthorization('save');
|
||||
$this->checkAuthorization('read');
|
||||
|
||||
$object = $this->getObject();
|
||||
if (!$object) {
|
||||
throw new RuntimeException('No object found!', 404);
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
$grav['twig']->init();
|
||||
$grav['theme'];
|
||||
$content = [
|
||||
'code' => 200,
|
||||
'id' => $object->getKey(),
|
||||
'exists' => $object->exists(),
|
||||
'html' => (string)$object->render('preview', ['nocache' => []])
|
||||
];
|
||||
|
||||
$accept = $this->getAccept(['application/json', 'text/html']);
|
||||
if ($accept === 'text/html') {
|
||||
return $this->createHtmlResponse($content['html']);
|
||||
}
|
||||
if ($accept === 'application/json') {
|
||||
return $this->createJsonResponse($content);
|
||||
}
|
||||
|
||||
throw new RuntimeException('Not found', 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action
|
||||
* @param string|null $scope
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function checkAuthorization(string $action, string $scope = null): void
|
||||
{
|
||||
$object = $this->getObject();
|
||||
|
||||
if (!$object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
if ($object instanceof FlexAuthorizeInterface) {
|
||||
if (!$object->isAuthorized($action, $scope, $this->user)) {
|
||||
throw new RuntimeException('Forbidden', 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string[] $actions
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function checkAuthorizations(array $actions): void
|
||||
{
|
||||
$object = $this->getObject();
|
||||
|
||||
if (!$object) {
|
||||
throw new RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
if ($object instanceof FlexAuthorizeInterface) {
|
||||
$test = false;
|
||||
foreach ($actions as $action) {
|
||||
$test |= $object->isAuthorized($action, null, $this->user);
|
||||
}
|
||||
|
||||
if (!$test) {
|
||||
throw new RuntimeException('Forbidden', 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $name
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function forwardMediaTask(string $type, string $name): ResponseInterface
|
||||
{
|
||||
/** @var Route $route */
|
||||
$route = $this->grav['route']->withGravParam('task', null)->withGravParam($type, $name);
|
||||
$object = $this->getObject();
|
||||
|
||||
/** @var ServerRequest $request */
|
||||
$request = $this->grav['request'];
|
||||
$request = $request
|
||||
->withAttribute($type, $name)
|
||||
->withAttribute('type', $this->type)
|
||||
->withAttribute('key', $this->key)
|
||||
->withAttribute('storage_key', $object && $object->exists() ? $object->getStorageKey() : null)
|
||||
->withAttribute('route', $route)
|
||||
->withAttribute('forwarded', true)
|
||||
->withAttribute('object', $object);
|
||||
|
||||
$controller = new MediaController();
|
||||
if ($this->user) {
|
||||
$controller->setUser($this->user);
|
||||
}
|
||||
|
||||
return $controller->handle($request);
|
||||
}
|
||||
}
|
||||
84
plugins/flex-objects/classes/Events/FlexTaskEvent.php
Normal file
84
plugins/flex-objects/classes/Events/FlexTaskEvent.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace Grav\Plugin\FlexObjects\Events;
|
||||
|
||||
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexDirectoryInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Object\Interfaces\ObjectInterface;
|
||||
use Grav\Plugin\FlexObjects\Controllers\AbstractController;
|
||||
|
||||
/**
|
||||
* @template T as FlexObjectInterface
|
||||
* @template C as FlexCollectionInterface
|
||||
*/
|
||||
class FlexTaskEvent
|
||||
{
|
||||
/** @var string */
|
||||
public $task;
|
||||
/** @var string */
|
||||
public $type;
|
||||
/** @var string */
|
||||
public $key;
|
||||
|
||||
/** @var ObjectInterface */
|
||||
private $object;
|
||||
/** @var AbstractController */
|
||||
private $controller;
|
||||
|
||||
/**
|
||||
* @param AbstractController $controller
|
||||
* @param string $task
|
||||
*/
|
||||
public function __construct(AbstractController $controller, ObjectInterface $object, string $task)
|
||||
{
|
||||
$this->task = $task;
|
||||
$this->type = $controller->getDirectoryType();
|
||||
$this->key = $controller->getObjectKey();
|
||||
$this->object = $object;
|
||||
$this->controller = $controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AbstractController
|
||||
*/
|
||||
public function getController(): AbstractController
|
||||
{
|
||||
return $this->controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FlexDirectoryInterface
|
||||
*/
|
||||
public function getDirectory(): FlexDirectoryInterface
|
||||
{
|
||||
return $this->getController()->getDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FlexObjectInterface
|
||||
* @phpstan-return T
|
||||
*/
|
||||
public function getModifiedObject(): FlexObjectInterface
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FlexObjectInterface
|
||||
* @phpstan-return T
|
||||
*/
|
||||
public function getOriginalObject(): FlexObjectInterface
|
||||
{
|
||||
return $this->controller->getObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FlexCollectionInterface
|
||||
* @phpstan-return C
|
||||
*/
|
||||
public function getCollection(): FlexCollectionInterface
|
||||
{
|
||||
return $this->getController()->getDirectory()->getCollection();
|
||||
}
|
||||
}
|
||||
444
plugins/flex-objects/classes/Flex.php
Normal file
444
plugins/flex-objects/classes/Flex.php
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\FlexObjects;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\FlexObject;
|
||||
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexCommonInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexDirectoryInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Plugin\FlexObjects\Admin\AdminController;
|
||||
use Grav\Plugin\FlexObjects\Table\DataTable;
|
||||
|
||||
/**
|
||||
* Class Flex
|
||||
* @package Grav\Plugin\FlexObjects
|
||||
*/
|
||||
class Flex implements FlexInterface
|
||||
{
|
||||
/** @var FlexInterface */
|
||||
protected $flex;
|
||||
/** @var array */
|
||||
protected $adminRoutes;
|
||||
/** @var array */
|
||||
protected $adminMenu;
|
||||
/** @var array */
|
||||
protected $managed;
|
||||
|
||||
/**
|
||||
* @param bool $newToOld
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public static function getLegacyBlueprintMap(bool $newToOld = true): array
|
||||
{
|
||||
$map = [
|
||||
'blueprints://flex-objects/pages.yaml' => 'blueprints://flex-objects/grav-pages.yaml',
|
||||
'blueprints://flex-objects/user-accounts.yaml' => 'blueprints://flex-objects/grav-accounts.yaml',
|
||||
'blueprints://flex-objects/user-groups.yaml' => 'blueprints://flex-objects/grav-user-groups.yaml'
|
||||
];
|
||||
|
||||
return $newToOld ? $map : array_flip($map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flex constructor.
|
||||
* @param FlexInterface $flex
|
||||
* @param array $types
|
||||
*/
|
||||
public function __construct(FlexInterface $flex, array $types)
|
||||
{
|
||||
$this->flex = $flex;
|
||||
$this->managed = [];
|
||||
|
||||
$legacy = static::getLegacyBlueprintMap(false);
|
||||
foreach ($types as $blueprint) {
|
||||
// Backwards compatibility to v1.0.0-rc.3
|
||||
$blueprint = $legacy[$blueprint] ?? $blueprint;
|
||||
|
||||
$type = Utils::basename((string)$blueprint, '.yaml');
|
||||
if ($type) {
|
||||
$this->managed[] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $blueprint
|
||||
* @param array $config
|
||||
* @return $this
|
||||
*/
|
||||
public function addDirectoryType(string $type, string $blueprint, array $config = [])
|
||||
{
|
||||
$this->flex->addDirectoryType($type, $blueprint, $config);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FlexDirectory $directory
|
||||
* @return $this
|
||||
*/
|
||||
public function addDirectory(FlexDirectory $directory)
|
||||
{
|
||||
$this->flex->addDirectory($directory);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDirectory(string $type): bool
|
||||
{
|
||||
return $this->flex->hasDirectory($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[]|null $types
|
||||
* @param bool $keepMissing
|
||||
* @return array<FlexDirectoryInterface|null>
|
||||
*/
|
||||
public function getDirectories(array $types = null, bool $keepMissing = false): array
|
||||
{
|
||||
return $this->flex->getDirectories($types, $keepMissing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get directories which are not hidden in the site.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultDirectories(): array
|
||||
{
|
||||
$list = $this->getDirectories();
|
||||
foreach ($list as $type => $directory) {
|
||||
if ($directory->getConfig('site.hidden', false)) {
|
||||
unset($list[$type]);
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @return FlexDirectory|null
|
||||
*/
|
||||
public function getDirectory(string $type): ?FlexDirectory
|
||||
{
|
||||
return $this->flex->getDirectory($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param array|null $keys
|
||||
* @param string|null $keyField
|
||||
* @return FlexCollectionInterface|null
|
||||
*/
|
||||
public function getCollection(string $type, array $keys = null, string $keyField = null): ?FlexCollectionInterface
|
||||
{
|
||||
return $this->flex->getCollection($type, $keys, $keyField);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @param array $options In addition to the options in getObjects(), following options can be passed:
|
||||
* collection_class: Class to be used to create the collection. Defaults to ObjectCollection.
|
||||
* @return FlexCollectionInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function getMixedCollection(array $keys, array $options = []): FlexCollectionInterface
|
||||
{
|
||||
return $this->flex->getMixedCollection($keys, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @param array $options Following optional options can be passed:
|
||||
* types: List of allowed types.
|
||||
* type: Allowed type if types isn't defined, otherwise acts as default_type.
|
||||
* default_type: Set default type for objects given without type (only used if key_field isn't set).
|
||||
* keep_missing: Set to true if you want to return missing objects as null.
|
||||
* key_field: Key field which is used to match the objects.
|
||||
* @return array
|
||||
*/
|
||||
public function getObjects(array $keys, array $options = []): array
|
||||
{
|
||||
return $this->flex->getObjects($keys, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string|null $type
|
||||
* @param string|null $keyField
|
||||
* @return FlexObjectInterface|null
|
||||
*/
|
||||
public function getObject(string $key, string $type = null, string $keyField = null): ?FlexObjectInterface
|
||||
{
|
||||
return $this->flex->getObject($key, $type, $keyField);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->flex->count();
|
||||
}
|
||||
|
||||
public function isManaged(string $type): bool
|
||||
{
|
||||
return \in_array($type, $this->managed, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
$directories = $this->getDirectories($this->managed);
|
||||
$all = $this->getBlueprints();
|
||||
|
||||
/** @var FlexDirectory $directory */
|
||||
foreach ($all as $type => $directory) {
|
||||
if (!isset($directories[$type])) {
|
||||
$directories[$type] = $directory;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($directories);
|
||||
|
||||
return $directories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getBlueprints(): array
|
||||
{
|
||||
$params = [
|
||||
'pattern' => '|\.yaml|',
|
||||
'value' => 'Url',
|
||||
'recursive' => false,
|
||||
'folders' => false
|
||||
];
|
||||
|
||||
$directories = [];
|
||||
$all = Folder::all('blueprints://flex-objects', $params);
|
||||
foreach ($all as $url) {
|
||||
$type = Utils::basename($url, '.yaml');
|
||||
$directory = new FlexDirectory($type, $url);
|
||||
if ($directory->getConfig('hidden') !== true) {
|
||||
$directories[$type] = $directory;
|
||||
}
|
||||
}
|
||||
|
||||
// Order blueprints by title.
|
||||
usort($directories, static function (FlexDirectory $a, FlexDirectory $b) {
|
||||
return $a->getTitle() <=> $b->getTitle();
|
||||
});
|
||||
|
||||
return $directories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|FlexDirectory $type
|
||||
* @param array $options
|
||||
* @return DataTable
|
||||
*/
|
||||
public function getDataTable($type, array $options = []): DataTable
|
||||
{
|
||||
$directory = $type instanceof FlexDirectory ? $type : $this->getDirectory($type);
|
||||
if (!$directory) {
|
||||
throw new \RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
$collection = $options['collection'] ?? $directory->getCollection();
|
||||
if (isset($options['filters']) && is_array($options['filters'])) {
|
||||
$collection = $collection->filterBy($options['filters']);
|
||||
}
|
||||
$table = new DataTable($options);
|
||||
$table->setCollection($collection);
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|object|null $type
|
||||
* @param array $params
|
||||
* @param string $extension
|
||||
* @return string
|
||||
*/
|
||||
public function adminRoute($type = null, array $params = [], string $extension = ''): string
|
||||
{
|
||||
if (\is_object($type)) {
|
||||
$object = $type;
|
||||
if ($object instanceof FlexCommonInterface || $object instanceof FlexDirectory) {
|
||||
$type = $type->getFlexType();
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
$object = null;
|
||||
}
|
||||
|
||||
$routes = $this->getAdminRoutes();
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
if (!Utils::isAdminPlugin()) {
|
||||
$parts = [
|
||||
trim($grav['base_url'], '/'),
|
||||
trim($config->get('plugins.admin.route'), '/')
|
||||
];
|
||||
}
|
||||
|
||||
if ($type && isset($routes[$type])) {
|
||||
if (!$routes[$type]) {
|
||||
// Directory has empty route.
|
||||
return '';
|
||||
}
|
||||
|
||||
// Directory has it's own menu item.
|
||||
$parts[] = trim($routes[$type], '/');
|
||||
} else {
|
||||
if (empty($routes[''])) {
|
||||
// Default route has been disabled.
|
||||
return '';
|
||||
}
|
||||
|
||||
// Use default route.
|
||||
$parts[] = trim($routes[''], '/');
|
||||
if ($type) {
|
||||
$parts[] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
// Append object key if available.
|
||||
if ($object instanceof FlexObject) {
|
||||
if ($object->exists()) {
|
||||
$parts[] = trim($object->getKey(), '/');
|
||||
} else {
|
||||
if ($object->hasKey()) {
|
||||
$parts[] = trim($object->getKey(), '/');
|
||||
}
|
||||
$params = ['' => 'add'] + $params;
|
||||
}
|
||||
}
|
||||
|
||||
$p = [];
|
||||
$separator = $config->get('system.param_sep');
|
||||
foreach ($params as $key => $val) {
|
||||
$p[] = $key . $separator . $val;
|
||||
}
|
||||
|
||||
$parts = array_filter($parts, static function ($val) { return $val !== ''; });
|
||||
$route = '/' . implode('/', $parts);
|
||||
$extension = $extension ? '.' . $extension : '';
|
||||
|
||||
return $route . $extension . ($p ? '/' . implode('/', $p) : '');
|
||||
}
|
||||
|
||||
public function getAdminController(): ?AdminController
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
if (!isset($grav['admin'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var PageInterface $page */
|
||||
$page = $grav['page'];
|
||||
$header = $page->header();
|
||||
$callable = $header->controller['controller']['instance'] ?? null;
|
||||
if (null !== $callable && \is_callable($callable)) {
|
||||
return $callable();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAdminRoutes(): array
|
||||
{
|
||||
if (null === $this->adminRoutes) {
|
||||
$routes = [];
|
||||
/** @var FlexDirectory $directory */
|
||||
foreach ($this->getDirectories() as $directory) {
|
||||
$config = $directory->getConfig('admin');
|
||||
if (!$directory->isEnabled() || !empty($config['disabled'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve route.
|
||||
$route = $config['router']['path']
|
||||
?? $config['menu']['list']['route']
|
||||
?? "/flex-objects/{$directory->getFlexType()}";
|
||||
|
||||
$routes[$directory->getFlexType()] = $route;
|
||||
}
|
||||
|
||||
$this->adminRoutes = $routes;
|
||||
}
|
||||
|
||||
return $this->adminRoutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAdminMenuItems(): array
|
||||
{
|
||||
if (null === $this->adminMenu) {
|
||||
$routes = [];
|
||||
$count = 0;
|
||||
|
||||
$directories = $this->getDirectories();
|
||||
/** @var FlexDirectory $directory */
|
||||
foreach ($directories as $directory) {
|
||||
$config = $directory->getConfig('admin');
|
||||
if (!$directory->isEnabled() || !empty($config['disabled'])) {
|
||||
continue;
|
||||
}
|
||||
$type = $directory->getFlexType();
|
||||
$items = $directory->getConfig('admin.menu') ?? [];
|
||||
if ($items) {
|
||||
foreach ($items as $view => $item) {
|
||||
$item += [
|
||||
'route' => '/' . $type,
|
||||
'title' => $directory->getTitle(),
|
||||
'icon' => 'fa fa-file',
|
||||
'directory' => $type
|
||||
];
|
||||
$routes[$type] = $item;
|
||||
}
|
||||
} else {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($count && !isset($routes[''])) {
|
||||
$routes[''] = ['route' => '/flex-objects'];
|
||||
}
|
||||
|
||||
$this->adminMenu = $routes;
|
||||
}
|
||||
|
||||
return $this->adminMenu;
|
||||
}
|
||||
}
|
||||
80
plugins/flex-objects/classes/FlexFormFactory.php
Normal file
80
plugins/flex-objects/classes/FlexFormFactory.php
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\FlexObjects;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Framework\Form\Interfaces\FormFactoryInterface;
|
||||
use Grav\Framework\Form\Interfaces\FormInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use function is_callable;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class FlexFormFactory
|
||||
* @package Grav\Plugin\FlexObjects
|
||||
*/
|
||||
class FlexFormFactory implements FormFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @param Page $page
|
||||
* @param string $name
|
||||
* @param array $form
|
||||
* @return FormInterface|null
|
||||
*/
|
||||
public function createPageForm(Page $page, string $name, array $form): ?FormInterface
|
||||
{
|
||||
return $this->createFormForPage($page, $name, $form);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PageInterface $page
|
||||
* @param string $name
|
||||
* @param array $form
|
||||
* @return FormInterface|null
|
||||
*/
|
||||
public function createFormForPage(PageInterface $page, string $name, array $form): ?FormInterface
|
||||
{
|
||||
// Fire event
|
||||
$grav = Grav::instance();
|
||||
$grav->fireEvent('onBeforeFlexFormInitialize', new Event(['page' => $page, 'name' => $name, 'form' => &$form]));
|
||||
$page->addForms([$form], true);
|
||||
|
||||
$formFlex = $form['flex'] ?? [];
|
||||
|
||||
$type = $formFlex['type'] ?? null;
|
||||
$key = $formFlex['key'] ?? null;
|
||||
if (null !== $key && !is_string($key)) {
|
||||
$key = (string)$key;
|
||||
}
|
||||
$layout = $formFlex['layout'] ?? $name;
|
||||
|
||||
/** @var Flex $flex */
|
||||
$flex = Grav::instance()['flex_objects'];
|
||||
if (is_string($type)) {
|
||||
$directory = $flex->getDirectory($type);
|
||||
if (!$directory) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$create = $form['actions']['create'] ?? true;
|
||||
$edit = $form['actions']['edit'] ?? true;
|
||||
|
||||
$object = $edit && null !== $key ? $directory->getObject($key) : null;
|
||||
if ($object) {
|
||||
if (is_callable([$object, 'refresh'])) {
|
||||
$object->refresh();
|
||||
}
|
||||
} elseif ($create) {
|
||||
$object = $directory->createObject([], $key ?? '');
|
||||
}
|
||||
} else {
|
||||
$object = $flex->getObject($key);
|
||||
}
|
||||
|
||||
return $object ? $object->getForm($layout, ['form' => $form]) : null;
|
||||
}
|
||||
}
|
||||
64
plugins/flex-objects/classes/FlexRouter.php
Normal file
64
plugins/flex-objects/classes/FlexRouter.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Grav\Plugin\FlexObjects;
|
||||
|
||||
use Grav\Framework\Route\Route;
|
||||
use Grav\Plugin\FlexObjects\Controllers\MediaController;
|
||||
use Grav\Plugin\FlexObjects\Controllers\ObjectController;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
/**
|
||||
* Class FlexRouter
|
||||
* @package Grav\Plugin\FlexObjects
|
||||
*/
|
||||
class FlexRouter implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$context = $request->getAttributes();
|
||||
|
||||
/** @var Route $route */
|
||||
$route = $context['route'];
|
||||
$post = $request->getParsedBody();
|
||||
|
||||
$task = $post['task'] ?? $route->getParam('task');
|
||||
|
||||
if (\in_array($task, ['cropupload', 'filesupload'])) {
|
||||
$task = 'media.upload';
|
||||
}
|
||||
|
||||
switch ($task) {
|
||||
case 'media.upload':
|
||||
case 'media.delete':
|
||||
case 'media.copy':
|
||||
case 'media.remove':
|
||||
case 'media.list':
|
||||
|
||||
case 'media.add':
|
||||
case 'listmedia':
|
||||
case 'addmedia':
|
||||
case 'delmedia':
|
||||
return (new MediaController())->handle($request);
|
||||
case 'save':
|
||||
case 'create':
|
||||
case 'update':
|
||||
case 'delete':
|
||||
case 'reset':
|
||||
case 'preview':
|
||||
|
||||
case 'move':
|
||||
return (new ObjectController())->handle($request);
|
||||
}
|
||||
|
||||
// No handler found.
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
410
plugins/flex-objects/classes/Table/DataTable.php
Normal file
410
plugins/flex-objects/classes/Table/DataTable.php
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\FlexObjects\Table;
|
||||
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Framework\Collection\CollectionInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexAuthorizeInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use JsonSerializable;
|
||||
use Throwable;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class DataTable
|
||||
* @package Grav\Plugin\Gitea
|
||||
*
|
||||
* https://github.com/ratiw/vuetable-2/wiki/Data-Format-(JSON)
|
||||
* https://github.com/ratiw/vuetable-2/wiki/Sorting
|
||||
*/
|
||||
class DataTable implements JsonSerializable
|
||||
{
|
||||
/** @var string */
|
||||
private $url;
|
||||
/** @var int */
|
||||
private $limit;
|
||||
/** @var int */
|
||||
private $page;
|
||||
/** @var array */
|
||||
private $sort;
|
||||
/** @var string */
|
||||
private $search;
|
||||
/** @var FlexCollectionInterface */
|
||||
private $collection;
|
||||
/** @var FlexCollectionInterface */
|
||||
private $filteredCollection;
|
||||
/** @var array */
|
||||
private $columns;
|
||||
/** @var Environment */
|
||||
private $twig;
|
||||
/** @var array */
|
||||
private $twig_context;
|
||||
|
||||
/**
|
||||
* DataTable constructor.
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(array $params)
|
||||
{
|
||||
$this->setUrl($params['url'] ?? '');
|
||||
$this->setLimit((int)($params['limit'] ?? 10));
|
||||
$this->setPage((int)($params['page'] ?? 1));
|
||||
$this->setSort($params['sort'] ?? ['id' => 'asc']);
|
||||
$this->setSearch($params['search'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return void
|
||||
*/
|
||||
public function setUrl(string $url): void
|
||||
{
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return void
|
||||
*/
|
||||
public function setLimit(int $limit): void
|
||||
{
|
||||
$this->limit = max(1, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $page
|
||||
* @return void
|
||||
*/
|
||||
public function setPage(int $page): void
|
||||
{
|
||||
$this->page = max(1, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|string[] $sort
|
||||
* @return void
|
||||
*/
|
||||
public function setSort($sort): void
|
||||
{
|
||||
if (is_string($sort)) {
|
||||
$sort = $this->decodeSort($sort);
|
||||
} elseif (!is_array($sort)) {
|
||||
$sort = [];
|
||||
}
|
||||
|
||||
$this->sort = $sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $search
|
||||
* @return void
|
||||
*/
|
||||
public function setSearch(string $search): void
|
||||
{
|
||||
$this->search = $search;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CollectionInterface $collection
|
||||
* @return void
|
||||
*/
|
||||
public function setCollection(CollectionInterface $collection): void
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->filteredCollection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLimit(): int
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLastPage(): int
|
||||
{
|
||||
return 1 + (int)floor(max(0, $this->getTotal()-1) / $this->getLimit());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTotal(): int
|
||||
{
|
||||
$collection = $this->filteredCollection ?? $this->getCollection();
|
||||
|
||||
return $collection ? $collection->count() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSort(): array
|
||||
{
|
||||
return $this->sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FlexCollectionInterface|null
|
||||
*/
|
||||
public function getCollection(): ?FlexCollectionInterface
|
||||
{
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $page
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUrl(int $page): ?string
|
||||
{
|
||||
if ($page < 1 || $page > $this->getLastPage()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "{$this->url}.json?page={$page}&per_page={$this->getLimit()}&sort={$this->encodeSort()}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getColumns(): array
|
||||
{
|
||||
if (null === $this->columns) {
|
||||
$collection = $this->getCollection();
|
||||
if (!$collection) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$blueprint = $collection->getFlexDirectory()->getBlueprint();
|
||||
$schema = $blueprint->schema();
|
||||
$columns = $blueprint->get('config/admin/views/list/fields') ?? $blueprint->get('config/admin/list/fields', []);
|
||||
|
||||
$list = [];
|
||||
foreach ($columns as $key => $options) {
|
||||
if (!isset($options['field'])) {
|
||||
$options['field'] = $schema->get($options['alias'] ?? $key);
|
||||
}
|
||||
if (!$options['field'] || !empty($options['field']['ignore'])) {
|
||||
continue;
|
||||
}
|
||||
$list[$key] = $options;
|
||||
}
|
||||
|
||||
$this->columns = $list;
|
||||
}
|
||||
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $grav['debugger'];
|
||||
$debugger->startTimer('datatable', 'Data Table');
|
||||
|
||||
$collection = $this->getCollection();
|
||||
if (!$collection) {
|
||||
return [];
|
||||
}
|
||||
if ($this->search !== '') {
|
||||
$collection = $collection->search($this->search);
|
||||
}
|
||||
|
||||
$columns = $this->getColumns();
|
||||
|
||||
$collection = $collection->sort($this->getSort());
|
||||
|
||||
$this->filteredCollection = $collection;
|
||||
|
||||
$limit = $this->getLimit();
|
||||
$page = $this->getPage();
|
||||
$to = $page * $limit;
|
||||
$from = $to - $limit + 1;
|
||||
|
||||
if ($from < 1 || $from > $this->getTotal()) {
|
||||
$debugger->stopTimer('datatable');
|
||||
return [];
|
||||
}
|
||||
|
||||
$array = $collection->slice($from-1, $limit);
|
||||
|
||||
$twig = $grav['twig'];
|
||||
$grav->fireEvent('onTwigSiteVariables');
|
||||
|
||||
$this->twig = $twig->twig;
|
||||
$this->twig_context = $twig->twig_vars;
|
||||
|
||||
$list = [];
|
||||
/** @var FlexObjectInterface $object */
|
||||
foreach ($array as $object) {
|
||||
$item = [
|
||||
'id' => $object->getKey(),
|
||||
'timestamp' => $object->getTimestamp()
|
||||
];
|
||||
foreach ($columns as $name => $column) {
|
||||
$item[str_replace('.', '_', $name)] = $this->renderColumn($name, $column, $object);
|
||||
}
|
||||
$item['_actions_'] = $this->renderActions($object);
|
||||
|
||||
$list[] = $item;
|
||||
}
|
||||
|
||||
$debugger->stopTimer('datatable');
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = $this->getData();
|
||||
$total = $this->getTotal();
|
||||
$limit = $this->getLimit();
|
||||
$page = $this->getPage();
|
||||
$to = $page * $limit;
|
||||
$from = $to - $limit + 1;
|
||||
|
||||
$empty = empty($data);
|
||||
|
||||
return [
|
||||
'links' => [
|
||||
'pagination' => [
|
||||
'total' => $total,
|
||||
'per_page' => $limit,
|
||||
'current_page' => $page,
|
||||
'last_page' => $this->getLastPage(),
|
||||
'next_page_url' => $this->getUrl($page+1),
|
||||
'prev_page_url' => $this->getUrl($page-1),
|
||||
'from' => $empty ? null : $from,
|
||||
'to' => $empty ? null : min($to, $total),
|
||||
]
|
||||
],
|
||||
'data' => $data
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $column
|
||||
* @param FlexObjectInterface $object
|
||||
* @return false|string
|
||||
* @throws Throwable
|
||||
* @throws LoaderError
|
||||
* @throws RuntimeError
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
protected function renderColumn(string $name, array $column, FlexObjectInterface $object)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$flex = $grav['flex_objects'];
|
||||
|
||||
$value = $object->getFormValue($name) ?? $object->getNestedProperty($name, $column['field']['default'] ?? null);
|
||||
$type = $column['field']['type'] ?? 'text';
|
||||
$hasLink = $column['link'] ?? null;
|
||||
$link = null;
|
||||
|
||||
$authorized = $object instanceof FlexAuthorizeInterface
|
||||
? ($object->isAuthorized('read') || $object->isAuthorized('update')) : true;
|
||||
|
||||
if ($hasLink && $authorized) {
|
||||
$route = $grav['route']->withExtension('');
|
||||
$link = $route->withAddedPath($object->getKey())->withoutParams()->getUri();
|
||||
}
|
||||
|
||||
$template = $this->twig->resolveTemplate(["forms/fields/{$type}/edit_list.html.twig", 'forms/fields/text/edit_list.html.twig']);
|
||||
|
||||
return $this->twig->load($template)->render([
|
||||
'value' => $value,
|
||||
'link' => $link,
|
||||
'field' => $column['field'],
|
||||
'object' => $object,
|
||||
'flex' => $flex,
|
||||
'route' => $grav['route']->withExtension('')
|
||||
] + $this->twig_context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FlexObjectInterface $object
|
||||
* @return false|string
|
||||
* @throws Throwable
|
||||
* @throws LoaderError
|
||||
* @throws RuntimeError
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
protected function renderActions(FlexObjectInterface $object)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$type = $object->getFlexType();
|
||||
$template = $this->twig->resolveTemplate(["flex-objects/types/{$type}/list/list_actions.html.twig", 'flex-objects/types/default/list/list_actions.html.twig']);
|
||||
|
||||
return $this->twig->load($template)->render([
|
||||
'object' => $object,
|
||||
'flex' => $grav['flex_objects'],
|
||||
'route' => $grav['route']->withExtension('')
|
||||
] + $this->twig_context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sort
|
||||
* @param string $fieldSeparator
|
||||
* @param string $orderSeparator
|
||||
* @return array
|
||||
*/
|
||||
protected function decodeSort(string $sort, string $fieldSeparator = ',', string $orderSeparator = '|'): array
|
||||
{
|
||||
$strings = explode($fieldSeparator, $sort);
|
||||
$list = [];
|
||||
foreach ($strings as $string) {
|
||||
$item = explode($orderSeparator, $string, 2);
|
||||
$key = array_shift($item);
|
||||
$order = array_shift($item) === 'desc' ? 'desc' : 'asc';
|
||||
$list[$key] = $order;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fieldSeparator
|
||||
* @param string $orderSeparator
|
||||
* @return string
|
||||
*/
|
||||
protected function encodeSort(string $fieldSeparator = ',', string $orderSeparator = '|'): string
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->getSort() as $key => $order) {
|
||||
$list[] = $key . $orderSeparator . ($order ?: 'asc');
|
||||
}
|
||||
|
||||
return implode($fieldSeparator, $list);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue