Merge branch 'master' into bootstrap5

This commit is contained in:
2021-07-30 20:31:46 +02:00
54 changed files with 1371 additions and 575 deletions

View File

@@ -1141,7 +1141,7 @@ class CRUDController extends AbstractController
*/
protected function getPaginatorFactory(): PaginatorFactory
{
return $this->container->get('chill_main.paginator_factory');
return $this->container->get(PaginatorFactory::class);
}
/**

View File

@@ -0,0 +1,59 @@
<?php
namespace Chill\MainBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Chill\MainBundle\Repository\NotificationRepository;
use Chill\MainBundle\Notification\NotificationRenderer;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Routing\Annotation\Route;
use Chill\MainBundle\Pagination\PaginatorFactory;
/**
* @Route("/{_locale}/notification")
*/
class NotificationController extends AbstractController
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
/**
* @Route("/show", name="chill_main_notification_show")
*/
public function showAction(
NotificationRepository $notificationRepository, NotificationRenderer $notificationRenderer,
PaginatorFactory $paginatorFactory)
{
$currentUser = $this->security->getUser();
$notificationsNbr = $notificationRepository->countAllForAttendee(($currentUser));
$paginator = $paginatorFactory->create($notificationsNbr);
$notifications = $notificationRepository->findAllForAttendee(
$currentUser,
$limit=$paginator->getItemsPerPage(),
$offset= $paginator->getCurrentPage()->getFirstItemNumber());
$templateData = array();
foreach ($notifications as $notification) {
$data = [
'template' => $notificationRenderer->getTemplate($notification),
'template_data' => $notificationRenderer->getTemplateData($notification),
'notification' => $notification
];
$templateData[] = $data;
}
return $this->render('@ChillMain/Notification/show.html.twig', [
'datas' => $templateData,
'notifications' => $notifications,
'paginator' => $paginator,
]);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Chill\MainBundle\Notification;
use Chill\MainBundle\Entity\Notification;
use Chill\PersonBundle\Notification\AccompanyingPeriodNotificationRenderer;
use Chill\ActivityBundle\Notification\ActivityNotificationRenderer;
final class NotificationRenderer
{
private array $renderers;
public function __construct(
AccompanyingPeriodNotificationRenderer $accompanyingPeriodNotificationRenderer,
ActivityNotificationRenderer $activityNotificationRenderer)
{
// TODO configure automatically
// TODO CREER UNE INTERFACE POUR ETRE SUR QUE LES RENDERERS SONT OK
$this->renderers[] = $accompanyingPeriodNotificationRenderer;
$this->renderers[] = $activityNotificationRenderer;
}
private function getRenderer(Notification $notification)
{
foreach ($this->renderers as $renderer) {
if($renderer->supports($notification)) {
return $renderer;
}
}
throw new \Exception('No renderer for '. $notification);
}
public function getTemplate(Notification $notification)
{
return $this->getRenderer($notification)->getTemplate();
}
public function getTemplateData(Notification $notification)
{
return $this->getRenderer($notification)->getTemplateData($notification);
}
}

View File

@@ -23,6 +23,8 @@ use Chill\MainBundle\Entity\Notification;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository;
use Chill\MainBundle\Entity\User;
use Doctrine\ORM\Query;
final class NotificationRepository implements ObjectRepository
{
@@ -59,8 +61,54 @@ final class NotificationRepository implements ObjectRepository
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
private function queryAllForAttendee(User $addressee, bool $countQuery=False): Query
{
$qb = $this->repository->createQueryBuilder('n');
$select = 'n';
if($countQuery) {
$select = 'count(n)';
}
$qb
->select($select)
->join('n.addressees', 'a')
->where('a = :addressee')
->setParameter('addressee', $addressee);
return $qb->getQuery();
}
/**
* @return int
*/
public function countAllForAttendee(User $addressee): int // TODO passer à attendees avec S
{
$query = $this->queryAllForAttendee($addressee, $countQuery=True);
return $query->getSingleScalarResult();
}
/**
* @return Notification[]
*/
public function findAllForAttendee(User $addressee, $limit = null, $offset = null): array // TODO passer à attendees avec S
{
$query = $this->queryAllForAttendee($addressee);
if($limit) {
$query = $query->setMaxResults($limit);
}
if($offset) {
$query = $query->setFirstResult($offset);
}
return $query->getResult();
}
public function getClassName() {
return Notification::class;
}
}

View File

@@ -0,0 +1,42 @@
{% extends "@ChillMain/layout.html.twig" %}
{% block content %}
<div id="container content">
<div class="grid-8 centered">
<h1>{{ "Notifications list" | trans }}</h1>
<!-- TODO : UNREAD & READ -->
{%for data in datas %}
{% set notification = data.notification %}
<dl class="chill_view_data">
<dt class="inline">{{ 'Message'|trans }}</dt>
<dd>{{ notification.message }}</dd>
</dl>
<dl class="chill_view_data">
<dt class="inline">{{ 'Date'|trans }}</dt>
<dd>{{ notification.date | date('long') }}</dd>
</dl>
<dl class="chill_view_data">
<dt class="inline">{{ 'Sender'|trans }}</dt>
<dd>{{ notification.sender }}</dd>
</dl>
<dl class="chill_view_data">
<dt class="inline">{{ 'Addressees'|trans }}</dt>
<dd>{{ notification.addressees |join(', ') }}</dd>
</dl>
<dl class="chill_view_data">
<dt class="inline">{{ 'Entity'|trans }}</dt>
<dd>
{% include data.template with data.template_data %}
</dd>
</dl>
{% endfor %}
</div>
</div>
{% endblock content %}

View File

@@ -54,9 +54,10 @@
<div class="row justify-content-center my-5">
{# Flash messages ! #}
{% if app.session.flashbag.all()|length > 0 %}
{% if app.session.flashbag.keys()|length > 0 %}
<div class="col-8 mb-5 flash_message">
{% for flashMessage in app.session.flashbag.get('success') %}
<div class="col-8 alert alert-success flash_message">
<span>{{ flashMessage|raw }}</span>

View File

@@ -1,5 +1,5 @@
{#
* Copyright (C) 2014-2021, Champs Libres Cooperative SCRLFS,
* Copyright (C) 2014-2021, Champs Libres Cooperative SCRLFS,
<info@champs-libres.coop> / <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
@@ -28,7 +28,7 @@
<div class="col-md-9 my-5">
{# Flash messages ! #}
{% if app.session.flashbag.all()|length > 0 %}
{% if app.session.flashbag.keys()|length > 0 %}
<div class="row justify-content-center mb-5">
{% for flashMessage in app.session.flashbag.get('success') %}

View File

@@ -26,7 +26,7 @@ use Symfony\Component\HttpFoundation\Request;
/**
* This class provide a set of tests for exports.
*
*
* The tests provided by this class will check basic things, like
* the type of value are conform to the expected, etc.
*
@@ -34,45 +34,45 @@ use Symfony\Component\HttpFoundation\Request;
*/
abstract class AbstractExportTest extends WebTestCase
{
use PrepareClientTrait;
/**
* Create an instance of the report to test
*
*
* @return \Chill\MainBundle\Export\ExportInterface an instance of the export to test
*/
public abstract function getExport();
/**
* Create possible combinaison of data (produced by the form).
*
*
* This data will be used to generate data providers using this data.
*
*
* @return array an array of data. Example : `array( array(), array('fields' => array(1,2,3), ...)` where an empty array and `array(1,2,3)` are possible values
*/
public abstract function getFormData();
/**
* get the possible modifiers which could apply in combination to this
* get the possible modifiers which could apply in combination to this
* export.
* .
*
* @return array of string[] an array which contains an array of possible modifiers. Example : `array( array('modifier_1', 'modifier_2'), array('modifier_1'), ...)`
* .
*
* @return array of string[] an array which contains an array of possible modifiers. Example : `array( array('modifier_1', 'modifier_2'), array('modifier_1'), ...)`
*/
abstract public function getModifiersCombination();
/**
* Return an array usable as ACL
*
*
* If this method is overridden, the returned result must be an array
* with this form :
*
*
* ```
* array(
* array(
* 'center' => //center instance
* array(
* 'center' => //center instance
* 'circles' => array(// array of circles instances )
* )
* );
@@ -84,15 +84,15 @@ abstract class AbstractExportTest extends WebTestCase
if (static::$kernel === null) {
static::bootKernel();
}
$em = static::$kernel->getContainer()
->get('doctrine.orm.entity_manager');
$centers = $em->getRepository('ChillMainBundle:Center')
->findAll();
$circles = $em->getRepository('ChillMainBundle:Scope')
->findAll();
if (count($centers) === 0) {
throw new \RuntimeException("No center found. Did you forget to "
. "run `doctrine:fixtures:load` command before ?");
@@ -101,7 +101,7 @@ abstract class AbstractExportTest extends WebTestCase
throw new \RuntimeException("No circle found. Did you forget to "
. "run `doctrine:fixtures:load` command before ?");
}
return array([
'center' => $centers[0],
'circles' => [
@@ -115,204 +115,203 @@ abstract class AbstractExportTest extends WebTestCase
public function testGetType()
{
$export = $this->getExport();
$this->assertInternalType('string', $export->getType(),
$this->assertInternalType('string', $export->getType(),
"Assert that the `getType` method return a string");
$this->assertNotEmpty($export->getType(), "Assert that the `getType` method"
. " does not return an empty string.");
}
/**
* Test that the description is not empty
*/
public function testGetDescription()
{
$export = $this->getExport();
$this->assertInternalType('string', $export->getDescription(),
$this->assertInternalType('string', $export->getDescription(),
"Assert that the `getDescription` method return a string");
$this->assertNotEmpty($export->getDescription(),
$this->assertNotEmpty($export->getDescription(),
"Assert that the `getDescription` method does not return an empty "
. "string.");
}
/**
* create data for `ìnitiateQuery` method
*/
public function dataProviderInitiateQuery()
{
$acl = $this->getAcl();
foreach($this->getModifiersCombination() as $modifiers) {
foreach($this->getFormData() as $data) {
yield array($modifiers, $acl, $data);
}
}
}
public function dataProviderGetQueryKeys()
{
foreach($this->getFormData() as $data) {
yield array($data);
}
}
/**
*
*
* test that the query returned is a QueryBuilder or a NativeQuery.
*
*
* If the query is a QueryBuilder, test that select and from is not empty.
*
* If the query is a native sql, test the query is not empty (length is
*
* If the query is a native sql, test the query is not empty (length is
* > 0).
*
*
* @dataProvider dataProviderInitiateQuery
*/
public function testInitiateQuery($modifiers, $acl, $data)
{
var_dump($data);
$query = $this->getExport()->initiateQuery($modifiers, $acl, $data);
$this->assertTrue($query instanceof QueryBuilder || $query instanceof NativeQuery,
sprintf("Assert that the returned query is an instance of %s or %s",
sprintf("Assert that the returned query is an instance of %s or %s",
QueryBuilder::class, Query::class));
if ($query instanceof QueryBuilder) {
$this->assertGreaterThanOrEqual(1, count($query->getDQLPart('select')),
"assert there is at least one 'select' part");
$this->assertGreaterThanOrEqual(1, count($query->getDQLPart('from')),
"assert there is at least one 'from' part");
} elseif ($query instanceof NativeQuery) {
$this->assertNotEmpty($query->getSQL(),
$this->assertNotEmpty($query->getSQL(),
"check that the SQL query is not empty");
}
}
/**
* Test that supportsModifier return :
*
*
* - an array of string, if the query is a QueryBuilder ;
* - nothing, if the query is a native SQL
*
*
* @dataProvider dataProviderInitiateQuery
*/
public function testSupportsModifier($modifiers, $acl, $data)
{
$export = $this->getExport();
$query = $export->initiateQuery($modifiers, $acl, $data);
if ($query instanceof QueryBuilder) {
$this->assertContainsOnly('string', $export->supportsModifiers(),
"Test that the `supportsModifiers` method returns only strings");
} elseif ($query instanceof NativeQuery) {
$this->assertTrue($export->supportsModifiers() === null ||
$this->assertTrue($export->supportsModifiers() === null ||
count($export->supportsModifiers()) === 0,
"Test that the `supportsModifier` methods returns null or an empty array");
}
}
/**
* Test required role is an instance of Role
*/
public function testRequiredRole()
{
$role = $this->getExport()->requiredRole();
$this->assertInstanceOf(Role::class, $role,
$this->assertInstanceOf(Role::class, $role,
sprintf("test that the returned value of `requiredRole` is an instance "
. "of %s", Role::class));
}
/**
* Test the formatters type are string
*/
public function testGetAllowedFormattersType()
{
$formattersTypes = $this->getExport()->getAllowedFormattersTypes();
$this->assertContainsOnly("string", $formattersTypes,
"Test that the method `getAllowedFormattersTypes` returns an array of string");
}
/**
* Test that the query keys are strings
*
*
* @param array $data
* @dataProvider dataProviderGetQueryKeys
*/
public function testGetQueryKeys(array $data)
{
$queryKeys = $this->getExport()->getQueryKeys($data);
$this->assertContainsOnly("string", $queryKeys,
"test that the query keys returned by `getQueryKeys` are only strings");
$this->assertGreaterThanOrEqual(1, count($queryKeys),
"test that there are at least one query key returned");
}
/**
*
* Test that
*
*
* Test that
*
* - the results have a correct form (are arrays or traversable)
* - each key in a row are present in getQueryKeys ;
* - each returned object of the `getLabels` method is callable
* - each result can be converted to string using this callable
* - each of this callable can provide a string for '_header'
*
*
* @param string[] $modifiers
* @param array $acl
* @param array $data
*
*
* @dataProvider dataProviderInitiateQuery
*/
public function testGetResultsAndLabels($modifiers, $acl, array $data)
{
// it is more convenient to group the `getResult` and `getLabels` test
// due to the fact that testing both methods use the same tools.
$queryKeys = $this->getExport()->getQueryKeys($data);
$query = $this->getExport()->initiateQuery($modifiers, $acl, $data);
// limit the result for the query for performance reason (only for QueryBuilder,
// not possible in NativeQuery)
if ($query instanceof QueryBuilder) {
$query->setMaxResults(1);
}
}
$results = $this->getExport()->getResult($query, $data);
$this->assertInternalType('array', $results,
$this->assertInternalType('array', $results,
"assert that the returned result is an array");
if (count($results) === 0) {
$this->markTestIncomplete("The result is empty. We cannot process tests "
. "on results");
}
// testing the result
$result = $results[0];
$this->assertTrue( $result instanceof \Traversable || is_array($result),
"test that each row in the result is traversable or an array");
foreach ($result as $key => $value) {
$this->assertContains($key, $queryKeys,
"test that each key is present in `getQueryKeys`");
$closure = $this->getExport()->getLabels($key, array($value), $data);
$this->assertTrue(is_callable($closure, false),
"test that the `getLabels` for key is a callable");
$this->assertTrue(is_string((string) call_user_func($closure, $value)),
sprintf("test that the callable return by `getLabels` for key %s "
. "is a string or an be converted to a string", $key));
$this->assertTrue(
// conditions
is_string((string) call_user_func($closure, '_header'))
@@ -322,13 +321,13 @@ abstract class AbstractExportTest extends WebTestCase
sprintf("Test that the callable return by `getLabels` for key %s "
. "can provide an header", $key)
);
}
}
}
/**
* Test that the translated title of the export is present the list,
* Test that the translated title of the export is present the list,
* and that the list of exports (under `/fr/exports/`) is still successfull
*/
public function testListExportPage()
@@ -338,17 +337,17 @@ abstract class AbstractExportTest extends WebTestCase
$export = $this->getExport();
$prophet= new \Prophecy\Prophet;
$container = static::$kernel->getContainer();
// store the locale in a request
$request = new Request();
$request->setLocale('fr');
$container->get('request_stack')->push($request);
// translate the title
$title = $container->get('translator')->trans($export->getTitle());
// performs the request to /fr/exports
$crawler = $client->request('GET', '/fr/exports/');
// and finally make tests
$this->assertTrue($client->getResponse()->isSuccessful(),
"test that the response of /fr/exports/ is successful");

View File

@@ -34,6 +34,10 @@ chill_password_recover:
resource: "@ChillMainBundle/config/routes/password_recover.yaml"
prefix: "public/{_locale}/password"
chill_main_notification:
resource: "@ChillMainBundle/config/routes/notification.yaml"
prefix: "{_locale}/notification"
chill_crud:
resource: "@ChillMainBundle"
type: CRUD

View File

@@ -33,3 +33,8 @@ services:
$logger: '@Psr\Log\LoggerInterface'
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
tags: ['controller.service_arguments']
Chill\MainBundle\Controller\NotificationController:
arguments:
$security: '@Symfony\Component\Security\Core\Security'
tags: ['controller.service_arguments']

View File

@@ -4,7 +4,11 @@ services:
$logger: '@Psr\Log\LoggerInterface'
$twig: '@Twig\Environment'
$mailer: '@swiftmailer.mailer.default'
# $mailerTransporter: '@swiftmailer.transport'
# $mailerTransporter: '@swiftmailer.transport'
$router: '@Symfony\Component\Routing\RouterInterface'
$translator: '@Symfony\Component\Translation\TranslatorInterface'
$routeParameters: '%chill_main.notifications%'
Chill\MainBundle\Notification\NotificationRenderer:
autoconfigure: true
autowire: true

View File

@@ -2,13 +2,15 @@ services:
chill_main.paginator_factory:
class: Chill\MainBundle\Pagination\PaginatorFactory
public: true
autowire: true
autoconfigure: true
arguments:
- "@request_stack"
- "@router"
- "%chill_main.pagination.item_per_page%"
Chill\MainBundle\Pagination\PaginatorFactory: '@chill_main.paginator_factory'
chill_main.paginator.twig_extensions:
class: Chill\MainBundle\Pagination\ChillPaginationTwig
tags:

View File

@@ -0,0 +1,6 @@
years_old: >-
{age, plural,
one {# an}
many {# ans}
other {# ans}
}