Feature: [saved export] Générate a report from a saved export

This commit is contained in:
Julien Fastré 2022-11-08 18:02:26 +01:00
parent aec4c52567
commit 79e9906a05
5 changed files with 86 additions and 6 deletions

View File

@ -19,21 +19,24 @@ use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Form\Type\Export\FormatterType;
use Chill\MainBundle\Form\Type\Export\PickCenterType;
use Chill\MainBundle\Redis\ChillRedis;
use Chill\MainBundle\Security\Authorization\SavedExportVoter;
use Doctrine\ORM\EntityManagerInterface;
use LogicException;
use Psr\Log\LoggerInterface;
use RedisException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function count;
use function serialize;
use function unserialize;
@ -152,6 +155,25 @@ class ExportController extends AbstractController
);
}
/**
* @Route("/{_locale}/exports/generate-from-saved/{id}", name="chill_main_export_generate_from_saved")
*
* @throws RedisException
*/
public function generateFromSavedExport(SavedExport $savedExport): RedirectResponse
{
$this->denyAccessUnlessGranted(SavedExportVoter::GENERATE, $savedExport);
$key = md5(uniqid((string) mt_rand(), false));
$this->redis->setEx($key, 3600, serialize($savedExport->getOptions()));
return $this->redirectToRoute(
'chill_main_export_download',
['alias' => $savedExport->getExportAlias(), 'key' => $key, 'prevent_save' => true]
);
}
/**
* Render the list of available exports.
*/

View File

@ -49,9 +49,14 @@ window.addEventListener("DOMContentLoaded", function(e) {
data-download-text="{{ "Download your report"|trans|escape('html_attr') }}"
><span id="waiting_text">{{ "Waiting for your report"|trans ~ '...' }}</span></div>
<div>
<a href="{{ chill_path_add_return_path('chill_main_export_save_from_key', { alias: alias, key: app.request.query.get('key')}) }}">{{ 'Save'|trans }}</a>
</div>
<ul class="record_actions sticky-form-buttons">
<li class="cancel"><a href="{{ chill_return_path_or('chill_main_export_index') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a></li>
</div>
{% if not app.request.query.has('prevent_save') %}
<li>
<a href="{{ chill_path_add_return_path('chill_main_export_save_from_key', { alias: alias, key: app.request.query.get('key')}) }}" class="btn btn-save">{{ 'Save'|trans }}</a>
</li>
{% endif %}
</ul>
</div>
{% endblock content %}

View File

@ -15,10 +15,16 @@
{% for s in saveds %}
<div class="col-6 col-md-4 mb-3">
<h2>{{ s.saved.title }}</h2>
<p>{{ s.export.title|trans }}</p>
<p><strong>{{ s.export.title|trans }}</strong></p>
<div>
{{ s.saved.description|chill_markdown_to_html }}
</div>
<ul class="record_actions">
<li><a href="#" class="btn btn-delete"></a></li>
<li><a href="#" class="btn btn-edit"></a></li>
<li><a href="{{ path('chill_main_export_generate_from_saved', { id: s.saved.id }) }}" class="btn btn-action"><i class="fa fa-cog"></i></a></li>
</ul>
</div>
{% endfor %}
</div>
@ -39,6 +45,12 @@
<div>
{{ s.saved.description|chill_markdown_to_html }}
</div>
<ul class="record_actions">
<li><a href="#" class="btn btn-delete"></a></li>
<li><a href="#" class="btn btn-edit"></a></li>
<li><a href="{{ path('chill_main_export_generate_from_saved', { id: s.saved.id }) }}" class="btn btn-action"><i class="fa fa-cog"></i></a></li>
</ul>
</div>
</div>
{% endfor %}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Entity\SavedExport;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use UnexpectedValueException;
class SavedExportVoter extends Voter
{
public const GENERATE = 'CHLL_MAIN_EXPORT_SAVED_GENERATE';
protected function supports($attribute, $subject): bool
{
return $subject instanceof SavedExport && self::GENERATE === $attribute;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
{
/** @var SavedExport $subject */
switch ($attribute) {
case self::GENERATE:
return $subject->getUser() === $token->getUser();
default:
throw new UnexpectedValueException('attribute not supported: ' . $attribute);
}
}
}

View File

@ -50,6 +50,8 @@ services:
tags:
- { name: security.voter }
Chill\MainBundle\Security\Authorization\SavedExportVoter: ~
Chill\MainBundle\Security\PasswordRecover\TokenManager:
arguments:
$secret: '%kernel.secret%'