diff --git a/Controller/LineTimetableController.php b/Controller/LineTimetableController.php index f96e9189..94395736 100644 --- a/Controller/LineTimetableController.php +++ b/Controller/LineTimetableController.php @@ -274,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'); @@ -372,6 +376,12 @@ public function loadCalendarAction(Request $request, $externalNetworkId, $blockI $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, 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/Resources/public/css/line.css b/Resources/public/css/line.css index 4ed76544..3e422dbd 100644 --- a/Resources/public/css/line.css +++ b/Resources/public/css/line.css @@ -105,9 +105,11 @@ /* 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; } 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/js/layout.js b/Resources/public/js/layout.js index a63cba90..3fdb64f3 100644 --- a/Resources/public/js/layout.js +++ b/Resources/public/js/layout.js @@ -1,4 +1,4 @@ -define(['jquery'], function($) { +define(['jquery', 'sf_routes'], function($) { var layout = {}; var global_params = {}; @@ -34,14 +34,14 @@ define(['jquery'], function($) { 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; diff --git a/Resources/views/Layouts/default-line-layout.html.twig b/Resources/views/Layouts/default-line-layout.html.twig index efa0f3a4..fb7d6676 100644 --- a/Resources/views/Layouts/default-line-layout.html.twig +++ b/Resources/views/Layouts/default-line-layout.html.twig @@ -52,7 +52,7 @@ {{ macros.img(lblock.domId, lblock, class, lblock.rank, mode) }} {% elseif lblock.type == 'calendar' %} {% set calendar = calendars|findCalendar(lblock.content, lblock.externalRouteId) %} - {{ macros.line_calendar(lblock.domId, lblock, class, lblock.rank, mode, calendar) }} + {{ macros.line_calendar(lblock.domId, lblock, class, lblock.rank, mode, calendar, 26) }} {% endif %} {% if mode == 'edit' and loop.last %} {{ macros.add_block(loop.index+1) }} diff --git a/Resources/views/Layouts/macros.html.twig b/Resources/views/Layouts/macros.html.twig index 93770aef..8176991b 100644 --- a/Resources/views/Layouts/macros.html.twig +++ b/Resources/views/Layouts/macros.html.twig @@ -283,7 +283,7 @@ data-type="{{ block.type }}" data-rank="{{ rank|default(0) }}" data-icon="calendar" - data-columns="24" + data-columns="{{ columnsLimit }}" {% endif %} > {% if mode == 'edit' and block and block.rank > 0 %} diff --git a/Services/CalendarManager.php b/Services/CalendarManager.php index 3451eadd..2b9bfcea 100644 --- a/Services/CalendarManager.php +++ b/Services/CalendarManager.php @@ -437,10 +437,19 @@ public function isIncluded($calendarId, Season $season) public function getCalendarForBlock( $externalCoverageId, Block $block, - $parameters = array() + $parameters ) { $calendar = $this->navitia->getCalendar($externalCoverageId, $block->getContent())->calendars[0]; + if (empty($parameters['hourOffset'])) { + throw new \Exception('The hour offset have to be set'); + } + + $parameters['startProductionDate'] = $this->navitia->getStartProductionDate( + $externalCoverageId, + $parameters['hourOffset'] + ); + return $this->getRouteSchedules( $externalCoverageId, $block->getExternalRouteId(), @@ -460,11 +469,21 @@ public function getCalendarForBlock( public function getCalendarsForLine( $externalCoverageId, $externalNetworkId, - $externalLineId + $externalLineId, + $parameters ) { $routes = $this->navitia->getLineRoutes($externalCoverageId, $externalNetworkId, $externalLineId); $calendars = $this->navitia->getLineCalendars($externalCoverageId, $externalNetworkId, $externalLineId); + if (empty($parameters['hourOffset'])) { + throw new \Exception('The hour offset have to be set'); + } + + $parameters['startProductionDate'] = $this->navitia->getStartProductionDate( + $externalCoverageId, + $parameters['hourOffset'] + ); + $schedule = array(); foreach ($routes as $route) { $externalRouteId = $route->id; @@ -474,7 +493,8 @@ public function getCalendarsForLine( $schedule[$externalRouteId]['calendars'][$calendar->id] = $this->getRouteSchedules( $externalCoverageId, $externalRouteId, - $calendar + $calendar, + $parameters ); } } @@ -493,14 +513,14 @@ private function getRouteSchedules( $externalCoverageId, $externalRouteId, &$calendar, - $parameters = array() + $parameters ) { try { $routeSchedules = $this->navitia->getRouteSchedulesByRouteAndCalendar( $externalCoverageId, $externalRouteId, $calendar->id, - \DateTime::createFromFormat('Ymd', $calendar->validity_pattern->beginning_date) + $parameters['startProductionDate'] ); } catch(\Exception $e) { return $this->createEmptyLineCalendar($calendar); @@ -516,7 +536,25 @@ private function getRouteSchedules( throw new \Exception('No stop points found for the route : '.$externalRouteId); } - $this->buildFullCalendar($routeSchedules); + $this->buildFullCalendar($routeSchedules, $parameters['hourOffset']); + + $stopTimesToDelete = array(); + + if (!empty($parameters['limits'])) { + $this->processTimeLimits( + $parameters['limits'], + $parameters['hourOffset'], + current($routeSchedules->route_schedules['metadata'])['firstHour'] + ); + $this->cutCalendar($routeSchedules->route_schedules, $parameters['limits'], $stopTimesToDelete); + } + + if (count($stopTimesToDelete) > 0) { + $this->deleteStopTimes( + $routeSchedules->route_schedules, + $stopTimesToDelete + ); + } unset($routeSchedules->headers, $routeSchedules->exceptions); @@ -569,9 +607,10 @@ function($stop) use ($stopPoint) { } /** - * Build full calendar. + * Building full calendar. * * @param mixed &$schedule + * @param integer $hourOffset * * Building a full one-line calendar array. * The calendar's structure in JSON is : @@ -598,7 +637,7 @@ function($stop) use ($stopPoint) { * It's type is just "trip" at the end of the function but it * can be replaced later by something else (frequency for example). */ - private function buildFullCalendar(&$schedule) + private function buildFullCalendar(&$schedule, $hourOffset) { $calendar = array( 'columns' => count($schedule->route_schedules[0]->date_times), @@ -608,18 +647,22 @@ private function buildFullCalendar(&$schedule) foreach ($schedule->route_schedules as $lineNumber => $stop) { $stopTimes = array(); - $previousDatetime = null; + $previousHour = null; $dayOffset = false; foreach ($stop->date_times as $columnNumber => $detail) { + $hour = intVal(substr($detail->date_time, 0, 2)); + if (empty($detail->date_time)) { if (!isset($calendar['metadata'][$columnNumber])) { $calendar['metadata'][$columnNumber] = null; } $stopTimes[] = null; } else { - // Detecting day change by hours comparison - if ($previousDatetime && !$dayOffset && $previousDatetime > intVal($detail->date_time)) { - $dayOffset = true; + // Detecting day / day+1 limit looking at hours and hourOffset + if ($previousHour && !$dayOffset && $previousHour > $hour) { + if ($previousHour > $hourOffset && $hour < $hourOffset) { + $dayOffset = true; + } } $date = strtotime($detail->date_time); @@ -654,7 +697,7 @@ function ($link) { $calendar['metadata'][$columnNumber]['arrivalStop'] = $stop->stop_point->name; } - $previousDatetime = intVal($detail->date_time); + $previousHour = $hour; } } @@ -667,4 +710,87 @@ function ($link) { $schedule->route_schedules = $calendar; } + + /** + * Processing time limits using a reference date and the first + * hour of the first trip in calendar + * + * @param array &$limits + * @param integer $hourOffset + * @param time $firstTrip + */ + private function processTimeLimits(&$limits, $hourOffset, $firstTrip) + { + if (empty($limits['min']) || intVal($limits['min']) < 0) { + throw new \Exception('The low limit is invalid'); + } + + if (empty($limits['max']) || intVal($limits['max']) < 0) { + throw new \Exception('The high limit is invalid'); + } + + $referenceDate = new \Datetime(date('Y-m-d\TH:i:s', $firstTrip)); + + // The date is D-1 if the first hour is < hourOffset + if (intval($referenceDate->format('H')) < $hourOffset) { + $referenceDate->sub(new \DateInterval('P1D')); + } + + $referenceDate = $referenceDate->format('Y-m-d'); + + foreach ($limits as $idx => $hour) + { + $hour = (int)$hour; + if ($hour > 24) { + $limits[$idx] = strtotime( + "+1 day", + strtotime($referenceDate." ".date('His', mktime($hour - 24, 0, 0))) + ); + } else { + $limits[$idx] = strtotime($referenceDate." ".date('His', mktime($hour, 0, 0))); + } + } + } + + /** + * Cutting calendar hours using limitations + * + * @param mixed &$schedule + * @param array $limits + * @param array &$stopTimesToDelete + */ + private function cutCalendar(&$schedule, $limits, &$stopTimesToDelete) + { + foreach ($schedule['metadata'] as $columnNumber => $metadata) + { + if ($metadata['firstHour'] < $limits['min'] + || $metadata['firstHour'] > $limits['max'] + ) { + unset($schedule['metadata'][$columnNumber]); + $stopTimesToDelete[] = $columnNumber; + $schedule['columns'] -= 1; + } + } + } + + /** + * Deleting stopTimes from stops in a calendar array. + * stopTimesToDelete contains the deleted stopTimes position. + * + * @param array $stops + * @param array $stopTimesToDelete + */ + private function deleteStopTimes(&$schedule, $stopTimesToDelete) + { + $columnsToDelete = 0; + foreach ($schedule['stops'] as $key => $stop) { + foreach (array_keys($stop['stopTimes']) as $columnNumber) { + if (in_array($columnNumber, $stopTimesToDelete)) { + unset($stop['stopTimes'][$columnNumber]); + $columnsToDelete++; + } + } + $schedule['stops'][$key]['stopTimes'] = $stop['stopTimes']; + } + } } diff --git a/Services/Navitia.php b/Services/Navitia.php index f6c3016f..dc7ca383 100644 --- a/Services/Navitia.php +++ b/Services/Navitia.php @@ -477,10 +477,35 @@ public function getRouteSchedulesByRouteAndCalendar( 'region' => $externalCoverageId, 'action' => 'route_schedules', 'path_filter' => 'routes/' . $externalRouteId, - 'parameters' => '?calendar=' . $externalCalendarId . '&show_codes=true&from_datetime=' . $fromDatetime->format('Ymd') + 'parameters' => '?calendar=' . $externalCalendarId . '&show_codes=true&from_datetime=' . $fromDatetime->format('Ymd\tHis') ) ); return $this->navitia_component->call($query); } + + /** + * Getting first date of production from navitia data + * + * @param string $externalCoverageId + * @param integer $limit + */ + public function getStartProductionDate($externalCoverageId, $hourOffset = null) + { + $query = array( + 'api' => 'coverage', + 'parameters' => array( + 'region' => $externalCoverageId + ) + ); + + $response = \DateTime::createFromFormat('Ymd', $this->navitia_component->call($query)->regions[0]->start_production_date); + + $response->add(new \DateInterval('P1D')); + if (!empty($hourOffset)) { + $response->setTime($hourOffset, 0); + } + + return $response; + } }