Commit 1d9a1ac6 authored by Tino Goratsch's avatar Tino Goratsch

Merge branch 'release/v4.13.1'

parents 0b4e7d70 df2c123e
......@@ -14,7 +14,7 @@ interface BootstrapInterface extends HttpKernelInterface
/**
* Contains the current ACP3 version string
*/
const VERSION = '4.13.0';
const VERSION = '4.13.1';
/**
* Performs some startup checks
......
......@@ -6,6 +6,8 @@
namespace ACP3\Core\NestedSet\Operation;
use Doctrine\DBAL\Connection;
/**
* Class Delete
* @package ACP3\Core\NestedSet\Operation
......@@ -23,7 +25,8 @@ class Delete extends AbstractOperation
$callback = function () use ($resultId) {
$nodes = $this->nestedSetRepository->fetchNodeWithSiblings((int)$resultId);
if (!empty($nodes)) {
$this->db->getConnection()->delete($this->nestedSetRepository->getTableName(), ['id' => (int)$resultId]);
$this->db->getConnection()->delete($this->nestedSetRepository->getTableName(),
['id' => (int)$resultId]);
$this->moveSiblingsOneLevelUp($nodes);
$this->adjustParentNodesAfterSeparation(2, $nodes[0]['left_id'], $nodes[0]['right_id']);
......@@ -45,19 +48,39 @@ class Delete extends AbstractOperation
*/
protected function moveSiblingsOneLevelUp(array $nodes)
{
array_shift($nodes);
// Update the root_id and parent_id of the siblings
foreach ($nodes as $node) {
$rootId = $this->nestedSetRepository->fetchRootNode($node['left_id'], $node['right_id']);
$parentId = $this->nestedSetRepository->fetchParentNode($node['left_id'], $node['right_id']);
// root_id und parent_id der Kinder aktualisieren
$this->db->getConnection()->executeUpdate(
"UPDATE {$this->nestedSetRepository->getTableName()} SET root_id = ?, parent_id = ?, left_id = left_id - 1, right_id = right_id - 1 WHERE id = ?",
"UPDATE {$this->nestedSetRepository->getTableName()} SET root_id = ?, parent_id = ? WHERE id = ?",
[
!empty($rootId) ? $rootId : $node['id'],
$nodes[0]['id'],
$parentId,
$node['id']
]
);
}
$this->db->getConnection()->executeUpdate(
"UPDATE {$this->nestedSetRepository->getTableName()} SET left_id = left_id - 1, right_id = right_id - 1 WHERE id IN(?)",
[$this->getNodeIds($nodes)],
[Connection::PARAM_INT_ARRAY]
);
}
/**
* @param array $nodes
* @return array
*/
private function getNodeIds(array $nodes)
{
$nodeIds = [];
foreach ($nodes as $node) {
$nodeIds[] = $node['id'];
}
return $nodeIds;
}
}
......@@ -88,7 +88,7 @@ class StepsTest extends \PHPUnit_Framework_TestCase
$expected = [
[
'title' => '{FOO_FOO}',
'uri' => '/acp/foo',
'uri' => '/acp/foo/',
'last' => true
]
];
......@@ -100,8 +100,9 @@ class StepsTest extends \PHPUnit_Framework_TestCase
* @param string $moduleName
* @param string $controller
* @param string $action
* @param string $parameters
*/
private function setUpRequestMockExpectations($area, $moduleName, $controller, $action)
protected function setUpRequestMockExpectations($area, $moduleName, $controller, $action, $parameters = '')
{
$this->requestMock->expects($this->atLeastOnce())
->method('getArea')
......@@ -120,6 +121,17 @@ class StepsTest extends \PHPUnit_Framework_TestCase
->willReturn(
($area === AreaEnum::AREA_ADMIN ? 'acp/' : '') . $moduleName . '/' . $controller
);
$this->requestMock->expects($this->any())
->method('getFullPath')
->willReturn(
($area === AreaEnum::AREA_ADMIN ? 'acp/' : '') . $moduleName . '/' . $controller . '/' . $action
);
$this->requestMock->expects($this->any())
->method('getQuery')
->willReturn($moduleName . '/' . $controller . '/' . $action . '/' . $parameters);
$this->requestMock->expects($this->any())
->method('getUriWithoutPages')
->willReturn($moduleName . '/' . $controller . '/' . $action . '/' . $parameters);
}
/**
......@@ -134,16 +146,16 @@ class StepsTest extends \PHPUnit_Framework_TestCase
->willReturn($serviceExists);
}
private function setUpRouterMockExpectations()
protected function setUpRouterMockExpectations()
{
$this->routerMock->expects($this->atLeastOnce())
->method('route')
->willReturnCallback(function ($path) {
return '/' . $path;
return '/' . $path . (!preg_match('=/$=', $path) ? '/' : '');
});
}
private function setUpTranslatorMockExpectations($callCount = 1)
protected function setUpTranslatorMockExpectations($callCount = 1)
{
$this->translatorMock->expects($this->atLeast($callCount))
->method('t')
......@@ -170,11 +182,11 @@ class StepsTest extends \PHPUnit_Framework_TestCase
$expected = [
[
'title' => '{FOO_FOO}',
'uri' => '/acp/foo',
'uri' => '/acp/foo/',
],
[
'title' => '{FOO_ADMIN_DETAILS_INDEX}',
'uri' => '/acp/foo/details',
'uri' => '/acp/foo/details/',
'last' => true
]
];
......@@ -197,11 +209,11 @@ class StepsTest extends \PHPUnit_Framework_TestCase
$expected = [
[
'title' => '{FOO_FOO}',
'uri' => '/acp/foo',
'uri' => '/acp/foo/',
],
[
'title' => 'FooBarBaz',
'uri' => '/acp/foo/bar/baz',
'uri' => '/acp/foo/bar/baz/',
'last' => true
]
];
......@@ -222,7 +234,7 @@ class StepsTest extends \PHPUnit_Framework_TestCase
$expected = [
[
'title' => '{FOO_FOO}',
'uri' => '/foo',
'uri' => '/foo/',
'last' => true
]
];
......@@ -247,11 +259,11 @@ class StepsTest extends \PHPUnit_Framework_TestCase
$expected = [
[
'title' => '{FOO_FOO}',
'uri' => '/foo',
'uri' => '/foo/',
],
[
'title' => '{FOO_FRONTEND_DETAILS_INDEX}',
'uri' => '/foo/details',
'uri' => '/foo/details/',
'last' => true
]
];
......@@ -274,7 +286,7 @@ class StepsTest extends \PHPUnit_Framework_TestCase
$expected = [
[
'title' => 'FooBarBaz',
'uri' => '/foo/bar/baz',
'uri' => '/foo/bar/baz/',
'last' => true
]
];
......@@ -299,7 +311,7 @@ class StepsTest extends \PHPUnit_Framework_TestCase
$expected = [
[
'title' => 'FooBarBaz',
'uri' => '/foo/bar/baz',
'uri' => '/foo/bar/baz/',
'last' => true
]
];
......@@ -326,15 +338,15 @@ class StepsTest extends \PHPUnit_Framework_TestCase
$expected = [
[
'title' => 'FooBarBaz',
'uri' => '/foo/bar/baz',
'uri' => '/foo/bar/baz/',
],
[
'title' => 'FooBarBaz2',
'uri' => '/foo/bar/baz2',
'uri' => '/foo/bar/baz2/',
],
[
'title' => 'Lorem Ipsum',
'uri' => '/lorem/ipsum/dolor',
'uri' => '/lorem/ipsum/dolor/',
'last' => true
],
];
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1"
},
"autoload": {
"psr-4": {
......
......@@ -72,7 +72,7 @@ class Details extends Core\Controller\AbstractFrontendAction
$article = $this->articlesCache->getCache($id);
$this->breadcrumb->replaceAncestor($article['title'], '', true);
$this->breadcrumb->append($article['title']);
return [
'page' => array_merge(
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1"
},
"suggest": {
"acp3/module-seo": "Provides additional SEO capabilities"
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0",
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1",
"google/recaptcha": "^1.1.0"
},
"autoload": {
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1"
},
"autoload": {
"psr-4": {
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1"
},
"suggest": {
"acp3/module-captcha": "Provides basic protection against spam bots."
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1"
},
"suggest": {
"acp3/module-captcha": "Provides basic protection against spam bots."
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1"
},
"autoload": {
"psr-4": {
......
......@@ -17,11 +17,11 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1"
},
"autoload": {
"psr-4": {
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0",
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1",
"mibe/feedwriter": "^1.0"
},
"autoload": {
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1"
},
"autoload": {
"psr-4": {
......
......@@ -17,13 +17,13 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0",
"acp3/module-categories": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1",
"acp3/module-categories": "^4.13.1"
},
"suggest": {
"acp3/module-seo": "Provides additional SEO capabilities"
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1"
},
"suggest": {
"acp3/module-seo": "Provides additional SEO capabilities"
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "^1.0",
"acp3/core": "^4.13.0",
"acp3/setup": "^4.13.0",
"acp3/module-errors": "^4.13.0",
"acp3/module-permissions": "^4.13.0",
"acp3/module-system": "^4.13.0",
"acp3/module-users": "^4.13.0"
"acp3/core": "^4.13.1",
"acp3/setup": "^4.13.1",
"acp3/module-errors": "^4.13.1",
"acp3/module-permissions": "^4.13.1",
"acp3/module-system": "^4.13.1",
"acp3/module-users": "^4.13.1"
},
"suggest": {
"acp3/module-captcha": "Provides basic protection against spam bots."
......
......@@ -30,12 +30,12 @@ class Steps extends Core\Breadcrumb\Steps
/**
* Breadcrumb constructor.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* @param \ACP3\Core\I18n\Translator $translator
* @param \ACP3\Core\Http\RequestInterface $request
* @param \ACP3\Core\Router\RouterInterface $router
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* @param \ACP3\Core\I18n\Translator $translator
* @param \ACP3\Core\Http\RequestInterface $request
* @param \ACP3\Core\Router\RouterInterface $router
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
* @param \ACP3\Modules\ACP3\Menus\Model\Repository\MenuItemRepository $menuItemRepository
* @param \ACP3\Modules\ACP3\Menus\Model\Repository\MenuItemRepository $menuItemRepository
*/
public function __construct(
ContainerInterface $container,
......@@ -48,43 +48,6 @@ class Steps extends Core\Breadcrumb\Steps
parent::__construct($container, $translator, $request, $router, $eventDispatcher);
$this->menuItemRepository = $menuItemRepository;
$this->prePopulate();
}
/**
* Initializes and pre populates the breadcrumb
*/
public function prePopulate()
{
if ($this->request->getArea() !== Core\Controller\AreaEnum::AREA_ADMIN) {
$in = [
$this->request->getQuery(),
$this->request->getUriWithoutPages(),
$this->request->getFullPath(),
$this->request->getModuleAndController(),
$this->request->getModule()
];
$items = $this->menuItemRepository->getMenuItemsByUri($in);
foreach ($items as $item) {
$this->appendFromDB($item['title'], $item['uri']);
}
}
}
/**
* Zuweisung einer neuen Stufe zur Brotkrümelspur
*
* @param string $title
* @param string $path
*
* @return $this
*/
protected function appendFromDB($title, $path = '')
{
$this->stepsFromDb[] = $this->buildStepItem($title, $path);
return $this;
}
/**
......@@ -101,12 +64,16 @@ class Steps extends Core\Breadcrumb\Steps
}
/**
* Sets the breadcrumb steps cache for frontend action requests
* @inheritdoc
*/
protected function buildBreadcrumbCacheForFrontend()
{
parent::buildBreadcrumbCacheForFrontend();
if (empty($this->stepsFromDb)) {
$this->prePopulate();
}
if (!empty($this->stepsFromDb)) {
$this->breadcrumbCache = $this->stepsFromDb;
......@@ -117,4 +84,67 @@ class Steps extends Core\Breadcrumb\Steps
}
}
}
/**
* Initializes and pre populates the breadcrumb
*/
private function prePopulate()
{
$items = $this->menuItemRepository->getMenuItemsByUri($this->possibleMatchedRoutes());
$matches = $this->findRestrictionInRoutes($items);
if (!empty($matches)) {
list($leftId, $rightId) = $matches;
foreach ($items as $item) {
if ($item['left_id'] <= $leftId && $item['right_id'] >= $rightId) {
$this->appendFromDB($item['title'], $item['uri']);
}
}
}
}
private function possibleMatchedRoutes() {
return [
$this->request->getQuery(),
$this->request->getUriWithoutPages(),
$this->request->getFullPath(),
$this->request->getModuleAndController(),
$this->request->getModule()
];
}
/**
* @param array $items
* @return array
*/
private function findRestrictionInRoutes(array $items)
{
rsort($items);
foreach ($items as $index => $item) {
if (in_array($item['uri'], $this->possibleMatchedRoutes())) {
return [
$item['left_id'],
$item['right_id'],
];
}
}
return [];
}
/**
* Zuweisung einer neuen Stufe zur Brotkrümelspur
*
* @param string $title
* @param string $path
*
* @return $this
*/
private function appendFromDB($title, $path = '')
{
$this->stepsFromDb[] = $this->buildStepItem($title, $path);
return $this;
}
}
......@@ -9,6 +9,12 @@ jQuery(document).ready(function ($) {
// Wenn Menüpunkt nicht angezeigt werden soll, Linkziel verstecken
$('input[name="display"]').change(function () {
$target.toggle(this.value == 1);
if (this.value == 0) {
// Force the link target to open on the same page programmatically,
// as the user can not select it for themself
$('#target').val(1);
}
});
var $mode = $('#mode'),
......
......@@ -6,6 +6,7 @@
namespace ACP3\Modules\ACP3\Menus\Test\Core\Breadcrumb;
use ACP3\Core\Controller\AreaEnum;
use ACP3\Modules\ACP3\Menus\Core\Breadcrumb\Steps;
use ACP3\Modules\ACP3\Menus\Model\Repository\MenuItemRepository;
......@@ -20,8 +21,6 @@ class StepsTest extends \ACP3\Core\Test\Breadcrumb\StepsTest
{
$this->initializeMockObjects();
$this->setUpMenuItemRepositoryExpectations();
$this->steps = new Steps(
$this->containerMock,
$this->translatorMock,
......@@ -41,11 +40,199 @@ class StepsTest extends \ACP3\Core\Test\Breadcrumb\StepsTest
->getMock();
}
protected function setUpMenuItemRepositoryExpectations()
protected function setUpMenuItemRepositoryExpectations(array $dbSteps = [])
{
$this->menuItemRepositoryMock->expects($this->once())
->method('getMenuItemsByUri')
->withAnyParameters()
->willReturn([]);
->willReturn($dbSteps);
}
public function testGetBreadcrumbWithSingleDbStep()
{
$this->setUpMenuItemRepositoryExpectations([
[
'title' => 'News',
'uri' => 'news',
'left_id' => 1,
'right_id' => 4
]
]);
$this->setUpRequestMockExpectations(
AreaEnum::AREA_FRONTEND,
'news',
'index',
'index'
);
$this->setUpRouterMockExpectations();
$this->setUpTranslatorMockExpectations(0);
$expected = [
[
'title' => 'News',
'uri' => '/news/',