redis = $chillRedis; $this->exportManager = $exportManager; $this->formFactory = $formFactory; $this->logger = $logger; $this->session = $session; $this->translator = $translator; } public function downloadResultAction(Request $request, $alias) { /** @var \Chill\MainBundle\Export\ExportManager $exportManager */ $exportManager = $this->exportManager; $key = $request->query->get('key', null); [$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key); $formatterAlias = $exportManager->getFormatterAlias($dataExport['export']); if (null !== $formatterAlias) { $formater = $exportManager->getFormatter($formatterAlias); } else { $formater = null; } $viewVariables = [ 'alias' => $alias, 'export' => $exportManager->getExport($alias), ]; if ($formater instanceof \Chill\MainBundle\Export\Formatter\CSVListFormatter) { // due to a bug in php, we add the mime type in the download view $viewVariables['mime_type'] = 'text/csv'; } return $this->render('@ChillMain/Export/download.html.twig', $viewVariables); } /** * Generate a report. * * This action must work with GET queries. * * @param string $alias * * @return \Symfony\Component\HttpFoundation\Response */ public function generateAction(Request $request, $alias) { /** @var \Chill\MainBundle\Export\ExportManager $exportManager */ $exportManager = $this->exportManager; $key = $request->query->get('key', null); [$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key); return $exportManager->generate( $alias, $dataCenters['centers'], $dataExport['export'], null !== $dataFormatter ? $dataFormatter['formatter'] : [] ); } /** * Render the list of available exports. * * @return \Symfony\Component\HttpFoundation\Response */ public function indexAction(Request $request) { $exportManager = $this->exportManager; $exports = $exportManager->getExportsGrouped(true); return $this->render('@ChillMain/Export/layout.html.twig', [ 'grouped_exports' => $exports, ]); } /** * handle the step to build a query for an export. * * This action has three steps : * * 1.'export', the export form. When the form is posted, the data is stored * in the session (if valid), and then a redirection is done to next step. * 2. 'formatter', the formatter form. When the form is posted, the data is * stored in the session (if valid), and then a redirection is done to next step. * 3. 'generate': gather data from session from the previous steps, and * make a redirection to the "generate" action with data in query (HTTP GET) * * @param string $request * @param Request $alias * * @return \Symfony\Component\HttpFoundation\Response */ public function newAction(Request $request, $alias) { // first check for ACL $exportManager = $this->exportManager; $export = $exportManager->getExport($alias); if ($exportManager->isGrantedForElement($export) === false) { throw $this->createAccessDeniedException('The user does not have access to this export'); } $step = $request->query->getAlpha('step', 'centers'); switch ($step) { case 'centers': return $this->selectCentersStep($request, $export, $alias); case 'export': return $this->exportFormStep($request, $export, $alias); break; case 'formatter': return $this->formatterFormStep($request, $export, $alias); break; case 'generate': return $this->forwardToGenerate($request, $export, $alias); break; default: throw $this->createNotFoundException("The given step '{$step}' is invalid"); } } /** * create a form to show on different steps. * * @param string $alias * @param array $data the data from previous step. Required for steps 'formatter' and 'generate_formatter' * @param mixed $step * * @return \Symfony\Component\Form\Form */ protected function createCreateFormExport($alias, $step, $data = []) { /** @var \Chill\MainBundle\Export\ExportManager $exportManager */ $exportManager = $this->exportManager; $isGenerate = strpos($step, 'generate_') === 0; $builder = $this->formFactory ->createNamedBuilder(null, FormType::class, [], [ 'method' => $isGenerate ? 'GET' : 'POST', 'csrf_protection' => $isGenerate ? false : true, ]); if ('centers' === $step || 'generate_centers' === $step) { $builder->add('centers', PickCenterType::class, [ 'export_alias' => $alias, ]); } if ('export' === $step || 'generate_export' === $step) { $builder->add('export', ExportType::class, [ 'export_alias' => $alias, 'picked_centers' => $exportManager->getPickedCenters($data['centers']), ]); } if ('formatter' === $step || 'generate_formatter' === $step) { $builder->add('formatter', FormatterType::class, [ 'formatter_alias' => $exportManager ->getFormatterAlias($data['export']), 'export_alias' => $alias, 'aggregator_aliases' => $exportManager ->getUsedAggregatorsAliases($data['export']), ]); } $builder->add('submit', SubmitType::class, [ 'label' => 'Generate', ]); return $builder->getForm(); } /** * Render the export form. * * When the method is POST, the form is stored if valid, and a redirection * is done to next step. * * @param string $alias * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export * * @return \Symfony\Component\HttpFoundation\Response */ protected function exportFormStep(Request $request, $export, $alias) { $exportManager = $this->exportManager; // check we have data from the previous step (export step) $data = $this->session->get('centers_step', null); if (null === $data) { return $this->redirectToRoute('chill_main_export_new', [ 'step' => $this->getNextStep('export', $export, true), 'alias' => $alias, ]); } $export = $exportManager->getExport($alias); $form = $this->createCreateFormExport($alias, 'export', $data); if ($request->getMethod() === 'POST') { $form->handleRequest($request); if ($form->isValid()) { $this->logger->debug('form export is valid', [ 'location' => __METHOD__, ]); // store data for reusing in next steps $data = $form->getData(); $this->session->set( 'export_step_raw', $request->request->all() ); $this->session->set('export_step', $data); //redirect to next step return $this->redirect( $this->generateUrl('chill_main_export_new', [ 'step' => $this->getNextStep('export', $export), 'alias' => $alias, ]) ); } $this->logger->debug('form export is invalid', [ 'location' => __METHOD__, ]); } return $this->render('@ChillMain/Export/new.html.twig', [ 'form' => $form->createView(), 'export_alias' => $alias, 'export' => $export, ]); } /** * Render the form for formatter. * * If the form is posted and valid, store the data in session and * redirect to the next step. * * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export * @param string $alias * * @return \Symfony\Component\HttpFoundation\Response */ protected function formatterFormStep(Request $request, $export, $alias) { // check we have data from the previous step (export step) $data = $this->session->get('export_step', null); if (null === $data) { return $this->redirectToRoute('chill_main_export_new', [ 'step' => $this->getNextStep('formatter', $export, true), 'alias' => $alias, ]); } $form = $this->createCreateFormExport($alias, 'formatter', $data); if ($request->getMethod() === 'POST') { $form->handleRequest($request); if ($form->isValid()) { $dataFormatter = $form->getData(); $this->session->set('formatter_step', $dataFormatter); $this->session->set( 'formatter_step_raw', $request->request->all() ); //redirect to next step return $this->redirect($this->generateUrl( 'chill_main_export_new', [ 'alias' => $alias, 'step' => $this->getNextStep('formatter', $export), ] )); } } return $this->render( '@ChillMain/Export/new_formatter_step.html.twig', [ 'form' => $form->createView(), 'export' => $export, ] ); } /** * Gather data stored in session from previous steps, store it inside redis * and redirect to the `generate` action. * * The data from previous steps is removed from session. * * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export * @param string $alias * * @return \Symfony\Component\HttpFoundation\RedirectResponse */ protected function forwardToGenerate(Request $request, $export, $alias) { $dataCenters = $this->session->get('centers_step_raw', null); $dataFormatter = $this->session->get('formatter_step_raw', null); $dataExport = $this->session->get('export_step_raw', null); if (null === $dataFormatter && $export instanceof \Chill\MainBundle\Export\ExportInterface) { return $this->redirectToRoute('chill_main_export_new', [ 'alias' => $alias, 'step' => $this->getNextStep('generate', $export, true), ]); } $parameters = [ 'formatter' => $dataFormatter ?? [], 'export' => $dataExport ?? [], 'centers' => $dataCenters ?? [], 'alias' => $alias, ]; unset($parameters['_token']); $key = md5(uniqid(rand(), false)); $this->redis->setEx($key, 3600, serialize($parameters)); // remove data from session $this->session->remove('export_step_raw'); $this->session->remove('export_step'); $this->session->remove('formatter_step_raw'); $this->session->remove('formatter_step'); return $this->redirectToRoute('chill_main_export_download', ['key' => $key, 'alias' => $alias]); } protected function rebuildData($key) { if (null === $key) { throw $this->createNotFoundException('key does not exists'); } if ($this->redis->exists($key) !== 1) { $this->addFlash('error', $this->translator->trans('This report is not available any more')); throw $this->createNotFoundException('key does not exists'); } $serialized = $this->redis->get($key); if (false === $serialized) { throw new LogicException('the key could not be reached from redis'); } $rawData = unserialize($serialized); $alias = $rawData['alias']; $formCenters = $this->createCreateFormExport($alias, 'generate_centers'); $formCenters->submit($rawData['centers']); $dataCenters = $formCenters->getData(); $formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters); $formExport->submit($rawData['export']); $dataExport = $formExport->getData(); if (count($rawData['formatter']) > 0) { $formFormatter = $this->createCreateFormExport( $alias, 'generate_formatter', $dataExport ); $formFormatter->submit($rawData['formatter']); $dataFormatter = $formFormatter->getData(); } return [$dataCenters, $dataExport, $dataFormatter ?? null]; } /** * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export * @param string $alias * * @throws type * * @return Response */ protected function selectCentersStep(Request $request, $export, $alias) { /** @var \Chill\MainBundle\Export\ExportManager $exportManager */ $exportManager = $this->exportManager; $form = $this->createCreateFormExport($alias, 'centers'); if ($request->getMethod() === 'POST') { $form->handleRequest($request); if ($form->isValid()) { $this->logger->debug('form centers is valid', [ 'location' => __METHOD__, ]); $data = $form->getData(); // check ACL if ($exportManager->isGrantedForElement( $export, null, $exportManager->getPickedCenters($data['centers']) ) === false) { throw $this->createAccessDeniedException('you do not have ' . 'access to this export for those centers'); } $this->session->set( 'centers_step_raw', $request->request->all() ); $this->session->set('centers_step', $data); return $this->redirectToRoute('chill_main_export_new', [ 'step' => $this->getNextStep('centers', $export), 'alias' => $alias, ]); } } return $this->render( '@ChillMain/Export/new_centers_step.html.twig', [ 'form' => $form->createView(), 'export' => $export, ] ); } /** * get the next step. If $reverse === true, the previous step is returned. * * This method provides a centralized way of handling next/previous step. * * @param string $step the current step * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export * @param bool $reverse set to true to get the previous step * * @throws LogicException if there is no step before or after the given step * * @return string the next/current step */ private function getNextStep($step, $export, $reverse = false) { switch ($step) { case 'centers': if (false !== $reverse) { throw new LogicException("there is no step before 'export'"); } return 'export'; case 'export': if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { return $reverse ? 'centers' : 'formatter'; } if ($export instanceof \Chill\MainBundle\Export\DirectExportInterface) { return $reverse ? 'centers' : 'generate'; } // no break case 'formatter': return $reverse ? 'export' : 'generate'; case 'generate': if (false === $reverse) { throw new LogicException("there is no step after 'generate'"); } return 'formatter'; default: throw new LogicException("the step {$step} is not defined."); } } }