Commit 2580f2a3 authored by Tino Goratsch's avatar Tino Goratsch

Merge branch 'release/v4.5.0'

parents abd0d419 e0efef9c
......@@ -12,7 +12,6 @@ php:
matrix:
fast_finish: true
allow_failures:
- php: 7.1
- php: nightly
- php: hhvm
......
......@@ -107,6 +107,8 @@ class Bootstrap extends AbstractBootstrap
} catch (\ACP3\Core\Controller\Exception\ControllerActionNotFoundException $e) {
$response = $this->handleException($e, $redirect, 'errors/index/not_found');
} catch (\Exception $e) {
$this->getContainer()->get('core.logger')->error('exception', $e);
$response = $this->handleException($e, $redirect, 'errors/index/server_error');
}
......
......@@ -14,7 +14,7 @@ interface BootstrapInterface extends HttpKernelInterface
/**
* Contains the current ACP3 version string
*/
const VERSION = '4.4.4';
const VERSION = '4.5.0';
/**
* Performs some startup checks
......
......@@ -9,6 +9,7 @@ namespace ACP3\Core\Application;
use ACP3\Core\Application\Event\ControllerActionAfterDispatchEvent;
use ACP3\Core\Application\Event\ControllerActionBeforeDispatchEvent;
use ACP3\Core\Controller\ActionInterface;
use ACP3\Core\Controller\Exception\ControllerActionNotFoundException;
use ACP3\Core\Http\RequestInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
......@@ -61,7 +62,7 @@ class ControllerActionDispatcher
* @param array $arguments
* @return Response|string
*
* @throws \ACP3\Core\Controller\Exception\ControllerActionNotFoundException
* @throws ControllerActionNotFoundException
* @throws \ACP3\Core\Controller\Exception\ResultNotExistsException
*/
public function dispatch($serviceId = '', array $arguments = [])
......@@ -89,11 +90,23 @@ class ControllerActionDispatcher
return $response;
}
throw new \ACP3\Core\Controller\Exception\ControllerActionNotFoundException(
throw new ControllerActionNotFoundException(
'Service-Id ' . $serviceId . ' was not found!'
);
}
/**
* @return string
*/
protected function buildControllerServiceId()
{
return $this->request->getModule()
. '.controller.'
. $this->request->getArea()
. '.' . $this->request->getController()
. '.' . $this->request->getAction();
}
/**
* @param \ACP3\Core\Controller\ActionInterface $controller
* @param array $arguments
......@@ -103,7 +116,7 @@ class ControllerActionDispatcher
*/
private function executeControllerAction(ActionInterface $controller, array $arguments)
{
$callable = [$controller, 'execute'];
$callable = $this->getCallable($controller);
if (empty($arguments)) {
$arguments = $this->argumentResolver->getArguments($this->request->getSymfonyRequest(), $callable);
......@@ -113,14 +126,20 @@ class ControllerActionDispatcher
}
/**
* @return string
* @param ActionInterface $controller
* @return array
*/
protected function buildControllerServiceId()
private function getCallable(ActionInterface $controller)
{
return $this->request->getModule()
. '.controller.'
. $this->request->getArea()
. '.' . $this->request->getController()
. '.' . $this->request->getAction();
$callable = [$controller, 'execute'];
if ($this->request->getPost()->has('submit') && method_exists($controller, 'executePost')) {
$reflection = new \ReflectionMethod($controller, 'executePost');
if ($reflection->isPublic()) {
$callable = [$controller, 'executePost'];
}
}
return $callable;
}
}
......@@ -14,21 +14,6 @@ use ACP3\Core;
*/
abstract class AbstractAdminAction extends Core\Controller\AbstractFrontendAction
{
/**
* @var \ACP3\Core\Session\SessionHandlerInterface
*/
protected $session;
/**
* @param \ACP3\Core\Controller\Context\AdminContext $context
*/
public function __construct(Context\AdminContext $context)
{
parent::__construct($context);
$this->session = $context->getSession();
}
/**
* @return $this
* @throws \ACP3\Core\Authentication\Exception\UnauthorizedAccessException
......
......@@ -10,41 +10,9 @@ use ACP3\Core;
/**
* Class AdminContext
* @package ACP3\Core\Controller\Context
*
* @deprecated since version 4.5.0, to be removed in version 5.0.0
*/
class AdminContext extends FrontendContext
{
/**
* @var \ACP3\Core\Session\SessionHandlerInterface
*/
protected $session;
/**
* AdminContext constructor.
*
* @param \ACP3\Core\Controller\Context\FrontendContext $context
* @param \ACP3\Core\Session\SessionHandlerInterface $session
*/
public function __construct(
Core\Controller\Context\FrontendContext $context,
Core\Session\SessionHandlerInterface $session
) {
parent::__construct(
$context,
$context->getAssets(),
$context->getBreadcrumb(),
$context->getTitle(),
$context->getActionHelper(),
$context->getResponse()
);
$this->session = $session;
}
/**
* @return \ACP3\Core\Session\SessionHandlerInterface
*/
public function getSession()
{
return $this->session;
}
}
......@@ -56,7 +56,8 @@ abstract class AbstractModel
{
$filteredData = $this->prepareData($rawData);
$this->dispatchBeforeSaveEvent($this->repository, $entryId, $filteredData, $rawData);
$isNewEntry = $entryId === null;
$this->dispatchBeforeSaveEvent($this->repository, $entryId, $filteredData, $rawData, $isNewEntry);
if ($entryId === null) {
$result = $this->repository->insert($filteredData);
......@@ -68,7 +69,7 @@ abstract class AbstractModel
$result = $this->repository->update($filteredData, $entryId);
}
$this->dispatchAfterSaveEvent($this->repository, $entryId, $filteredData, $rawData);
$this->dispatchAfterSaveEvent($this->repository, $entryId, $filteredData, $rawData, $isNewEntry);
return $result;
}
......@@ -78,17 +79,20 @@ abstract class AbstractModel
* @param int|null|array $entryId
* @param array $filteredData
* @param array $rawData
* @param bool $isNewEntry
*/
protected function dispatchBeforeSaveEvent(
AbstractRepository $repository,
$entryId,
array $filteredData,
array $rawData
array $rawData,
$isNewEntry
) {
$this->dispatchEvent('core.model.before_save', $entryId, $filteredData, $rawData);
$this->dispatchEvent('core.model.before_save', $entryId, $isNewEntry, $filteredData, $rawData);
$this->dispatchEvent(
static::EVENT_PREFIX . '.model.' . $repository::TABLE_NAME . '.before_save',
$entryId,
$isNewEntry,
$filteredData,
$rawData
);
......@@ -97,14 +101,15 @@ abstract class AbstractModel
/**
* @param string $eventName
* @param int|null|array $entryId
* @param bool $isNewEntry
* @param array $filteredData
* @param array $rawData
*/
protected function dispatchEvent($eventName, $entryId, array $filteredData = [], array $rawData = [])
protected function dispatchEvent($eventName, $entryId, $isNewEntry, array $filteredData = [], array $rawData = [])
{
$this->eventDispatcher->dispatch(
$eventName,
new ModelSaveEvent(static::EVENT_PREFIX, $filteredData, $rawData, $entryId)
new ModelSaveEvent(static::EVENT_PREFIX, $filteredData, $rawData, $entryId, $isNewEntry)
);
}
......@@ -127,17 +132,20 @@ abstract class AbstractModel
* @param int|null|array $entryId
* @param array $filteredData
* @param array $rawData
* @param bool $isNewEntry
*/
protected function dispatchAfterSaveEvent(
AbstractRepository $repository,
$entryId,
array $filteredData,
array $rawData
array $rawData,
$isNewEntry
) {
$this->dispatchEvent('core.model.after_save', $entryId, $filteredData, $rawData);
$this->dispatchEvent('core.model.after_save', $entryId, $isNewEntry, $filteredData, $rawData);
$this->dispatchEvent(
static::EVENT_PREFIX . '.model.' . $repository::TABLE_NAME . '.after_save',
$entryId,
$isNewEntry,
$filteredData,
$rawData
);
......@@ -155,9 +163,9 @@ abstract class AbstractModel
$entryId = [$entryId];
}
$this->dispatchEvent('core.model.before_delete', $entryId);
$this->dispatchEvent('core.model.before_delete', $entryId, false);
$this->dispatchEvent(
static::EVENT_PREFIX . '.model.' . $repository::TABLE_NAME . '.before_delete', $entryId
static::EVENT_PREFIX . '.model.' . $repository::TABLE_NAME . '.before_delete', $entryId, false
);
$affectedRows = 0;
......@@ -165,9 +173,9 @@ abstract class AbstractModel
$affectedRows += (int)$this->repository->delete($item);
}
$this->dispatchEvent('core.model.before_delete', $entryId);
$this->dispatchEvent('core.model.before_delete', $entryId, false);
$this->dispatchEvent(
static::EVENT_PREFIX . '.model.' . $repository::TABLE_NAME . '.after_delete', $entryId
static::EVENT_PREFIX . '.model.' . $repository::TABLE_NAME . '.after_delete', $entryId, false
);
return $affectedRows;
......
......@@ -61,7 +61,9 @@ abstract class AbstractNestedSetModel extends AbstractModel
{
$filteredData = $this->prepareData($rawData);
$this->dispatchBeforeSaveEvent($this->repository, $entryId, $filteredData, $rawData);
$isNewEntry = $entryId === null;
$this->dispatchBeforeSaveEvent($this->repository, $entryId, $filteredData, $rawData, $isNewEntry);
if ($entryId === null) {
$result = $this->insertOperation->execute($filteredData, $rawData['parent_id']);
......@@ -78,7 +80,7 @@ abstract class AbstractNestedSetModel extends AbstractModel
);
}
$this->dispatchAfterSaveEvent($this->repository, $entryId, $filteredData, $rawData);
$this->dispatchAfterSaveEvent($this->repository, $entryId, $filteredData, $rawData, $isNewEntry);
return $result;
}
......@@ -95,9 +97,9 @@ abstract class AbstractNestedSetModel extends AbstractModel
$entryId = [$entryId];
}
$this->dispatchEvent('core.model.before_delete', $entryId);
$this->dispatchEvent('core.model.before_delete', $entryId, false);
$this->dispatchEvent(
static::EVENT_PREFIX . '.model.' . $repository::TABLE_NAME . '.before_delete', $entryId
static::EVENT_PREFIX . '.model.' . $repository::TABLE_NAME . '.before_delete', $entryId, false
);
$affectedRows = 0;
......@@ -105,9 +107,9 @@ abstract class AbstractNestedSetModel extends AbstractModel
$affectedRows += (int)$this->deleteOperation->execute($item);
}
$this->dispatchEvent('core.model.before_delete', $entryId);
$this->dispatchEvent('core.model.before_delete', $entryId, false);
$this->dispatchEvent(
static::EVENT_PREFIX . '.model.' . $repository::TABLE_NAME . '.after_delete', $entryId
static::EVENT_PREFIX . '.model.' . $repository::TABLE_NAME . '.after_delete', $entryId, false
);
return $affectedRows;
......
......@@ -27,6 +27,10 @@ class ModelSaveEvent extends Event
* @var array
*/
private $rawData;
/**
* @var bool
*/
private $isNewEntry;
/**
* ModelSaveEvent constructor.
......@@ -34,13 +38,15 @@ class ModelSaveEvent extends Event
* @param array $filteredData
* @param array $rawData
* @param int|null|array $entryId
* @param bool $isNewEntry
*/
public function __construct($moduleName, array $filteredData, array $rawData, $entryId)
public function __construct($moduleName, array $filteredData, array $rawData, $entryId, $isNewEntry)
{
$this->moduleName = $moduleName;
$this->filteredData = $filteredData;
$this->rawData = $rawData;
$this->entryId = $entryId;
$this->isNewEntry = $isNewEntry;
}
/**
......@@ -82,4 +88,12 @@ class ModelSaveEvent extends Event
{
return count($this->filteredData) === 0 && is_array($this->entryId);
}
/**
* @return bool
*/
public function isIsNewEntry()
{
return $this->isNewEntry;
}
}
......@@ -173,14 +173,12 @@ class Action
* @param null|string $path
*
* @return string|array|\Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
*
* @deprecated since 4.4.4, to be removed with version 4.5.0
*/
public function handleCreatePostAction(callable $callback, $path = null)
{
return $this->handlePostAction(function () use ($callback, $path) {
$result = $callback();
return $this->prepareRedirectMessageAfterPost($result, 'create', $path);
});
return $this->handleSaveAction($callback, $path);
}
/**
......@@ -188,28 +186,41 @@ class Action
* @param null|string $path
*
* @return string|array|\Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
*
* @deprecated since 4.4.4, to be removed with version 4.5.0
*/
public function handleEditPostAction(callable $callback, $path = null)
{
return $this->handleSaveAction($callback, $path);
}
/**
* @param callable $callback
* @param null|string $path
*
* @return array|string|JsonResponse|RedirectResponse
*/
public function handleSaveAction(callable $callback, $path = null)
{
return $this->handlePostAction(function () use ($callback, $path) {
$result = $callback();
return $this->prepareRedirectMessageAfterPost($result, 'edit', $path);
return $this->prepareRedirectMessageAfterPost($result, 'save', $path);
});
}
/**
* @param bool|int $result
* @param string $localization
* @param string $phrase
* @param null|string $path
*
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
*/
private function prepareRedirectMessageAfterPost($result, $localization, $path = null)
private function prepareRedirectMessageAfterPost($result, $phrase, $path = null)
{
return $this->redirectMessages->setMessage(
$result,
$this->translator->t('system', $localization . ($result !== false ? '_success' : '_error')),
$this->translator->t('system', $phrase . ($result !== false ? '_success' : '_error')),
$path
);
}
......
......@@ -28,6 +28,7 @@ class NotEmptyValidationRuleTest extends AbstractValidationRuleTest
'invalid-data-string-whitespaces-newlines' => [" \r\n", '', [], false],
'invalid-data-empty-array' => [['foo' => []], 'foo', [], false],
'invalid-data-empty-array-2' => [[], '', [], false],
'invalid-data-missing-key' => [['foo' => ['bar-baz']], 'baz', [], false],
'invalid-data-array' => [['foo' => ''], 'foo', [], false],
'invalid-data-array-whitespace' => [['foo' => ' '], 'foo', [], false],
'invalid-data-array-whitespaces-newlines' => [['foo' => " \r\n"], 'foo', [], false],
......
......@@ -12,10 +12,20 @@ class NotEmptyValidationRule extends AbstractValidationRule
*/
public function isValid($data, $field = '', array $extra = [])
{
if (is_array($data) && array_key_exists($field, $data)) {
return $this->isValid($data[$field], $field, $extra);
if (is_scalar($data)) {
return !empty(trim($data));
}
return !empty(is_array($data) ? $data : trim($data));
if (is_array($data)) {
if (empty($field)) {
return count($data) > 0;
}
if (array_key_exists($field, $data)) {
return !empty(is_scalar($data[$field]) ? trim($data[$field]) : $data[$field]);
}
}
return false;
}
}
......@@ -57,19 +57,9 @@ class LoadModule extends AbstractFunction
$pathArray = $this->convertPathToArray($params['module']);
$path = $pathArray[0] . '/' . $pathArray[1] . '/' . $pathArray[2] . '/' . $pathArray[3];
$arguments = [];
if (isset($params['args']) && is_array($params['args'])) {
$arguments = array_map(
function($item) {
return urlencode($item);
},
$params['args']
);
}
$response = '';
if ($this->acl->hasPermission($path) === true) {
$response = $this->esiInclude($path, $arguments);
$response = $this->esiInclude($path, $this->parseControllerActionArguments($params));
}
return $response;
......@@ -93,6 +83,34 @@ class LoadModule extends AbstractFunction
return $pathArray;
}
/**
* @param array $arguments
* @return array
*/
protected function parseControllerActionArguments(array $arguments)
{