diff --git a/Controller/ArticleController.php b/Controller/ArticleController.php index c7ff777d1..21144fc23 100644 --- a/Controller/ArticleController.php +++ b/Controller/ArticleController.php @@ -375,6 +375,13 @@ public function postTriggerAction($uuid, Request $request) $data = $this->getDocumentManager()->find($copiedPath, $locale); break; + case 'order': + $this->orderPages($this->getRequestParameter($request, 'pages', true), $locale); + $this->getDocumentManager()->flush(); + $this->getDocumentManager()->clear(); + + $data = $this->getDocumentManager()->find($uuid, $locale); + break; default: throw new RestException('Unrecognized action: ' . $action); } @@ -393,6 +400,23 @@ public function postTriggerAction($uuid, Request $request) return $this->handleView($view); } + /** + * Ordering given pages. + * + * @param array $pages + * @param string $locale + */ + private function orderPages(array $pages, $locale) + { + $documentManager = $this->getDocumentManager(); + + $tmp = []; + for ($i = 0; $i < count($pages); ++$i) { + $tmp[] = $document = $documentManager->find($pages[$i], $locale); + $documentManager->reorder($document, null); + } + } + /** * {@inheritdoc} */ diff --git a/Document/Subscriber/ArticleSubscriber.php b/Document/Subscriber/ArticleSubscriber.php index c453afdbc..ace6d4548 100644 --- a/Document/Subscriber/ArticleSubscriber.php +++ b/Document/Subscriber/ArticleSubscriber.php @@ -29,8 +29,10 @@ use Sulu\Component\DocumentManager\Event\PublishEvent; use Sulu\Component\DocumentManager\Event\RemoveDraftEvent; use Sulu\Component\DocumentManager\Event\RemoveEvent; +use Sulu\Component\DocumentManager\Event\ReorderEvent; use Sulu\Component\DocumentManager\Event\UnpublishEvent; use Sulu\Component\DocumentManager\Events; +use Sulu\Component\Util\SortUtils; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -123,6 +125,7 @@ public static function getSubscribedEvents() ['publishChildren', 0], ['persistPageData', -2000], ], + Events::REORDER => [['persistPageDataOnReorder', -2000]], Events::UNPUBLISH => 'handleUnpublish', Events::REMOVE_DRAFT => [['handleScheduleIndex', -1024], ['removeDraftChildren', 0]], Events::FLUSH => [['handleFlush', -2048], ['handleFlushLive', -2048]], @@ -236,6 +239,24 @@ public function publishChildren(PublishEvent $event) } } + /** + * Persist page-data for reordering children. + * + * @param ReorderEvent $event + */ + public function persistPageDataOnReorder(ReorderEvent $event) + { + $document = $event->getDocument(); + if (!$document instanceof ArticlePageDocument) { + return; + } + + $document = $document->getParent(); + $node = $this->documentInspector->getNode($document); + + $this->setPageData($document, $node, $document->getLocale()); + } + /** * Persist page-data. * @@ -248,6 +269,18 @@ public function persistPageData($event) return; } + $this->setPageData($document, $event->getNode(), $event->getLocale()); + } + + /** + * Set page-data for given document on given node. + * + * @param ArticleDocument $document + * @param NodeInterface $node + * @param string $locale + */ + private function setPageData(ArticleDocument $document, NodeInterface $node, $locale) + { $pages = [ [ 'uuid' => $document->getUuid(), @@ -270,9 +303,11 @@ public function persistPageData($event) } } + $pages = SortUtils::multisort($pages, '[pageNumber]'); + $document->setPages($pages); - $event->getNode()->setProperty( - $this->propertyEncoder->localizedSystemName(self::PAGES_PROPERTY, $event->getLocale()), + $node->setProperty( + $this->propertyEncoder->localizedSystemName(self::PAGES_PROPERTY, $locale), json_encode($pages) ); } diff --git a/Document/Subscriber/PageSubscriber.php b/Document/Subscriber/PageSubscriber.php index 6253ca2de..f2ecee942 100644 --- a/Document/Subscriber/PageSubscriber.php +++ b/Document/Subscriber/PageSubscriber.php @@ -14,10 +14,12 @@ use Sulu\Bundle\ArticleBundle\Document\Behavior\PageBehavior; use Sulu\Component\DocumentManager\Behavior\Mapping\ChildrenBehavior; use Sulu\Component\DocumentManager\DocumentInspector; +use Sulu\Component\DocumentManager\DocumentManagerInterface; use Sulu\Component\DocumentManager\Event\HydrateEvent; use Sulu\Component\DocumentManager\Event\PersistEvent; use Sulu\Component\DocumentManager\Event\PublishEvent; use Sulu\Component\DocumentManager\Event\RemoveEvent; +use Sulu\Component\DocumentManager\Event\ReorderEvent; use Sulu\Component\DocumentManager\Event\RestoreEvent; use Sulu\Component\DocumentManager\Events; use Sulu\Component\DocumentManager\PropertyEncoder; @@ -39,15 +41,24 @@ class PageSubscriber implements EventSubscriberInterface * @var PropertyEncoder */ private $propertyEncoder; + /** + * @var DocumentManagerInterface + */ + private $documentManager; /** * @param DocumentInspector $documentInspector * @param PropertyEncoder $propertyEncoder + * @param DocumentManagerInterface $documentManager */ - public function __construct(DocumentInspector $documentInspector, PropertyEncoder $propertyEncoder) - { + public function __construct( + DocumentInspector $documentInspector, + PropertyEncoder $propertyEncoder, + DocumentManagerInterface $documentManager + ) { $this->documentInspector = $documentInspector; $this->propertyEncoder = $propertyEncoder; + $this->documentManager = $documentManager; } /** @@ -60,6 +71,7 @@ public static function getSubscribedEvents() Events::PERSIST => [['handlePersist', -1024]], Events::REMOVE => [['handleRemove', 5]], Events::PUBLISH => [['handlePublishPageNumber', -1024]], + Events::REORDER => [['handleReorder', 0]], Events::RESTORE => [['handleRestore', -1024]], ]; } @@ -115,6 +127,33 @@ public function handlePersist(PersistEvent $event) $document->setPageNumber($page); } + /** + * Adjust the page-numbers of siblings when reordering a page. + * + * @param ReorderEvent $event + */ + public function handleReorder(ReorderEvent $event) + { + $document = $event->getDocument(); + if (!$document instanceof PageBehavior) { + return; + } + + $propertyName = $this->propertyEncoder->systemName(static::FIELD); + $parentNode = $this->documentInspector->getNode($document->getParent()); + + $page = 1; + foreach ($parentNode->getNodes() as $childNode) { + $child = $this->documentManager->find($childNode->getIdentifier(), $event->getLocale()); + if (!$child instanceof PageBehavior) { + continue; + } + + $childNode->setProperty($propertyName, ++$page); + $child->setPageNumber($page); + } + } + /** * Copy page-number to live workspace. * diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 5b0b78763..f45e89ba9 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -125,7 +125,7 @@ class="Sulu\Bundle\ArticleBundle\Document\Subscriber\PageSubscriber"> - + diff --git a/Tests/Functional/Controller/ArticleControllerTest.php b/Tests/Functional/Controller/ArticleControllerTest.php index 2fd7334c3..d3ff4c7a9 100644 --- a/Tests/Functional/Controller/ArticleControllerTest.php +++ b/Tests/Functional/Controller/ArticleControllerTest.php @@ -74,6 +74,24 @@ protected function post($title = 'Test-Article', $template = 'default') return json_decode($client->getResponse()->getContent(), true); } + protected function postPage($article, $pageTitle = 'Test-Page') + { + $client = $this->createAuthenticatedClient(); + $client->request( + 'POST', + '/api/articles/' . $article['id'] . '/pages?locale=de', + [ + 'pageTitle' => $pageTitle, + 'template' => $article['template'], + 'authored' => '2016-01-01', + ] + ); + + $this->assertHttpStatusCode(200, $client->getResponse()); + + return json_decode($client->getResponse()->getContent(), true); + } + public function testPost($title = 'Test-Article', $template = 'default') { $response = $this->post($title, $template); @@ -1048,6 +1066,33 @@ public function testPostPageTreeRouteGenerateRemovePage() ); } + public function testOrderPages() + { + $article = $this->post(); + $pages = [ + $this->postPage($article, 'Page 1'), + $this->postPage($article, 'Page 2'), + $this->postPage($article, 'Page 3'), + ]; + $expectedPages = [$pages[1]['id'], $pages[2]['id'], $pages[0]['id']]; + + $client = $this->createAuthenticatedClient(); + $client->request( + 'POST', + '/api/articles/' . $article['id'] . '?action=order&locale=de', + ['pages' => $expectedPages] + ); + + $this->assertHttpStatusCode(200, $client->getResponse()); + $response = json_decode($client->getResponse()->getContent(), true); + + $responsePages = $response['_embedded']['pages']; + for ($i = 0; $i < count($expectedPages); ++$i) { + $this->assertEquals($expectedPages[$i], $responsePages[$i]['id']); + $this->assertEquals($i + 2, $responsePages[$i]['pageNumber']); + } + } + private function postPageTreeRoute($routePathData, $title = 'Test Article') { $client = $this->createAuthenticatedClient(); diff --git a/Tests/Unit/Document/Subscriber/ArticleSubscriberTest.php b/Tests/Unit/Document/Subscriber/ArticleSubscriberTest.php index 1b370c8bc..3c8e3e876 100644 --- a/Tests/Unit/Document/Subscriber/ArticleSubscriberTest.php +++ b/Tests/Unit/Document/Subscriber/ArticleSubscriberTest.php @@ -29,6 +29,7 @@ use Sulu\Component\DocumentManager\Event\PublishEvent; use Sulu\Component\DocumentManager\Event\RemoveDraftEvent; use Sulu\Component\DocumentManager\Event\RemoveEvent; +use Sulu\Component\DocumentManager\Event\ReorderEvent; class ArticleSubscriberTest extends \PHPUnit_Framework_TestCase { @@ -451,4 +452,56 @@ public function testPersistPageData() $this->articleSubscriber->persistPageData($event->reveal()); } + + public function testPersistPageDataOnReorder() + { + $node = $this->prophesize(NodeInterface::class); + + $orderedDocument = $this->prophesize(ArticlePageDocument::class); + $orderedDocument->getParent()->willReturn($this->document->reveal()); + $this->document->getLocale()->willReturn($this->locale); + $this->documentInspector->getNode($this->document->reveal())->willReturn($node->reveal()); + + $event = $this->prophesize(ReorderEvent::class); + $event->getDocument()->willReturn($orderedDocument->reveal()); + + $pages = [ + [ + 'uuid' => '123-123-123', + 'title' => 'Test article: page 1', + 'routePath' => '/test-article', + 'pageNumber' => 1, + ], + [ + 'uuid' => '321-321-321', + 'title' => 'Test article: page 2', + 'routePath' => '/test-article/page-2', + 'pageNumber' => 2, + ], + ]; + + $this->document->getUuid()->willReturn($pages[0]['uuid']); + $this->document->getPageTitle()->willReturn($pages[0]['title']); + $this->document->getRoutePath()->willReturn($pages[0]['routePath']); + $this->document->getPageNumber()->willReturn($pages[0]['pageNumber']); + + $child = $this->prophesize(ArticlePageDocument::class); + $child->getUuid()->willReturn($pages[1]['uuid']); + $child->getPageTitle()->willReturn($pages[1]['title']); + $child->getRoutePath()->willReturn($pages[1]['routePath']); + $child->getPageNumber()->willReturn($pages[1]['pageNumber']); + $this->document->getChildren()->willReturn(new \ArrayIterator([$child->reveal()])); + + $this->documentInspector->getLocalizationState($child->reveal())->willReturn(LocalizationState::LOCALIZED); + + $propertyName = 'i18n:' . $this->locale . '-' . ArticleSubscriber::PAGES_PROPERTY; + $this->propertyEncoder->localizedSystemName(ArticleSubscriber::PAGES_PROPERTY, $this->locale)->willReturn( + $propertyName + ); + + $this->document->setPages($pages)->shouldBeCalled(); + $node->setProperty($propertyName, json_encode($pages))->shouldBeCalled(); + + $this->articleSubscriber->persistPageDataOnReorder($event->reveal()); + } } diff --git a/Tests/Unit/Document/Subscriber/PageSubscriberTest.php b/Tests/Unit/Document/Subscriber/PageSubscriberTest.php index b450a90fa..6b09e7989 100644 --- a/Tests/Unit/Document/Subscriber/PageSubscriberTest.php +++ b/Tests/Unit/Document/Subscriber/PageSubscriberTest.php @@ -16,9 +16,11 @@ use Sulu\Bundle\ArticleBundle\Document\Subscriber\PageSubscriber; use Sulu\Component\DocumentManager\Behavior\Mapping\ChildrenBehavior; use Sulu\Component\DocumentManager\DocumentInspector; +use Sulu\Component\DocumentManager\DocumentManagerInterface; use Sulu\Component\DocumentManager\Event\PersistEvent; use Sulu\Component\DocumentManager\Event\PublishEvent; use Sulu\Component\DocumentManager\Event\RemoveEvent; +use Sulu\Component\DocumentManager\Event\ReorderEvent; use Sulu\Component\DocumentManager\Event\RestoreEvent; use Sulu\Component\DocumentManager\PropertyEncoder; @@ -34,6 +36,11 @@ class PageSubscriberTest extends \PHPUnit_Framework_TestCase */ private $propertyEncoder; + /** + * @var DocumentManagerInterface + */ + private $documentManager; + /** * @var PageSubscriber */ @@ -56,13 +63,13 @@ protected function setUp() { $this->documentInspector = $this->prophesize(DocumentInspector::class); $this->propertyEncoder = $this->prophesize(PropertyEncoder::class); + $this->documentManager = $this->prophesize(DocumentManagerInterface::class); $this->document = $this->prophesize(PageBehavior::class); $this->node = $this->prophesize(NodeInterface::class); $this->pageSubscriber = new PageSubscriber( - $this->documentInspector->reveal(), - $this->propertyEncoder->reveal() + $this->documentInspector->reveal(), $this->propertyEncoder->reveal(), $this->documentManager->reveal() ); } @@ -167,4 +174,57 @@ public function testHandleRestore() $this->pageSubscriber->handleRestore($event->reveal()); } + + public function testHandleReorder() + { + $parentDocument = $this->prophesize(ChildrenBehavior::class); + $this->document->getParent()->willReturn($parentDocument->reveal()); + + $nodes = [ + $this->prophesize(NodeInterface::class), + $this->prophesize(NodeInterface::class), + $this->prophesize(NodeInterface::class), + ]; + + $nodes[0]->getIdentifier()->willReturn('1-1-1'); + $nodes[1]->getIdentifier()->willReturn('1-2-2'); + $nodes[2]->getIdentifier()->willReturn('1-2-3'); + + $parentNode = $this->prophesize(NodeInterface::class); + $parentNode->getNodes()->willReturn( + array_map( + function ($item) { + return $item->reveal(); + }, + $nodes + ) + ); + + $this->documentInspector->getNode($parentDocument->reveal())->willReturn($parentNode->reveal()); + + $documents = [ + $this->prophesize(PageBehavior::class), + $this->prophesize(PageBehavior::class), + $this->prophesize(PageBehavior::class), + ]; + + $this->documentManager->find('1-1-1', 'de')->willReturn($documents[0]->reveal()); + $this->documentManager->find('1-2-2', 'de')->willReturn($documents[1]->reveal()); + $this->documentManager->find('1-2-3', 'de')->willReturn($documents[2]->reveal()); + + $event = $this->prophesize(ReorderEvent::class); + $event->getDocument()->willReturn($this->document->reveal()); + $event->getLocale()->willReturn('de'); + + $this->propertyEncoder->systemName(PageSubscriber::FIELD)->willReturn('sulu:' . PageSubscriber::FIELD); + + $documents[0]->setPageNumber(2)->shouldBeCalled(); + $nodes[0]->setProperty('sulu:' . PageSubscriber::FIELD, 2)->shouldBeCalled(); + $documents[1]->setPageNumber(3)->shouldBeCalled(); + $nodes[1]->setProperty('sulu:' . PageSubscriber::FIELD, 3)->shouldBeCalled(); + $documents[2]->setPageNumber(4)->shouldBeCalled(); + $nodes[2]->setProperty('sulu:' . PageSubscriber::FIELD, 4)->shouldBeCalled(); + + $this->pageSubscriber->handleReorder($event->reveal()); + } } diff --git a/Tests/app/Resources/articles/default.xml b/Tests/app/Resources/articles/default.xml index 2f5be13b7..27114ddda 100644 --- a/Tests/app/Resources/articles/default.xml +++ b/Tests/app/Resources/articles/default.xml @@ -13,5 +13,6 @@ +