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 efc404e4..72337fed 100644 --- a/Controller/BlockController.php +++ b/Controller/BlockController.php @@ -2,8 +2,14 @@ namespace CanalTP\MttBundle\Controller; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; 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 +18,189 @@ 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(), + ) + ); + } + + public function autoCreateAction( + Request $request, + $externalNetworkId, + $timetableId, + $type, + $blockType, + $domId, + $rank + ) { + $this->isGranted( + array( + 'BUSINESS_MANAGE_LINE_TIMETABLE', + 'BUSINESS_MANAGE_STOP_TIMETABLE' + ) + ); + + $this->isPostAjax($request); + + $timetableManager = $this->get(Timetable::$managers[$type]); + $timetable = $timetableManager->find($timetableId); + + $perimeter = $this->get('nmm.perimeter_manager')->findOneByExternalNetworkId( + $this->getUser()->getCustomer(), + $externalNetworkId + ); + + // 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%' => 0) + ), + 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 + ); + } + + $data = array( + 'type' => $blockType, + 'rank' => $rank, + 'domId' => $domId + ); + + $blockManager = $this->get('canal_tp_mtt.block_manager'); + $block = $blockManager->findOrCreate(-1, $timetable, $data); + + $blockManager->save($block); + + return new Response(); + } + + /** + * 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( - 'dom_id' => $domId, - 'type_id' => $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 +212,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 +297,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..94395736 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. * @@ -184,10 +274,14 @@ public function showScheduleAction($externalNetworkId, $externalLineId) $externalCoverageId = $perimeter->getExternalCoverageId(); + $parameters = array(); + $parameters['hourOffset'] = intVal($this->container->getParameter('canal_tp_mtt.hour_offset')); + $schedule = $this->get('canal_tp_mtt.calendar_manager')->getCalendarsForLine( $externalCoverageId, $externalNetworkId, - $externalLineId + $externalLineId, + $parameters ); $navitia = $this->get('canal_tp_mtt.navitia'); @@ -213,4 +307,96 @@ 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; + } + + $layoutConfig = $block->getLineTimetable()->getLineConfig()->getLayoutConfig(); + + $parameters['limits']['min'] = $layoutConfig->getCalendarStart(); + $parameters['limits']['max'] = $layoutConfig->getCalendarEnd(); + $parameters['hourOffset'] = intVal($this->container->getParameter('canal_tp_mtt.hour_offset')); + + $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/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(); } diff --git a/DependencyInjection/CanalTPMttExtension.php b/DependencyInjection/CanalTPMttExtension.php index fbedc10f..4c67da52 100644 --- a/DependencyInjection/CanalTPMttExtension.php +++ b/DependencyInjection/CanalTPMttExtension.php @@ -28,6 +28,8 @@ public function load(array $configs, ContainerBuilder $container) new FileLocator(__DIR__.'/../Resources/config') ); + $container->setParameter('canal_tp_mtt.hour_offset', $config['hour_offset']); + $loader->load('services.yml'); $loader->load('blocks.yml'); $loader->load('permissions.yml'); diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index a11b42ca..8bfbc58f 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -18,7 +18,7 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('canal_tp_meth'); + $rootNode = $treeBuilder->root('canal_tp_mtt'); $rootNode ->children() @@ -31,6 +31,11 @@ public function getConfigTreeBuilder() ->scalarNode('server')->defaultValue( 'http://localhost' )->end() + ->end() + ->end() + ->scalarNode('hour_offset') + ->info('Hour used for day switching in navitia route_schedules') + ->defaultValue(4) ->end() ->end() ; diff --git a/Entity/Block.php b/Entity/Block.php index 4da4cf0d..408fcc82 100644 --- a/Entity/Block.php +++ b/Entity/Block.php @@ -15,13 +15,28 @@ class Block extends AbstractEntity /** * @var string */ - private $typeId; + private $type; /** * @var string */ 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 */ @@ -57,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; } @@ -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,42 @@ 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 + * + * @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 * @@ -206,7 +381,7 @@ public function getFrequencies() */ public function isImg() { - return ($this->getTypeId() == BlockRepository::IMG_TYPE); + return ($this->getType() == BlockRepository::IMG_TYPE); } /** @@ -216,7 +391,7 @@ public function isImg() */ public function isText() { - return ($this->getTypeId() == BlockRepository::TEXT_TYPE); + return ($this->getType() == BlockRepository::TEXT_TYPE); } /** @@ -226,15 +401,7 @@ public function isText() */ public function isCalendar() { - return ($this->getTypeId() == BlockRepository::CALENDAR_TYPE); - } - - /** - * Getting the Timetable - */ - public function getTimetable() - { - return $this->stopTimetable; + return ($this->getType() == BlockRepository::CALENDAR_TYPE); } /** diff --git a/Entity/BlockRepository.php b/Entity/BlockRepository.php index 3bd5ccea..532583a1 100644 --- a/Entity/BlockRepository.php +++ b/Entity/BlockRepository.php @@ -10,7 +10,8 @@ */ class BlockRepository extends EntityRepository { - const TEXT_TYPE = 'text'; - const IMG_TYPE = 'img'; - const CALENDAR_TYPE = 'calendar'; + const TEXT_TYPE = 'text'; + const IMG_TYPE = 'img'; + const CALENDAR_TYPE = 'calendar'; + const PAGE_BREAK_TYPE = 'pagebreak'; } 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 9a366fc3..69713c6e 100644 --- a/Form/Handler/Block/AbstractHandler.php +++ b/Form/Handler/Block/AbstractHandler.php @@ -16,10 +16,11 @@ 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->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..56652bf5 --- /dev/null +++ b/Form/Type/Block/BlockType.php @@ -0,0 +1,99 @@ +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', + BlockRepository::IMG_TYPE => 'block.'.BlockRepository::IMG_TYPE.'.labels.content', + BlockRepository::PAGE_BREAK_TYPE => 'block.'.BlockRepository::PAGE_BREAK_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 1edeefb9..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('dom_id', 'hidden', array('data' => $options['data']['dom_id'])); - $builder->add('type_id', 'hidden', array('data' => $options['data']['type_id'])); + $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/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/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 e09308c1..b16b6c01 100644 --- a/Resources/config/doctrine/Block.orm.yml +++ b/Resources/config/doctrine/Block.orm.yml @@ -8,13 +8,24 @@ CanalTP\MttBundle\Entity\Block: id: true generator: strategy: IDENTITY - typeId: + type: type: string - column: type_id + column: type domId: 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 diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index c4602b9b..8627fc9a 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,55 @@ 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_auto_create: + pattern: /block/create/networks/{externalNetworkId}/timetable/{timetableId}/type/{type}/rank/{rank}/dom/{domId}/type/{blockType} + defaults: { _controller: CanalTPMttBundle:Block:autoCreate } + requirements: + externalNetworkId: -?[a-zA-Z0-9_\-:]* + type: line|stop + timetableId: \d+ + rank: \d+ + domId: -?[a-zA-Z0-9_\-:]* + blockType: calendar|text|img|pagebreak + 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+ - blockType: calendar|text|img + type: line|stop + blockId: -?\d+ + blockType: calendar|text|img|pagebreak 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..02f1f358 --- /dev/null +++ b/Resources/public/css/line.css @@ -0,0 +1,180 @@ +/* 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; +} +.pagebreak { + height:25px; + min-height:25px; + text-align:center; + font-weight:bold; + font-size:12px; + position:relative; +} +/* 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%; + font-family:ralewayregular; +} +.line-timetable-calendar tr { + height:14px; + line-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%; + top:0; + z-index:1; + font-size:13px; +} +.action-bar ul { + list-style:none; + margin:0; + padding:5px; +} diff --git a/Resources/public/css/main.css b/Resources/public/css/main.css index 90a8e40c..daa224e2 100644 --- a/Resources/public/css/main.css +++ b/Resources/public/css/main.css @@ -223,9 +223,11 @@ li.active .add-stop-point { } .line-timetable-calendar { width: 100%; + font-family:ralewayregular; } .line-timetable-calendar tr { height:14px; + line-height:14px; vertical-align:middle; font-size:10px; } diff --git a/Resources/public/img/ajax-loader.gif b/Resources/public/img/ajax-loader.gif new file mode 100644 index 00000000..9f74292d Binary files /dev/null and b/Resources/public/img/ajax-loader.gif differ diff --git a/Resources/public/img/default-line-layout.png b/Resources/public/img/default-line-layout.png new file mode 100644 index 00000000..0123d50b Binary files /dev/null and b/Resources/public/img/default-line-layout.png differ 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..dba2b284 100644 --- a/Resources/public/js/layout.js +++ b/Resources/public/js/layout.js @@ -1,30 +1,73 @@ -define(['jquery'], function($) { +define(['jquery', 'sf_routes'], 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(); + _bind_auto_create(); + + }; + + var _bind_auto_create = function() + { + $(document).ready(function() { + $(document).find(".auto-create").each(function() { + var params = { + 'rank' : $(this).data('rank'), + 'domId' : $(this).attr('id') === undefined ? '' : $(this).attr('id'), + 'blockType' : $(this).data('type'), + }; + $.extend(params, global_params); + + $.ajax({ + type: "POST", + url: Routing.generate('canal_tp_mtt_block_auto_create', params), + cache: false, + error: function(data) { + $loader.detach(); + $(this).html('').show(1000); + } + }); + }); + }); + }; + + 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() { var $blocks = _get_blocks(); - $blocks.each(function(){ + $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){ + $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; @@ -37,10 +80,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 +126,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..16dda256 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,9 @@ 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_data: Charger les données + delete_block: Supprimer le bloc calendar: start: Heure de début des calendriers @@ -114,6 +118,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 +152,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 @@ -151,10 +165,14 @@ block: labels: title: Titre content: Texte + pagebreak: + labels: + content: Saut de page calendar: 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/color-stop-layout.html.twig b/Resources/views/Layouts/color-stop-layout.html.twig index f8706c53..b1f2ef12 100644 --- a/Resources/views/Layouts/color-stop-layout.html.twig +++ b/Resources/views/Layouts/color-stop-layout.html.twig @@ -7,7 +7,7 @@ {% endblock %} -{% block stop_timetable_content %} +{% block timetable_content %}
    {{ macros.img('line_map_block', stopTimetable.BlockByDomId('line_map_block'), 'line_map_img') }} @@ -35,7 +35,17 @@ {% for i in 1..2 %} {% set id = 'timegrid_block_' ~ i %} {% set block = stopTimetable.BlockByDomId(id) %} -
    +
    {% if block %}{{ block.title }}{% endif %}
    @@ -50,13 +60,13 @@ {% endif %} {% set hours_range = layout|calendarRange %} {{ macros.hours_thead(hours_range)}} - {% if block.frequencies|length > 0 and (calendar.schedules.additional_informations is empty) %} + {% if block.frequencies is not empty and (calendar.schedules.additional_informations is empty) %} {% include "CanalTPMttBundle:Layouts:display.html.twig" with { 'frequencies' : block.frequencies, 'hours_range' : hours_range } %} {% endif %} - {{ macros.calendar(id, externalNetworkId, stopTimetable, layout, block, calendar, notes, hours_range, null, notesType) }} + {{ macros.stop_calendar(id, externalNetworkId, stopTimetable, layout, block, calendar, notes, hours_range, null, notesType) }} {# position is specific to this layout ie i==1... #} @@ -84,7 +94,8 @@ with { 'editable' : editable, 'externalNetworkId' : externalNetworkId, - 'timetable' : stopTimetable, + 'timetableId' : stopTimetable.id, + 'type' : 'stop', 'selector' : '#layout-1-wrapper' } %} 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' %}