From 62d6b421040b65b712623a7aa8a043e1ac921f40 Mon Sep 17 00:00:00 2001 From: Vincent Passama Date: Wed, 2 Dec 2015 15:58:23 +0100 Subject: [PATCH 1/7] link block with linetimetable --- Entity/Block.php | 165 ++++++++++++++++++++++-- Migrations/pdo_pgsql/Version016.php | 35 +++++ Resources/config/doctrine/Block.orm.yml | 16 +++ 3 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 Migrations/pdo_pgsql/Version016.php diff --git a/Entity/Block.php b/Entity/Block.php index 4da4cf0d..a10df16c 100644 --- a/Entity/Block.php +++ b/Entity/Block.php @@ -22,6 +22,21 @@ class Block extends AbstractEntity */ private $domId; + /** + * @var integer + */ + private $rank; + + /** + * @var string + */ + private $externalLineId; + + /** + * @var string + */ + private $externalRouteId; + /** * @var string */ @@ -37,6 +52,11 @@ class Block extends AbstractEntity */ private $stopTimetable; + /** + * @var Object + */ + private $lineTimetable; + /** * @var Object */ @@ -102,6 +122,101 @@ public function getDomId() return $this->domId; } + /** + * Set rank + * + * @param integer $rank + * @return Block + */ + public function setRank($rank) + { + $this->rank = $rank; + + return $this; + } + + /** + * Get rank + * + * @return integer + */ + public function getRank() + { + return $this->rank; + } + + /** + * Incrementing the block's rank. + * + * @param integer $nb + */ + public function incRank($nb = 1) + { + if ($nb >= 1) { + $this->rank += $nb; + } else { + $this->rank++; + } + } + + /** + * Decrementing the block's rank. + * + * @param integer $nb + */ + public function decRank($nb = 1) + { + if ($nb >= 1 && ($this->rank - $nb) > 0) { + $this->rank -= $nb; + } + } + + /** + * Set externalLineId + * + * @param string $externalLineId + * @return Block + */ + public function setExternalLineId($externalLineId) + { + $this->externalLineId = $externalLineId; + + return $this; + } + + /** + * Get externalLineId + * + * @return string + */ + public function getExternalLineId() + { + return $this->externalLineId; + } + + /** + * Set externalRouteId + * + * @param string $externalRouteId + * @return Block + */ + public function setExternalRouteId($externalRouteId) + { + $this->externalRouteId = $externalRouteId; + + return $this; + } + + /** + * Get externalRouteId + * + * @return string + */ + public function getExternalRouteId() + { + return $this->externalRouteId; + } + /** * Set content * @@ -148,6 +263,30 @@ public function getTitle() return $this->title; } + /** + * Get lineTimetable + * + * @return LineTimetable + */ + public function getLineTimetable() + { + return $this->lineTimetable; + } + + /** + * Set lineTimetable + * + * @param LineTimetable $lineTimetable + * + * @return Block + */ + public function setLineTimetable(LineTimetable $lineTimetable) + { + $this->lineTimetable = $lineTimetable; + + return $this; + } + /** * Set stopTimetable * @@ -172,6 +311,24 @@ public function getStopTimetable() return $this->stopTimetable; } + /** + * Get timetable + * + * @return LineTimetable or StopTimetable + */ + public function getTimetable() + { + if ($this->stopTimetable !== null) { + return $this->stopTimetable; + } + + if ($this->lineTimetable !== null) { + return $this->lineTimetable; + } + + return null; + } + /** * Set frequencies * @@ -229,14 +386,6 @@ public function isCalendar() return ($this->getTypeId() == BlockRepository::CALENDAR_TYPE); } - /** - * Getting the Timetable - */ - public function getTimetable() - { - return $this->stopTimetable; - } - /** * Checking the timetable is locked or not */ diff --git a/Migrations/pdo_pgsql/Version016.php b/Migrations/pdo_pgsql/Version016.php new file mode 100644 index 00000000..bc266bb4 --- /dev/null +++ b/Migrations/pdo_pgsql/Version016.php @@ -0,0 +1,35 @@ +addSql('ALTER TABLE mtt.block ADD COLUMN rank INTEGER default 0;'); + $this->addSql('ALTER TABLE mtt.block ADD COLUMN external_line_id VARCHAR(255);'); + $this->addSql('ALTER TABLE mtt.block ADD COLUMN external_route_id VARCHAR(255);'); + $this->addSql('ALTER TABLE mtt.block ADD COLUMN line_timetable_id INT REFERENCES mtt.line_timetable(id) NOT DEFERRABLE INITIALLY IMMEDIATE;'); + } + + public function down(Schema $schema) + { + $this->addSql('ALTER TABLE mtt.block DROP COLUMN rank;'); + $this->addSql('ALTER TABLE mtt.block DROP COLUMN external_line_id;'); + $this->addSql('ALTER TABLE mtt.block DROP COLUMN external_route_id;'); + $this->addSql('ALTER TABLE mtt.block DROP COLUMN line_timetable_id;'); + } + + public function getName() + { + return self::VERSION; + } +} diff --git a/Resources/config/doctrine/Block.orm.yml b/Resources/config/doctrine/Block.orm.yml index e09308c1..c5faa288 100644 --- a/Resources/config/doctrine/Block.orm.yml +++ b/Resources/config/doctrine/Block.orm.yml @@ -15,6 +15,17 @@ CanalTP\MttBundle\Entity\Block: type: string length: '128' column: dom_id + rank: + type: integer + column: rank + externalLineId: + type: string + length: 255 + column: external_line_id + externalRouteId: + type: string + length: 255 + column: external_route_id content: type: text nullable: true @@ -39,6 +50,11 @@ CanalTP\MttBundle\Entity\Block: joinColumn: name: stop_timetable_id referencedColumnName: id + lineTimetable: + targetEntity: LineTimetable + joinColumn: + name: line_timetable_id + referencedColumnName: id oneToMany: frequencies: targetEntity: Frequency From 7bf8f6e1f6270877b0a18023e35a4e9fee3ff8d1 Mon Sep 17 00:00:00 2001 From: Vincent Passama Date: Wed, 2 Dec 2015 16:42:59 +0100 Subject: [PATCH 2/7] change block attributes names --- Controller/BlockController.php | 4 +-- Entity/Block.php | 22 ++++++------- Form/Handler/Block/AbstractHandler.php | 2 +- Form/Type/BlockType.php | 4 +-- Migrations/pdo_pgsql/Version017.php | 32 +++++++++++++++++++ Resources/config/doctrine/Block.orm.yml | 4 +-- Services/CalendarManager.php | 2 +- Tests/DataFixtures/ORM/Fixture.php | 4 +-- .../Controller/TimetableControllerTest.php | 2 +- 9 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 Migrations/pdo_pgsql/Version017.php diff --git a/Controller/BlockController.php b/Controller/BlockController.php index efc404e4..de15a0e1 100644 --- a/Controller/BlockController.php +++ b/Controller/BlockController.php @@ -36,8 +36,8 @@ public function editAction( ); $data = array( - 'dom_id' => $domId, - 'type_id' => $blockType + 'domId' => $domId, + 'type' => $blockType ); $block = $stopTimetable->getBlockByDomId($domId); diff --git a/Entity/Block.php b/Entity/Block.php index a10df16c..0f4fb718 100644 --- a/Entity/Block.php +++ b/Entity/Block.php @@ -15,7 +15,7 @@ class Block extends AbstractEntity /** * @var string */ - private $typeId; + private $type; /** * @var string @@ -77,24 +77,24 @@ public function getId() } /** - * Get typeId + * Get type * * @return string */ - public function getTypeId() + public function getType() { - return $this->typeId; + return $this->type; } /** - * Set typeId + * Set type * - * @param string $typeId + * @param string $type * @return Block */ - public function setTypeId($typeId) + public function setType($type) { - $this->typeId = $typeId; + $this->type = $type; return $this; } @@ -363,7 +363,7 @@ public function getFrequencies() */ public function isImg() { - return ($this->getTypeId() == BlockRepository::IMG_TYPE); + return ($this->getType() == BlockRepository::IMG_TYPE); } /** @@ -373,7 +373,7 @@ public function isImg() */ public function isText() { - return ($this->getTypeId() == BlockRepository::TEXT_TYPE); + return ($this->getType() == BlockRepository::TEXT_TYPE); } /** @@ -383,7 +383,7 @@ public function isText() */ public function isCalendar() { - return ($this->getTypeId() == BlockRepository::CALENDAR_TYPE); + return ($this->getType() == BlockRepository::CALENDAR_TYPE); } /** diff --git a/Form/Handler/Block/AbstractHandler.php b/Form/Handler/Block/AbstractHandler.php index 9a366fc3..4f491393 100644 --- a/Form/Handler/Block/AbstractHandler.php +++ b/Form/Handler/Block/AbstractHandler.php @@ -16,7 +16,7 @@ protected function saveBlock(Block $formBlock, $timetable) $this->block->setTitle($formBlock->getTitle()); $this->block->setContent($formBlock->getContent()); - $this->block->setTypeId($formBlock->getTypeId()); + $this->block->setType($formBlock->getType()); $this->block->setDomId($formBlock->getDomId()); } $this->block->setStopTimetable($timetable); diff --git a/Form/Type/BlockType.php b/Form/Type/BlockType.php index 1edeefb9..f046a9ec 100644 --- a/Form/Type/BlockType.php +++ b/Form/Type/BlockType.php @@ -11,8 +11,8 @@ class BlockType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { - $builder->add('dom_id', 'hidden', array('data' => $options['data']['dom_id'])); - $builder->add('type_id', 'hidden', array('data' => $options['data']['type_id'])); + $builder->add('domId', 'hidden', array('data' => $options['data']['domId'])); + $builder->add('type', 'hidden', array('data' => $options['data']['type'])); $builder->addEventSubscriber(new SeasonLockedSubscriber()); } diff --git a/Migrations/pdo_pgsql/Version017.php b/Migrations/pdo_pgsql/Version017.php new file mode 100644 index 00000000..7b3d52fd --- /dev/null +++ b/Migrations/pdo_pgsql/Version017.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE mtt.block RENAME COLUMN type_id TO type;'); + $this->addSql('ALTER TABLE mtt.block ALTER column dom_id DROP NOT NULL;'); + } + + public function down(Schema $schema) + { + $this->addSql('ALTER TABLE mtt.block RENAME COLUMN type TO type_id;'); + $this->addSql('UPDATE mtt.block SET dom_id = \'\' WHERE dom_id IS NULL;'); + $this->addSql('ALTER TABLE mtt.block ALTER column dom_id SET NOT NULL;'); + } + + public function getName() + { + return self::VERSION; + } +} diff --git a/Resources/config/doctrine/Block.orm.yml b/Resources/config/doctrine/Block.orm.yml index c5faa288..b16b6c01 100644 --- a/Resources/config/doctrine/Block.orm.yml +++ b/Resources/config/doctrine/Block.orm.yml @@ -8,9 +8,9 @@ CanalTP\MttBundle\Entity\Block: id: true generator: strategy: IDENTITY - typeId: + type: type: string - column: type_id + column: type domId: type: string length: '128' diff --git a/Services/CalendarManager.php b/Services/CalendarManager.php index bec25038..b5c9c0c1 100644 --- a/Services/CalendarManager.php +++ b/Services/CalendarManager.php @@ -355,7 +355,7 @@ public function getCalendarsForStopPointAndStopTimetable( // calendar blocks are defined on route/stopTimetable level if (count($stopTimetable->getBlocks()) > 0) { foreach ($stopTimetable->getBlocks() as $block) { - if ($block->getTypeId() == 'calendar') { + if ($block->getType() == 'calendar') { $calendar = $this->findCalendar($block->getContent(), $calendarsSorted); $stopSchedulesData = $this->navitia->getCalendarStopSchedulesByRoute( $externalCoverageId, diff --git a/Tests/DataFixtures/ORM/Fixture.php b/Tests/DataFixtures/ORM/Fixture.php index 6e20db03..4be100f5 100644 --- a/Tests/DataFixtures/ORM/Fixture.php +++ b/Tests/DataFixtures/ORM/Fixture.php @@ -82,11 +82,11 @@ private function createStopPoint( return $stopPoint; } - private function createBlock(ObjectManager $em, $stopTimetable, $typeId = BlockRepository::TEXT_TYPE) + private function createBlock(ObjectManager $em, $stopTimetable, $type = BlockRepository::TEXT_TYPE) { $block = new Block(); $block->setStopTimetable($stopTimetable); - $block->setTypeId($typeId); + $block->setType($type); $block->setDomId('timegrid_block_1'); $block->setContent('test'); $block->setTitle('title'); diff --git a/Tests/Functional/Controller/TimetableControllerTest.php b/Tests/Functional/Controller/TimetableControllerTest.php index 4b4867d5..87a9e018 100644 --- a/Tests/Functional/Controller/TimetableControllerTest.php +++ b/Tests/Functional/Controller/TimetableControllerTest.php @@ -241,7 +241,7 @@ public function testCalendarExceptionsColors() $block->setTitle('Semaine scolaire'); $block->setContent('idcalendar2'); $block->setDomId('timegrid_block_2'); - $block->setTypeId('calendar'); + $block->setType('calendar'); $block->setStopTimetable($tt); $this->getEm()->persist($block); From a77bad19c3f984eb310a78209e2d5008d8373d37 Mon Sep 17 00:00:00 2001 From: Vincent Passama Date: Wed, 2 Dec 2015 17:24:40 +0100 Subject: [PATCH 3/7] add fixtures to load a default line template --- DataFixtures/ORM/FixtureLayout.php | 23 +++++++++++++++++++++++ DataFixtures/ORM/FixtureTemplate.php | 11 +++++++++++ 2 files changed, 34 insertions(+) diff --git a/DataFixtures/ORM/FixtureLayout.php b/DataFixtures/ORM/FixtureLayout.php index 475b5e18..351095d3 100644 --- a/DataFixtures/ORM/FixtureLayout.php +++ b/DataFixtures/ORM/FixtureLayout.php @@ -79,6 +79,18 @@ private function createLayouts() $this->attachToCustomerCtp($colorStopLayout); $this->addReference('color-stop-layout', $colorStopLayout); + $defaultLineLayout = $this->createLayout( + array( + 'label' => 'Template ligne par défaut', + 'previewPath' => '/bundles/canaltpmtt/img/default-line-layout.png', + 'orientation' => Layout::ORIENTATION_PORTRAIT, + 'notesModes' => array(LayoutConfig::NOTES_MODE_DISPATCHED), + 'cssVersion' => 1 + ) + ); + $this->attachToCustomerCtp($defaultLineLayout); + $this->addReference('default-line-layout', $defaultLineLayout); + $layoutConfig = $this->createLayoutConfig( array( 'label' => 'Template arrêt par défaut (exposant)', @@ -100,6 +112,17 @@ private function createLayouts() ), $colorStopLayout ); + + $layoutConfig = $this->createLayoutConfig( + array( + 'label' => 'Template ligne par défaut', + 'calendarStart' => 5, + 'calendarEnd' => 22, + 'notesMode' => 1, + 'notesType' => LayoutConfig::NOTES_TYPE_EXPONENT + ), + $defaultLineLayout + ); } public function load(ObjectManager $om) diff --git a/DataFixtures/ORM/FixtureTemplate.php b/DataFixtures/ORM/FixtureTemplate.php index b6ff89ab..004c5fa1 100644 --- a/DataFixtures/ORM/FixtureTemplate.php +++ b/DataFixtures/ORM/FixtureTemplate.php @@ -50,6 +50,17 @@ public function load(ObjectManager $om) ); $this->om->persist($colorStopLayout); + $defaultLineLayout = $this->getReference('default-line-layout'); + $defaultLineLayout->addTemplate( + $this->createTemplate( + array( + 'type' => 'line', + 'path' => 'default-line-layout.html.twig' + ) + ) + ); + $this->om->persist($defaultLineLayout); + $this->om->flush(); } From 567b461ee27a1b9a71c5ea51ca5d8674befd57e9 Mon Sep 17 00:00:00 2001 From: Vincent Passama Date: Wed, 9 Dec 2015 14:49:51 +0100 Subject: [PATCH 4/7] add line-timetable block edition. --- Controller/AbstractController.php | 28 +++ Controller/BlockController.php | 236 ++++++++++++++---- Controller/LineTimetableController.php | 188 +++++++++++++- Controller/StopTimetableController.php | 2 +- Entity/Block.php | 18 ++ Entity/LineTimetable.php | 13 + Entity/Timetable.php | 33 +++ .../EntityToIntTransformer.php | 61 +++++ Form/Handler/Block/AbstractHandler.php | 3 +- Form/Handler/Block/ImgHandler.php | 14 +- Form/Type/Block/BlockType.php | 97 +++++++ Form/Type/Block/CalendarType.php | 71 ++++-- Form/Type/BlockType.php | 3 +- MediaManager/Category/CategoryType.php | 1 + .../Category/Factory/CategoryFactory.php | 4 + MediaManager/Category/LineCategory.php | 17 ++ Resources/config/routing.yml | 43 +++- Resources/config/services.yml | 2 +- Resources/public/css/line.css | 169 +++++++++++++ Resources/public/img/ajax-loader.gif | Bin 0 -> 2608 bytes Resources/public/img/default-line-layout.png | Bin 0 -> 30902 bytes Resources/public/js/LineTimetable/edit.js | 108 ++++++++ Resources/public/js/layout.js | 110 +++++++- Resources/public/js/layout/validator.js | 4 +- Resources/translations/default.fr.yml | 3 + Resources/translations/messages.fr.yml | 14 ++ Resources/views/Block/form.html.twig | 27 ++ Resources/views/Block/get_form.html.twig | 3 + Resources/views/Layouts/base_layout.html.twig | 6 +- Resources/views/Layouts/common.html.twig | 11 +- .../Layouts/default-line-layout.html.twig | 82 ++++++ .../Layouts/default-stop-layout.html.twig | 23 +- Resources/views/Layouts/macros.html.twig | 117 +++++++-- .../LineTimetable/blockCalendar.html.twig | 7 + Resources/views/LineTimetable/list.html.twig | 6 + .../views/LineTimetable/schedule.html.twig | 2 +- Resources/views/base.html.twig | 4 +- Resources/views/config.js.html.twig | 3 +- Services/BlockManager.php | 115 +++++++++ Services/BlockTypeFactory.php | 8 +- Services/CalendarManager.php | 110 ++++++-- Services/LineTimetableManager.php | 7 +- Services/MediaManager.php | 88 +++++-- .../Controller/BlockControllerTest.php | 3 +- Twig/ScheduleExtension.php | 18 ++ 45 files changed, 1705 insertions(+), 177 deletions(-) create mode 100644 Form/DataTransformer/EntityToIntTransformer.php create mode 100644 Form/Type/Block/BlockType.php create mode 100644 MediaManager/Category/LineCategory.php create mode 100644 Resources/public/css/line.css create mode 100644 Resources/public/img/ajax-loader.gif create mode 100644 Resources/public/img/default-line-layout.png create mode 100644 Resources/public/js/LineTimetable/edit.js create mode 100644 Resources/views/Block/form.html.twig create mode 100644 Resources/views/Layouts/default-line-layout.html.twig create mode 100644 Resources/views/LineTimetable/blockCalendar.html.twig diff --git a/Controller/AbstractController.php b/Controller/AbstractController.php index fa5bea60..1462b09d 100644 --- a/Controller/AbstractController.php +++ b/Controller/AbstractController.php @@ -4,6 +4,8 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\JsonResponse; abstract class AbstractController extends Controller { @@ -14,6 +16,17 @@ protected function isGranted($businessId, $object = null) } } + /** + * Checking the request is POST ajax + * + * @param Request $request + */ + protected function isPostAjax(Request $request) + { + if (!($request->isXmlHttpRequest() && $request->isMethod('POST'))) + throw new AccessDeniedException(); + } + protected function addFlashIfSeasonLocked($season) { $isLocked = (!empty($season) && $season->isLocked()); @@ -50,4 +63,19 @@ protected function addFlashMessage($type, $message, $parameters = array()) ) ); } + + /** + * Preparing a JsonResponse + * + * @param array $data + * @param integer $statusCode + */ + protected function prepareJsonResponse($data = array(), $statusCode = JsonResponse::HTTP_STATUS_OK) + { + $response = new JsonResponse(); + $response->setData($data); + $response->setStatusCode($statusCode); + + return $response; + } } diff --git a/Controller/BlockController.php b/Controller/BlockController.php index de15a0e1..a5496cf2 100644 --- a/Controller/BlockController.php +++ b/Controller/BlockController.php @@ -2,8 +2,13 @@ namespace CanalTP\MttBundle\Controller; +use Symfony\Component\HttpFoundation\Request; use CanalTP\MttBundle\Entity\Block; use CanalTP\MttBundle\Entity\BlockRepository; +use CanalTP\MttBundle\Entity\Timetable; +use CanalTP\MttBundle\Entity\LineTimetable; +use CanalTP\MttBundle\Entity\StopTimetable; +use CanalTP\MttBundle\Form\Type\Block\BlockType; class BlockController extends AbstractController { @@ -12,40 +17,129 @@ class BlockController extends AbstractController * * @param string $externalNetworkId * @param integer $timetableId - * @param string $blockType - * @param string $domId + * @param string $type + * @param integer $rank + */ + public function addAction(Request $request, $externalNetworkId, $timetableId, $type, $rank) + { + $this->isGranted( + array( + 'BUSINESS_MANAGE_LINE_TIMETABLE', + 'BUSINESS_MANAGE_STOP_TIMETABLE' + ) + ); + + $blockManager = $this->get('canal_tp_mtt.block_manager'); + $timetableManager = $this->get(Timetable::$managers[$type]); + $timetable = $timetableManager->find($timetableId); + + $data = array( + 'rank' => $rank, + 'type' => $type, + 'domId' => null + ); + + $block = $blockManager->findOrCreate(-1, $timetable, $data); + + $form = $this->createForm( + new BlockType(), + $block, + array( + 'action' => $this->generateUrl( + 'canal_tp_mtt_block_add', + array( + 'externalNetworkId' => $externalNetworkId, + 'timetableId' => $timetableId, + 'type' => $type, + 'rank' => $rank + ) + ), + 'em' => $this->get('doctrine.orm.entity_manager') + ) + ); + + $form->handleRequest($request); + if ($form->isValid()) { + $blockManager = $this->get('canal_tp_mtt.block_manager'); + $block = $form->getData(); + $blockManager->save($block, $form['number']->getData()); + + if ($timetable instanceof StopTimetable) { + return $this->redirect( + $this->generateUrl( + 'canal_tp_mtt_stop_timetable_edit', + array( + 'externalNetworkId' => $externalNetworkId, + 'seasonId' => $timetable->getLineConfig()->getSeason()->getId(), + 'externalLineId' => $timetable->getLineConfig()->getExternalLineId(), + 'externalRouteId' => $timetable->getExternalRouteId() + ) + ) + ); + } elseif ($timetable instanceof LineTimetable) { + return $this->redirect( + $this->generateUrl( + 'canal_tp_mtt_line_timetable_render', + array( + 'externalNetworkId' => $externalNetworkId, + 'seasonId' => $timetable->getLineConfig()->getSeason()->getId(), + 'externalLineId' => $timetable->getLineConfig()->getExternalLineId(), + 'mode' => 'edit' + ) + ) + ); + } + } + + return $this->render( + 'CanalTPMttBundle:Block:form.html.twig', + array( + 'form' => $form->createView(), + ) + ); + } + + /** + * returns form for a given block type + * or save content of the block using Form factory */ public function editAction( + Request $request, $externalNetworkId, $timetableId, + $type, + $blockId, $blockType, - $domId - ) - { + $domId, + $rank + ) { + $this->isGranted( + array( + 'BUSINESS_MANAGE_LINE_TIMETABLE', + 'BUSINESS_MANAGE_STOP_TIMETABLE' + ) + ); + + $timetableManager = $this->get(Timetable::$managers[$type]); + $timetable = $timetableManager->find($timetableId); + $blockTypeFactory = $this->get('canal_tp_mtt.form.factory.block'); - $stopTimetableManager = $this->get('canal_tp_mtt.stop_timetable_manager'); + $blockManager = $this->get('canal_tp_mtt.block_manager'); + $perimeterManager = $this->get('nmm.perimeter_manager'); $perimeter = $perimeterManager->findOneByExternalNetworkId( $this->getUser()->getCustomer(), $externalNetworkId ); - $stopTimetable = $stopTimetableManager->getStopTimetableById( - $timetableId, - $perimeter->getExternalCoverageId() - ); $data = array( - 'domId' => $domId, - 'type' => $blockType + 'type' => $blockType, + 'rank' => $rank, + 'domId' => $domId ); - $block = $stopTimetable->getBlockByDomId($domId); - - if (empty($block)) { - $block = new Block(); - $block->setStopTimetable($stopTimetable); - } + $block = $blockManager->findOrCreate($blockId, $timetable, $data); $blockTypeFactory->init( $blockType, @@ -57,42 +151,83 @@ public function editAction( $form = $blockTypeFactory->buildForm() ->setAction($this->getRequest()->getRequestUri()) ->setMethod('POST')->getForm(); - $form->handleRequest($this->getRequest()); + + $form->handleRequest($request); + if ($form->isValid()) { - $blockTypeFactory->buildHandler()->process($form->getData(), $stopTimetable); + $blockTypeFactory->buildHandler()->process($form->getData(), $timetable); - return $this->redirect( - $this->generateUrl( + if ($timetable instanceof StopTimetable) { + return $this->redirectToRoute( 'canal_tp_mtt_stop_timetable_edit', array( - 'externalNetworkId' => $externalNetworkId, - 'seasonId' => $stopTimetable->getLineConfig()->getSeason()->getId(), - 'externalLineId' => $stopTimetable->getLineConfig()->getExternalLineId(), - 'externalRouteId' => $stopTimetable->getExternalRouteId() + 'externalNetworkId' => $externalNetworkId, + 'seasonId' => $timetable->getLineConfig()->getSeason()->getId(), + 'externalLineId' => $timetable->getLineConfig()->getExternalLineId(), + 'externalRouteId' => $timetable->getExternalRouteId() ) - ) - ); + ); + } elseif ($timetable instanceof LineTimetable) { + return $this->redirectToRoute( + 'canal_tp_mtt_line_timetable_render', + array( + 'externalNetworkId' => $externalNetworkId, + 'seasonId' => $timetable->getLineConfig()->getSeason()->getId(), + 'externalLineId' => $timetable->getLineConfig()->getExternalLineId(), + 'mode' => 'edit' + ) + ); + } } return $this->render( 'CanalTPMttBundle:Block:get_form.html.twig', array( - 'form' => $form->createView(), + 'form' => $form->createView(), ) ); } - public function deleteAction($timetableId, $blockId, $externalNetworkId) - { + /** + * Deleting a block from a Timetable + * + * @param string $externalNetworkId + * @param integer $timetableId + * @param string $type + * @param integer $blockId + */ + public function deleteAction( + $externalNetworkId, + $timetableId, + $type, + $blockId + ) { + $this->isGranted( + array( + 'BUSINESS_MANAGE_LINE_TIMETABLE', + 'BUSINESS_MANAGE_STOP_TIMETABLE' + ) + ); + $perimeterManager = $this->get('nmm.perimeter_manager'); $perimeter = $perimeterManager->findOneByExternalNetworkId( $this->getUser()->getCustomer(), $externalNetworkId ); - $stopTimetableManager = $this->get('canal_tp_mtt.stop_timetable_manager'); - $repo = $this->getDoctrine()->getRepository('CanalTPMttBundle:Block'); - $block = $repo->find($blockId); + $timetableManager = $this->get(Timetable::$managers[$type]); + $timetable = $timetableManager->find($timetableId); + + if ($timetable->getLineConfig()->getSeason()->getPerimeter() !== $perimeter) { + throw new Exception('This timetable is not accessible via the network: ' . $externalNetworkId); + } + + $block = $timetable->getBlockById($blockId); + + if (empty($block)) { + throw new Exception('The block ' . $blockId . ' is not linked to Timetable ' . $timetableId); + } + if (!$block) { throw $this->createNotFoundException( $this->get('translator')->trans( @@ -101,24 +236,29 @@ public function deleteAction($timetableId, $blockId, $externalNetworkId) ) ); } else { - $this->getDoctrine()->getEntityManager()->remove($block); - $this->getDoctrine()->getEntityManager()->flush(); + $this->get('canal_tp_mtt.block_manager')->delete($block); } - $stopTimetable = $stopTimetableManager->getStopTimetableById( - $timetableId, - $perimeter->getExternalCoverageId() - ); - return $this->redirect( - $this->generateUrl( + if ($timetable instanceof StopTimetable) { + return $this->redirectToRoute( 'canal_tp_mtt_stop_timetable_edit', array( - 'externalNetworkId' => $externalNetworkId, - 'seasonId' => $stopTimetable->getLineConfig()->getSeason()->getId(), - 'externalLineId' => $stopTimetable->getLineConfig()->getExternalLineId(), - 'externalRouteId' => $stopTimetable->getExternalRouteId() + 'externalNetworkId' => $externalNetworkId, + 'seasonId' => $timetable->getLineConfig()->getSeason()->getId(), + 'externalLineId' => $timetable->getLineConfig()->getExternalLineId(), + 'externalRouteId' => $timetable->getExternalRouteId() ) - ) - ); + ); + } elseif ($timetable instanceof LineTimetable) { + return $this->redirectToRoute( + 'canal_tp_mtt_line_timetable_render', + array( + 'externalNetworkId' => $externalNetworkId, + 'seasonId' => $timetable->getLineConfig()->getSeason()->getId(), + 'externalLineId' => $timetable->getLineConfig()->getExternalLineId(), + 'mode' => 'edit' + ) + ); + } } } diff --git a/Controller/LineTimetableController.php b/Controller/LineTimetableController.php index 5eea872c..f96e9189 100644 --- a/Controller/LineTimetableController.php +++ b/Controller/LineTimetableController.php @@ -4,10 +4,16 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\JsonResponse; use CanalTP\MttBundle\Entity\Template; +use CanalTP\MttBundle\Entity\BlockRepository; class LineTimetableController extends AbstractController { + const EDIT_MODE = 'edit'; + const VIEW_MODE = 'view'; + const PRINT_MODE = 'print'; + /** * Listing all available lines and displaying a LineTimetable configuration for selected one * If no externalLineId is provided, selecting first line found in Navitia by default @@ -40,12 +46,6 @@ public function listAction($externalNetworkId, $externalLineId = null, $seasonId ); } - $externalLineData = $navitia->getLine( - $perimeter->getExternalCoverageId(), - $externalNetworkId, - $externalLineId - ); - $lineConfig = $this->getDoctrine() ->getRepository('CanalTPMttBundle:LineConfig') ->findOneBy( @@ -60,6 +60,12 @@ public function listAction($externalNetworkId, $externalLineId = null, $seasonId ->findOrCreateLineTimetable($lineConfig); } + $externalLineData = $navitia->getLine( + $perimeter->getExternalCoverageId(), + $externalNetworkId, + $externalLineId + ); + return $this->render( 'CanalTPMttBundle:LineTimetable:list.html.twig', array( @@ -73,6 +79,90 @@ public function listAction($externalNetworkId, $externalLineId = null, $seasonId ); } + /** + * Rendering a layout + * + * @param string $externalNetworkId + * @param string $externalLineId + * @param string $mode + */ + public function renderAction($externalNetworkId, $externalLineId, $seasonId, $mode) + { + $this->isGranted('BUSINESS_MANAGE_LINE_TIMETABLE'); + + $perimeter = $this->get('nmm.perimeter_manager')->findOneByExternalNetworkId( + $this->getUser()->getCustomer(), + $externalNetworkId + ); + + $seasons = $this->get('canal_tp_mtt.season_manager')->findByPerimeter($perimeter); + $currentSeason = $this->get('canal_tp_mtt.season_manager')->getSelected($seasonId, $seasons); + $this->addFlashIfSeasonLocked($currentSeason); + + $lineConfig = $this->getDoctrine() + ->getRepository('CanalTPMttBundle:LineConfig') + ->findOneBy( + array( + 'externalLineId' => $externalLineId, + 'season' => $currentSeason + ) + ); + + if (!empty($lineConfig)) { + $lineTimetable = $this->get('canal_tp_mtt.line_timetable_manager') + ->findOrCreateLineTimetable($lineConfig); + } + + if (empty($lineTimetable)) { + $this->addFlashMessage('danger', 'error.line_timetable.not_found'); + $this->redirect($request->headers->get('referer')); + } + + // Checking the associated Layout has a Template of type LINE_TYPE before rendering it + if (!$lineTimetable->getLineConfig()->getLayoutConfig()->getLayout()->getTemplate(Template::LINE_TYPE)) { + $this->addFlashMessage('danger', 'error.template.not_found', array('%type%' => Template::LINE_TYPE)); + return $this->redirect($request->headers->get('referer')); + } + + $navitia = $this->get('canal_tp_mtt.navitia'); + + $externalCoverageId = $perimeter->getExternalCoverageId(); + + $line = $navitia->getLine( + $externalCoverageId, + $externalNetworkId, + $externalLineId + ); + + $routes = $navitia->getLineRoutes( + $externalCoverageId, + $externalNetworkId, + $externalLineId + ); + + //setlocale(LC_TIME, "fr_FR.UTF8"); => if needed later, set a parameter in container + $layoutId = $lineTimetable->getLineConfig()->getLayoutConfig()->getLayout()->getId(); + $templateFile = $lineTimetable->getLineConfig()->getLayoutConfig()->getLayout()->getTemplate(Template::LINE_TYPE)->getPath(); + + return $this->render( + 'CanalTPMttBundle:Layouts:' . $templateFile, + array( + 'pageTitle' => 'line_timetable.title.' . $mode, + 'timetable' => $lineTimetable, + 'externalNetworkId' => $externalNetworkId, + 'externalLineId' => $externalLineId, + 'currentSeason' => $currentSeason, + 'orientation' => $lineTimetable->getLineConfig()->getLayoutConfig()->getLayout()->getOrientationAsString(), + 'line' => $line, + 'routes' => $routes, + 'mode' => $mode, + 'templatePath' => '@CanalTPMtt/Layouts/uploads/' . $layoutId . '/', + 'imgPath' => 'bundles/canaltpmtt/img/uploads/' . $layoutId . '/', + 'cssPath' => 'bundles/canaltpmtt/css/uploads/' . $layoutId . '/' + ) + ); + } + /** * Selecting stops displayed in the LineTimetable. * @@ -213,4 +303,90 @@ public function showScheduleAction($externalNetworkId, $externalLineId) ) ); } + + /** + * Loading a calendar (Ajax request) + * + * @param string $externalNetworkId + * @param integer $blockId + * @param integer $columnsLimit + * + */ + public function loadCalendarAction(Request $request, $externalNetworkId, $blockId, $columnsLimit) + { + $this->isGranted('BUSINESS_MANAGE_LINE_TIMETABLE'); + + $this->isPostAjax($request); + + $perimeter = $this->get('nmm.perimeter_manager')->findOneByExternalNetworkId( + $this->getUser()->getCustomer(), + $externalNetworkId + ); + + $block = $this->get('canal_tp_mtt.block_manager')->findBlock($blockId); + $timetable = $block->getLineTimetable(); + + // checking that the provided block is a calendar with data and is accessible via the network + if (empty($timetable)) { + return $this->prepareJsonResponse( + $this->get('translator')->trans( + 'line_timetable.error.block_not_linked', + array('%blockId%' => $blockId) + ), + JsonResponse::HTTP_UNPROCESSABLE_ENTITY + ); + } + if ($timetable->getLineConfig()->getSeason()->getPerimeter() != $perimeter) { + return $this->prepareJsonResponse( + $this->get('translator')->trans( + 'line_timetable.error.bad_external_network', + array('%externalNetworkId%' => $externalNetworkId) + ), + JsonResponse::HTTP_UNAUTHORIZED + ); + } + if ($block->getContent() == null) { + return $this->prepareJsonResponse( + $this->get('translator')->trans( + 'line_timetable.error.block_empty', + array('%blockId%' => $blockId) + ), + JsonResponse::HTTP_UNPROCESSABLE_ENTITY + ); + } + if ($block->getType() !== BlockRepository::CALENDAR_TYPE) { + return $this->prepareJsonResponse( + $this->get('translator')->trans( + 'line_timetable.error.block_not_calendar', + array('%blockId%' => $blockId) + ), + JsonResponse::HTTP_UNPROCESSABLE_ENTITY + ); + } + + $parameters = array(); + + $selectedStopPoints = $block->getLineTimetable()->getSelectedStopPointsByRoute($block->getExternalRouteId()); + + if (!$selectedStopPoints->isEmpty()) { + $parameters['stopPoints'] = $selectedStopPoints; + } + + $schedule = $this->get('canal_tp_mtt.calendar_manager')->getCalendarForBlock( + $perimeter->getExternalCoverageId(), + $block, + $parameters + ); + + $layoutId = $block->getLineTimetable()->getLineConfig()->getLayoutConfig()->getLayout()->getId(); + + return $this->render( + 'CanalTPMttBundle:LineTimetable:blockCalendar.html.twig', + array( + 'templatePath' => '@CanalTPMtt/Layouts/uploads/' . $layoutId . '/', + 'schedule' => $schedule, + 'columnsLimit' => $columnsLimit + ) + ); + } } diff --git a/Controller/StopTimetableController.php b/Controller/StopTimetableController.php index ad19ae4f..77889d9c 100644 --- a/Controller/StopTimetableController.php +++ b/Controller/StopTimetableController.php @@ -91,7 +91,7 @@ private function renderLayout(Request $request, StopTimetable $stopTimetable, $e 'layout' => $stopTimetable->getLineConfig()->getLayoutConfig(), 'editable' => $editable, 'displayMenu' => $displayMenu, - 'templatePath' => '@CanalTPMtt/Layouts/uploads/' . $stopTimetable->getLineConfig()->getLayoutConfig()->getLayout()->getId() . '/', + 'templatePath' => '@CanalTPMtt/Layouts/uploads/' . $layoutId . '/', 'imgPath' => 'bundles/canaltpmtt/img/uploads/' . $layoutId . '/', 'cssPath' => 'bundles/canaltpmtt/css/uploads/' . $layoutId . '/', 'externalStopPointId' => $externalStopPointId diff --git a/Entity/Block.php b/Entity/Block.php index 0f4fb718..408fcc82 100644 --- a/Entity/Block.php +++ b/Entity/Block.php @@ -311,6 +311,24 @@ public function getStopTimetable() return $this->stopTimetable; } + /** + * Set timetable + * + * @param Timetable + */ + public function setTimetable(Timetable $timetable) + { + if ($timetable instanceof LineTimetable) { + $this->lineTimetable = $timetable; + } elseif ($timetable instanceof StopTimetable) { + $this->stopTimetable = $timetable; + } else { + throw new \Exception('Timetable object is not StopTimetable nor LineTimetable instance'); + } + + return $this; + } + /** * Get timetable * diff --git a/Entity/LineTimetable.php b/Entity/LineTimetable.php index f53770ee..4724d7a8 100644 --- a/Entity/LineTimetable.php +++ b/Entity/LineTimetable.php @@ -123,4 +123,17 @@ public function getSelectedStopPointsByRoute($externalRouteId) return $this->selectedStopPoints->matching($criteria); } + + /** + * Has templateOfType + * + * @param $type + * @return boolean + */ + public function hasTemplateOfType($type) + { + $template = $this->lineConfig->getLayoutConfig()->getLayout()->getTemplate($type); + + return ($template !== null); + } } diff --git a/Entity/Timetable.php b/Entity/Timetable.php index 0ab67fa1..81d4497f 100644 --- a/Entity/Timetable.php +++ b/Entity/Timetable.php @@ -94,6 +94,39 @@ public function getBlockByDomId($domId) return $blocks->isEmpty() ? null : $blocks->first(); } + /** + * Get block by id + * + * @param integer $blockId + * @return Block|null + */ + public function getBlockById($blockId) + { + $criteria = Criteria::create() + ->where(Criteria::expr()->eq('id', $blockId)) + ->setMaxResults(1) + ; + + $blocks = $this->blocks->matching($criteria); + + return $blocks->isEmpty() ? null : $blocks->first(); + } + + /** + * Get ranked blocks + * + * @return Collection + */ + public function getRankedBlocks() + { + $criteria = Criteria::create() + ->where(Criteria::expr()->gt('rank', 0)) + ->orderBy(array('rank' => Criteria::ASC)) + ; + + return $this->blocks->matching($criteria); + } + /** * Is locked * diff --git a/Form/DataTransformer/EntityToIntTransformer.php b/Form/DataTransformer/EntityToIntTransformer.php new file mode 100644 index 00000000..8998e165 --- /dev/null +++ b/Form/DataTransformer/EntityToIntTransformer.php @@ -0,0 +1,61 @@ +om = $om; + $this->class = $class; + } + + /** + * @param mixed $entity + * + * @return integer + */ + public function transform($entity) + { + if (null === $entity || !($entity instanceof $this->class)) { + return null; + } + + return $entity->getId(); + } + + /** + * @param mixed $id + * @throws \Symfony\Component\Form\Exception\TransformationFailedException + * @return mixed|object + */ + public function reverseTransform($identifier) + { + if (!$identifier) { + return null; + } + + $entity = $this->om->getRepository($this->class)->find($identifier); + + if ($entity === null) { + throw new TransformationFailedException(sprintf( + '%s with id %s not found', + $this->class, + $identifier + )); + } + + return $entity; + } +} diff --git a/Form/Handler/Block/AbstractHandler.php b/Form/Handler/Block/AbstractHandler.php index 4f491393..69713c6e 100644 --- a/Form/Handler/Block/AbstractHandler.php +++ b/Form/Handler/Block/AbstractHandler.php @@ -18,8 +18,9 @@ protected function saveBlock(Block $formBlock, $timetable) $this->block->setContent($formBlock->getContent()); $this->block->setType($formBlock->getType()); $this->block->setDomId($formBlock->getDomId()); + $this->block->setRank($formBlock->getRank()); } - $this->block->setStopTimetable($timetable); + $this->block->setTimetable($timetable); $this->om->persist($this->block); $this->om->flush(); diff --git a/Form/Handler/Block/ImgHandler.php b/Form/Handler/Block/ImgHandler.php index 77558aab..55653b00 100644 --- a/Form/Handler/Block/ImgHandler.php +++ b/Form/Handler/Block/ImgHandler.php @@ -6,11 +6,10 @@ use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\Filesystem\Filesystem; use Doctrine\Common\Persistence\ObjectManager; - use CanalTP\MttBundle\Services\MediaManager; use CanalTP\MediaManagerBundle\Entity\Media; use CanalTP\MttBundle\Entity\Block; -use CanalTP\MttBundle\Form\TYPE\Block\ImgType; +use CanalTP\MttBundle\Form\Type\Block\ImgType; class ImgHandler extends AbstractHandler { @@ -24,8 +23,7 @@ public function __construct( MediaManager $mediaManager, $block, $lastImgPath - ) - { + ) { $this->co = $co; $this->om = $om; $this->mediaManager = $mediaManager; @@ -55,11 +53,11 @@ public function process(Block $formBlock, $timetable) imagepng($output, $file->getRealPath() . '.png'); imagedestroy($output); imagedestroy($input); - $pngFile = new File($file->getRealPath() . '.png'); - $media = $this->mediaManager->saveByStopTimetable($timetable, $pngFile, $this->block->getDomId()); - } else { - $media = $this->mediaManager->saveByStopTimetable($timetable, $file, $this->block->getDomId()); + $file = new File($file->getRealPath() . '.png'); } + + $media = $this->mediaManager->saveByTimetable($timetable, $file, $this->block->getDomId()); + // TODO: saved with domain, we should store without it. Waiting for mediaDataCollector to be updated $formBlock->setContent($this->mediaManager->getUrlByMedia($media) . '?' . time()); $this->saveBlock($formBlock, $timetable); diff --git a/Form/Type/Block/BlockType.php b/Form/Type/Block/BlockType.php new file mode 100644 index 00000000..b0e9b7c6 --- /dev/null +++ b/Form/Type/Block/BlockType.php @@ -0,0 +1,97 @@ +getData()->getTimetable(); + + if (empty($timetable)) { + throw new \Exception('The block should have a timetable object linked to it'); + } + + $entityTransformer = new EntityToIntTransformer( + $options["em"], + get_class($timetable) + ); + + $builder->add( + $builder->create( + 'timetable', + 'hidden' + )->addModelTransformer($entityTransformer) + ); + + $builder + ->add( + 'type', + 'choice', + array( + 'label' => 'block.labels.type', + 'choices' => array( + BlockRepository::CALENDAR_TYPE => 'block.'.BlockRepository::CALENDAR_TYPE.'.labels.content', + BlockRepository::TEXT_TYPE => 'block.'.BlockRepository::TEXT_TYPE.'.labels.content' + ), + 'required' => true + ) + ) + ; + $builder->add( + 'rank', + 'hidden' + ); + $builder->add( + 'number', + 'choice', + array( + 'label' => 'block.labels.number', + 'mapped' => false, + 'choices' => array(1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5) + ) + ); + $builder->add( + 'externalLineId', + 'hidden' + ); + + $builder->addEventSubscriber(new SeasonLockedSubscriber()); + } + + /** + * @param OptionsResolverInterface $resolver + */ + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver + ->setDefaults(array( + 'data_class' => 'CanalTP\MttBundle\Entity\Block' + )) + ->setRequired(array( + 'em', + )) + ->setAllowedTypes(array( + 'em' => 'Doctrine\Common\Persistence\ObjectManager', + )); + } + + /** + * @return string + */ + public function getName() + { + return 'mtt_block'; + } +} diff --git a/Form/Type/Block/CalendarType.php b/Form/Type/Block/CalendarType.php index 9fa37968..48077627 100644 --- a/Form/Type/Block/CalendarType.php +++ b/Form/Type/Block/CalendarType.php @@ -6,8 +6,9 @@ use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormError; use Symfony\Component\Validator\Constraints\NotBlank; - use CanalTP\MttBundle\Form\Type\BlockType; +use CanalTP\MttBundle\Entity\LineTimetable; +use CanalTP\MttBundle\Entity\StopTimetable; class CalendarType extends BlockType { @@ -15,38 +16,76 @@ class CalendarType extends BlockType private $externalCoverageId = null; private $blockInstance = null; private $choices = null; + private $navitia = null; + private $routeList = null; - public function __construct($calendarManager, $instance, $externalCoverageId) + public function __construct($calendarManager, $instance, $externalCoverageId, $navitia) { $this->calendarManager = $calendarManager; $this->blockInstance = $instance; $this->externalCoverageId = $externalCoverageId; + $this->navitia = $navitia; } public function buildForm(FormBuilderInterface $builder, array $options) { - $season = $this->blockInstance->getStopTimetable()->getLineConfig()->getSeason(); - $calendars = $this->calendarManager->getCalendarsForRoute( - $this->externalCoverageId, - $this->blockInstance->getStopTimetable()->getExternalRouteId(), - $season->getStartDate(), - $season->getEndDate() - ); + $season = $this->blockInstance->getTimetable()->getLineConfig()->getSeason(); + + if ($this->blockInstance->getTimetable() instanceof StopTimetable) { + $calendars = $this->calendarManager->getCalendarsForRoute( + $this->externalCoverageId, + $this->blockInstance->getStopTimetable()->getExternalRouteId(), + $season->getStartDate(), + $season->getEndDate() + ); + } elseif ($this->blockInstance->getTimetable() instanceof LineTimetable) { + $calendars = $this->navitia->getLineCalendars( + $this->externalCoverageId, + $this->blockInstance->getLineTimetable()->getLineConfig()->getSeason()->getPerimeter()->getExternalNetworkId(), + $this->blockInstance->getExternalLineId() + ); + + $routes = $this->navitia->getLineRoutes( + $this->externalCoverageId, + $this->blockInstance->getLineTimetable()->getLineConfig()->getSeason()->getPerimeter()->getExternalNetworkId(), + $this->blockInstance->getExternalLineId() + ); + + $this->routeList = $this->getChoices($routes); + ; + + $builder + ->add( + 'externalRouteId', + 'choice', + array( + 'choices' => $this->routeList, + 'disabled' => $this->isDisabled($this->routeList), + 'label' => 'block.calendar.labels.route', + 'constraints' => array( + new NotBlank() + ) + ) + ); + } else { + throw new \Exception('Bad Timetable object'); + } + $this->choices = $this->getChoices($calendars); $builder ->add( 'title', 'text', - array('label' => 'block.calendar.labels.title',) + array('label' => 'block.calendar.labels.title', ) ) ->add( 'content', 'choice', array( - 'choices' => $this->choices, - 'disabled' => $this->isDisabled(), - 'label' => 'block.calendar.labels.content', + 'choices' => $this->choices, + 'disabled' => $this->isDisabled(), + 'label' => 'block.calendar.labels.content', 'attr' => array( // attribute to tell javascript to fill automatically title // when a change occurs on this field @@ -66,11 +105,11 @@ private function isDisabled() return (count($this->choices) == 1 && $this->blockInstance->getContent() != null); } - private function getChoices($calendars) + private function getChoices($list) { $choices = array(); - foreach ($calendars as $calendar) { - $choices[$calendar->id] = $calendar->name; + foreach ($list as $item) { + $choices[$item->id] = $item->name; } return $choices; diff --git a/Form/Type/BlockType.php b/Form/Type/BlockType.php index f046a9ec..f6473fb1 100644 --- a/Form/Type/BlockType.php +++ b/Form/Type/BlockType.php @@ -11,8 +11,9 @@ class BlockType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { - $builder->add('domId', 'hidden', array('data' => $options['data']['domId'])); + $builder->add('rank', 'hidden', array('data' => $options['data']['rank'])); $builder->add('type', 'hidden', array('data' => $options['data']['type'])); + $builder->add('domId', 'hidden', array('data' => $options['data']['domId'])); $builder->addEventSubscriber(new SeasonLockedSubscriber()); } diff --git a/MediaManager/Category/CategoryType.php b/MediaManager/Category/CategoryType.php index 096c7c01..338cedd4 100644 --- a/MediaManager/Category/CategoryType.php +++ b/MediaManager/Category/CategoryType.php @@ -7,5 +7,6 @@ class CategoryType const NETWORK = 'network'; const ROUTE = 'route'; const SEASON = 'season'; + const LINE = 'line'; const STOP_POINT = 'stop_point'; } diff --git a/MediaManager/Category/Factory/CategoryFactory.php b/MediaManager/Category/Factory/CategoryFactory.php index 151e04aa..2d4384c5 100644 --- a/MediaManager/Category/Factory/CategoryFactory.php +++ b/MediaManager/Category/Factory/CategoryFactory.php @@ -6,6 +6,7 @@ use CanalTP\MttBundle\MediaManager\Category\CategoryType; use CanalTP\MttBundle\MediaManager\Category\NetworkCategory; use CanalTP\MttBundle\MediaManager\Category\RouteCategory; +use CanalTP\MttBundle\MediaManager\Category\LineCategory; use CanalTP\MttBundle\MediaManager\Category\StopPointCategory; use CanalTP\MttBundle\MediaManager\Category\SeasonCategory; @@ -25,6 +26,9 @@ private function product($type) case CategoryType::STOP_POINT: $category = new StopPointCategory(); break; + case CategoryType::LINE: + $category = new LineCategory(); + break; case CategoryType::SEASON: $category = new SeasonCategory(); break; diff --git a/MediaManager/Category/LineCategory.php b/MediaManager/Category/LineCategory.php new file mode 100644 index 00000000..99c4a377 --- /dev/null +++ b/MediaManager/Category/LineCategory.php @@ -0,0 +1,17 @@ +id = 'line'; + $this->name = 'Line'; + $this->ressourceId = 'lines'; + } +} diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index c4602b9b..1082f52f 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -98,6 +98,15 @@ canal_tp_mtt_line_timetable_list: externalLineId: -?[a-zA-Z0-9_\-:]* seasonId: -?\d+ +canal_tp_mtt_line_timetable_render: + pattern: /line-timetable/render/networks/{externalNetworkId}/line/{externalLineId}/seasons/{seasonId}/mode/{mode} + defaults: { _controller: CanalTPMttBundle:LineTimetable:render } + requirements: + externalNetworkId: -?[a-zA-Z0-9_\-:]* + externalLineId: -?[a-zA-Z0-9_\-:]* + seasonId: \d+ + mode: edit + canal_tp_mtt_line_timetable_select_stops: pattern: /line-timetable/{lineTimetableId}/networks/{externalNetworkId}/season/{seasonId}/select-stops/route/{externalRouteId} defaults: { _controller: CanalTPMttBundle:LineTimetable:selectStops, externalRouteId: null } @@ -116,6 +125,16 @@ canal_tp_mtt_line_timetable_show_schedule: externalNetworkId: -?[a-zA-Z0-9_\-:]* externalLineId: -?[a-zA-Z0-9_\-:]* +canal_tp_mtt_line_timetable_load_calendar: + pattern: /line-timetable/networks/{externalNetworkId}/calendar/load/{blockId}/columns/{columnsLimit} + defaults: { _controller: CanalTPMttBundle:LineTimetable:loadCalendar } + requirements: + externalNetworkId: -?[a-zA-Z0-9_\-:]* + blockId: \d+ + columnsLimit: \d+ + options: + expose: true + #StopTimetable canal_tp_mtt_stop_timetable_edit: pattern: /stopTimetable/edit/networks/{externalNetworkId}/line/{externalLineId}/route/{externalRouteId}/seasons/{seasonId} @@ -153,24 +172,42 @@ canal_tp_mtt_stop_timetable_download_pdf: externalStopPointId: -?[a-zA-Z0-9_\-:]* #Block +canal_tp_mtt_block_add: + pattern: /block/edit/networks/{externalNetworkId}/timetable/{timetableId}/type/{type}/rank/{rank} + defaults: { _controller: CanalTPMttBundle:Block:add } + requirements: + externalNetworkId: -?[a-zA-Z0-9_\-:]* + type: line|stop + timetableId: \d+ + rank: \d+ + options: + expose: true + canal_tp_mtt_block_edit: - pattern: /block/edit/networks/{externalNetworkId}/timetable/{timetableId}/blockType/{blockType}/domId/{domId} - defaults: { _controller: CanalTPMttBundle:Block:edit } + # stop point must be the last param so it can be empty + pattern: /block/edit/networks/{externalNetworkId}/timetable/{timetableId}/type/{type}/block/{blockId}/type/{blockType}/dom/{domId}/rank/{rank} + defaults: { _controller: CanalTPMttBundle:Block:edit, blockId: -1, rank: 0, domId: 0 } requirements: externalNetworkId: -?[a-zA-Z0-9_\-:]* timetableId: \d+ + type: line|stop + blockId: -?\d+ blockType: calendar|text|img domId: -?[a-zA-Z0-9_\-:]* + rank: -?\d+ options: expose: true canal_tp_mtt_block_delete: - pattern: /block/delete/networks/{externalNetworkId}/timetable/{timetableId}/block/{blockId} + pattern: /block/delete/networks/{externalNetworkId}/timetable/{timetableId}/type/{type}/block/{blockId} defaults: { _controller: CanalTPMttBundle:Block:delete } requirements: externalNetworkId: -?[a-zA-Z0-9_\-:]* timetableId: \d+ + type: line|stop blockId: \d+ + options: + expose: true #Calendar canal_tp_mtt_calendar_view: diff --git a/Resources/config/services.yml b/Resources/config/services.yml index fa4dd135..aa52dfac 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -141,7 +141,7 @@ services: arguments: ['@doctrine.orm.entity_manager', '@canal_tp_mtt.navitia', '@jms_serializer'] canal_tp_mtt.form.factory.block: class: %canal_tp_mtt.form.factory.class% - arguments: ['@service_container', '@doctrine.orm.entity_manager', '@form.factory', '@canal_tp_mtt.media_manager'] + arguments: ['@service_container', '@doctrine.orm.entity_manager', '@form.factory', '@canal_tp_mtt.media_manager', '@canal_tp_mtt.navitia'] canal_tp_mtt.calendar_manager: class: %canal_tp_mtt.calendar_manager.class% arguments: ['@canal_tp_mtt.navitia', '@translator'] diff --git a/Resources/public/css/line.css b/Resources/public/css/line.css new file mode 100644 index 00000000..4ed76544 --- /dev/null +++ b/Resources/public/css/line.css @@ -0,0 +1,169 @@ +/* general style */ +#layout-1-wrapper { + padding:8px; +} +#layout-main-wrapper { + height:auto; + width:1000px; +}; +/* header style */ +#header { + min-height:60px; + border:2px solid grey; + font-size:30px; + padding:10px; +} +#header-wrapper { + margin-top:15px; +} +#map-block { + width:80%; + text-align:center; + float:left; + padding-left:15px; + margin-bottom:30px; +} +#map-block:before { + content:""; + display:inline-block; + height:100%; + vertical-align:middle; +} +.map-img { + max-height:95%; + max-width:95%; + vertical-align:middle; +} +#map-block, #left-blocks { + height:150px; +} +#left-blocks { + font-size:12px; + padding-right:14px; + width:20%; + border-right:1px solid black; +} +#left-blocks .small-block .title { + font-weight:bold; +} +#line-map-block, #left-blocks { + float:left; + padding-left:15px; +} +/* block style */ +.small-block { + border:1px solid white; + border-bottom-color:black; + margin:5px 0; + height:70px; +} +.small-block:last-child { + border-bottom:none; +} +.editable .block:hover { + border:1px dashed black; +} +.editable .block.empty { + background-color:#eee; +} +/* main-content style */ +#main-content-wrapper { + margin-top:5px; + width:100%; +} +#main-content-wrapper .timegrid-container { + font-size:12px; +} +#main-content-wrapper .block .title, +#main-content-wrapper .timegrid-header { + background-color:white; + border-bottom:2px solid black; + color:black; + font-weight:bold; + font-size:14px; + line-height:25px; + padding:0px 5px; + height:25px; +} +#main-content-wrapper .timegrid-body { + background-color:white; + min-height:150px; + width:100%; +} +#main-content-wrapper .block { + margin-top:5px; + min-height:50px; + line-height:20px; + padding:2px; + position:relative; +} +.timegrid-container .loading-indicator { + position:absolute; + left:50%; + top:45%; +} +/* line-timetable style */ +.line-timetable-calendar { + width:100%; +} +.line-timetable-calendar tr { + height:14px; + vertical-align:middle; + font-size:10px; +} +.line-timetable-calendar tr.pattern { + visibility:hidden; +} +.line-timetable-calendar tr.separator { + height:3px; + background-color:#343434; +} +.line-timetable-calendar tr.schedule td { + text-align:center; +} +.line-timetable-calendar tr.schedule td.first-hour { + font-weight:bold; +} +.line-timetable-calendar tr.schedule td.has-note { + background-color:#444; +} +.line-timetable-calendar tr.schedule:nth-child(odd) { + background-color:#eee; +} +.line-timetable-calendar tr.schedule:nth-child(even) { + background-color:inherit; +} +.line-timetable-calendar tr .empty-calendar { + font-weight:bold; + font-size:12px; +} +/* add block style */ +.add-block { + height:25px; + border:1px solid white; + width:100%; + margin:0 auto; + position:relative; +} +.add-block:hover { + cursor:pointer; + border:1px dashed black; +} +.add-block .glyphicon { + position:absolute; + right:5px; + top:5px; +} +/* action bar style */ +.action-bar { + position:absolute; + display:block; + right:-4%; + z-index:1; + font-size:13px; +} +.action-bar ul { + list-style:none; + margin:0; + padding:5px; +} diff --git a/Resources/public/img/ajax-loader.gif b/Resources/public/img/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..9f74292d315a4add49a04e1776125aea46296526 GIT binary patch literal 2608 zcmdVcdr(tX9tZGC9yiG~=H_*Q-0(;+AfxiARf^T^KrYX$KoDxfM!SSdLV{Id8Wm() zK?DJ@WqB+Lt)o0Fprt~s1r;Fz5e1b;)F5vpV!MiHeZkgcgKekNKejtN)Be%(_nA3o zKJz=@?+FeK@?E2F2AqMX05Cs4@20u&&qs6S$!8t6zH7Sv=!?#W+27b3CoL>|k7#)TAO9NMWdJ}D|2MPa;VmhTluZ$P$ z(&*I!MWQ14y(C4vr)HZf2@uYbXpnW+iiImJAOPUJUz#?AOQ({FX>kUTQ+u^O*-upW zj-T?J#rLhKn!RZrTbefIEnTXyL$oKap3sJB3rql0E4h6YG>;A`L2}!+D znz?a%PJuz&PL7`=4gf)Qo}I9C zL_f!cm^Ln1Be9X~oh;D@C@+@NN9nOIEJV8x$59xP6{)6E+iAg!w(P>8qd6NblZWV@ zmSo4!hRVyFH~p<6_G?4*{{roJKj4=>O1cL7NoT)|6rEn2s8PKO{2;?QBBxqm%IYw- zDhOvZM*dwMjKeJfFdl+e8X-Is&ohddxTL=y>@}Z8Mn<^gt|7b85!(`3hnaU5u_rd0 z`RFw5EBxyga2n~TmC0T{?lCx0gp{BrN&oedeEt4a+aHr!9kO5yYBm(8BP)Up4avlD zp~Tq_mr5YR*a1i1W%c(X+dwymHH(CCD>IC;Vnz{uR$H+{bh*GS;vUK74?}eDrq$<~ zJgj}FQy$(tWROLi=|780Yh@=}PT^kyl!`j1w0ER7jZcF5OBRD~Mg!c$kYjJi*dFCO zgc(_oz1_RVxK*%UC=ZL{KIvdM04`Y8(YCwsl01QZBXkE;O6c<+zRkm5UqXQ?&W$|( z@_l>BbXsoBZ~e}G$1(a}M(Iv<$8oDsQI(iN0M3vV)F73Q~o%C8=NoNykG?6 z2rvS==P>Td_Z*!+8ef)B4SMT2UkW=MO7X#>*8#ZM4Z@Ay8$x(ic`n*2gnUb7S z#b>QD*1jmpgvwYV$sylI&s?d$*=y^fTCVQFt)t{5K#)B@N47PR#Lo8F*?;*VvgIU< zQC0?&yvikqYQ)h=k8#Sa!2~xT5^Jxi2($*2ZwM%q6HBXkSFGVgvTd;I!yuf@ZIIsn z7;l}p0^%P{Fel0QiM}Cu7!KbxRT6Q=-ELWg>a9?|513o*!E$qNTdlH7Bc_d|(~8Cr z;I(*mGQsuRh?=cigA_5Qdy%3&E*^m~r=}QzD;HBk>xomA;y)#tP5YKy+Gx*t!>c#s zgiH0Yz^y^~?urS~P>JBbI{i)LZ43Pv+|e(#YnRDZ2n-e%Mt=>Z&?D*G=DjZK;?u0{ zUs@ka6wfrCAEbKCrNktp(tyP~0wDS(K#B6~qzmn)PM~kWNF|6P$XBpgnQ9V7Nd=sC z0$WmJ0jiG0`CQDYR9h1~mHjowP9+d^3`4*_)WlEJ@MSFMI9XTP3+c$9j@c__>cG+7 zA@#`UxO)A~$pLkku4lAgV^nvJc5RMm>21>;?G{HZmwB!^4^$T1slBm$O+Da?7YPX7 zk(HpU@9F?nhbP0yea5Gz&(-sA`S-n@`OFGTfi|wNG{9x#1JwHa;!ET0NMWi;wATC7 ZqAst=-Kz)BSSxO=wc^$;{EuF({{UO2wMPH| literal 0 HcmV?d00001 diff --git a/Resources/public/img/default-line-layout.png b/Resources/public/img/default-line-layout.png new file mode 100644 index 0000000000000000000000000000000000000000..0123d50bfa6ceea2ac2fcf43183bb3ae45aa9bcc GIT binary patch literal 30902 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 literal 0 HcmV?d00001 diff --git a/Resources/public/js/LineTimetable/edit.js b/Resources/public/js/LineTimetable/edit.js new file mode 100644 index 00000000..49814ede --- /dev/null +++ b/Resources/public/js/LineTimetable/edit.js @@ -0,0 +1,108 @@ +define(['jquery'], function($) { + + var layout = {}; + var global_params = {}; + var $icon_tpl = $(''); + + layout.init = function($wrapper, lineTimetableId, externalNetworkId) + { + // store global parameters + global_params.externalNetworkId = externalNetworkId; + global_params.timetableId = lineTimetableId; + global_params.type = 'line'; + + // needed properties + $(document).ready(function() { + $('.action-button').tooltip({ + placement: 'right', + animation: true, + delay: { "show": 500, "hide": 100 } + }); + $('button.edit-note').button({ + icons: { + primary: "ui-icon-gear" + } + }); + }); + + _bind_listeners(); + _bind_add_block_listener(); + _bind_blocks_listeners(); + }; + + var _bind_listeners = function() + { + $('#base-modal').on('loaded.bs.modal', function () { + var $field = $(this).find('*[data-fill-title]'); + if ($field.length == 1 && $field.data('fill-title') === 1) + { + var $titleField = $field.parents('form').find("input[name*='[title]']"); + if ($titleField.length == 1 && $titleField.val() === '') + { + $field.change(function(){ + $titleField.val($field.find(':selected').text()); + }); + // set default value + $field.change(); + // unbind if user types sthg in this title field + $titleField.keypress(function(){ + $field.unbind('change'); + }); + } + } + }); + }; + + + var _bind_blocks_listeners = function() + { + var $blocks = _get_blocks(); + $blocks.each(function() { + var $block = $(this); + // bind click listener if there is no menu inside + if ($block.find('*[role=menu]').length === 0) { + $block.click(_get_remote_modal); + } else { + $block.find('*[role=menu]').siblings('button.btn').dropdown(); + $block.click(function(event) { + if ($block.find('*[role=menu]').parents('.btn-group').hasClass('open') === false) { + $block.find('*[role=menu]').siblings('button.btn').click(); + return false; + } + }); + } + }); + }; + + var _get_remote_modal = function() + { + var params = { + 'rank' : $(this).data('rank'), + 'domId' : $(this).id('domId'), + 'blockType' : $(this).data('type'), + 'blockId' : $(this).data('id') + }; + $.extend(params, global_params); + var url = Routing.generate( + 'canal_tp_mtt_block_edit', + params + ); + $('#base-modal').modal({remote: url}); + }; + + var _get_blocks = function() + { + var $blocks = $('.block').each(function() { + var icon_class = 'glyphicon-'+$(this).data('icon'); + var data = $(this).data(); + $(this).prepend($icon_tpl.clone().addClass(icon_class)); + }); + + // return editable blocks only + return $blocks.filter(function() { + return $(this).data("block-level") == layout.blockLevel; + }); + }; + + return layout; +}); diff --git a/Resources/public/js/layout.js b/Resources/public/js/layout.js index a7998a52..a63cba90 100644 --- a/Resources/public/js/layout.js +++ b/Resources/public/js/layout.js @@ -1,17 +1,34 @@ define(['jquery'], function($) { var layout = {}; - var url_params = {}; + var global_params = {}; var $icon_tpl = $(''); - layout.init = function($wrapper, stopTimetableId, externalNetworkId) + layout.init = function($wrapper, globalParameters) { - // store url params for later - url_params.externalNetworkId = externalNetworkId; - url_params.timetableId = stopTimetableId; + global_params = globalParameters; _bind_listeners(); + _bind_add_block_listener(); _bind_blocks_listeners(); + _bind_action_bar(); + }; + + var _bind_add_block_listener = function() + { + $(document).on("click", ".add-block", function(event) { + event.preventDefault(); + var params = { + 'rank': $(this).data('rank') + }; + $.extend(params, global_params); + var url = Routing.generate( + 'canal_tp_mtt_block_add', + params + ); + + $('#base-modal').modal({remote: url}); + }); }; var _bind_blocks_listeners = function() @@ -37,10 +54,12 @@ define(['jquery'], function($) { var _get_remote_modal = function() { var params = { - 'domId' : $(this).attr('id'), - 'blockType': $(this).data('type') + 'rank' : $(this).data('rank'), + 'domId' : $(this).attr('id') === undefined ? '' : $(this).attr('id'), + 'blockType' : $(this).data('type'), + 'blockId' : $(this).data('id') }; - $.extend(params, url_params); + $.extend(params, global_params); var url = Routing.generate( 'canal_tp_mtt_block_edit', params @@ -81,5 +100,80 @@ define(['jquery'], function($) { }); }; + var _bind_action_bar = function() + { + $('.action-bar .action-button').each(function() { + var action = $(this); + if (action.data('action-type') !== undefined) { + switch (action.data('action-type')) { + case 'load-calendar': + _action_data_load(action); + break; + case 'delete-calendar': + _action_delete_calendar(action); + break; + default: + // do nothing + } + } + }); + }; + + var _action_data_load = function(action) { + action.on('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + var $container = $(this).parents('.' + $(this).data('action-container')); + var $target = $container.find('.' + $(this).data('action-target')); + + var params = { + 'blockId': $(this).data('id'), + 'columnsLimit': $(this).closest('.line').data('columns') + }; + $.extend(params, {'externalNetworkId': global_params.externalNetworkId}); + $loader = $('.loading-indicator').clone(); + $target.hide().empty().parent().prepend($loader.show()); + $.ajax({ + type: "POST", + url: Routing.generate('canal_tp_mtt_line_timetable_load_calendar', params), + cache: false, + success: function(data) { + $loader.detach(); + $target.html(data.content).show(1000); + }, + error: function(data) { + $loader.detach(); + $target.html('').show(1000); + } + }); + + return false; + }); + }; + + var _action_delete_calendar = function(action) { + action.on('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + + var params = { + 'blockId': $(this).data('id') + }; + + $.extend(params, global_params); + + $.ajax({ + type: "POST", + url: Routing.generate('canal_tp_mtt_block_delete', params), + success: function(data) { + window.location = data.location; + }, + error: function(data) { + console.log(data.content); + } + }); + }); + }; + return layout; }); diff --git a/Resources/public/js/layout/validator.js b/Resources/public/js/layout/validator.js index 2d5adcd0..19bd423c 100644 --- a/Resources/public/js/layout/validator.js +++ b/Resources/public/js/layout/validator.js @@ -14,7 +14,7 @@ define(['jquery', 'utils', 'translations/default'], function($, utils) { var msg_added = false; // check if content is bigger than block wrapper $wrapper.find('*[data-validate-size="1"]').each(function(){ - if ($(this).parents('.frequency-content').length != 0 && + if ($(this).parents('.frequency-content').length !== 0 && ($(this).find('span').height() > $(this).height() || $(this).find('span').width() > $(this).width()) ) { $(this).addClass('error'); @@ -42,4 +42,4 @@ define(['jquery', 'utils', 'translations/default'], function($, utils) { }; return validator; -}) +}); diff --git a/Resources/translations/default.fr.yml b/Resources/translations/default.fr.yml index 64201a94..e16ac076 100644 --- a/Resources/translations/default.fr.yml +++ b/Resources/translations/default.fr.yml @@ -39,11 +39,14 @@ line_timetable: title: select_stops: Sélection des points d'arrêt schedule: Horaires de la ligne + edit: Edition + view: Prévisualisation action: select_stops: Choisir les arrêts select_all_stops: Ajouter tous les arrêts remove_all_stops: Vider la sélection des arrêts show_schedule: Voir les horaires + edit: Editer la fiche horaire label: way: vers available_stops: Arrêts disponibles diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 9d25833a..50ace979 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -46,6 +46,7 @@ global: go_back: Retour error: + invalid_status_code: Le code utilisé est invalide. element_locked: Cet élément est verrouillé et ne peut être modifié. start_end_time_incoherent: Les heures de début et de fin ne sont pas cohérentes entities_overlapping: Les éléments n°%firstElement% et n°%secondElement% se chevauchent @@ -58,6 +59,8 @@ error: help: edit_block: Cliquez pour éditer ce bloc drop_here: Déposez les éléments ici + add_block: Ajouter un ou plusieurs blocs + load_calendar: Charger les données du calendrier calendar: start: Heure de début des calendriers @@ -114,6 +117,11 @@ line_timetable: message: save_stop_selection: Souhaitez-vous enregistrer les changements avant de changer de route ? empty_calendar: Aucune horaire présente dans ce calendrier. + error: + block_not_linked: Le bloc %blockId% n'est pas lié à une fiche horaire de ligne + bad_external_network: La fiche horaire n'est pas accessible via le réseau %externalNetworkId% + block_empty: Le bloc %blockId% ne contient pas de données + block_not_calendar: Le bloc %blockId% n'est pas de type calendrier frequency: labels: @@ -143,6 +151,11 @@ layout_model: archive_path: Chemin de l'archive block: + title: + add: Ajouter un bloc + labels: + type: Type de bloc + number: Nombre d'ajouts img: labels: title: Titre @@ -155,6 +168,7 @@ block: labels: title: Titre content: Calendrier + route: Direction customer: layout_list: Liste des modèles diff --git a/Resources/views/Block/form.html.twig b/Resources/views/Block/form.html.twig new file mode 100644 index 00000000..b56c8010 --- /dev/null +++ b/Resources/views/Block/form.html.twig @@ -0,0 +1,27 @@ +{% extends "::modal.html.twig" %} + +{% form_theme form 'CanalTPMttBundle:Form:fields.html.twig' %} + +{% block open_form %} + {{ form_start(form) }} +{% endblock %} + +{% block modal_title %} + {{ 'block.title.add'|trans({}, 'default') }} +{% endblock %} + +{% block modal_body %} + {{ form_errors(form) }} + {{ form_row(form.type) }} + {{ form_row(form.number) }} + {{ form_row(form.externalLineId) }} + {{ form_row(form.rank) }} + {{ form_row(form.timetable) }} +{% endblock %} + +{% block modal_actions %} + + {{ form_end(form) }} +{% endblock %} diff --git a/Resources/views/Block/get_form.html.twig b/Resources/views/Block/get_form.html.twig index 2cc8ffb2..93e0f59d 100644 --- a/Resources/views/Block/get_form.html.twig +++ b/Resources/views/Block/get_form.html.twig @@ -17,6 +17,9 @@ {{ form_row(form.title) }} {{ form_row(form.content) }} + {% if form.externalRouteId %} + {{ form_row(form.externalRouteId) }} + {% endif %} {% endblock %} {% block modal_actions %} diff --git a/Resources/views/Layouts/base_layout.html.twig b/Resources/views/Layouts/base_layout.html.twig index 62dca683..0c344a09 100644 --- a/Resources/views/Layouts/base_layout.html.twig +++ b/Resources/views/Layouts/base_layout.html.twig @@ -19,7 +19,7 @@ ) }}"> {{'menu.edit_timetables'|trans({}, 'messages')}} -
  • {{ pageTitle|trans({}, 'default') }}
  • +
  • {{ pageTitle|trans }}
  • {% endif %} {% endblock %} @@ -116,8 +116,8 @@ %} {% endif %} -
    - {% block stop_timetable_content %}{% endblock %} +
    + {% block timetable_content %}{% endblock %}
    {% block scripts %}{% endblock %} diff --git a/Resources/views/Layouts/common.html.twig b/Resources/views/Layouts/common.html.twig index 4366a249..fc64baef 100644 --- a/Resources/views/Layouts/common.html.twig +++ b/Resources/views/Layouts/common.html.twig @@ -1,10 +1,15 @@ -{% if editable == true %} +{% if editable == true or mode == 'edit' %} diff --git a/Services/BlockManager.php b/Services/BlockManager.php index 57ec16fe..4afae81c 100644 --- a/Services/BlockManager.php +++ b/Services/BlockManager.php @@ -67,15 +67,15 @@ public function findOrCreate($blockId, $timetable, $attributes) */ public function save(Block $block, $blocksNumber = null) { - $this->synchronizeRanks($block, 'add', $blocksNumber); - - if (!empty($blocksNumber) && $blocksNumber > 1) - { - for($i = 1; $i < $blocksNumber; $i++) - { - $newBlock = clone $block; - $newBlock->incRank($i); - $this->om->persist($newBlock); + if ($block->getRank() > 0) { + $this->synchronizeRanks($block, 'add', $blocksNumber); + + if (!empty($blocksNumber) && $blocksNumber > 1) { + for ($i = 1; $i < $blocksNumber; $i++) { + $newBlock = clone $block; + $newBlock->incRank($i); + $this->om->persist($newBlock); + } } }