mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-12 13:24:25 +00:00
Merge branch 'upgrade-sf3' into edit-user-password
This commit is contained in:
commit
af803cc87d
@ -16,19 +16,11 @@ stages:
|
|||||||
- build-doc
|
- build-doc
|
||||||
- deploy-doc
|
- deploy-doc
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test:php-7.1:
|
|
||||||
stage: test
|
|
||||||
<<: *test_definition
|
|
||||||
image: chill/ci-image:php-7.1
|
|
||||||
script: vendor/bin/phpunit
|
|
||||||
|
|
||||||
test:php-7.2:
|
test:php-7.2:
|
||||||
stage: test
|
stage: test
|
||||||
<<: *test_definition
|
<<: *test_definition
|
||||||
image: chill/ci-image:php-7.2
|
image: chill/ci-image:php-7.2
|
||||||
script: vendor/bin/phpunit
|
script: APP_ENV=test vendor/bin/phpunit
|
||||||
|
|
||||||
# deploy documentation
|
# deploy documentation
|
||||||
api-doc-build:
|
api-doc-build:
|
||||||
|
@ -10,6 +10,8 @@ use Chill\MainBundle\DependencyInjection\TimelineCompilerClass;
|
|||||||
use Chill\MainBundle\DependencyInjection\RoleProvidersCompilerPass;
|
use Chill\MainBundle\DependencyInjection\RoleProvidersCompilerPass;
|
||||||
use Chill\MainBundle\DependencyInjection\CompilerPass\ExportsCompilerPass;
|
use Chill\MainBundle\DependencyInjection\CompilerPass\ExportsCompilerPass;
|
||||||
use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass;
|
use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass;
|
||||||
|
use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass;
|
||||||
|
use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass;
|
||||||
|
|
||||||
|
|
||||||
class ChillMainBundle extends Bundle
|
class ChillMainBundle extends Bundle
|
||||||
@ -23,5 +25,7 @@ class ChillMainBundle extends Bundle
|
|||||||
$container->addCompilerPass(new RoleProvidersCompilerPass());
|
$container->addCompilerPass(new RoleProvidersCompilerPass());
|
||||||
$container->addCompilerPass(new ExportsCompilerPass());
|
$container->addCompilerPass(new ExportsCompilerPass());
|
||||||
$container->addCompilerPass(new WidgetsCompilerPass());
|
$container->addCompilerPass(new WidgetsCompilerPass());
|
||||||
|
$container->addCompilerPass(new NotificationCounterCompilerPass());
|
||||||
|
$container->addCompilerPass(new MenuCompilerPass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -412,9 +412,29 @@ class ExportController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function downloadResultAction(Request $request, $alias)
|
public function downloadResultAction(Request $request, $alias)
|
||||||
{
|
{
|
||||||
return $this->render("ChillMainBundle:Export:download.html.twig", [
|
/* @var $exportManager \Chill\MainBundle\Export\ExportManager */
|
||||||
|
$exportManager = $this->get('chill.main.export_manager');
|
||||||
|
$formCenters = $this->createCreateFormExport($alias, 'generate_centers');
|
||||||
|
$formCenters->handleRequest($request);
|
||||||
|
$dataCenters = $formCenters->getData();
|
||||||
|
|
||||||
|
$formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters);
|
||||||
|
$formExport->handleRequest($request);
|
||||||
|
$dataExport = $formExport->getData();
|
||||||
|
|
||||||
|
$formatterAlias = $exportManager->getFormatterAlias($dataExport['export']);
|
||||||
|
$formater = $exportManager->getFormatter($formatterAlias);
|
||||||
|
|
||||||
|
$viewVariables = [
|
||||||
'alias' => $alias
|
'alias' => $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("ChillMainBundle:Export:download.html.twig", $viewVariables);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,41 +5,34 @@ namespace Chill\MainBundle\Controller;
|
|||||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Security\Core\SecurityContextInterface;
|
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||||
|
|
||||||
class LoginController extends Controller
|
class LoginController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
|
* @var AuthenticationUtils
|
||||||
|
*/
|
||||||
|
protected $helper;
|
||||||
|
|
||||||
|
public function __construct(AuthenticationUtils $helper)
|
||||||
|
{
|
||||||
|
$this->helper = $helper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a login form
|
||||||
*
|
*
|
||||||
* @todo Improve this with http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements#added-a-security-error-helper
|
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function loginAction(Request $request)
|
public function loginAction(Request $request)
|
||||||
{
|
{
|
||||||
|
|
||||||
$session = $request->getSession();
|
|
||||||
|
|
||||||
if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
|
|
||||||
$error = $request->attributes->get(
|
|
||||||
SecurityContextInterface::AUTHENTICATION_ERROR
|
|
||||||
);
|
|
||||||
} elseif (null !== $session && $session->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
|
|
||||||
$error = $session->get(SecurityContextInterface::AUTHENTICATION_ERROR);
|
|
||||||
$session->remove(SecurityContextInterface::AUTHENTICATION_ERROR);
|
|
||||||
} else {
|
|
||||||
$error = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$lastUsername = (null === $session) ?
|
|
||||||
'' : $session->get(SecurityContextInterface::LAST_USERNAME);
|
|
||||||
|
|
||||||
|
|
||||||
return $this->render('ChillMainBundle:Login:login.html.twig', array(
|
return $this->render('ChillMainBundle:Login:login.html.twig', array(
|
||||||
'last_username' => $lastUsername,
|
'last_username' => $this->helper->getLastUsername(),
|
||||||
'error' => (empty($error)) ? $error : $error->getMessage()
|
'error' => $this->helper->getLastAuthenticationError()
|
||||||
));
|
));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function LoginCheckAction(Request $request)
|
public function LoginCheckAction(Request $request)
|
||||||
|
@ -11,6 +11,7 @@ use Chill\MainBundle\Form\PermissionsGroupType;
|
|||||||
use Symfony\Component\Security\Core\Role\Role;
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
use Symfony\Component\Security\Core\Role\RoleInterface;
|
use Symfony\Component\Security\Core\Role\RoleInterface;
|
||||||
use Chill\MainBundle\Entity\Scope;
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
use Chill\MainBundle\Form\Type\ComposedRoleScopeType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PermissionsGroup controller.
|
* PermissionsGroup controller.
|
||||||
@ -491,7 +492,7 @@ class PermissionsGroupController extends Controller
|
|||||||
->setAction($this->generateUrl('admin_permissionsgroup_add_role_scope',
|
->setAction($this->generateUrl('admin_permissionsgroup_add_role_scope',
|
||||||
array('id' => $permissionsGroup->getId())))
|
array('id' => $permissionsGroup->getId())))
|
||||||
->setMethod('PUT')
|
->setMethod('PUT')
|
||||||
->add('composed_role_scope', 'composed_role_scope')
|
->add('composed_role_scope', ComposedRoleScopeType::class)
|
||||||
->add('submit', SubmitType::class, array('label' => 'Add permission'))
|
->add('submit', SubmitType::class, array('label' => 'Add permission'))
|
||||||
->getForm()
|
->getForm()
|
||||||
;
|
;
|
||||||
|
89
Controller/PostalCodeController.php
Normal file
89
Controller/PostalCodeController.php
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class PostalCodeController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var TranslatableStringHelper
|
||||||
|
*/
|
||||||
|
protected $translatableStringHelper;
|
||||||
|
|
||||||
|
public function __construct(TranslatableStringHelper $translatableStringHelper)
|
||||||
|
{
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @Route(
|
||||||
|
* "{_locale}/postalcode/search"
|
||||||
|
* )
|
||||||
|
* @param Request $request
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function searchAction(Request $request)
|
||||||
|
{
|
||||||
|
$pattern = $request->query->getAlnum('q', '');
|
||||||
|
|
||||||
|
if (empty($pattern)) {
|
||||||
|
return new JsonResponse(["results" => [], "pagination" => [ "more" => false]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $this->getDoctrine()->getManager()
|
||||||
|
->createQuery(sprintf(
|
||||||
|
"SELECT p.id AS id, p.name AS name, p.code AS code, "
|
||||||
|
. "country.name AS country_name, "
|
||||||
|
. "country.countryCode AS country_code "
|
||||||
|
. "FROM %s p "
|
||||||
|
. "JOIN p.country country "
|
||||||
|
. "WHERE LOWER(p.name) LIKE LOWER(:pattern) OR LOWER(p.code) LIKE LOWER(:pattern) "
|
||||||
|
. "ORDER BY code"
|
||||||
|
, PostalCode::class)
|
||||||
|
)
|
||||||
|
->setParameter('pattern', '%'.$pattern.'%')
|
||||||
|
->setMaxResults(30)
|
||||||
|
;
|
||||||
|
|
||||||
|
$results = \array_map(function($row) {
|
||||||
|
$row['country_name'] = $this->translatableStringHelper->localize($row['country_name']);
|
||||||
|
$row['text'] = $row['code']." ".$row["name"]." (".$row['country_name'].")";
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}, $query->getResult(Query::HYDRATE_ARRAY));
|
||||||
|
|
||||||
|
return new JsonResponse([ 'results' => $results, "pagination" => [ "more" => false ] ]);
|
||||||
|
}
|
||||||
|
}
|
39
Controller/UIController.php
Normal file
39
Controller/UIController.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
use Chill\MainBundle\Templating\UI\CountNotificationUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class UIController extends Controller
|
||||||
|
{
|
||||||
|
public function showNotificationUserCounterAction(
|
||||||
|
CountNotificationUser $counter
|
||||||
|
) {
|
||||||
|
$nb = $counter->getSumNotification($this->getUser());
|
||||||
|
|
||||||
|
return $this->render('ChillMainBundle:UI:notification_user_counter.html.twig', [
|
||||||
|
'nb' => $nb
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,7 @@ use Chill\MainBundle\DependencyInjection\Configuration;
|
|||||||
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
|
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
|
||||||
use Chill\MainBundle\Doctrine\DQL\Unaccent;
|
use Chill\MainBundle\Doctrine\DQL\Unaccent;
|
||||||
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
|
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
|
||||||
|
use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class load config for chillMainExtension.
|
* This class load config for chillMainExtension.
|
||||||
@ -92,7 +93,13 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
|
|||||||
$loader->load('services/pagination.yml');
|
$loader->load('services/pagination.yml');
|
||||||
$loader->load('services/export.yml');
|
$loader->load('services/export.yml');
|
||||||
$loader->load('services/form.yml');
|
$loader->load('services/form.yml');
|
||||||
|
$loader->load('services/validator.yml');
|
||||||
|
$loader->load('services/widget.yml');
|
||||||
|
$loader->load('services/controller.yml');
|
||||||
|
$loader->load('services/routing.yml');
|
||||||
|
$loader->load('services/fixtures.yml');
|
||||||
|
$loader->load('services/menu.yml');
|
||||||
|
$loader->load('services/security.yml');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getConfiguration(array $config, ContainerBuilder $container)
|
public function getConfiguration(array $config, ContainerBuilder $container)
|
||||||
@ -136,11 +143,23 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
|
|||||||
'unaccent' => Unaccent::class,
|
'unaccent' => Unaccent::class,
|
||||||
'GET_JSON_FIELD_BY_KEY' => GetJsonFieldByKey::class,
|
'GET_JSON_FIELD_BY_KEY' => GetJsonFieldByKey::class,
|
||||||
'AGGREGATE' => JsonAggregate::class
|
'AGGREGATE' => JsonAggregate::class
|
||||||
)
|
),
|
||||||
|
'numeric_functions' => [
|
||||||
|
'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class
|
||||||
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
//add dbal types (default entity_manager)
|
||||||
|
$container->prependExtensionConfig('doctrine', array(
|
||||||
|
'dbal' => [
|
||||||
|
'types' => [
|
||||||
|
'dateinterval' => \Chill\MainBundle\Doctrine\Type\NativeDateIntervalType::class
|
||||||
|
]
|
||||||
|
]
|
||||||
|
));
|
||||||
|
|
||||||
//add current route to chill main
|
//add current route to chill main
|
||||||
$container->prependExtensionConfig('chill_main', array(
|
$container->prependExtensionConfig('chill_main', array(
|
||||||
'routing' => array(
|
'routing' => array(
|
||||||
|
@ -31,6 +31,7 @@ use Symfony\Component\DependencyInjection\Definition;
|
|||||||
* - chill.export_formatter
|
* - chill.export_formatter
|
||||||
* - chill.export_aggregator
|
* - chill.export_aggregator
|
||||||
* - chill.export_filter
|
* - chill.export_filter
|
||||||
|
* - chill.export_elements_provider
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
@ -53,6 +54,7 @@ class ExportsCompilerPass implements CompilerPassInterface
|
|||||||
$this->compileFilters($chillManagerDefinition, $container);
|
$this->compileFilters($chillManagerDefinition, $container);
|
||||||
$this->compileAggregators($chillManagerDefinition, $container);
|
$this->compileAggregators($chillManagerDefinition, $container);
|
||||||
$this->compileFormatters($chillManagerDefinition, $container);
|
$this->compileFormatters($chillManagerDefinition, $container);
|
||||||
|
$this->compileExportElementsProvider($chillManagerDefinition, $container);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function compileExports(Definition $chillManagerDefinition,
|
private function compileExports(Definition $chillManagerDefinition,
|
||||||
@ -174,5 +176,35 @@ class ExportsCompilerPass implements CompilerPassInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function compileExportElementsProvider(Definition $chillManagerDefinition,
|
||||||
|
ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
$taggedServices = $container->findTaggedServiceIds(
|
||||||
|
'chill.export_elements_provider'
|
||||||
|
);
|
||||||
|
|
||||||
|
$knownAliases = array();
|
||||||
|
|
||||||
|
foreach ($taggedServices as $id => $tagAttributes) {
|
||||||
|
foreach ($tagAttributes as $attributes) {
|
||||||
|
if (!isset($attributes["prefix"])) {
|
||||||
|
throw new \LogicException("the 'prefix' attribute is missing in your ".
|
||||||
|
"service '$id' definition");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_search($attributes["prefix"], $knownAliases)) {
|
||||||
|
throw new \LogicException("There is already a chill.export_elements_provider service with prefix "
|
||||||
|
.$attributes["prefix"].". Choose another prefix.");
|
||||||
|
}
|
||||||
|
$knownAliases[] = $attributes["prefix"];
|
||||||
|
|
||||||
|
$chillManagerDefinition->addMethodCall(
|
||||||
|
'addExportElementsProvider',
|
||||||
|
array(new Reference($id), $attributes["prefix"])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
49
DependencyInjection/CompilerPass/MenuCompilerPass.php
Normal file
49
DependencyInjection/CompilerPass/MenuCompilerPass.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\DependencyInjection\CompilerPass;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
use Chill\MainBundle\Routing\MenuComposer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class MenuCompilerPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
if (!$container->hasDefinition('chill.main.menu_composer')) {
|
||||||
|
throw new \LogicException(sprintf("The service %s does not exists in "
|
||||||
|
. "container.", MenuComposer::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
$menuComposerDefinition = $container->getDefinition('chill.main.menu_composer');
|
||||||
|
|
||||||
|
foreach ($container->findTaggedServiceIds('chill.menu_builder') as $id => $tags) {
|
||||||
|
$class = $container->getDefinition($id)->getClass();
|
||||||
|
foreach ($class::getMenuIds() as $menuId) {
|
||||||
|
$menuComposerDefinition
|
||||||
|
->addMethodCall('addLocalMenuBuilder', [new Reference($id), $menuId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\DependencyInjection\CompilerPass;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Chill\MainBundle\Templating\UI\CountNotificationUser;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class NotificationCounterCompilerPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
if (!$container->hasDefinition(CountNotificationUser::class)) {
|
||||||
|
throw new \LogicException("The service ".CountNotificationUser::class." "
|
||||||
|
. "should be defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
$notificationCounterDefinition = $container->getDefinition(CountNotificationUser::class);
|
||||||
|
|
||||||
|
foreach ($container->findTaggedServiceIds('chill.count_notification.user') as $id => $tags) {
|
||||||
|
$notificationCounterDefinition
|
||||||
|
->addMethodCall('addNotificationCounter', [new Reference($id)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
Doctrine/DQL/JsonbExistsInArray.php
Normal file
41
Doctrine/DQL/JsonbExistsInArray.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Doctrine\DQL;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||||
|
use Doctrine\ORM\Query\Lexer;
|
||||||
|
use Doctrine\ORM\Query\Parser;
|
||||||
|
use Doctrine\ORM\Query\SqlWalker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class JsonbExistsInArray extends FunctionNode
|
||||||
|
{
|
||||||
|
private $expr1;
|
||||||
|
private $expr2;
|
||||||
|
|
||||||
|
public function getSql(SqlWalker $sqlWalker): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'jsonb_exists(%s, %s)',
|
||||||
|
$this->expr1->dispatch($sqlWalker),
|
||||||
|
$sqlWalker->walkInputParameter($this->expr2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parse(Parser $parser): void
|
||||||
|
{
|
||||||
|
$parser->match(Lexer::T_IDENTIFIER);
|
||||||
|
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||||
|
$this->expr1 = $parser->StringPrimary();
|
||||||
|
$parser->match(Lexer::T_COMMA);
|
||||||
|
$this->expr2 = $parser->InputParameter();
|
||||||
|
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||||
|
}
|
||||||
|
}
|
111
Doctrine/Type/NativeDateIntervalType.php
Normal file
111
Doctrine/Type/NativeDateIntervalType.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Doctrine\Type;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Types\DateIntervalType;
|
||||||
|
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||||
|
use Doctrine\DBAL\Types\ConversionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-declare the date interval to use the implementation of date interval in
|
||||||
|
* postgreql
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class NativeDateIntervalType extends DateIntervalType
|
||||||
|
{
|
||||||
|
const FORMAT = '%rP%YY%MM%DDT%HH%IM%SS';
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return \Doctrine\DBAL\Types\Type::DATEINTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
|
||||||
|
{
|
||||||
|
return 'INTERVAL';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
if (null === $value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof \DateInterval) {
|
||||||
|
return $value->format(self::FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateInterval']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
if ($value === null || $value instanceof \DateInterval) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$strings = explode(' ', $value);
|
||||||
|
|
||||||
|
if (count($strings) === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$intervalSpec = 'P';
|
||||||
|
\reset($strings);
|
||||||
|
|
||||||
|
do {
|
||||||
|
$intervalSpec .= $this->convertEntry($strings);
|
||||||
|
} while (next($strings) !== FALSE);
|
||||||
|
|
||||||
|
return new \DateInterval($intervalSpec);
|
||||||
|
} catch (\Exception $exception) {
|
||||||
|
throw $this->createConversionException($value, $exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertEntry(&$strings)
|
||||||
|
{
|
||||||
|
$current = \current($strings);
|
||||||
|
|
||||||
|
if (is_numeric($current)) {
|
||||||
|
$next = \next($strings);
|
||||||
|
switch($next) {
|
||||||
|
case 'year':
|
||||||
|
case 'years':
|
||||||
|
$unit = 'Y';
|
||||||
|
break;
|
||||||
|
case 'mon':
|
||||||
|
case 'mons':
|
||||||
|
$unit = 'M';
|
||||||
|
break;
|
||||||
|
case 'day':
|
||||||
|
case 'days':
|
||||||
|
$unit = 'D';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw $this->createConversionException(implode('', $strings));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $current.$unit;
|
||||||
|
|
||||||
|
} elseif (\preg_match('/([0-9]{2}\:[0-9]{2}:[0-9]{2})/', $v) === 1) {
|
||||||
|
$tExploded = explode(':', $v);
|
||||||
|
$intervalSpec = 'T';
|
||||||
|
$intervalSpec.= $tExploded[0].'H';
|
||||||
|
$intervalSpec.= $tExploded[1].'M';
|
||||||
|
$intervalSpec.= $tExploded[2].'S';
|
||||||
|
|
||||||
|
return $intervalSpec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createConversionException($value, $exception = null)
|
||||||
|
{
|
||||||
|
return ConversionException::conversionFailedFormat($value, $this->getName(), 'xx year xx mons xx days 01:02:03', $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -141,6 +141,15 @@ class Address
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function createFromAddress(Address $original) : Address
|
||||||
|
{
|
||||||
|
return (new Address())
|
||||||
|
->setPostcode($original->getPostcode())
|
||||||
|
->setStreetAddress1($original->getStreetAddress1())
|
||||||
|
->setStreetAddress2($original->getStreetAddress2())
|
||||||
|
->setValidFrom($original->getValidFrom())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,24 @@ class User implements AdvancedUserInterface {
|
|||||||
*/
|
*/
|
||||||
private $username;
|
private $username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $usernameCanonical;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $emailCanonical;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
@ -115,9 +133,47 @@ class User implements AdvancedUserInterface {
|
|||||||
return $this->username;
|
return $this->username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getUsernameCanonical()
|
||||||
|
{
|
||||||
|
return $this->usernameCanonical;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEmail()
|
||||||
|
{
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEmailCanonical()
|
||||||
|
{
|
||||||
|
return $this->emailCanonical;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUsernameCanonical($usernameCanonical)
|
||||||
|
{
|
||||||
|
$this->usernameCanonical = $usernameCanonical;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEmail($email)
|
||||||
|
{
|
||||||
|
$this->email = $email;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEmailCanonical($emailCanonical)
|
||||||
|
{
|
||||||
|
$this->emailCanonical = $emailCanonical;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function setPassword($password)
|
function setPassword($password)
|
||||||
{
|
{
|
||||||
$this->password = $password;
|
$this->password = $password;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
Export/ExportElementsProviderInterface.php
Normal file
35
Export/ExportElementsProviderInterface.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Export;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to provide export elements dynamically.
|
||||||
|
*
|
||||||
|
* The typical use case is providing exports or aggregators depending on
|
||||||
|
* dynamic data. Example: providing exports for reports, reports depending
|
||||||
|
* on data stored in database.
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
interface ExportElementsProviderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return ExportElementInterface[]
|
||||||
|
*/
|
||||||
|
public function getExportElements();
|
||||||
|
}
|
@ -169,6 +169,26 @@ class ExportManager
|
|||||||
$this->formatters[$alias] = $formatter;
|
$this->formatters[$alias] = $formatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addExportElementsProvider(ExportElementsProviderInterface $provider, $prefix)
|
||||||
|
{
|
||||||
|
foreach ($provider->getExportElements() as $suffix => $element) {
|
||||||
|
$alias = $prefix.'_'.$suffix;
|
||||||
|
|
||||||
|
if ($element instanceof ExportInterface) {
|
||||||
|
$this->addExport($element, $alias);
|
||||||
|
} elseif ($element instanceof FilterInterface) {
|
||||||
|
$this->addFilter($element, $alias);
|
||||||
|
} elseif ($element instanceof AggregatorInterface) {
|
||||||
|
$this->addAggregator($element, $alias);
|
||||||
|
} elseif ($element instanceof FormatterInterface) {
|
||||||
|
$this->addFormatter($element, $alias);
|
||||||
|
} else {
|
||||||
|
throw new \LogicException("This element ".\get_class($element)." "
|
||||||
|
. "is not an instance of export element");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @return string[] the existing type for known exports
|
* @return string[] the existing type for known exports
|
||||||
@ -437,16 +457,16 @@ class ExportManager
|
|||||||
|
|
||||||
$result = $export->getResult($query, $data[ExportType::EXPORT_KEY]);
|
$result = $export->getResult($query, $data[ExportType::EXPORT_KEY]);
|
||||||
|
|
||||||
if (!is_array($result)) {
|
if (!is_iterable($result)) {
|
||||||
throw new \UnexpectedValueException(
|
throw new \UnexpectedValueException(
|
||||||
sprintf(
|
sprintf(
|
||||||
'The result of the export should be an array, %s given',
|
'The result of the export should be an iterable, %s given',
|
||||||
gettype($result)
|
gettype($result)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @var $formatter Formatter\CSVFormatter */
|
/* @var $formatter FormatterInterface */
|
||||||
$formatter = $this->getFormatter($this->getFormatterAlias($data));
|
$formatter = $this->getFormatter($this->getFormatterAlias($data));
|
||||||
$filtersData = array();
|
$filtersData = array();
|
||||||
$aggregatorsData = array();
|
$aggregatorsData = array();
|
||||||
|
85
Form/ChoiceLoader/PostalCodeChoiceLoader.php
Normal file
85
Form/ChoiceLoader/PostalCodeChoiceLoader.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs-Libres <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Form\ChoiceLoader;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||||
|
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||||
|
use Chill\MainBundle\Repository\PostalCodeRepository;
|
||||||
|
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class PostalCodeChoiceLoader implements ChoiceLoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var PostalCodeRepository
|
||||||
|
*/
|
||||||
|
protected $postalCodeRepository;
|
||||||
|
|
||||||
|
protected $lazyLoadedPostalCodes = [];
|
||||||
|
|
||||||
|
public function __construct(PostalCodeRepository $postalCodeRepository)
|
||||||
|
{
|
||||||
|
$this->postalCodeRepository = $postalCodeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadChoiceList($value = null): ChoiceListInterface
|
||||||
|
{
|
||||||
|
$list = new \Symfony\Component\Form\ChoiceList\ArrayChoiceList(
|
||||||
|
$this->lazyLoadedPostalCodes,
|
||||||
|
function(PostalCode $pc) use ($value) {
|
||||||
|
return \call_user_func($value, $pc);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadChoicesForValues(array $values, $value = null)
|
||||||
|
{
|
||||||
|
$choices = [];
|
||||||
|
|
||||||
|
foreach($values as $value) {
|
||||||
|
$choices[] = $this->postalCodeRepository->find($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadValuesForChoices(array $choices, $value = null)
|
||||||
|
{
|
||||||
|
$values = [];
|
||||||
|
|
||||||
|
foreach ($choices as $choice) {
|
||||||
|
if (NULL === $choice) {
|
||||||
|
$values[] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = \call_user_func($value, $choice);
|
||||||
|
$values[] = $id;
|
||||||
|
$this->lazyLoadedPostalCodes[$id] = $choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,9 @@ use Symfony\Component\Form\AbstractType;
|
|||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||||
|
use Chill\MainBundle\Entity\Address;
|
||||||
|
use Chill\MainBundle\Form\Type\PostalCodeType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A type to create/update Address entity
|
* A type to create/update Address entity
|
||||||
@ -46,12 +49,17 @@ class AddressType extends AbstractType
|
|||||||
'placeholder' => 'Choose a postal code',
|
'placeholder' => 'Choose a postal code',
|
||||||
'required' => true
|
'required' => true
|
||||||
))
|
))
|
||||||
->add('validFrom', 'date', array(
|
->add('validFrom', DateType::class, array(
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'widget' => 'single_text',
|
'widget' => 'single_text',
|
||||||
'format' => 'dd-MM-yyyy'
|
'format' => 'dd-MM-yyyy'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver->setDefault('data_class', Address::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
61
Form/Type/ChillCollectionType.php
Normal file
61
Form/Type/ChillCollectionType.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Form\Type;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormView;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Form\FormInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available options :
|
||||||
|
*
|
||||||
|
* - `button_add_label`
|
||||||
|
* - `button_remove_label`
|
||||||
|
* - `identifier`: an identifier to identify the kind of collecton. Useful if some
|
||||||
|
* javascript should be launched associated to `add_entry`, `remove_entry` events.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class ChillCollectionType extends AbstractType
|
||||||
|
{
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver
|
||||||
|
->setDefaults([
|
||||||
|
'button_add_label' => 'Add an entry',
|
||||||
|
'button_remove_label' => 'Remove entry',
|
||||||
|
'identifier' => ''
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||||
|
{
|
||||||
|
$view->vars['button_add_label'] = $options['button_add_label'];
|
||||||
|
$view->vars['button_remove_label'] = $options['button_remove_label'];
|
||||||
|
$view->vars['allow_delete'] = (int) $options['allow_delete'];
|
||||||
|
$view->vars['allow_add'] = (int) $options['allow_add'];
|
||||||
|
$view->vars['identifier'] = $options['identifier'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent()
|
||||||
|
{
|
||||||
|
return \Symfony\Component\Form\Extension\Core\Type\CollectionType::class;
|
||||||
|
}
|
||||||
|
}
|
92
Form/Type/DataTransformer/DateIntervalTransformer.php
Normal file
92
Form/Type/DataTransformer/DateIntervalTransformer.php
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Form\Type\DataTransformer;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\DataTransformerInterface;
|
||||||
|
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||||
|
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class DateIntervalTransformer implements DataTransformerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param \DateInterval $value
|
||||||
|
* @throws UnexpectedTypeException
|
||||||
|
*/
|
||||||
|
public function transform($value)
|
||||||
|
{
|
||||||
|
if (empty($value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$value instanceof \DateInterval) {
|
||||||
|
throw new UnexpectedTypeException($value, \DateInterval::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value->d > 0) {
|
||||||
|
// we check for weeks (weeks are converted to 7 days)
|
||||||
|
if ($value->d % 7 === 0) {
|
||||||
|
return [
|
||||||
|
'n' => $value->d / 7,
|
||||||
|
'unit' => 'W'
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
'n' => $value->d,
|
||||||
|
'unit' => 'D'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} elseif ($value->m > 0) {
|
||||||
|
return [
|
||||||
|
'n' => $value->m,
|
||||||
|
'unit' => 'M'
|
||||||
|
];
|
||||||
|
} elseif ($value->y > 0) {
|
||||||
|
return [
|
||||||
|
'n' => $value->y,
|
||||||
|
'unit' => 'Y'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TransformationFailedException('the date interval does not '
|
||||||
|
. 'contains any days, months or years');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reverseTransform($value)
|
||||||
|
{
|
||||||
|
if (empty($value) or empty($value['n'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$string = 'P'.$value['n'].$value['unit'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new \DateInterval($string);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new TransformationFailedException("Could not transform value "
|
||||||
|
. "into DateInterval", 1542, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
57
Form/Type/DateIntervalType.php
Normal file
57
Form/Type/DateIntervalType.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Form\Type;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Chill\MainBundle\Form\Type\DataTransformer\DateIntervalTransformer;
|
||||||
|
use Symfony\Component\Validator\Constraints\GreaterThan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class DateIntervalType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('n', IntegerType::class, [
|
||||||
|
'scale' => 0,
|
||||||
|
'constraints' => [
|
||||||
|
new GreaterThan([
|
||||||
|
'value' => 0
|
||||||
|
])
|
||||||
|
]
|
||||||
|
])
|
||||||
|
->add('unit', ChoiceType::class, [
|
||||||
|
'choices' => [
|
||||||
|
'Days' => 'D',
|
||||||
|
'Weeks' => 'W',
|
||||||
|
'Months' => 'M',
|
||||||
|
'Years' => 'Y'
|
||||||
|
],
|
||||||
|
'choices_as_values' => true
|
||||||
|
])
|
||||||
|
;
|
||||||
|
|
||||||
|
$builder->addModelTransformer(new DateIntervalTransformer());
|
||||||
|
}
|
||||||
|
}
|
@ -79,8 +79,6 @@ class ExportType extends AbstractType
|
|||||||
$filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']);
|
$filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']);
|
||||||
$filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, array('compound' => true));
|
$filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, array('compound' => true));
|
||||||
|
|
||||||
dump($this->exportManager);
|
|
||||||
|
|
||||||
foreach($filters as $alias => $filter) {
|
foreach($filters as $alias => $filter) {
|
||||||
$filterBuilder->add($alias, FilterType::class, array(
|
$filterBuilder->add($alias, FilterType::class, array(
|
||||||
'filter_alias' => $alias,
|
'filter_alias' => $alias,
|
||||||
|
@ -54,7 +54,7 @@ class PickFormatterType extends AbstractType
|
|||||||
}
|
}
|
||||||
|
|
||||||
$builder->add('alias', ChoiceType::class, array(
|
$builder->add('alias', ChoiceType::class, array(
|
||||||
'choices' => array_combine(array_values($choices),array_keys($choices)),
|
'choices' => $choices,
|
||||||
'choices_as_values' => true,
|
'choices_as_values' => true,
|
||||||
'multiple' => false
|
'multiple' => false
|
||||||
));
|
));
|
||||||
|
@ -21,10 +21,14 @@ namespace Chill\MainBundle\Form\Type;
|
|||||||
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||||
use Chill\MainBundle\Entity\PostalCode;
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Symfony\Component\Form\FormInterface;
|
||||||
|
use Symfony\Component\Form\FormView;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Chill\MainBundle\Form\ChoiceLoader\PostalCodeChoiceLoader;
|
||||||
|
use Symfony\Component\Translation\TranslatorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A form to pick between PostalCode
|
* A form to pick between PostalCode
|
||||||
@ -39,10 +43,35 @@ class PostalCodeType extends AbstractType
|
|||||||
* @var TranslatableStringHelper
|
* @var TranslatableStringHelper
|
||||||
*/
|
*/
|
||||||
protected $translatableStringHelper;
|
protected $translatableStringHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var UrlGeneratorInterface
|
||||||
|
*/
|
||||||
|
protected $urlGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var PostalCodeChoiceLoader
|
||||||
|
*/
|
||||||
|
protected $choiceLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var TranslatorInterface
|
||||||
|
*/
|
||||||
|
protected $translator;
|
||||||
|
|
||||||
public function __construct(TranslatableStringHelper $helper)
|
public function __construct(
|
||||||
{
|
TranslatableStringHelper $helper,
|
||||||
|
UrlGeneratorInterface $urlGenerator,
|
||||||
|
PostalCodeChoiceLoader $choiceLoader,
|
||||||
|
TranslatorInterface $translator
|
||||||
|
) {
|
||||||
$this->translatableStringHelper = $helper;
|
$this->translatableStringHelper = $helper;
|
||||||
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
$this->choiceLoader = $choiceLoader;
|
||||||
|
$this->translator = $translator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -55,11 +84,26 @@ class PostalCodeType extends AbstractType
|
|||||||
{
|
{
|
||||||
// create a local copy for usage in Closure
|
// create a local copy for usage in Closure
|
||||||
$helper = $this->translatableStringHelper;
|
$helper = $this->translatableStringHelper;
|
||||||
$resolver->setDefault('class', PostalCode::class)
|
$resolver
|
||||||
|
->setDefault('class', PostalCode::class)
|
||||||
->setDefault('choice_label', function(PostalCode $code) use ($helper) {
|
->setDefault('choice_label', function(PostalCode $code) use ($helper) {
|
||||||
return $code->getCode().' '.$code->getName().' ['.
|
return $code->getCode().' '.$code->getName().' ['.
|
||||||
$helper->localize($code->getCountry()->getName()).']';
|
$helper->localize($code->getCountry()->getName()).']';
|
||||||
}
|
})
|
||||||
);
|
->setDefault('choice_loader', $this->choiceLoader)
|
||||||
|
->setDefault('placeholder', 'Select a postal code')
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||||
|
{
|
||||||
|
$view->vars['attr']['data-postal-code'] = 'data-postal-code';
|
||||||
|
$view->vars['attr']['data-search-url'] = $this->urlGenerator
|
||||||
|
->generate('chill_main_postal_code_search');
|
||||||
|
$view->vars['attr']['data-placeholder'] = $this->translator->trans($options['placeholder']);
|
||||||
|
$view->vars['attr']['data-no-results-label'] = $this->translator->trans('select2.no_results');
|
||||||
|
$view->vars['attr']['data-error-load-label'] = $this->translator->trans('select2.error_loading');
|
||||||
|
$view->vars['attr']['data-searching-label'] = $this->translator->trans('select2.searching');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
|||||||
use Chill\MainBundle\Entity\Scope;
|
use Chill\MainBundle\Entity\Scope;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||||
|
use Chill\MainBundle\Entity\Center;
|
||||||
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow to pick amongst available scope for the current
|
* Allow to pick amongst available scope for the current
|
||||||
@ -82,10 +84,10 @@ class ScopePickerType extends AbstractType
|
|||||||
$resolver
|
$resolver
|
||||||
// create `center` option
|
// create `center` option
|
||||||
->setRequired('center')
|
->setRequired('center')
|
||||||
->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class ])
|
->setAllowedTypes('center', [Center::class ])
|
||||||
// create ``role` option
|
// create ``role` option
|
||||||
->setRequired('role')
|
->setRequired('role')
|
||||||
->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class ])
|
->setAllowedTypes('role', ['string', Role::class ])
|
||||||
;
|
;
|
||||||
|
|
||||||
$resolver
|
$resolver
|
||||||
@ -95,25 +97,7 @@ class ScopePickerType extends AbstractType
|
|||||||
return $this->translatableStringHelper->localize($c->getName());
|
return $this->translatableStringHelper->localize($c->getName());
|
||||||
})
|
})
|
||||||
->setNormalizer('query_builder', function(Options $options) {
|
->setNormalizer('query_builder', function(Options $options) {
|
||||||
$qb = $this->scopeRepository->createQueryBuilder('s');
|
return $this->buildAccessibleScopeQuery($options['center'], $options['role']);
|
||||||
$qb
|
|
||||||
// jointure to center
|
|
||||||
->join('s.roleScopes', 'rs')
|
|
||||||
->join('rs.permissionsGroups', 'pg')
|
|
||||||
->join('pg.groupCenters', 'gc')
|
|
||||||
//->join('gc.users', 'user')
|
|
||||||
// add center constraint
|
|
||||||
->where($qb->expr()->eq('IDENTITY(gc.center)', ':center'))
|
|
||||||
->setParameter('center', $options['center']->getId())
|
|
||||||
// role constraints
|
|
||||||
->andWhere($qb->expr()->eq('rs.role', ':role'))
|
|
||||||
->setParameter('role', $options['role'])
|
|
||||||
// user contraint
|
|
||||||
->andWhere(':user MEMBER OF gc.users')
|
|
||||||
->setParameter('user', $this->tokenStorage->getToken()->getUser())
|
|
||||||
;
|
|
||||||
|
|
||||||
return $qb;
|
|
||||||
})
|
})
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
@ -122,4 +106,38 @@ class ScopePickerType extends AbstractType
|
|||||||
{
|
{
|
||||||
return EntityType::class;
|
return EntityType::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return \Doctrine\ORM\QueryBuilder
|
||||||
|
*/
|
||||||
|
protected function buildAccessibleScopeQuery(Center $center, Role $role)
|
||||||
|
{
|
||||||
|
$roles = $this->authorizationHelper->getParentRoles($role);
|
||||||
|
$roles[] = $role;
|
||||||
|
|
||||||
|
$qb = $this->scopeRepository->createQueryBuilder('s');
|
||||||
|
$qb
|
||||||
|
// jointure to center
|
||||||
|
->join('s.roleScopes', 'rs')
|
||||||
|
->join('rs.permissionsGroups', 'pg')
|
||||||
|
->join('pg.groupCenters', 'gc')
|
||||||
|
// add center constraint
|
||||||
|
->where($qb->expr()->eq('IDENTITY(gc.center)', ':center'))
|
||||||
|
->setParameter('center', $center->getId())
|
||||||
|
// role constraints
|
||||||
|
->andWhere($qb->expr()->in('rs.role', ':roles'))
|
||||||
|
->setParameter('roles', \array_map(
|
||||||
|
function(Role $role) {
|
||||||
|
return $role->getRole();
|
||||||
|
},
|
||||||
|
$roles
|
||||||
|
))
|
||||||
|
// user contraint
|
||||||
|
->andWhere(':user MEMBER OF gc.users')
|
||||||
|
->setParameter('user', $this->tokenStorage->getToken()->getUser())
|
||||||
|
;
|
||||||
|
|
||||||
|
return $qb;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
||||||
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,29 +83,14 @@ class UserPickerType extends AbstractType
|
|||||||
|
|
||||||
$resolver
|
$resolver
|
||||||
->setDefault('class', User::class)
|
->setDefault('class', User::class)
|
||||||
->setDefault('empty_data', $this->tokenStorage->getToken()->getUser())
|
->setDefault('placeholder', 'Choose an user')
|
||||||
->setDefault('choice_label', function(User $u) {
|
->setDefault('choice_label', function(User $u) {
|
||||||
return $u->getUsername();
|
return $u->getUsername();
|
||||||
})
|
})
|
||||||
->setNormalizer('query_builder', function(Options $options) {
|
->setNormalizer('choices', function(Options $options) {
|
||||||
$qb = $this->userRepository->createQueryBuilder('u');
|
|
||||||
$qb
|
return $this->authorizationHelper
|
||||||
// add center constraint
|
->findUsersReaching($options['role'], $options['center']);
|
||||||
->join('u.groupCenters', 'ug')
|
|
||||||
->where($qb->expr()->eq('ug.center', ':center'))
|
|
||||||
->setParameter('center', $options['center'])
|
|
||||||
// link to permission groups
|
|
||||||
->join('ug.permissionsGroup', 'pg')
|
|
||||||
// role constraints
|
|
||||||
->join('pg.roleScopes', 'roleScope')
|
|
||||||
->andWhere($qb->expr()->eq('roleScope.role', ':role'))
|
|
||||||
->setParameter('role', $options['role'])
|
|
||||||
// add active constraint
|
|
||||||
->andWhere('u.enabled = :enabled')
|
|
||||||
->setParameter('enabled', true)
|
|
||||||
;
|
|
||||||
|
|
||||||
return $qb;
|
|
||||||
})
|
})
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,10 @@ class UserType extends AbstractType
|
|||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('username')
|
->add('username')
|
||||||
|
->add('email')
|
||||||
;
|
;
|
||||||
if ($options['is_creation']) {
|
if ($options['is_creation']) {
|
||||||
$builder->add('plainPassword', new UserPasswordType(), array(
|
$builder->add('plainPassword', UserPasswordType::class, array(
|
||||||
'mapped' => false
|
'mapped' => false
|
||||||
));
|
));
|
||||||
|
|
||||||
|
28
Repository/PostalCodeRepository.php
Normal file
28
Repository/PostalCodeRepository.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs-Libres <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class PostalCodeRepository extends \Doctrine\ORM\EntityRepository
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
Chill\MainBundle\Entity\PostalCode:
|
Chill\MainBundle\Entity\PostalCode:
|
||||||
type: entity
|
type: entity
|
||||||
table: chill_main_postal_code
|
table: chill_main_postal_code
|
||||||
|
repositoryClass: Chill\MainBundle\Repository\PostalCodeRepository
|
||||||
|
indexes:
|
||||||
|
- { name: search_name_code, columns: [ "code", "label" ] }
|
||||||
id:
|
id:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
@ -14,6 +14,21 @@ Chill\MainBundle\Entity\User:
|
|||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
length: 80
|
length: 80
|
||||||
|
usernameCanonical:
|
||||||
|
name: username_canonical
|
||||||
|
type: string
|
||||||
|
length: 80
|
||||||
|
unique: true
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
length: 150
|
||||||
|
nullable: true
|
||||||
|
emailCanonical:
|
||||||
|
name: email_canonical
|
||||||
|
type: string
|
||||||
|
length: 150
|
||||||
|
nullable: true
|
||||||
|
unique: true
|
||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
length: 255
|
length: 255
|
||||||
|
@ -17,6 +17,10 @@ chill_main_admin:
|
|||||||
chill_main_exports:
|
chill_main_exports:
|
||||||
resource: "@ChillMainBundle/Resources/config/routing/exports.yml"
|
resource: "@ChillMainBundle/Resources/config/routing/exports.yml"
|
||||||
prefix: "{_locale}/exports"
|
prefix: "{_locale}/exports"
|
||||||
|
|
||||||
|
chill_postal_code:
|
||||||
|
resource: "@ChillMainBundle/Resources/config/routing/postal-code.yml"
|
||||||
|
prefix: "{_locale}/postal-code"
|
||||||
|
|
||||||
root:
|
root:
|
||||||
path: /
|
path: /
|
||||||
@ -32,12 +36,6 @@ chill_main_homepage_without_locale:
|
|||||||
chill_main_homepage:
|
chill_main_homepage:
|
||||||
path: /{_locale}/homepage
|
path: /{_locale}/homepage
|
||||||
defaults: { _controller: ChillMainBundle:Default:index }
|
defaults: { _controller: ChillMainBundle:Default:index }
|
||||||
options:
|
|
||||||
menus:
|
|
||||||
section:
|
|
||||||
order: 10
|
|
||||||
label: Homepage
|
|
||||||
icons: [home]
|
|
||||||
|
|
||||||
|
|
||||||
chill_main_admin_central:
|
chill_main_admin_central:
|
||||||
@ -80,13 +78,7 @@ login_check:
|
|||||||
|
|
||||||
logout:
|
logout:
|
||||||
path: /logout
|
path: /logout
|
||||||
options:
|
|
||||||
menus:
|
|
||||||
user:
|
|
||||||
order: 10
|
|
||||||
label: Logout
|
|
||||||
icon: power-off
|
|
||||||
|
|
||||||
password:
|
password:
|
||||||
path: /password
|
path: /password
|
||||||
defaults: { _controller: ChillMainBundle:Password:userPassword }
|
defaults: { _controller: ChillMainBundle:Password:userPassword }
|
@ -1,12 +1,6 @@
|
|||||||
chill_main_export_index:
|
chill_main_export_index:
|
||||||
path: /
|
path: /
|
||||||
defaults: { _controller: ChillMainBundle:Export:index }
|
defaults: { _controller: ChillMainBundle:Export:index }
|
||||||
options:
|
|
||||||
menus:
|
|
||||||
section:
|
|
||||||
order: 20
|
|
||||||
label: Export Menu
|
|
||||||
icons: [upload]
|
|
||||||
|
|
||||||
chill_main_export_new:
|
chill_main_export_new:
|
||||||
path: /new/{alias}
|
path: /new/{alias}
|
||||||
|
4
Resources/config/routing/postal-code.yml
Normal file
4
Resources/config/routing/postal-code.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
chill_main_postal_code_search:
|
||||||
|
path: /search
|
||||||
|
defaults: { _controller: ChillMainBundle:PostalCode:search }
|
||||||
|
|
@ -2,27 +2,6 @@ parameters:
|
|||||||
# cl_chill_main.example.class: Chill\MainBundle\Example
|
# cl_chill_main.example.class: Chill\MainBundle\Example
|
||||||
|
|
||||||
services:
|
services:
|
||||||
chill.main.routes_loader:
|
|
||||||
class: Chill\MainBundle\Routing\Loader\ChillRoutesLoader
|
|
||||||
arguments:
|
|
||||||
- "%chill_main.routing.resources%"
|
|
||||||
tags:
|
|
||||||
- { name: routing.loader }
|
|
||||||
|
|
||||||
chill.main.menu_composer:
|
|
||||||
class: Chill\MainBundle\Routing\MenuComposer
|
|
||||||
#must be set in function to avoid circular reference with chill.main.twig.chill_menu
|
|
||||||
calls:
|
|
||||||
- [setContainer, ["@service_container"]]
|
|
||||||
|
|
||||||
chill.main.twig.chill_menu:
|
|
||||||
class: Chill\MainBundle\Routing\MenuTwig
|
|
||||||
arguments:
|
|
||||||
- "@chill.main.menu_composer"
|
|
||||||
calls:
|
|
||||||
- [setContainer, ["@service_container"]]
|
|
||||||
tags:
|
|
||||||
- { name: twig.extension }
|
|
||||||
|
|
||||||
twig_intl:
|
twig_intl:
|
||||||
class: Twig_Extensions_Extension_Intl
|
class: Twig_Extensions_Extension_Intl
|
||||||
@ -41,6 +20,7 @@ services:
|
|||||||
arguments:
|
arguments:
|
||||||
- "@request_stack"
|
- "@request_stack"
|
||||||
- "@translator.default"
|
- "@translator.default"
|
||||||
|
Chill\MainBundle\Templating\TranslatableStringHelper: '@chill.main.helper.translatable_string'
|
||||||
|
|
||||||
chill.main.twig.translatable_string:
|
chill.main.twig.translatable_string:
|
||||||
class: Chill\MainBundle\Templating\TranslatableStringTwig
|
class: Chill\MainBundle\Templating\TranslatableStringTwig
|
||||||
@ -75,15 +55,7 @@ services:
|
|||||||
- "@doctrine.orm.entity_manager"
|
- "@doctrine.orm.entity_manager"
|
||||||
calls:
|
calls:
|
||||||
- [ setContainer, ["@service_container"]]
|
- [ setContainer, ["@service_container"]]
|
||||||
|
|
||||||
chill.main.security.authorization.helper:
|
|
||||||
class: Chill\MainBundle\Security\Authorization\AuthorizationHelper
|
|
||||||
arguments:
|
|
||||||
- "@security.role_hierarchy"
|
|
||||||
|
|
||||||
chill.main.role_provider:
|
|
||||||
class: Chill\MainBundle\Security\RoleProvider
|
|
||||||
|
|
||||||
chill.main.validator.role_scope_scope_presence:
|
chill.main.validator.role_scope_scope_presence:
|
||||||
class: Chill\MainBundle\Validation\Validator\RoleScopeScopePresence
|
class: Chill\MainBundle\Validation\Validator\RoleScopeScopePresence
|
||||||
arguments:
|
arguments:
|
||||||
|
6
Resources/config/services/controller.yml
Normal file
6
Resources/config/services/controller.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
services:
|
||||||
|
|
||||||
|
Chill\MainBundle\Controller\:
|
||||||
|
autowire: true
|
||||||
|
resource: '../../../Controller'
|
||||||
|
tags: ['controller.service_arguments']
|
4
Resources/config/services/fixtures.yml
Normal file
4
Resources/config/services/fixtures.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
services:
|
||||||
|
Chill\MainBundle\DataFixtures\ORM\:
|
||||||
|
resource: ../../../DataFixtures/ORM
|
||||||
|
tags: [ 'doctrine.fixture.orm' ]
|
@ -54,9 +54,17 @@ services:
|
|||||||
class: Chill\MainBundle\Form\Type\PostalCodeType
|
class: Chill\MainBundle\Form\Type\PostalCodeType
|
||||||
arguments:
|
arguments:
|
||||||
- "@chill.main.helper.translatable_string"
|
- "@chill.main.helper.translatable_string"
|
||||||
|
- '@Symfony\Component\Routing\Generator\UrlGeneratorInterface'
|
||||||
|
- '@chill.main.form.choice_loader.postal_code'
|
||||||
|
- '@Symfony\Component\Translation\TranslatorInterface'
|
||||||
tags:
|
tags:
|
||||||
- { name: form.type }
|
- { name: form.type }
|
||||||
|
|
||||||
|
chill.main.form.choice_loader.postal_code:
|
||||||
|
class: Chill\MainBundle\Form\ChoiceLoader\PostalCodeChoiceLoader
|
||||||
|
arguments:
|
||||||
|
- '@Chill\MainBundle\Repository\PostalCodeRepository'
|
||||||
|
|
||||||
chill.main.form.type.export:
|
chill.main.form.type.export:
|
||||||
class: Chill\MainBundle\Form\Type\Export\ExportType
|
class: Chill\MainBundle\Form\Type\Export\ExportType
|
||||||
arguments:
|
arguments:
|
||||||
|
10
Resources/config/services/menu.yml
Normal file
10
Resources/config/services/menu.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
Chill\MainBundle\Routing\MenuBuilder\UserMenuBuilder:
|
||||||
|
tags:
|
||||||
|
- { name: 'chill.menu_builder' }
|
||||||
|
|
||||||
|
Chill\MainBundle\Routing\MenuBuilder\SectionMenuBuilder:
|
||||||
|
arguments:
|
||||||
|
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
|
||||||
|
tags:
|
||||||
|
- { name: 'chill.menu_builder' }
|
@ -5,6 +5,7 @@ services:
|
|||||||
- "@request_stack"
|
- "@request_stack"
|
||||||
- "@router"
|
- "@router"
|
||||||
- "%chill_main.pagination.item_per_page%"
|
- "%chill_main.pagination.item_per_page%"
|
||||||
|
Chill\MainBundle\Pagination\PaginatorFactory: '@chill_main.paginator_factory'
|
||||||
|
|
||||||
chill_main.paginator.twig_extensions:
|
chill_main.paginator.twig_extensions:
|
||||||
class: Chill\MainBundle\Pagination\ChillPaginationTwig
|
class: Chill\MainBundle\Pagination\ChillPaginationTwig
|
||||||
|
@ -15,4 +15,13 @@ services:
|
|||||||
class: Doctrine\ORM\EntityRepository
|
class: Doctrine\ORM\EntityRepository
|
||||||
factory: ["@doctrine.orm.entity_manager", getRepository]
|
factory: ["@doctrine.orm.entity_manager", getRepository]
|
||||||
arguments:
|
arguments:
|
||||||
- "Chill\\MainBundle\\Entity\\Scope"
|
- "Chill\\MainBundle\\Entity\\Scope"
|
||||||
|
|
||||||
|
chill.main.postalcode_repository:
|
||||||
|
class: Doctrine\ORM\EntityRepository
|
||||||
|
factory: ["@doctrine.orm.entity_manager", getRepository]
|
||||||
|
arguments:
|
||||||
|
- "Chill\\MainBundle\\Entity\\PostalCode"
|
||||||
|
|
||||||
|
Chill\MainBundle\Repository\PostalCodeRepository: '@chill.main.postalcode_repository'
|
||||||
|
|
24
Resources/config/services/routing.yml
Normal file
24
Resources/config/services/routing.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
services:
|
||||||
|
chill.main.menu_composer:
|
||||||
|
class: Chill\MainBundle\Routing\MenuComposer
|
||||||
|
arguments:
|
||||||
|
- '@Symfony\Component\Routing\RouterInterface'
|
||||||
|
- '@Knp\Menu\FactoryInterface'
|
||||||
|
- '@Symfony\Component\Translation\TranslatorInterface'
|
||||||
|
Chill\MainBundle\Routing\MenuComposer: '@chill.main.menu_composer'
|
||||||
|
|
||||||
|
chill.main.routes_loader:
|
||||||
|
class: Chill\MainBundle\Routing\Loader\ChillRoutesLoader
|
||||||
|
arguments:
|
||||||
|
- "%chill_main.routing.resources%"
|
||||||
|
tags:
|
||||||
|
- { name: routing.loader }
|
||||||
|
|
||||||
|
chill.main.twig.chill_menu:
|
||||||
|
class: Chill\MainBundle\Routing\MenuTwig
|
||||||
|
arguments:
|
||||||
|
- "@chill.main.menu_composer"
|
||||||
|
calls:
|
||||||
|
- [setContainer, ["@service_container"]]
|
||||||
|
tags:
|
||||||
|
- { name: twig.extension }
|
23
Resources/config/services/security.yml
Normal file
23
Resources/config/services/security.yml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
services:
|
||||||
|
chill.main.security.authorization.helper:
|
||||||
|
class: Chill\MainBundle\Security\Authorization\AuthorizationHelper
|
||||||
|
arguments:
|
||||||
|
$roleHierarchy: "@security.role_hierarchy"
|
||||||
|
$hierarchy: "%security.role_hierarchy.roles%"
|
||||||
|
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||||
|
Chill\MainBundle\Security\Authorization\AuthorizationHelper: '@chill.main.security.authorization.helper'
|
||||||
|
|
||||||
|
chill.main.role_provider:
|
||||||
|
class: Chill\MainBundle\Security\RoleProvider
|
||||||
|
|
||||||
|
chill.main.user_provider:
|
||||||
|
class: Chill\MainBundle\Security\UserProvider\UserProvider
|
||||||
|
arguments:
|
||||||
|
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||||
|
|
||||||
|
Chill\MainBundle\Security\Authorization\ChillExportVoter:
|
||||||
|
arguments:
|
||||||
|
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
|
||||||
|
tags:
|
||||||
|
- { name: security.voter }
|
||||||
|
|
13
Resources/config/services/validator.yml
Normal file
13
Resources/config/services/validator.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
services:
|
||||||
|
chill_main.validator_user_circle_consistency:
|
||||||
|
class: Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistencyValidator
|
||||||
|
arguments:
|
||||||
|
- "@chill.main.security.authorization.helper"
|
||||||
|
tags:
|
||||||
|
- { name: "validator.constraint_validator" }
|
||||||
|
|
||||||
|
Chill\MainBundle\Validation\Validator\UserUniqueEmailAndUsername:
|
||||||
|
arguments:
|
||||||
|
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||||
|
tags:
|
||||||
|
- { name: "validator.constraint_validator" }
|
2
Resources/config/services/widget.yml
Normal file
2
Resources/config/services/widget.yml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
services:
|
||||||
|
Chill\MainBundle\Templating\UI\CountNotificationUser: ~
|
@ -1,27 +1,32 @@
|
|||||||
Chill\MainBundle\Entity\PermissionsGroup:
|
Chill\MainBundle\Entity\PermissionsGroup:
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
- NotBlank: ~
|
- NotBlank: ~
|
||||||
- Length:
|
- Length:
|
||||||
max: 50
|
max: 50
|
||||||
roleScopes:
|
roleScopes:
|
||||||
- Valid: ~
|
- Valid: ~
|
||||||
constraints:
|
constraints:
|
||||||
- Callback: [isRoleScopePresentOnce]
|
- Callback:
|
||||||
|
callback: isRoleScopePresentOnce
|
||||||
|
|
||||||
Chill\MainBundle\Entity\User:
|
Chill\MainBundle\Entity\User:
|
||||||
properties:
|
properties:
|
||||||
username:
|
username:
|
||||||
- Length:
|
- Length:
|
||||||
max: 70
|
max: 70
|
||||||
min: 3
|
min: 3
|
||||||
|
email:
|
||||||
|
- Email: ~
|
||||||
constraints:
|
constraints:
|
||||||
- Callback: [isGroupCenterPresentOnce]
|
- Callback:
|
||||||
|
callback: isGroupCenterPresentOnce
|
||||||
|
- \Chill\MainBundle\Validation\Constraint\UserUniqueEmailAndUsernameConstraint: ~
|
||||||
|
|
||||||
Chill\MainBundle\Entity\RoleScope:
|
Chill\MainBundle\Entity\RoleScope:
|
||||||
constraints:
|
constraints:
|
||||||
- \Chill\MainBundle\Validation\Constraint\RoleScopeScopePresenceConstraint: ~
|
- \Chill\MainBundle\Validation\Constraint\RoleScopeScopePresenceConstraint: ~
|
||||||
|
|
||||||
Chill\MainBundle\Entity\Center:
|
Chill\MainBundle\Entity\Center:
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
@ -41,11 +46,11 @@ Chill\MainBundle\Entity\Address:
|
|||||||
validFrom:
|
validFrom:
|
||||||
- NotNull: ~
|
- NotNull: ~
|
||||||
- Date: ~
|
- Date: ~
|
||||||
|
|
||||||
Chill\MainBundle\Entity\PostalCode:
|
Chill\MainBundle\Entity\PostalCode:
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
- Length:
|
- Length:
|
||||||
max: 250
|
max: 250
|
||||||
min: 2
|
min: 2
|
||||||
code:
|
code:
|
||||||
@ -53,4 +58,4 @@ Chill\MainBundle\Entity\PostalCode:
|
|||||||
min: 2
|
min: 2
|
||||||
max: 100
|
max: 100
|
||||||
country:
|
country:
|
||||||
- NotNull: ~
|
- NotNull: ~
|
||||||
|
33
Resources/migrations/Version20180703191509.php
Normal file
33
Resources/migrations/Version20180703191509.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Application\Migrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Migrations\AbstractMigration;
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add index to postal code
|
||||||
|
*/
|
||||||
|
final class Version20180703191509 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(Schema $schema) : void
|
||||||
|
{
|
||||||
|
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->addSql('CREATE EXTENSION IF NOT EXISTS pg_trgm');
|
||||||
|
$this->addSql('CREATE INDEX search_name_code ON chill_main_postal_code USING GIN (LOWER(code) gin_trgm_ops, LOWER(label) gin_trgm_ops)');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->skipIf(true, "Could not create extension pg_trgm");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema) : void
|
||||||
|
{
|
||||||
|
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||||
|
|
||||||
|
$this->addSql('DROP INDEX search_name_code');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
87
Resources/migrations/Version20180709181423.php
Normal file
87
Resources/migrations/Version20180709181423.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Application\Migrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Migrations\AbstractMigration;
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add username and username canonical, email and email canonical columns
|
||||||
|
* to users
|
||||||
|
*/
|
||||||
|
final class Version20180709181423 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(Schema $schema) : void
|
||||||
|
{
|
||||||
|
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||||
|
|
||||||
|
$this->addSql('ALTER TABLE users ADD usernameCanonical VARCHAR(80) DEFAULT NULL');
|
||||||
|
$this->addSql('UPDATE users SET usernameCanonical=LOWER(UNACCENT(username))');
|
||||||
|
$this->addSql('ALTER TABLE users ALTER usernameCanonical DROP NOT NULL');
|
||||||
|
$this->addSql('ALTER TABLE users ALTER usernameCanonical SET DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE users ADD email VARCHAR(150) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE users ADD emailCanonical VARCHAR(150) DEFAULT NULL');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9F5A5DC32 ON users (usernameCanonical)');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9885281E ON users (emailCanonical)');
|
||||||
|
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE OR REPLACE FUNCTION canonicalize_user_on_update() RETURNS TRIGGER AS
|
||||||
|
$BODY$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.username <> OLD.username OR NEW.email <> OLD.email OR OLD.emailcanonical IS NULL OR OLD.usernamecanonical IS NULL THEN
|
||||||
|
UPDATE users SET usernamecanonical=LOWER(UNACCENT(NEW.username)), emailcanonical=LOWER(UNACCENT(NEW.email)) WHERE id=NEW.id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$BODY$ LANGUAGE PLPGSQL
|
||||||
|
SQL
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->addSql(<<<SQL
|
||||||
|
CREATE TRIGGER canonicalize_user_on_update
|
||||||
|
AFTER UPDATE
|
||||||
|
ON users
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE canonicalize_user_on_update();
|
||||||
|
SQL
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE OR REPLACE FUNCTION canonicalize_user_on_insert() RETURNS TRIGGER AS
|
||||||
|
$BODY$
|
||||||
|
BEGIN
|
||||||
|
UPDATE users SET usernamecanonical=LOWER(UNACCENT(NEW.username)), emailcanonical=LOWER(UNACCENT(NEW.email)) WHERE id=NEW.id;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$BODY$ LANGUAGE PLPGSQL;
|
||||||
|
SQL
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->addSql(<<<SQL
|
||||||
|
CREATE TRIGGER canonicalize_user_on_insert
|
||||||
|
AFTER INSERT
|
||||||
|
ON users
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE canonicalize_user_on_insert();
|
||||||
|
SQL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema) : void
|
||||||
|
{
|
||||||
|
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||||
|
|
||||||
|
$this->addSql('DROP INDEX UNIQ_1483A5E9F5A5DC32');
|
||||||
|
$this->addSql('DROP INDEX UNIQ_1483A5E9885281E');
|
||||||
|
$this->addSql('ALTER TABLE users DROP usernameCanonical');
|
||||||
|
$this->addSql('ALTER TABLE users DROP email');
|
||||||
|
$this->addSql('ALTER TABLE users DROP emailCanonical');
|
||||||
|
$this->addSql('DROP TRIGGER canonicalize_user_on_insert ON users');
|
||||||
|
$this->addSql('DROP FUNCTION canonicalize_user_on_insert()');
|
||||||
|
$this->addSql('DROP TRIGGER canonicalize_user_on_update ON users');
|
||||||
|
$this->addSql('DROP FUNCTION canonicalize_user_on_update()');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -4585,7 +4585,6 @@ header {
|
|||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
background-image: url("/bundles/chillmain/img/background/desert.jpg");
|
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
@ -93,7 +93,7 @@ var chill = function() {
|
|||||||
*
|
*
|
||||||
* @param{string} form_id An identification string of the form
|
* @param{string} form_id An identification string of the form
|
||||||
* @param{string} alert_message The alert message to display
|
* @param{string} alert_message The alert message to display
|
||||||
* @param{boolean} check_unsaved_data If true display the alert message only when the form
|
* @param{boolean} check_unsaved_data If true display the alert message only when the form
|
||||||
* contains some modified fields otherwise always display the alert when leaving
|
* contains some modified fields otherwise always display the alert when leaving
|
||||||
* @return nothing
|
* @return nothing
|
||||||
*/
|
*/
|
||||||
@ -123,12 +123,12 @@ var chill = function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark the choices "not specified" as check by default.
|
* Mark the choices "not specified" as check by default.
|
||||||
*
|
*
|
||||||
* This function apply to `custom field choices` when the `required`
|
* This function apply to `custom field choices` when the `required`
|
||||||
* option is false and `expanded` is true (checkboxes or radio buttons).
|
* option is false and `expanded` is true (checkboxes or radio buttons).
|
||||||
*
|
*
|
||||||
* @param{string} choice_name the name of the input
|
* @param{string} choice_name the name of the input
|
||||||
*/
|
*/
|
||||||
function checkNullValuesInChoices(choice_name) {
|
function checkNullValuesInChoices(choice_name) {
|
||||||
@ -184,21 +184,21 @@ var chill = function() {
|
|||||||
* child) of a given form : each parent option has a category, the
|
* child) of a given form : each parent option has a category, the
|
||||||
* child select only display options that have the same category of the
|
* child select only display options that have the same category of the
|
||||||
* parent optionn
|
* parent optionn
|
||||||
*
|
*
|
||||||
* The parent must have the class "chill-category-link-parent".
|
* The parent must have the class "chill-category-link-parent".
|
||||||
*
|
*
|
||||||
* The children must have the class "chill-category-link-child". Each option
|
* The children must have the class "chill-category-link-child". Each option
|
||||||
* of the parent must have the attribute `data-link-category`, with the value of
|
* of the parent must have the attribute `data-link-category`, with the value of
|
||||||
* the connected option in parent.
|
* the connected option in parent.
|
||||||
*
|
*
|
||||||
* Example :
|
* Example :
|
||||||
*
|
*
|
||||||
* ```html
|
* ```html
|
||||||
* <select name="country" class="chill-category-link-parent">
|
* <select name="country" class="chill-category-link-parent">
|
||||||
* <option value="BE">Belgium</option>
|
* <option value="BE">Belgium</option>
|
||||||
* <option value="FR">France</option>
|
* <option value="FR">France</option>
|
||||||
* </select>
|
* </select>
|
||||||
*
|
*
|
||||||
* <select name="cities">class="chill-category-link-children">
|
* <select name="cities">class="chill-category-link-children">
|
||||||
* <option value="paris" data-link-category="FR">Paris</option>
|
* <option value="paris" data-link-category="FR">Paris</option>
|
||||||
* <option value="toulouse" data-link-category="FR">Toulouse</option>
|
* <option value="toulouse" data-link-category="FR">Toulouse</option>
|
||||||
@ -207,7 +207,7 @@ var chill = function() {
|
|||||||
* <option value="mons" data-link-category="BE">Mons</option>
|
* <option value="mons" data-link-category="BE">Mons</option>
|
||||||
* </select>
|
* </select>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* TODO ECRIRE LA DOC METTRE LES TESTS DANS git :
|
* TODO ECRIRE LA DOC METTRE LES TESTS DANS git :
|
||||||
* tester que init est ok :
|
* tester que init est ok :
|
||||||
- quand vide
|
- quand vide
|
||||||
@ -224,7 +224,7 @@ var chill = function() {
|
|||||||
form.old_category = null;
|
form.old_category = null;
|
||||||
form.link_parent = $(form).find('.chill-category-link-parent');
|
form.link_parent = $(form).find('.chill-category-link-parent');
|
||||||
form.link_child = $(form).find('.chill-category-link-child');
|
form.link_child = $(form).find('.chill-category-link-child');
|
||||||
|
|
||||||
// check if the parent allow multiple or single results
|
// check if the parent allow multiple or single results
|
||||||
parent_multiple = $(form).find('.chill-category-link-parent').get(0).multiple;
|
parent_multiple = $(form).find('.chill-category-link-parent').get(0).multiple;
|
||||||
// if we use select2, parent_multiple will be `undefined`
|
// if we use select2, parent_multiple will be `undefined`
|
||||||
@ -233,10 +233,10 @@ var chill = function() {
|
|||||||
// we suppose that multiple is false (old behaviour)
|
// we suppose that multiple is false (old behaviour)
|
||||||
parent_multiple = false
|
parent_multiple = false
|
||||||
}
|
}
|
||||||
|
|
||||||
$(form.link_parent).addClass('select2');
|
$(form.link_parent).addClass('select2');
|
||||||
$(form.link_parant).select2({allowClear: true}); // it is weird: when I fix the typo here, the whole stuff does not work anymore...
|
$(form.link_parant).select2({allowClear: true}); // it is weird: when I fix the typo here, the whole stuff does not work anymore...
|
||||||
|
|
||||||
if (parent_multiple == false) {
|
if (parent_multiple == false) {
|
||||||
|
|
||||||
form.old_category = null;
|
form.old_category = null;
|
||||||
@ -279,9 +279,9 @@ var chill = function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var i=0,
|
var i=0,
|
||||||
selected_items = $(form.link_parent).find(':selected');
|
selected_items = $(form.link_parent).find(':selected');
|
||||||
|
|
||||||
form.old_categories = [];
|
form.old_categories = [];
|
||||||
for (i=0;i < selected_items.length; i++) {
|
for (i=0;i < selected_items.length; i++) {
|
||||||
form.old_categories.push(selected_items[i].value);
|
form.old_categories.push(selected_items[i].value);
|
||||||
@ -314,13 +314,13 @@ var chill = function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
form.link_parent.change(function() {
|
form.link_parent.change(function() {
|
||||||
var new_categories = [],
|
var new_categories = [],
|
||||||
selected_items = $(form.link_parent).find(':selected'),
|
selected_items = $(form.link_parent).find(':selected'),
|
||||||
visible;
|
visible;
|
||||||
for (i=0;i < selected_items.length; i++) {
|
for (i=0;i < selected_items.length; i++) {
|
||||||
new_categories.push(selected_items[i].value);
|
new_categories.push(selected_items[i].value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(new_categories != form.old_categories) {
|
if(new_categories != form.old_categories) {
|
||||||
$(form.link_child).find('option')
|
$(form.link_child).find('option')
|
||||||
.each(function(i,e) {
|
.each(function(i,e) {
|
||||||
@ -352,16 +352,16 @@ var chill = function() {
|
|||||||
form.old_categories = new_categories;
|
form.old_categories = new_categories;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _displayHideTargetWithCheckbox(checkbox) {
|
function _displayHideTargetWithCheckbox(checkbox) {
|
||||||
var target = checkbox.dataset.displayTarget,
|
var target = checkbox.dataset.displayTarget,
|
||||||
hideableElements;
|
hideableElements;
|
||||||
|
|
||||||
hideableElements = document.querySelectorAll('[data-display-show-hide="' + target + '"]');
|
hideableElements = document.querySelectorAll('[data-display-show-hide="' + target + '"]');
|
||||||
|
|
||||||
if (checkbox.checked) {
|
if (checkbox.checked) {
|
||||||
for (let i=0; i < hideableElements.length; i = i+1) {
|
for (let i=0; i < hideableElements.length; i = i+1) {
|
||||||
hideableElements[i].style.display = "unset";
|
hideableElements[i].style.display = "unset";
|
||||||
@ -371,36 +371,36 @@ var chill = function() {
|
|||||||
hideableElements[i].style.display = "none";
|
hideableElements[i].style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create an interaction between a checkbox and element to show if the
|
* create an interaction between a checkbox and element to show if the
|
||||||
* checkbox is checked, or hide if the checkbox is not checked.
|
* checkbox is checked, or hide if the checkbox is not checked.
|
||||||
*
|
*
|
||||||
* The checkbox must have the data `data-display-target` with an id,
|
* The checkbox must have the data `data-display-target` with an id,
|
||||||
* and the parts to show/hide must have the data `data-display-show-hide`
|
* and the parts to show/hide must have the data `data-display-show-hide`
|
||||||
* with the same value.
|
* with the same value.
|
||||||
*
|
*
|
||||||
* Example :
|
* Example :
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* <input data-display-target="export_abc" value="1" type="checkbox">
|
* <input data-display-target="export_abc" value="1" type="checkbox">
|
||||||
*
|
*
|
||||||
* <div data-display-show-hide="export_abc">
|
* <div data-display-show-hide="export_abc">
|
||||||
* <!-- your content here will be hidden / shown according to checked state -->
|
* <!-- your content here will be hidden / shown according to checked state -->
|
||||||
* </div>
|
* </div>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* Hint: for forms in symfony, you could use the `id` of the form element,
|
* Hint: for forms in symfony, you could use the `id` of the form element,
|
||||||
* accessible through `{{ form.vars.id }}`. This id should be unique.
|
* accessible through `{{ form.vars.id }}`. This id should be unique.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @returns {undefined}
|
* @returns {undefined}
|
||||||
*/
|
*/
|
||||||
function listenerDisplayCheckbox() {
|
function listenerDisplayCheckbox() {
|
||||||
var elements = document.querySelectorAll("[data-display-target]");
|
var elements = document.querySelectorAll("[data-display-target]");
|
||||||
|
|
||||||
for (let i=0; i < elements.length; i = i+1) {
|
for (let i=0; i < elements.length; i = i+1) {
|
||||||
elements[i].addEventListener("change", function(e) {
|
elements[i].addEventListener("change", function(e) {
|
||||||
_displayHideTargetWithCheckbox(e.target);
|
_displayHideTargetWithCheckbox(e.target);
|
||||||
@ -421,3 +421,5 @@ var chill = function() {
|
|||||||
listenerDisplayCheckbox: listenerDisplayCheckbox,
|
listenerDisplayCheckbox: listenerDisplayCheckbox,
|
||||||
};
|
};
|
||||||
} ();
|
} ();
|
||||||
|
|
||||||
|
export { chill };
|
||||||
|
23
Resources/public/js/collection/collection.scss
Normal file
23
Resources/public/js/collection/collection.scss
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
div.chill-collection {
|
||||||
|
ul.chill-collection__list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
|
li.chill-collection__list__entry:nth-child(2n) {
|
||||||
|
background-color: var(--chill-light-gray);
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all entries, except the last one
|
||||||
|
li.chill-collection__list__entry:nth-last-child(1n+2) {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.chill-collection__button--add {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
116
Resources/public/js/collection/collections.js
Normal file
116
Resources/public/js/collection/collections.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* Javascript file which handle ChillCollectionType
|
||||||
|
*
|
||||||
|
* Two events are emitted by this module, both on window and on collection / ul.
|
||||||
|
*
|
||||||
|
* Collection (an UL element) and entry (a li element) are associated with those
|
||||||
|
* events.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* window.addEventListener('collection-add-entry', function(e) {
|
||||||
|
* console.log(e.detail.collection);
|
||||||
|
* console.log(e.detail.entry);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* window.addEventListener('collection-remove-entry', function(e) {
|
||||||
|
* console.log(e.detail.collection);
|
||||||
|
* console.log(e.detail.entry);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* collection.addEventListener('collection-add-entry', function(e) {
|
||||||
|
* console.log(e.detail.collection);
|
||||||
|
* console.log(e.detail.entry);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* collection.addEventListener('collection-remove-entry', function(e) {
|
||||||
|
* console.log(e.detail.collection);
|
||||||
|
* console.log(e.detail.entry);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
require('./collection.scss');
|
||||||
|
|
||||||
|
class CollectionEvent {
|
||||||
|
constructor(collection, entry) {
|
||||||
|
this.collection = collection;
|
||||||
|
this.entry = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {type} button
|
||||||
|
* @returns {handleAdd}
|
||||||
|
*/
|
||||||
|
var handleAdd = function(button) {
|
||||||
|
var
|
||||||
|
form_name = button.dataset.collectionAddTarget,
|
||||||
|
prototype = button.dataset.formPrototype,
|
||||||
|
collection = document.querySelector('ul[data-collection-name="'+form_name+'"]'),
|
||||||
|
entry = document.createElement('li'),
|
||||||
|
event = new CustomEvent('collection-add-entry', { detail: { collection: collection, entry: entry } }),
|
||||||
|
counter = collection.childNodes.length,
|
||||||
|
content
|
||||||
|
;
|
||||||
|
content = prototype.replace(new RegExp('__name__', 'g'), counter);
|
||||||
|
entry.innerHTML = content;
|
||||||
|
entry.classList.add('chill-collection__list__entry');
|
||||||
|
initializeRemove(collection, entry);
|
||||||
|
collection.appendChild(entry);
|
||||||
|
chill.initPikaday('fr');
|
||||||
|
|
||||||
|
collection.dispatchEvent(event);
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
var initializeRemove = function(collection, entry) {
|
||||||
|
var
|
||||||
|
button = document.createElement('button'),
|
||||||
|
isPersisted = entry.dataset.collectionIsPersisted,
|
||||||
|
content = collection.dataset.collectionButtonRemoveLabel,
|
||||||
|
allowDelete = collection.dataset.collectionAllowDelete,
|
||||||
|
event = new CustomEvent('collection-remove-entry', { detail: { collection: collection, entry: entry } })
|
||||||
|
;
|
||||||
|
|
||||||
|
if (allowDelete === '0' && isPersisted === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.classList.add('sc-button', 'bt-delete');
|
||||||
|
button.textContent = content;
|
||||||
|
|
||||||
|
button.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
entry.remove();
|
||||||
|
collection.dispatchEvent(event);
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
entry.appendChild(button);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
var
|
||||||
|
addButtons = document.querySelectorAll("button[data-collection-add-target]"),
|
||||||
|
collections = document.querySelectorAll("ul[data-collection-name]")
|
||||||
|
;
|
||||||
|
|
||||||
|
for (let i = 0; i < addButtons.length; i ++) {
|
||||||
|
let addButton = addButtons[i];
|
||||||
|
addButton.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleAdd(e.target);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < collections.length; i ++) {
|
||||||
|
let entries = collections[i].querySelectorAll(':scope > li');
|
||||||
|
|
||||||
|
for (let j = 0; j < entries.length; j ++) {
|
||||||
|
initializeRemove(collections[i], entries[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
3
Resources/public/modules/breadcrumb/index.js
Normal file
3
Resources/public/modules/breadcrumb/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
require("./layout.scss");
|
||||||
|
|
||||||
|
|
21
Resources/public/modules/breadcrumb/layout.scss
Normal file
21
Resources/public/modules/breadcrumb/layout.scss
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
nav.chill-breadcrumb {
|
||||||
|
ul.list.on-left {
|
||||||
|
li {
|
||||||
|
border-right: 10px solid var(--chill-red);
|
||||||
|
}
|
||||||
|
li.active {
|
||||||
|
border-right: 10px solid var(--chill-green);
|
||||||
|
}
|
||||||
|
li.past {
|
||||||
|
border-right: 10px solid var(--chill-blue);
|
||||||
|
}
|
||||||
|
li.no-jump{
|
||||||
|
border-right: 10px solid var(--chill-light-gray);
|
||||||
|
background-color: var(--chill-yellow);
|
||||||
|
padding: 0.3em 0.3em 0.3em 0.6em;
|
||||||
|
color: var(--chill-dark-gray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
67
Resources/public/modules/download-report/download-report.js
Normal file
67
Resources/public/modules/download-report/download-report.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mime = require('mime-types')
|
||||||
|
|
||||||
|
var download_report = (url, container) => {
|
||||||
|
var download_text = container.dataset.downloadText,
|
||||||
|
alias = container.dataset.alias;
|
||||||
|
|
||||||
|
window.fetch(url, { credentials: 'same-origin' })
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw Error(response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.blob();
|
||||||
|
}).then(blob => {
|
||||||
|
|
||||||
|
var content = URL.createObjectURL(blob),
|
||||||
|
link = document.createElement("a"),
|
||||||
|
type = blob.type,
|
||||||
|
hasForcedType = 'mimeType' in container.dataset,
|
||||||
|
extension;
|
||||||
|
|
||||||
|
if (hasForcedType) {
|
||||||
|
// force a type
|
||||||
|
type = container.dataset.mimeType;
|
||||||
|
blob = new Blob([ blob ], { 'type': type });
|
||||||
|
content = URL.createObjectURL(blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension = mime.extension(type);
|
||||||
|
|
||||||
|
link.appendChild(document.createTextNode(download_text));
|
||||||
|
link.classList.add("sc-button", "btn-action");
|
||||||
|
link.href = content;
|
||||||
|
link.download = alias;
|
||||||
|
if (extension !== false) {
|
||||||
|
link.download = link.download + '.' + extension;
|
||||||
|
}
|
||||||
|
container.innerHTML = "";
|
||||||
|
container.appendChild(link);
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.log(error);
|
||||||
|
var problem_text =
|
||||||
|
document.createTextNode("Problem during download");
|
||||||
|
|
||||||
|
container
|
||||||
|
.replaceChild(problem_text, container.firstChild);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = download_report;
|
18
Resources/public/modules/download-report/index.js
Normal file
18
Resources/public/modules/download-report/index.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
chill.download_report = require("./download-report.js");
|
3
Resources/public/modules/login_page/index.js
Normal file
3
Resources/public/modules/login_page/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
require('./login.scss');
|
||||||
|
|
||||||
|
|
63
Resources/public/modules/login_page/login.scss
Normal file
63
Resources/public/modules/login_page/login.scss
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
@import './../../fonts/OpenSans/OpenSans';
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
position: relative;
|
||||||
|
height: 90%;
|
||||||
|
padding-top: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content:before {
|
||||||
|
bottom: 0;
|
||||||
|
content: "";
|
||||||
|
left: 0;
|
||||||
|
opacity: 0.2;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: -1;
|
||||||
|
background-image: url('./../../img/background/desert.jpg');
|
||||||
|
background-attachment: fixed;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
width : 15em;
|
||||||
|
text-align: right;
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 300;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
margin-left: 15em;
|
||||||
|
margin-top: 1em;
|
||||||
|
background-color: #df4949;
|
||||||
|
border: medium none;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
color: #fff;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
36
Resources/public/modules/postal-code/index.js
Normal file
36
Resources/public/modules/postal-code/index.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
window.addEventListener('load', function (e) {
|
||||||
|
var
|
||||||
|
postalCodes = document.querySelectorAll('[data-postal-code]')
|
||||||
|
;
|
||||||
|
|
||||||
|
for (let i = 0; i < postalCodes.length; i++) {
|
||||||
|
let
|
||||||
|
searchUrl = postalCodes[i].dataset.searchUrl,
|
||||||
|
noResultsLabel = postalCodes[i].dataset.noResultsLabel,
|
||||||
|
errorLoadLabel = postalCodes[i].dataset.errorLoadLabel,
|
||||||
|
searchingLabel = postalCodes[i].dataset.searchingLabel
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
$(postalCodes[i]).select2({
|
||||||
|
allowClear: true,
|
||||||
|
language: {
|
||||||
|
errorLoading: function () {
|
||||||
|
return errorLoadLabel;
|
||||||
|
},
|
||||||
|
noResults: function () {
|
||||||
|
return noResultsLabel;
|
||||||
|
},
|
||||||
|
searching: function () {
|
||||||
|
return searchingLabel;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ajax: {
|
||||||
|
url: searchUrl,
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 250
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -1,4 +1,7 @@
|
|||||||
// YOUR CUSTOM SCSS
|
// YOUR CUSTOM SCSS
|
||||||
|
@import 'custom/config/colors';
|
||||||
|
@import 'custom/config/variables';
|
||||||
|
@import 'custom/fonts';
|
||||||
@import 'custom/timeline';
|
@import 'custom/timeline';
|
||||||
@import 'custom/mixins/entity';
|
@import 'custom/mixins/entity';
|
||||||
@import 'custom/activity';
|
@import 'custom/activity';
|
||||||
@ -11,7 +14,7 @@
|
|||||||
@import 'custom/flash_messages';
|
@import 'custom/flash_messages';
|
||||||
|
|
||||||
|
|
||||||
html,body {
|
html,body {
|
||||||
min-height:100%;
|
min-height:100%;
|
||||||
font-family: 'Open Sans';
|
font-family: 'Open Sans';
|
||||||
}
|
}
|
||||||
@ -34,7 +37,7 @@ header {
|
|||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
background-image: url("/bundles/chillmain/img/background/desert.jpg");
|
background-image: url('./../../img/background/desert.jpg');
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
@ -74,18 +77,14 @@ ul.custom_fields.choice li {
|
|||||||
color: $red;
|
color: $red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blop label {
|
|
||||||
line-height: 1 + ($base-spacing / 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
p {
|
p {
|
||||||
font-family: 'Open Sans';
|
font-family: 'Open Sans';
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: white;
|
color: white;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,7 +96,7 @@ ul.custom_fields.choice li {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
margin-left: 0.2em;
|
margin-left: 0.2em;
|
||||||
margin-right: 0.2em;
|
margin-right: 0.2em;
|
||||||
@ -107,7 +106,7 @@ ul.custom_fields.choice li {
|
|||||||
.open_sansbold {
|
.open_sansbold {
|
||||||
font-family: 'Open Sans';
|
font-family: 'Open Sans';
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -140,21 +139,21 @@ div.input_with_post_text input {
|
|||||||
|
|
||||||
dl.chill_report_view_data,
|
dl.chill_report_view_data,
|
||||||
dl.chill_view_data {
|
dl.chill_view_data {
|
||||||
|
|
||||||
dt {
|
dt {
|
||||||
margin-top: 1.5em;
|
margin-top: 1.5em;
|
||||||
color: $chill-blue;
|
color: $chill-blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
dd {
|
dd {
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
margin-top: 0.2em;
|
margin-top: 0.2em;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -164,13 +163,13 @@ blockquote.chill-user-quote {
|
|||||||
padding: 0.5em 10px;
|
padding: 0.5em 10px;
|
||||||
quotes: "\201C""\201D""\2018""\2019";
|
quotes: "\201C""\201D""\2018""\2019";
|
||||||
background-color: $chill-llight-gray;
|
background-color: $chill-llight-gray;
|
||||||
|
|
||||||
|
|
||||||
p { display: inline; }
|
p { display: inline; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chill-no-data-statement {
|
.chill-no-data-statement {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
// note that other level are defined in modules/_alerts.scss
|
// note that other level are defined in modules/_alerts.scss
|
||||||
|
|
||||||
.alert {
|
// .alert {
|
||||||
// override in modules/_alerts.scss
|
// // override in modules/_alerts.scss
|
||||||
@include alert($red);
|
// @include alert($red);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
.warning {
|
// .warning {
|
||||||
@include alert($orange);
|
// @include alert($orange);
|
||||||
}
|
// }
|
||||||
|
2
Resources/public/sass/custom/_fonts.scss
Normal file
2
Resources/public/sass/custom/_fonts.scss
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
@import './../../fonts/OpenSans/OpenSans';
|
@ -6,29 +6,36 @@ ul.record_actions li {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
ul.record_actions {
|
ul.record_actions, ul.record_actions_column {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
order: 99;
|
order: 99;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
li.cancel {
|
li.cancel {
|
||||||
order: 1;
|
order: 1;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.record_actions {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.record_actions_column {
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.record_actions.sticky-form-buttons {
|
ul.record_actions.sticky-form-buttons {
|
||||||
@ -42,4 +49,4 @@ td ul.record_actions,
|
|||||||
li {
|
li {
|
||||||
margin-right: 0.2em;
|
margin-right: 0.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,13 @@ $chill-yellow: #eec84a;
|
|||||||
$chill-orange: #e2793d;
|
$chill-orange: #e2793d;
|
||||||
$chill-red: #df4949;
|
$chill-red: #df4949;
|
||||||
$chill-gray: #ececec;
|
$chill-gray: #ececec;
|
||||||
$chill-beige :#cabb9f;
|
$chill-beige: #cabb9f;
|
||||||
$chill-pink :#dd506d;
|
$chill-pink: #dd506d;
|
||||||
$chill-dark-gray: #333333;
|
$chill-dark-gray: #333333;
|
||||||
$chill-light-gray: #b2b2b2;
|
$chill-light-gray: #b2b2b2;
|
||||||
$chill-llight-gray: $grey-10;
|
$chill-llight-gray: #e6e6e6;
|
||||||
|
|
||||||
|
$dark-grey: $chill-dark-gray;
|
||||||
|
|
||||||
$color-name: "blue" "green" "green-dark" "yellow" "orange" "red" "gray" "beige" "pink" "dark-gray" "light-gray";
|
$color-name: "blue" "green" "green-dark" "yellow" "orange" "red" "gray" "beige" "pink" "dark-gray" "light-gray";
|
||||||
$color-code: #334d5c #43b29d #328474 #eec84a #e2793d #df4949 #ececec #cabb9f #dd506d #333333 #b2b2b2;
|
$color-code: #334d5c #43b29d #328474 #eec84a #e2793d #df4949 #ececec #cabb9f #dd506d #333333 #b2b2b2;
|
||||||
@ -27,3 +29,37 @@ $green: $chill-green;
|
|||||||
$blue: $chill-blue;
|
$blue: $chill-blue;
|
||||||
$yellow: $chill-yellow;
|
$yellow: $chill-yellow;
|
||||||
|
|
||||||
|
$black: #111111;
|
||||||
|
$white: #ffffff;
|
||||||
|
$light-grey: $chill-light-gray;
|
||||||
|
|
||||||
|
/*
|
||||||
|
due to a bug in sass, we must re-declare the variable in css
|
||||||
|
(use of a sass variable after -- does not work)
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--chill-blue: #334d5c;
|
||||||
|
--chill-green: #43b29d;
|
||||||
|
--chill-green-dark: #328474;
|
||||||
|
--chill-yellow: #eec84a;
|
||||||
|
--chill-orange: #e2793d;
|
||||||
|
--chill-red: #df4949;
|
||||||
|
--chill-gray: #ececec;
|
||||||
|
--chill-beige: #cabb9f;
|
||||||
|
--chill-pink: #dd506d;
|
||||||
|
--chill-dark-gray: #333333;
|
||||||
|
--chill-light-gray: #b2b2b2;
|
||||||
|
--chill-llight-gray: #e6e6e6;
|
||||||
|
|
||||||
|
--dark-grey: #333333;
|
||||||
|
|
||||||
|
--orange: #e2793d;
|
||||||
|
--red: #df4949;
|
||||||
|
--green: #43b29d;
|
||||||
|
--blue: #334d5c;
|
||||||
|
--yellow: #eec84a;
|
||||||
|
|
||||||
|
--black: #111111;
|
||||||
|
--white: #ffffff;
|
||||||
|
--light-grey: #b2b2b2;
|
||||||
|
}
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
.sc-button {
|
.sc-button {
|
||||||
&.bt-submit, &.bt-save, &.bt-create, &.bt-new {
|
&.bt-submit, &.bt-save, &.bt-create, &.bt-new {
|
||||||
@include button($green, $white);
|
@include button($green, $white);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bt-reset, &.bt-delete {
|
&.bt-reset, &.bt-delete {
|
||||||
@include button($red, $white);
|
@include button($red, $white);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bt-action, &.bt-edit, &.bt-update {
|
&.bt-action, &.bt-edit, &.bt-update {
|
||||||
@include button($orange, $white);
|
@include button($orange, $white);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bt-show, &.bt-view {
|
&.bt-show, &.bt-view {
|
||||||
@include button($blue, $white);
|
@include button($blue, $white);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.change-icon) {
|
&:not(.change-icon) {
|
||||||
|
|
||||||
&.bt-create::before,
|
&.bt-create::before,
|
||||||
&.bt-save::before,
|
&.bt-save::before,
|
||||||
&.bt-new::before,
|
&.bt-new::before,
|
||||||
&.bt-delete::before,
|
&.bt-delete::before,
|
||||||
&.bt-update::before,
|
&.bt-update::before,
|
||||||
&.bt-edit::before,
|
&.bt-edit::before,
|
||||||
&.bt-cancel::before,
|
&.bt-cancel::before,
|
||||||
&.bt-view::before,
|
&.bt-view::before,
|
||||||
@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
&.bt-cancel::before {
|
&.bt-cancel::before {
|
||||||
// add an arrow left
|
// add an arrow left
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bt-show::before, &.bt-view::before {
|
&.bt-show::before, &.bt-view::before {
|
||||||
@ -70,11 +70,11 @@
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
&:not(.change-icon) {
|
&:not(.change-icon) {
|
||||||
&.bt-create::before,
|
&.bt-create::before,
|
||||||
&.bt-save::before,
|
&.bt-save::before,
|
||||||
&.bt-new::before,
|
&.bt-new::before,
|
||||||
&.bt-delete::before,
|
&.bt-delete::before,
|
||||||
&.bt-update::before,
|
&.bt-update::before,
|
||||||
&.bt-edit::before,
|
&.bt-edit::before,
|
||||||
&.bt-cancel::before,
|
&.bt-cancel::before,
|
||||||
&.bt-view::before,
|
&.bt-view::before,
|
||||||
@ -83,13 +83,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-hidden > span.show-on-hover {
|
&.has-hidden > span.show-on-hover {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-hidden:hover {
|
&.has-hidden:hover {
|
||||||
|
|
||||||
> span.show-on-hover {
|
> span.show-on-hover {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@ -97,13 +97,13 @@
|
|||||||
> i.fa {
|
> i.fa {
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.change-icon) {
|
&:not(.change-icon) {
|
||||||
&.bt-create::before,
|
&.bt-create::before,
|
||||||
&.bt-save::before,
|
&.bt-save::before,
|
||||||
&.bt-new::before,
|
&.bt-new::before,
|
||||||
&.bt-delete::before,
|
&.bt-delete::before,
|
||||||
&.bt-update::before,
|
&.bt-update::before,
|
||||||
&.bt-edit::before,
|
&.bt-edit::before,
|
||||||
&.bt-cancel::before,
|
&.bt-cancel::before,
|
||||||
&.bt-view::before,
|
&.bt-view::before,
|
||||||
@ -144,4 +144,4 @@
|
|||||||
.sticky-form-buttons .margin-10 {
|
.sticky-form-buttons .margin-10 {
|
||||||
margin-left: 10%;
|
margin-left: 10%;
|
||||||
margin-right: 10%;
|
margin-right: 10%;
|
||||||
}
|
}
|
||||||
|
@ -6,4 +6,23 @@ textarea {
|
|||||||
|
|
||||||
span.force-inline-label label {
|
span.force-inline-label label {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.chill-form-money {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
span.chill-form-money__money {
|
||||||
|
align-self: center;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.chill-form__errors {
|
||||||
|
.chill-form_errors__entry.chill-form__errors__entry--warning {
|
||||||
|
color: var(--chill-green-dark);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,52 +1,88 @@
|
|||||||
.navigation {
|
.navigation {
|
||||||
background-color: $chill-blue;
|
background-color: $chill-blue;
|
||||||
|
|
||||||
|
|
||||||
a.more:after {
|
a.more:after {
|
||||||
color: $chill-dark-gray;
|
color: $chill-dark-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.nav-link2 {
|
li.nav-link2 {
|
||||||
a {
|
a {
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.lang-selection {
|
&.lang-selection {
|
||||||
color: $chill-light-gray;
|
|
||||||
font-size: 0.7em;
|
|
||||||
|
|
||||||
a.more:after {
|
|
||||||
color: $chill-light-gray;
|
color: $chill-light-gray;
|
||||||
}
|
font-size: 0.7em;
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
a.more:after {
|
||||||
top: 58px;
|
color: $chill-light-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
ul {
|
||||||
padding-left: 0;
|
top: 58px;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.nav, div.navigation-search {
|
a {
|
||||||
float: right;
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
input[type=search] {
|
div.nav, div.navigation-search {
|
||||||
padding: 0.2em;
|
float: right;
|
||||||
float: left;
|
|
||||||
|
|
||||||
border: none;
|
input[type=search] {
|
||||||
}
|
padding: 0.2em;
|
||||||
|
float: left;
|
||||||
button {
|
|
||||||
color: $chill-light-gray;
|
border: none;
|
||||||
background-color: $chill-blue;
|
}
|
||||||
padding: 0 0 0 7px;
|
|
||||||
top: inherit;
|
button {
|
||||||
font-size: 1.2em;
|
color: $chill-light-gray;
|
||||||
position: unset;
|
background-color: $chill-blue;
|
||||||
float: left;
|
padding: 0 0 0 7px;
|
||||||
}
|
top: inherit;
|
||||||
}
|
font-size: 1.2em;
|
||||||
|
position: unset;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.user-menu {
|
||||||
|
min-width: 14rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.user-menu-list {
|
||||||
|
|
||||||
|
li.user-menu__entry {
|
||||||
|
display: block;
|
||||||
|
background-color: $chill-dark-gray;
|
||||||
|
border-bottom: 1px solid #FFF;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.user-menu__entry--warning-entry {
|
||||||
|
background-color: $chill-red;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span.notification-counter {
|
||||||
|
display: inline-block;
|
||||||
|
padding: .25em .6em .25rem .6em;
|
||||||
|
font-size: 100%;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
border-radius: 10rem;
|
||||||
|
background-color: $chill-red;
|
||||||
|
color: $white;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-left: .5rem;
|
||||||
|
}
|
||||||
}
|
}
|
@ -17,6 +17,8 @@ class AppKernel extends Kernel
|
|||||||
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
|
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
|
||||||
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
|
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
|
||||||
new Symfony\Bundle\MonologBundle\MonologBundle(),
|
new Symfony\Bundle\MonologBundle\MonologBundle(),
|
||||||
|
new Knp\Bundle\MenuBundle\KnpMenuBundle(),
|
||||||
|
new Symfony\Bundle\DebugBundle\DebugBundle()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,6 @@ security:
|
|||||||
form_login:
|
form_login:
|
||||||
csrf_parameter: _csrf_token
|
csrf_parameter: _csrf_token
|
||||||
csrf_token_id: authenticate
|
csrf_token_id: authenticate
|
||||||
csrf_provider: form.csrf_provider
|
|
||||||
|
|
||||||
logout: ~
|
logout: ~
|
||||||
|
|
||||||
|
@ -31,7 +31,8 @@ not valid: non valide
|
|||||||
Confirm: Confirmer
|
Confirm: Confirmer
|
||||||
Cancel: Annuler
|
Cancel: Annuler
|
||||||
Save: Enregistrer
|
Save: Enregistrer
|
||||||
|
This form contains errors: Ce formulaire contient des erreurs
|
||||||
|
Choose an user: Choisir un utilisateur
|
||||||
'You are going to leave a page with unsubmitted data. Are you sure you want to leave ?': "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?"
|
'You are going to leave a page with unsubmitted data. Are you sure you want to leave ?': "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?"
|
||||||
|
|
||||||
Edit: Modifier
|
Edit: Modifier
|
||||||
@ -63,7 +64,9 @@ Advanced search: Recherche avancée
|
|||||||
#admin
|
#admin
|
||||||
Create: Créer
|
Create: Créer
|
||||||
show: voir
|
show: voir
|
||||||
|
Show: Voir
|
||||||
edit: modifier
|
edit: modifier
|
||||||
|
Edit: Modifier
|
||||||
Main admin menu: Menu d'administration principal
|
Main admin menu: Menu d'administration principal
|
||||||
Actions: Actions
|
Actions: Actions
|
||||||
Users and permissions: Utilisateurs et permissions
|
Users and permissions: Utilisateurs et permissions
|
||||||
@ -179,4 +182,9 @@ column: colonne
|
|||||||
Comma separated values (CSV): Valeurs séparées par des virgules (CSV - tableur)
|
Comma separated values (CSV): Valeurs séparées par des virgules (CSV - tableur)
|
||||||
|
|
||||||
# spreadsheet formatter
|
# spreadsheet formatter
|
||||||
Choose the format: Choisir le format
|
Choose the format: Choisir le format
|
||||||
|
|
||||||
|
# select2
|
||||||
|
'select2.no_results': Aucun résultat
|
||||||
|
'select2.error_loading': Erreur de chargement des résultats
|
||||||
|
'select2.searching': Recherche en cours...
|
@ -5,4 +5,7 @@ The role "%role%" should not be associated with a scope.: Le rôle "%role%" ne d
|
|||||||
"The password must contains one letter, one capitalized letter, one number and one special character as *[@#$%!,;:+\"'-/{}~=µ()£]). Other characters are allowed.": "Le mot de passe doit contenir une majuscule, une minuscule, et au moins un caractère spécial parmi *[@#$%!,;:+\"'-/{}~=µ()£]). Les autres caractères sont autorisés."
|
"The password must contains one letter, one capitalized letter, one number and one special character as *[@#$%!,;:+\"'-/{}~=µ()£]). Other characters are allowed.": "Le mot de passe doit contenir une majuscule, une minuscule, et au moins un caractère spécial parmi *[@#$%!,;:+\"'-/{}~=µ()£]). Les autres caractères sont autorisés."
|
||||||
The password fields must match: Les mots de passe doivent correspondre
|
The password fields must match: Les mots de passe doivent correspondre
|
||||||
|
|
||||||
A permission is already present for the same role and scope: Une permission est déjà présente pour le même rôle et cercle.
|
A permission is already present for the same role and scope: Une permission est déjà présente pour le même rôle et cercle.
|
||||||
|
|
||||||
|
#UserCircleConsistency
|
||||||
|
"{{ username }} is not allowed to see entities published in this circle": "{{ username }} n'est pas autorisé à voir l'élément publié dans ce cercle."
|
@ -27,50 +27,8 @@ window.addEventListener("DOMContentLoaded", function(e) {
|
|||||||
query = window.location.search,
|
query = window.location.search,
|
||||||
container = document.querySelector("#download_container")
|
container = document.querySelector("#download_container")
|
||||||
;
|
;
|
||||||
|
|
||||||
window.fetch(url+query, { credentials: 'same-origin' })
|
|
||||||
.then(function(response) {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw Error(response.statusText);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.blob();
|
|
||||||
}).then(function(blob) {
|
|
||||||
var content = URL.createObjectURL(blob),
|
|
||||||
link = document.createElement("a"),
|
|
||||||
suffix_file;
|
|
||||||
|
|
||||||
switch (blob.type) {
|
|
||||||
case 'application/vnd.oasis.opendocument.spreadsheet':
|
|
||||||
suffix_file = '.ods';
|
|
||||||
break;
|
|
||||||
case 'text/csv':
|
|
||||||
suffix_file = '.csv';
|
|
||||||
break;
|
|
||||||
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
|
||||||
suffix_file = '.xlsx';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
suffix_file = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
link.appendChild(document.createTextNode("{{ "Download your report"|trans }}"));
|
|
||||||
link.classList.add("sc-button", "btn-action");
|
|
||||||
link.href = content;
|
|
||||||
link.download = "{{ alias }}"+suffix_file;
|
|
||||||
let waiting_text = container.querySelector("#waiting_text");
|
|
||||||
container.removeChild(waiting_text);
|
|
||||||
container.appendChild(link);
|
|
||||||
}).catch(function(error) {
|
|
||||||
var problem_text =
|
|
||||||
document.createTextNode("{{ "Problem during download"|trans }}");
|
|
||||||
|
|
||||||
container
|
|
||||||
.replaceChild(problem_text, container.firstChild);
|
|
||||||
})
|
|
||||||
;
|
|
||||||
|
|
||||||
|
|
||||||
|
chill.download_report(url+query, container);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -79,6 +37,6 @@ window.addEventListener("DOMContentLoaded", function(e) {
|
|||||||
|
|
||||||
<h1>{{ "Download export"|trans }}</h1>
|
<h1>{{ "Download export"|trans }}</h1>
|
||||||
|
|
||||||
<div id="download_container"><span id="waiting_text">{{ "Waiting for your report"|trans }}...</span></div>
|
<div id="download_container" data-alias="{{ alias|escape('html_attr') }}" {% if mime_type is defined %}data-mime-type="{{ mime_type|escape('html_attr') }}"{% endif %} data-download-text="{{ "Download your report"|trans|escape('html_attr') }}"><span id="waiting_text">{{ "Waiting for your report"|trans }}...</span></div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -61,7 +61,11 @@
|
|||||||
<br/>
|
<br/>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">chill.checkNullValuesInChoices("{{ form.vars.full_name }}");</script>
|
<script type="text/javascript">
|
||||||
|
document.addEventListener('load', function(e) {
|
||||||
|
chill.checkNullValuesInChoices("{{ form.vars.full_name }}");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
{% endblock choice_widget_expanded %}
|
{% endblock choice_widget_expanded %}
|
||||||
|
|
||||||
@ -90,11 +94,20 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
chill.checkNullValuesInChoices("{{ form._choices.vars.full_name }}");
|
document.addEventListener('load', function(e) {
|
||||||
|
chill.checkNullValuesInChoices("{{ form._choices.vars.full_name }}");
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
{% endblock choice_with_other_widget %}
|
{% endblock choice_with_other_widget %}
|
||||||
|
|
||||||
|
{% block money_widget %}
|
||||||
|
<div class="chill-form-money">
|
||||||
|
{{ block('form_widget_simple') }}
|
||||||
|
<span class="chill-form-money__money">{{ money_pattern|form_encode_currency }}</span>
|
||||||
|
</div>
|
||||||
|
{% endblock money_widget %}
|
||||||
|
|
||||||
|
|
||||||
{% block date_widget %}
|
{% block date_widget %}
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
@ -128,9 +141,9 @@
|
|||||||
{% block form_errors %}
|
{% block form_errors %}
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
{% if errors|length > 0 %}
|
{% if errors|length > 0 %}
|
||||||
<ul class="errors">
|
<ul class="errors chill-form__errors">
|
||||||
{% for error in errors %}
|
{% for error in errors %}
|
||||||
<li>{{ error.message }}</li>
|
<li class="chill-form_errors__entry {% if 'severity' in error.cause.constraint.payload|keys %}chill-form__errors__entry--{{ error.cause.constraint.payload.severity }}{% endif %}">{{ error.message }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -152,3 +165,21 @@
|
|||||||
{{ form_row(form.order) }}
|
{{ form_row(form.order) }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block chill_collection_widget %}
|
||||||
|
<div class="chill-collection">
|
||||||
|
<ul class="chill-collection__list" data-collection-name="{{ form.vars.name|escape('html_attr') }}" data-collection-identifier="{{ form.vars.identifier|escape('html_attr') }}" data-collection-button-remove-label="{{ form.vars.button_remove_label|trans|e }}" data-collection-allow-add="{{ form.vars.allow_add|escape('html_attr') }}" data-collection-allow-delete="{{ form.vars.allow_delete|escape('html_attr') }}" >
|
||||||
|
{% for entry in form %}
|
||||||
|
<li class="chill-collection__list__entry" data-collection-is-persisted="1">
|
||||||
|
<div>
|
||||||
|
{{ form_widget(entry) }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% if form.vars.allow_add == 1 %}
|
||||||
|
<button class="chill-collection__button--add sc-button" data-collection-add-target="{{ form.vars.name|escape('html_attr') }}" data-form-prototype="{{ ('<div>' ~ form_widget(form.vars.prototype) ~ '</div>')|escape('html_attr') }}" >{{ form.vars.button_add_label|trans }}</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
@ -23,81 +23,16 @@
|
|||||||
<title>
|
<title>
|
||||||
{{ 'Login to %installation_name%' | trans({ '%installation_name%' : installation.name } ) }}
|
{{ 'Login to %installation_name%' | trans({ '%installation_name%' : installation.name } ) }}
|
||||||
</title>
|
</title>
|
||||||
<link rel="shortcut icon" href="/bundles/chillmain/img/favicon.ico" type="image/x-icon">
|
<link rel="shortcut icon" href="{{ asset('build/images/favicon.ico') }}" type="image/x-icon">
|
||||||
{% stylesheets output="css/login.css" filter="cssrewrite"
|
<link rel="stylesheet" href="{{ asset('build/login.css') }}"/>
|
||||||
|
|
||||||
"bundles/chillmain/fonts/OpenSans/OpenSans.css" %}
|
|
||||||
<link rel="stylesheet" href="{{ asset_url }}"/>
|
|
||||||
{% endstylesheets %}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
html, body {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #333;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content {
|
|
||||||
position: relative;
|
|
||||||
height: 90%;
|
|
||||||
padding-top: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content:before {
|
|
||||||
bottom: 0;
|
|
||||||
content: "";
|
|
||||||
left: 0;
|
|
||||||
opacity: 0.2;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
z-index: -1;
|
|
||||||
background-image: url("/bundles/chillmain/img/background/desert.jpg");
|
|
||||||
background-attachment: fixed;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
width : 15em;
|
|
||||||
text-align: right;
|
|
||||||
display: inline-block;
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
font-weight: 300;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
margin-left: 15em;
|
|
||||||
margin-top: 1em;
|
|
||||||
background-color: #df4949;
|
|
||||||
border: medium none;
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
color: #fff;
|
|
||||||
padding: 4px 8px;
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<img class="logo" src="/bundles/chillmain/img/logo-chill-outil-accompagnement_white.png">
|
<img class="logo" src="{{ asset('build/images/logo-chill-outil-accompagnement_white.png') }}">
|
||||||
|
|
||||||
<p>{{ error|trans }}</p>
|
{% if error is not null %}
|
||||||
|
<p>{{ error.message|trans }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<form method="POST" action="{{ path('login_check') }}">
|
<form method="POST" action="{{ path('login_check') }}">
|
||||||
<label for="_username">{{ 'Username'|trans }}</label>
|
<label for="_username">{{ 'Username'|trans }}</label>
|
||||||
|
@ -21,16 +21,18 @@
|
|||||||
<a href="javascript:void(0)" class="more">Sections</a>
|
<a href="javascript:void(0)" class="more">Sections</a>
|
||||||
</div>
|
</div>
|
||||||
<ul class="submenu width-15-em" style="padding-left: 0; padding-right: 0; background-color:transparent;">
|
<ul class="submenu width-15-em" style="padding-left: 0; padding-right: 0; background-color:transparent;">
|
||||||
{% for route in routes %}
|
{% for menu in menus %}
|
||||||
<li style="display:block; background-color: #333333; padding-left:1.5em; border-bottom:1px; border-bottom: 1px solid #FFF;padding-top:0; padding-bottom:0;">
|
<li style="display:block; background-color: #333333; padding-left:1.5em; border-bottom:1px; border-bottom: 1px solid #FFF;padding-top:0; padding-bottom:0;">
|
||||||
<div style="margin-bottom:2px;">
|
<div style="margin-bottom:2px;">
|
||||||
<div style="font-family: 'Open Sans'; font-weight:300; font-size: 0.75em; text-align:left; height: 46px; display:inline-block; width: calc(100% - 5em - 1px); vertical-align:top;">
|
<div style="font-family: 'Open Sans'; font-weight:300; font-size: 0.75em; text-align:left; height: 46px; display:inline-block; width: calc(100% - 5em - 1px); vertical-align:top;">
|
||||||
<a href="{{ path(route.key, args ) }}">{{ route.label|trans }}</a>
|
<a href="{{ menu.uri }}">{{ menu.label|trans }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div style="background-color: #333333; text-align:center;width: 2em; margin-left:-0.15em; font-size:1.5em; color:#FFF; height: 46px; display:inline-block; vertical-align:top;float:right">{% spaceless %}
|
<div style="background-color: #333333; text-align:center;width: 2em; margin-left:-0.15em; font-size:1.5em; color:#FFF; height: 46px; display:inline-block; vertical-align:top;float:right">{% spaceless %}
|
||||||
{% for icon in route.icons %}
|
{% if menu.extras.icons is defined %}
|
||||||
|
{% for icon in menu.extras.icons %}
|
||||||
<i class="fa fa-{{ icon }}"></i>
|
<i class="fa fa-{{ icon }}"></i>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
{% endspaceless %}</div>
|
{% endspaceless %}</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{#
|
{#
|
||||||
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
|
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
|
||||||
<info@champs-libres.coop> / <http://www.champs-libres.coop>
|
<info@champs-libres.coop> / <http://www.champs-libres.coop>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -16,16 +16,27 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#}
|
#}
|
||||||
|
|
||||||
<li class="nav-link2">
|
<li class="nav-link2 user-menu">
|
||||||
<div class="li-content">
|
<div class="li-content">
|
||||||
<a href="javascript:void(0)" style="font-size: 0.8em; font-family: 'Open Sans'; font-weight:300;">
|
<a href="javascript:void(0)" style="font-size: 0.8em; font-family: 'Open Sans'; font-weight:300;">
|
||||||
{{ 'Welcome' | trans }}<br/>
|
{{ 'Welcome' | trans }}<br/>
|
||||||
<b>{{ app.user.username }}</b>
|
<b>{{ app.user.username }}{{ render(controller('ChillMainBundle:UI:showNotificationUserCounter')) }}</b>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<ul class="submenu width-11-em">
|
<ul class="submenu width-11-em user-menu-list" style="padding-left: 0; padding-right: 0; background-color:transparent;">
|
||||||
{% for route in routes %}
|
{% for menu in menus %}
|
||||||
<li><a href="{{ path(route.key, args ) }}" style="font-family: 'Open Sans'; font-weight:300; font-size: 0.9em;"><i class="fa fa-{{ route.icon }}"></i> {{ route.label|trans }}</a></li>
|
<li style="display:block; background-color: #333333; padding-left:1.5em; border-bottom:1px; border-bottom: 1px solid #FFF;padding-top:0; padding-bottom:0;">
|
||||||
|
<div style="margin-bottom:2px;">
|
||||||
|
<div style="font-family: 'Open Sans'; font-weight:300; font-size: 0.75em; text-align:left; height: 46px; display:inline-block; width: calc(100% - 5em - 1px); vertical-align:top;">
|
||||||
|
<a href="{{ menu.uri }}">{{ menu.label|trans }}</a>
|
||||||
|
</div>
|
||||||
|
<div style="background-color: #333333; text-align:center;width: 2em; margin-left:-0.15em; font-size:1.5em; color:#FFF; height: 46px; display:inline-block; vertical-align:top;float:right">{% spaceless %}
|
||||||
|
{% if menu.extras.icon is defined %}
|
||||||
|
<i class="fa fa-{{ menu.extras.icon }}"></i>
|
||||||
|
{% endif %}
|
||||||
|
{% endspaceless %}</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
1
Resources/views/UI/notification_user_counter.html.twig
Normal file
1
Resources/views/UI/notification_user_counter.html.twig
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% if nb > 0 %}<span class="notification-counter">{{ nb }}{% endif %}
|
@ -8,6 +8,7 @@
|
|||||||
{{ form_start(edit_form) }}
|
{{ form_start(edit_form) }}
|
||||||
|
|
||||||
{{ form_row(edit_form.username) }}
|
{{ form_row(edit_form.username) }}
|
||||||
|
{{ form_row(edit_form.email) }}
|
||||||
{{ form_row(edit_form.enabled, { 'label': "User'status"}) }}
|
{{ form_row(edit_form.enabled, { 'label': "User'status"}) }}
|
||||||
|
|
||||||
{{ form_widget(edit_form.submit, { 'attr': { 'class' : 'sc-button green center' } } ) }}
|
{{ form_widget(edit_form.submit, { 'attr': { 'class' : 'sc-button green center' } } ) }}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
{{ form_row(form.username) }}
|
{{ form_row(form.username) }}
|
||||||
|
{{ form_row(form.email) }}
|
||||||
{{ form_row(form.plainPassword.password) }}
|
{{ form_row(form.plainPassword.password) }}
|
||||||
{{ form_widget(form.submit, { 'attr' : { 'class': 'sc-button blue' } }) }}
|
{{ form_widget(form.submit, { 'attr' : { 'class': 'sc-button blue' } }) }}
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{#
|
{#
|
||||||
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
|
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
|
||||||
<info@champs-libres.coop> / <http://www.champs-libres.coop>
|
<info@champs-libres.coop> / <http://www.champs-libres.coop>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -22,19 +22,13 @@
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>{{ installation.name }} - {% block title %}{% endblock %}</title>
|
<title>{{ installation.name }} - {% block title %}{% endblock %}</title>
|
||||||
<link rel="shortcut icon" href="/bundles/chillmain/img/favicon.ico" type="image/x-icon">
|
<link rel="shortcut icon" href="{{ asset('build/images/favicon.ico') }}" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="{{ asset('build/chill.css') }}"/>
|
||||||
{% stylesheets output="css/all.css" filter="cssrewrite"
|
|
||||||
"bundles/chillmain/css/scratch.css"
|
|
||||||
"bundles/chillmain/css/chillmain.css"
|
|
||||||
"bundles/chillmain/css/select2/select2.css"
|
|
||||||
"bundles/chillmain/fonts/OpenSans/OpenSans.css"
|
|
||||||
"bundles/chillmain/css/pikaday.css" %}
|
|
||||||
<link rel="stylesheet" href="{{ asset_url }}"/>
|
|
||||||
{% endstylesheets %}
|
|
||||||
|
|
||||||
{% block css%}<!-- nothing added to css -->{% endblock %}
|
{% block css%}<!-- nothing added to css -->{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -43,7 +37,7 @@
|
|||||||
<div class="grid-4 hide-tablet hide-mobile parent">
|
<div class="grid-4 hide-tablet hide-mobile parent">
|
||||||
<div class="grid-10 push-2 grid-tablet-12 grid-mobile-12 push-tablet-0 grid-mobile-0 logo-container">
|
<div class="grid-10 push-2 grid-tablet-12 grid-mobile-12 push-tablet-0 grid-mobile-0 logo-container">
|
||||||
<a href="{{ path('chill_main_homepage') }}">
|
<a href="{{ path('chill_main_homepage') }}">
|
||||||
<img class="logo" src="/bundles/chillmain/img/logo-chill-sans-slogan_white.png">
|
<img class="logo" src="{{ asset('build/images/logo-chill-sans-slogan_white.png') }}">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -107,7 +101,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% for flashMessage in app.session.flashbag.get('error') %}
|
{% for flashMessage in app.session.flashbag.get('error') %}
|
||||||
<div class="grid-8 centered error flash_message">
|
<div class="grid-8 centered error flash_message">
|
||||||
<span>
|
<span>
|
||||||
@ -115,14 +109,14 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% for flashMessage in app.session.flashbag.get('notice') %}
|
{% for flashMessage in app.session.flashbag.get('notice') %}
|
||||||
<div class="grid-8 centered notice flash_message">
|
<div class="grid-8 centered notice flash_message">
|
||||||
<span>
|
<span>
|
||||||
{{ flashMessage|raw }}
|
{{ flashMessage|raw }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -137,12 +131,12 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="homepage_widget">
|
<div class="homepage_widget">
|
||||||
{{ chill_widget('homepage', {} ) }}
|
{{ chill_widget('homepage', {} ) }}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -150,16 +144,8 @@
|
|||||||
<p>{{ 'This program is free software: you can redistribute it and/or modify it under the terms of the <strong>GNU Affero General Public License</strong>'|trans|raw }}
|
<p>{{ 'This program is free software: you can redistribute it and/or modify it under the terms of the <strong>GNU Affero General Public License</strong>'|trans|raw }}
|
||||||
<br/> <a href="https://{{ app.request.locale }}.wikibooks.org/wiki/Chill">{{ 'User manual'|trans }}</a></p>
|
<br/> <a href="https://{{ app.request.locale }}.wikibooks.org/wiki/Chill">{{ 'User manual'|trans }}</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
{% javascripts output="js/libs.js"
|
<script type="text/javascript" src="{{ asset('build/chill.js') }}"></script>
|
||||||
"bundles/chillmain/js/jquery.js"
|
|
||||||
"bundles/chillmain/js/moment.js"
|
|
||||||
"bundles/chillmain/js/pikaday/pikaday.js"
|
|
||||||
"bundles/chillmain/js/select2/select2.js"
|
|
||||||
"bundles/chillmain/js/pikaday/plugins/pikaday.jquery.js"
|
|
||||||
"bundles/chillmain/js/chill.js" %}
|
|
||||||
<script src="{{ asset_url }}" type="text/javascript"></script>
|
|
||||||
{% endjavascripts %}
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
chill.initPikaday('{{ app.request.locale }}');
|
chill.initPikaday('{{ app.request.locale }}');
|
||||||
|
38
Routing/LocalMenuBuilderInterface.php
Normal file
38
Routing/LocalMenuBuilderInterface.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Routing;
|
||||||
|
|
||||||
|
use Knp\Menu\MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
interface LocalMenuBuilderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* return an array of menu ids
|
||||||
|
*
|
||||||
|
* @internal this method is static to 1. keep all config in the class,
|
||||||
|
* instead of tags arguments; 2. avoid a "supports" method, which could lead
|
||||||
|
* to parsing all instances to get only one or two working instance.
|
||||||
|
*/
|
||||||
|
public static function getMenuIds(): array;
|
||||||
|
|
||||||
|
public function buildMenu($menuId, MenuItem $menu, array $parameters);
|
||||||
|
}
|
68
Routing/MenuBuilder/SectionMenuBuilder.php
Normal file
68
Routing/MenuBuilder/SectionMenuBuilder.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Routing\MenuBuilder;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||||
|
use Knp\Menu\MenuItem;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class SectionMenuBuilder implements LocalMenuBuilderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var AuthorizationCheckerInterface
|
||||||
|
*/
|
||||||
|
protected $authorizationChecker;
|
||||||
|
|
||||||
|
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
|
||||||
|
{
|
||||||
|
$this->authorizationChecker = $authorizationChecker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||||
|
{
|
||||||
|
$menu->addChild('Homepage', [
|
||||||
|
'route' => 'chill_main_homepage'
|
||||||
|
])
|
||||||
|
->setExtras([
|
||||||
|
'icons' => ['home'],
|
||||||
|
'order' => 0
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($this->authorizationChecker->isGranted(ChillExportVoter::EXPORT)) {
|
||||||
|
$menu->addChild('Export Menu', [
|
||||||
|
'route' => 'chill_main_export_index'
|
||||||
|
])
|
||||||
|
->setExtras([
|
||||||
|
'icons' => ['upload'],
|
||||||
|
'order' => 20
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMenuIds(): array
|
||||||
|
{
|
||||||
|
return [ 'section' ];
|
||||||
|
}
|
||||||
|
}
|
46
Routing/MenuBuilder/UserMenuBuilder.php
Normal file
46
Routing/MenuBuilder/UserMenuBuilder.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Routing\MenuBuilder;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class UserMenuBuilder implements LocalMenuBuilderInterface
|
||||||
|
{
|
||||||
|
public function buildMenu($menuId, \Knp\Menu\MenuItem $menu, array $parameters)
|
||||||
|
{
|
||||||
|
$menu->addChild(
|
||||||
|
'Logout',
|
||||||
|
[
|
||||||
|
'route' => 'logout'
|
||||||
|
])
|
||||||
|
->setExtras([
|
||||||
|
'order'=> 99999999999,
|
||||||
|
'icon' => 'power-off'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMenuIds(): array
|
||||||
|
{
|
||||||
|
return [ 'user' ];
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,11 @@
|
|||||||
namespace Chill\MainBundle\Routing;
|
namespace Chill\MainBundle\Routing;
|
||||||
|
|
||||||
use Symfony\Component\Routing\RouteCollection;
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
use Symfony\Component\Routing\RouterInterface;
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
use Knp\Menu\FactoryInterface;
|
||||||
|
use Knp\Menu\ItemInterface;
|
||||||
|
use Symfony\Component\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class permit to build menu from the routing information
|
* This class permit to build menu from the routing information
|
||||||
@ -14,27 +17,42 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||||||
*
|
*
|
||||||
* @author julien
|
* @author julien
|
||||||
*/
|
*/
|
||||||
class MenuComposer implements ContainerAwareInterface
|
class MenuComposer
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @var ContainerInterface
|
* @var RouterInterface
|
||||||
*/
|
*/
|
||||||
private $container;
|
private $router;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @internal using the service router in container cause circular references
|
* @var FactoryInterface
|
||||||
* @param ContainerInterface $container
|
|
||||||
*/
|
*/
|
||||||
public function setContainer(ContainerInterface $container = null)
|
private $menuFactory;
|
||||||
{
|
|
||||||
if (NULL === $container) {
|
/**
|
||||||
throw new \LogicException('container should not be null');
|
*
|
||||||
}
|
* @var TranslatorInterface
|
||||||
//see remark in MenuComposer::setRouteCollection
|
*/
|
||||||
$this->container = $container;
|
private $translator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var
|
||||||
|
*/
|
||||||
|
private $localMenuBuilders = [];
|
||||||
|
|
||||||
|
|
||||||
|
function __construct(
|
||||||
|
RouterInterface $router,
|
||||||
|
FactoryInterface $menuFactory,
|
||||||
|
TranslatorInterface $translator
|
||||||
|
) {
|
||||||
|
$this->router = $router;
|
||||||
|
$this->menuFactory = $menuFactory;
|
||||||
|
$this->translator = $translator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,10 +79,11 @@ class MenuComposer implements ContainerAwareInterface
|
|||||||
public function getRoutesFor($menuId, array $parameters = array())
|
public function getRoutesFor($menuId, array $parameters = array())
|
||||||
{
|
{
|
||||||
$routes = array();
|
$routes = array();
|
||||||
$routeCollection = $this->container->get('router')->getRouteCollection();
|
$routeCollection = $this->router->getRouteCollection();
|
||||||
|
|
||||||
foreach ($routeCollection->all() as $routeKey => $route) {
|
foreach ($routeCollection->all() as $routeKey => $route) {
|
||||||
if ($route->hasOption('menus')) {
|
if ($route->hasOption('menus')) {
|
||||||
|
|
||||||
if (array_key_exists($menuId, $route->getOption('menus'))) {
|
if (array_key_exists($menuId, $route->getOption('menus'))) {
|
||||||
$route = $route->getOption('menus')[$menuId];
|
$route = $route->getOption('menus')[$menuId];
|
||||||
|
|
||||||
@ -83,6 +102,37 @@ class MenuComposer implements ContainerAwareInterface
|
|||||||
return $routes;
|
return $routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMenuFor($menuId, array $parameters = array())
|
||||||
|
{
|
||||||
|
$routes = $this->getRoutesFor($menuId, $parameters);
|
||||||
|
$menu = $this->menuFactory->createItem($menuId);
|
||||||
|
|
||||||
|
// build menu from routes
|
||||||
|
foreach ($routes as $order => $route) {
|
||||||
|
$menu->addChild($this->translator->trans($route['label']), [
|
||||||
|
'route' => $route['key'],
|
||||||
|
'routeParameters' => $parameters['args'],
|
||||||
|
'order' => $order
|
||||||
|
])
|
||||||
|
->setExtras([
|
||||||
|
'icon' => $route['icon'],
|
||||||
|
'order' => $order
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->hasLocalMenuBuilder($menuId)) {
|
||||||
|
foreach ($this->localMenuBuilders[$menuId] as $builder) {
|
||||||
|
/* @var $builder LocalMenuBuilderInterface */
|
||||||
|
$builder->buildMenu($menuId, $menu, $parameters['args']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->reorderMenu($menu);
|
||||||
|
|
||||||
|
return $menu;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* recursive function to resolve the order of a array of routes.
|
* recursive function to resolve the order of a array of routes.
|
||||||
* If the order chosen in routing.yml is already in used, find the
|
* If the order chosen in routing.yml is already in used, find the
|
||||||
@ -92,12 +142,55 @@ class MenuComposer implements ContainerAwareInterface
|
|||||||
* @param int $order
|
* @param int $order
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
private function resolveOrder($routes, $order){
|
private function resolveOrder($routes, $order)
|
||||||
|
{
|
||||||
if (isset($routes[$order])) {
|
if (isset($routes[$order])) {
|
||||||
return $this->resolveOrder($routes, $order + 1);
|
return $this->resolveOrder($routes, $order + 1);
|
||||||
} else {
|
} else {
|
||||||
return $order;
|
return $order;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function reorderMenu(ItemInterface $menu)
|
||||||
|
{
|
||||||
|
$ordered = [];
|
||||||
|
$unordered = [];
|
||||||
|
|
||||||
|
foreach ($menu->getChildren() as $name => $item) {
|
||||||
|
$order = $item->getExtra('order');
|
||||||
|
|
||||||
|
if ($order !== null) {
|
||||||
|
$ordered[$this->resolveOrder($ordered, $order)] = $name;
|
||||||
|
} else {
|
||||||
|
$unordered = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($ordered);
|
||||||
|
|
||||||
|
$menus = \array_merge(\array_values($ordered), $unordered);
|
||||||
|
$menu->reorderChildren($menus);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function addLocalMenuBuilder(LocalMenuBuilderInterface $menuBuilder, $menuId)
|
||||||
|
{
|
||||||
|
$this->localMenuBuilders[$menuId][] = $menuBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the menu has at least one builder.
|
||||||
|
*
|
||||||
|
* This function is a helper to determine if the method `getMenuFor`
|
||||||
|
* should be used, or `getRouteFor`. The method `getMenuFor` should be used
|
||||||
|
* if the result is true (it **does** exists at least one menu builder.
|
||||||
|
*
|
||||||
|
* @param string $menuId
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasLocalMenuBuilder($menuId): bool
|
||||||
|
{
|
||||||
|
return \array_key_exists($menuId, $this->localMenuBuilders);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,10 @@ class MenuTwig extends \Twig_Extension implements ContainerAwareInterface
|
|||||||
public function getFunctions()
|
public function getFunctions()
|
||||||
{
|
{
|
||||||
return [new \Twig_SimpleFunction('chill_menu',
|
return [new \Twig_SimpleFunction('chill_menu',
|
||||||
array($this, 'chillMenu'), array('is_safe' => array('html')))
|
array($this, 'chillMenu'), array(
|
||||||
|
'is_safe' => array('html'),
|
||||||
|
'needs_environment' => true
|
||||||
|
))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,17 +84,22 @@ class MenuTwig extends \Twig_Extension implements ContainerAwareInterface
|
|||||||
* @param string $menuId
|
* @param string $menuId
|
||||||
* @param mixed[] $params
|
* @param mixed[] $params
|
||||||
*/
|
*/
|
||||||
public function chillMenu($menuId, array $params = array())
|
public function chillMenu(\Twig_Environment $env, $menuId, array $params = array())
|
||||||
{
|
{
|
||||||
$resolvedParams = array_merge($this->defaultParams, $params);
|
$resolvedParams = array_merge($this->defaultParams, $params);
|
||||||
|
|
||||||
$layout = $resolvedParams['layout'];
|
$layout = $resolvedParams['layout'];
|
||||||
unset($resolvedParams['layout']);
|
unset($resolvedParams['layout']);
|
||||||
|
|
||||||
$resolvedParams['routes'] = $this->menuComposer->getRoutesFor($menuId);
|
if ($this->menuComposer->hasLocalMenuBuilder($menuId) === false) {
|
||||||
|
$resolvedParams['routes'] = $this->menuComposer->getRoutesFor($menuId, $resolvedParams);
|
||||||
|
|
||||||
return $this->container->get('templating')
|
return $env->render($layout, $resolvedParams);
|
||||||
->render($layout, $resolvedParams);
|
} else {
|
||||||
|
$resolvedParams['menus'] = $this->menuComposer->getMenuFor($menuId, $resolvedParams);
|
||||||
|
|
||||||
|
return $env->render($layout, $resolvedParams);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName()
|
public function getName()
|
||||||
|
@ -26,6 +26,10 @@ use Chill\MainBundle\Entity\HasScopeInterface;
|
|||||||
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
||||||
use Symfony\Component\Security\Core\Role\Role;
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
use Chill\MainBundle\Entity\Scope;
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
use Chill\MainBundle\Security\RoleProvider;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Chill\MainBundle\Entity\GroupCenter;
|
||||||
|
use Chill\MainBundle\Entity\RoleScope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for authorizations.
|
* Helper for authorizations.
|
||||||
@ -42,12 +46,28 @@ class AuthorizationHelper
|
|||||||
*/
|
*/
|
||||||
protected $roleHierarchy;
|
protected $roleHierarchy;
|
||||||
|
|
||||||
protected $existingRoles = array('CHILL_MASTER_ROLE', 'CHILL_PERSON_SEE',
|
/**
|
||||||
'CHILL_PERSON_UPDATE',);
|
* The role in a hierarchy, given by the parameter
|
||||||
|
* `security.role_hierarchy.roles` from the container.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $hierarchy;
|
||||||
|
|
||||||
public function __construct(RoleHierarchyInterface $roleHierarchy)
|
/**
|
||||||
{
|
*
|
||||||
|
* @var EntityManagerInterface
|
||||||
|
*/
|
||||||
|
protected $em;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
RoleHierarchyInterface $roleHierarchy,
|
||||||
|
$hierarchy,
|
||||||
|
EntityManagerInterface $em
|
||||||
|
) {
|
||||||
$this->roleHierarchy = $roleHierarchy;
|
$this->roleHierarchy = $roleHierarchy;
|
||||||
|
$this->hierarchy = $hierarchy;
|
||||||
|
$this->em = $em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,7 +227,40 @@ class AuthorizationHelper
|
|||||||
return $scopes;
|
return $scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param Role $role
|
||||||
|
* @param Center $center
|
||||||
|
* @param Scope $circle
|
||||||
|
* @return Users
|
||||||
|
*/
|
||||||
|
public function findUsersReaching(Role $role, Center $center, Scope $circle = null)
|
||||||
|
{
|
||||||
|
$parents = $this->getParentRoles($role);
|
||||||
|
$parents[] = $role;
|
||||||
|
$parentRolesString = \array_map(function(Role $r) { return $r->getRole(); }, $parents);
|
||||||
|
|
||||||
|
$qb = $this->em->createQueryBuilder();
|
||||||
|
$qb
|
||||||
|
->select('u')
|
||||||
|
->from(User::class, 'u')
|
||||||
|
->join('u.groupCenters', 'gc')
|
||||||
|
->join('gc.permissionsGroup', 'pg')
|
||||||
|
->join('pg.roleScopes', 'rs')
|
||||||
|
->where('gc.center = :center')
|
||||||
|
->andWhere($qb->expr()->in('rs.role', $parentRolesString))
|
||||||
|
;
|
||||||
|
|
||||||
|
$qb->setParameter('center', $center);
|
||||||
|
|
||||||
|
if ($circle !== null) {
|
||||||
|
$qb->andWhere('rs.scope = :circle')
|
||||||
|
->setParameter('circle', $circle)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $qb->getQuery()->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if a parent role may give access to a given child role
|
* Test if a parent role may give access to a given child role
|
||||||
@ -223,4 +276,33 @@ class AuthorizationHelper
|
|||||||
|
|
||||||
return in_array($childRole, $reachableRoles);
|
return in_array($childRole, $reachableRoles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all the role which give access to the given role. Only the role
|
||||||
|
* which are registered into Chill are taken into account.
|
||||||
|
*
|
||||||
|
* @param Role $role
|
||||||
|
* @return Role[] the role which give access to the given $role
|
||||||
|
*/
|
||||||
|
public function getParentRoles(Role $role)
|
||||||
|
{
|
||||||
|
$parentRoles = [];
|
||||||
|
// transform the roles from role hierarchy from string to Role
|
||||||
|
$roles = \array_map(
|
||||||
|
function($string) {
|
||||||
|
return new Role($string);
|
||||||
|
},
|
||||||
|
\array_keys($this->hierarchy)
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($roles as $r) {
|
||||||
|
$childRoles = $this->roleHierarchy->getReachableRoles([$r]);
|
||||||
|
|
||||||
|
if (\in_array($role, $childRoles)) {
|
||||||
|
$parentRoles[] = $r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parentRoles;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
62
Security/Authorization/ChillExportVoter.php
Normal file
62
Security/Authorization/ChillExportVoter.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Security\Authorization;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class ChillExportVoter extends Voter
|
||||||
|
{
|
||||||
|
const EXPORT = 'chill_export';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var AuthorizationHelper
|
||||||
|
*/
|
||||||
|
protected $authorizationHelper;
|
||||||
|
|
||||||
|
public function __construct(AuthorizationHelper $authorizationHelper)
|
||||||
|
{
|
||||||
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function supports($attribute, $subject): bool
|
||||||
|
{
|
||||||
|
return $attribute === self::EXPORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
|
||||||
|
{
|
||||||
|
if (!$token->getUser() instanceof User) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$centers = $this->authorizationHelper
|
||||||
|
->getReachableCenters($token->getUser(), new Role($attribute));
|
||||||
|
|
||||||
|
return count($centers) > 0;
|
||||||
|
}
|
||||||
|
}
|
83
Security/UserProvider/UserProvider.php
Normal file
83
Security/UserProvider/UserProvider.php
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Security\UserProvider;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class UserProvider implements UserProviderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var EntityManagerInterface
|
||||||
|
*/
|
||||||
|
protected $em;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function loadUserByUsername($username): UserInterface
|
||||||
|
{
|
||||||
|
$user = $this->em->createQuery(sprintf(
|
||||||
|
"SELECT u FROM %s u "
|
||||||
|
. "WHERE u.usernameCanonical = UNACCENT(LOWER(:pattern)) "
|
||||||
|
. "OR "
|
||||||
|
. "u.emailCanonical = UNACCENT(LOWER(:pattern))",
|
||||||
|
User::class))
|
||||||
|
->setParameter('pattern', $username)
|
||||||
|
->getSingleResult();
|
||||||
|
|
||||||
|
if (NULL === $user) {
|
||||||
|
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshUser(UserInterface $user): UserInterface
|
||||||
|
{
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new UnsupportedUserException("Unsupported user class: cannot reload this user");
|
||||||
|
}
|
||||||
|
|
||||||
|
$reloadedUser = $this->em->getRepository(User::class)->find($user->getId());
|
||||||
|
|
||||||
|
if (NULL === $reloadedUser) {
|
||||||
|
throw new UsernameNotFoundException(sprintf('User with ID "%s" could not be reloaded.', $user->getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $reloadedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsClass($class): bool
|
||||||
|
{
|
||||||
|
return $class === User::class;
|
||||||
|
}
|
||||||
|
}
|
51
Templating/UI/CountNotificationUser.php
Normal file
51
Templating/UI/CountNotificationUser.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Templating\UI;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a number of notification to user in the upper right corner
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class CountNotificationUser
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var NotificationCounterInterface[]
|
||||||
|
*/
|
||||||
|
protected $counters = [];
|
||||||
|
|
||||||
|
public function addNotificationCounter(NotificationCounterInterface $counter)
|
||||||
|
{
|
||||||
|
$this->counters[] = $counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSumNotification(UserInterface $u): int
|
||||||
|
{
|
||||||
|
$sum = 0;
|
||||||
|
|
||||||
|
foreach ($this->counters as $counter) {
|
||||||
|
$sum += $counter->addNotification($u);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sum;
|
||||||
|
}
|
||||||
|
}
|
32
Templating/UI/NotificationCounterInterface.php
Normal file
32
Templating/UI/NotificationCounterInterface.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Templating\UI;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
interface NotificationCounterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Add a number of notification
|
||||||
|
*/
|
||||||
|
public function addNotification(UserInterface $u): int;
|
||||||
|
}
|
@ -443,6 +443,36 @@ class AuthorizationHelperTest extends KernelTestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGetParentRoles()
|
||||||
|
{
|
||||||
|
$parentRoles = $this->getAuthorizationHelper()
|
||||||
|
->getParentRoles(new Role('CHILL_INHERITED_ROLE_1'));
|
||||||
|
|
||||||
|
$this->assertContains(
|
||||||
|
'CHILL_MASTER_ROLE',
|
||||||
|
\array_map(
|
||||||
|
function(Role $role) {
|
||||||
|
return $role->getRole();
|
||||||
|
},
|
||||||
|
$parentRoles
|
||||||
|
),
|
||||||
|
"Assert that `CHILL_MASTER_ROLE` is a parent of `CHILL_INHERITED_ROLE_1`");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFindUsersReaching()
|
||||||
|
{
|
||||||
|
$centerA = static::$kernel->getContainer()
|
||||||
|
->get('doctrine.orm.entity_manager')
|
||||||
|
->getRepository(Center::class)
|
||||||
|
->findOneByName('Center A');
|
||||||
|
|
||||||
|
$users = $this->getAuthorizationHelper()
|
||||||
|
->findUsersReaching(new Role('CHILL_PERSON_SEE'),
|
||||||
|
$centerA);
|
||||||
|
|
||||||
|
$usernames = \array_map(function(User $u) { return $u->getUsername(); }, $users);
|
||||||
|
|
||||||
|
$this->assertContains('center a_social', $usernames);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Validation\Constraint;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Chill\MainBundle\Validation\Validator\UserUniqueEmailAndUsername;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class UserUniqueEmailAndUsernameConstraint extends Constraint
|
||||||
|
{
|
||||||
|
public $messageDuplicateUsername = "A user with the same or a close username already exists";
|
||||||
|
public $messageDuplicateEmail = "A user with the same or a close email already exists";
|
||||||
|
|
||||||
|
public function validatedBy()
|
||||||
|
{
|
||||||
|
return UserUniqueEmailAndUsername::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTargets()
|
||||||
|
{
|
||||||
|
return [ self::CLASS_CONSTRAINT ];
|
||||||
|
}
|
||||||
|
}
|
117
Validation/Validator/UserUniqueEmailAndUsername.php
Normal file
117
Validation/Validator/UserUniqueEmailAndUsername.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Validation\Validator;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\ConstraintValidator;
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||||
|
*/
|
||||||
|
class UserUniqueEmailAndUsername extends ConstraintValidator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var EntityManagerInterface
|
||||||
|
*/
|
||||||
|
protected $em;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function validate($value, Constraint $constraint)
|
||||||
|
{
|
||||||
|
if (!$value instanceof User) {
|
||||||
|
throw new \UnexpectedValueException("This validation should happens "
|
||||||
|
. "only on class ".User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value->getId() !== null) {
|
||||||
|
$countUsersByUsername = $this->em->createQuery(
|
||||||
|
sprintf(
|
||||||
|
"SELECT COUNT(u) FROM %s u "
|
||||||
|
. "WHERE u.usernameCanonical = LOWER(UNACCENT(:username)) "
|
||||||
|
. "AND u != :user",
|
||||||
|
User::class)
|
||||||
|
)
|
||||||
|
->setParameter('username', $value->getUsername())
|
||||||
|
->setParameter('user', $value)
|
||||||
|
->getSingleScalarResult();
|
||||||
|
} else {
|
||||||
|
$countUsersByUsername = $this->em->createQuery(
|
||||||
|
sprintf(
|
||||||
|
"SELECT COUNT(u) FROM %s u "
|
||||||
|
. "WHERE u.usernameCanonical = LOWER(UNACCENT(:username)) ",
|
||||||
|
User::class)
|
||||||
|
)
|
||||||
|
->setParameter('username', $value->getUsername())
|
||||||
|
->getSingleScalarResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($countUsersByUsername > 0) {
|
||||||
|
$this->context
|
||||||
|
->buildViolation($constraint->messageDuplicateUsername)
|
||||||
|
->setParameters([
|
||||||
|
'%username%' => $value->getUsername()
|
||||||
|
])
|
||||||
|
->atPath('username')
|
||||||
|
->addViolation()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value->getId() !== null) {
|
||||||
|
$countUsersByEmail = $this->em->createQuery(
|
||||||
|
sprintf(
|
||||||
|
"SELECT COUNT(u) FROM %s u "
|
||||||
|
. "WHERE u.emailCanonical = LOWER(UNACCENT(:email)) "
|
||||||
|
. "AND u != :user",
|
||||||
|
User::class)
|
||||||
|
)
|
||||||
|
->setParameter('email', $value->getEmail())
|
||||||
|
->setParameter('user', $value)
|
||||||
|
->getSingleScalarResult();
|
||||||
|
} else {
|
||||||
|
$countUsersByEmail = $this->em->createQuery(
|
||||||
|
sprintf(
|
||||||
|
"SELECT COUNT(u) FROM %s u "
|
||||||
|
. "WHERE u.emailCanonical = LOWER(UNACCENT(:email))",
|
||||||
|
User::class)
|
||||||
|
)
|
||||||
|
->setParameter('email', $value->getEmail())
|
||||||
|
->getSingleScalarResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($countUsersByEmail > 0) {
|
||||||
|
$this->context
|
||||||
|
->buildViolation($constraint->messageDuplicateEmail)
|
||||||
|
->setParameters([
|
||||||
|
'%email%' => $value->getEmail()
|
||||||
|
])
|
||||||
|
->atPath('email')
|
||||||
|
->addViolation()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
Validator/Constraints/Entity/UserCircleConsistency.php
Normal file
52
Validator/Constraints/Entity/UserCircleConsistency.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Validator\Constraints\Entity;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @Annotation
|
||||||
|
*/
|
||||||
|
class UserCircleConsistency extends Constraint
|
||||||
|
{
|
||||||
|
public $message = "{{ username }} is not allowed to see entities published in this circle";
|
||||||
|
|
||||||
|
public $role;
|
||||||
|
|
||||||
|
public $getUserFunction = 'getUser';
|
||||||
|
|
||||||
|
public $path = 'circle';
|
||||||
|
|
||||||
|
public function getDefaultOption()
|
||||||
|
{
|
||||||
|
return 'role';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequiredOptions()
|
||||||
|
{
|
||||||
|
return [ 'role' ];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTargets()
|
||||||
|
{
|
||||||
|
return self::CLASS_CONSTRAINT;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Validator\Constraints\Entity;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Symfony\Component\Validator\ConstraintValidator;
|
||||||
|
use Chill\MainBundle\Entity\HasScopeInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class UserCircleConsistencyValidator extends ConstraintValidator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var AuthorizationHelper
|
||||||
|
*/
|
||||||
|
protected $autorizationHelper;
|
||||||
|
|
||||||
|
function __construct(AuthorizationHelper $autorizationHelper)
|
||||||
|
{
|
||||||
|
$this->autorizationHelper = $autorizationHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param object $value
|
||||||
|
* @param UserCircleConsistency $constraint
|
||||||
|
*/
|
||||||
|
public function validate($value, Constraint $constraint)
|
||||||
|
{
|
||||||
|
/* @var $user \Chill\MainBundle\Entity\User */
|
||||||
|
$user = \call_user_func([$value, $constraint->getUserFunction ]);
|
||||||
|
|
||||||
|
if ($user === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FALSE === $this->autorizationHelper->userHasAccess($user, $value, $constraint->role)) {
|
||||||
|
$this->context
|
||||||
|
->buildViolation($constraint->message)
|
||||||
|
->setParameter('{{ username }}', $user->getUsername())
|
||||||
|
->atPath($constraint->path)
|
||||||
|
->addViolation()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
chill.webpack.config.js
Normal file
35
chill.webpack.config.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// this file loads all assets from the Chill main bundle
|
||||||
|
|
||||||
|
// import jQuery
|
||||||
|
const $ = require('jquery');
|
||||||
|
// create global $ and jQuery variables
|
||||||
|
global.$ = global.jQuery = $;
|
||||||
|
|
||||||
|
const moment = require('moment');
|
||||||
|
global.moment = moment;
|
||||||
|
|
||||||
|
const pikaday = require('pikaday-jquery');
|
||||||
|
|
||||||
|
const select2 = require('select2');
|
||||||
|
global.select2 = select2;
|
||||||
|
|
||||||
|
// import js
|
||||||
|
import {chill} from './Resources/public/js/chill.js';
|
||||||
|
global.chill = chill;
|
||||||
|
|
||||||
|
// css
|
||||||
|
require('./Resources/public/sass/scratch.scss');
|
||||||
|
require('./Resources/public/css/chillmain.css');
|
||||||
|
require('./Resources/public/css/pikaday.css');
|
||||||
|
require('./Resources/public/js/collection/collections.js');
|
||||||
|
require('./Resources/public/modules/breadcrumb/index.js');
|
||||||
|
require('./Resources/public/modules/download-report/index.js');
|
||||||
|
//require('./Resources/public/css/scratch.css');
|
||||||
|
//require('./Resources/public/css/select2/select2.css');
|
||||||
|
require('select2/dist/css/select2.css');
|
||||||
|
require('./Resources/public/modules/postal-code/index.js');
|
||||||
|
|
||||||
|
// img
|
||||||
|
require('./Resources/public/img/favicon.ico');
|
||||||
|
require('./Resources/public/img/logo-chill-sans-slogan_white.png');
|
||||||
|
require('./Resources/public/img/logo-chill-outil-accompagnement_white.png');
|
@ -25,28 +25,26 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "~5.5|~7.0",
|
"php": "~7.2",
|
||||||
"twig/extensions": "~1.0",
|
"twig/extensions": "~1.5",
|
||||||
"symfony/assetic-bundle": "~2.3",
|
"symfony/assetic-bundle": "~2.8",
|
||||||
"symfony/monolog-bundle": "~2.4",
|
"symfony/monolog-bundle": "~3.2",
|
||||||
"symfony/framework-bundle": "~2.8",
|
"symfony/symfony": "~3.4",
|
||||||
"symfony/yaml": "~2.7",
|
"doctrine/dbal": "~2.7",
|
||||||
"symfony/symfony": "~2.7",
|
"doctrine/orm": "~2.6",
|
||||||
"doctrine/dbal": "~2.5",
|
"doctrine/common": "~2.8",
|
||||||
"doctrine/orm": "~2.4",
|
"doctrine/doctrine-bundle": "~1.9",
|
||||||
"doctrine/common": "~2.4",
|
|
||||||
"doctrine/doctrine-bundle": "~1.2",
|
|
||||||
"champs-libres/composer-bundle-migration": "~1.0",
|
"champs-libres/composer-bundle-migration": "~1.0",
|
||||||
"doctrine/doctrine-migrations-bundle": "~1.1",
|
"doctrine/doctrine-migrations-bundle": "~1.3",
|
||||||
"doctrine/migrations": "~1.0",
|
"doctrine/migrations": "~1.0",
|
||||||
"phpoffice/phpspreadsheet": "~1.2",
|
"phpoffice/phpspreadsheet": "~1.2",
|
||||||
"sensio/distribution-bundle": "^5.0"
|
"sensio/distribution-bundle": "^5.0",
|
||||||
|
"knplabs/knp-menu-bundle": "^2.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/dom-crawler": "2.5",
|
"symfony/dom-crawler": "~3.4",
|
||||||
"doctrine/doctrine-fixtures-bundle": "~2.2",
|
"doctrine/doctrine-fixtures-bundle": "~3.0",
|
||||||
"symfony/security": "~2.5",
|
"symfony/phpunit-bridge": "~3.4",
|
||||||
"symfony/phpunit-bridge": "^2.7",
|
|
||||||
"phpunit/phpunit": "~5.6"
|
"phpunit/phpunit": "~5.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<phpunit bootstrap="./Resources/test/Fixtures/App/app/autoload.php" colors="true">
|
<phpunit bootstrap="./vendor/autoload.php" colors="true">
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="ChillMain test suite">
|
<testsuite name="ChillMain test suite">
|
||||||
<directory suffix="Test.php">./Tests</directory>
|
<directory suffix="Test.php">./Tests</directory>
|
||||||
@ -19,5 +19,6 @@
|
|||||||
<php>
|
<php>
|
||||||
<server name="KERNEL_DIR" value="./Resources/test/Fixtures/App/app" />
|
<server name="KERNEL_DIR" value="./Resources/test/Fixtures/App/app" />
|
||||||
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
|
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
|
||||||
|
<env name="APP_ENV" value="test" />
|
||||||
</php>
|
</php>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user