This commit is contained in:
Tchama 2020-04-22 11:57:47 +02:00
commit b6cc3716e1
74 changed files with 3058 additions and 107 deletions

View File

@ -81,10 +81,47 @@ Version 1.5.13
- allow to customize logo on login screen and main layout ;
- remove desert background image on page, handle it from cache in login screen;
Master branch
=============
Version 1.5.14
==============
- fix errors in pagination
- fix search: usage of parenthesis
- add DQL function REPLACE for replacing in strings: "REPLACE(string, 'from', 'to')"
- add function to format phonenumber
- improve `chill_print_or_message` to support date time;
- add a module `show_hide` for javascript;
- load assets using functions ;
- load a `runtime.js` assets for objects shared by webpack ;
Version 1.5.15
==============
- create an api for rendering entities
- css: render the placeholder in expanded choice item as italic (the "no specified" choice")
- css: add an extra space around choices expanded widget
- add Tabs parametric feature to easily render tabs panels
- css: add a margin on the button "delete entry" in collection
- module `show_hide`: add the possibility to launch a show hide manually and not on page loading. Useful when show/hide occurs in collection.
- module `show_hide`: add events to module
- [phonenumber validation] allow to validate against mobile **or** landline/voip phonenumbers;
- [phonenumber validation & format] format and validation does not make the app fail when network is not available;
Version 1.5.16
==============
- [translation] in french, replace "Modifier" by "Enregistrer" in the edit form
- [entity render] do not throw an exception when null element are passed to `chill_entity_render_box` and `chill_entity_render_string`
Version 1.5.17
==============
- [chill entity render] fix error when fallback to default entity render (usage of `__toString()`)
- [CRUD] add step delete
- [CRUD] check that action exists before inserting them in edit and view template
- [CRUD] fix error when no crud are created
Version 1.5.18
==============
- [webpack] add namespace for import sass ;
- [activity] move activity.scss to own bundle ;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2020, 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\CRUD\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
/**
*
*
*/
class CRUDDeleteEntityForm extends AbstractType
{
}

102
CRUD/Resolver/Resolver.php Normal file
View File

@ -0,0 +1,102 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2019, 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\CRUD\Resolver;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
/**
*
*
*/
class Resolver
{
/**
*
* @var EntityManagerInterface
*/
protected $em;
/**
*
* @var \Symfony\Component\PropertyAccess\PropertyAccessor
*/
protected $propertyAccess;
/**
*
* @var array
*/
protected $crudConfig;
/**
* @deprecated
*/
const ROLE_VIEW = 'role.view';
/**
* @deprecated
*/
const ROLE_EDIT = 'role.edit';
/**
* The key to get the role necessary for the action
*/
const ROLE = 'role';
function __construct(EntityManagerInterface $em, array $crudConfig)
{
$this->em = $em;
foreach($crudConfig as $conf) {
$this->crudConfig[$conf['name']] = $conf;
}
}
public function getConfigValue($key, $crudName, $action = null)
{
$config = $this->crudConfig[$crudName];
switch ($key) {
case self::ROLE:
return $config['actions'][$action]['role'] ?? $this->buildDefaultRole($crudName, $action);
}
}
public function buildDefaultRole($crudName, $action)
{
if (empty($this->crudConfig[$crudName]['base_role'])) {
throw new \LogicException(sprintf("the base role is not defined. You must define "
. "on or override %s or %s methods", __METHOD__, "getRoleFor"));
}
return \strtoupper(
$this->crudConfig[$crudName]['base_role'].
'_'.
$action);
}
public function hasAction($crudName, $action)
{
return \array_key_exists($action,
$this->crudConfig[$crudName]['actions']);
}
}

View File

@ -0,0 +1,78 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2019, 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\CRUD\Routing;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Load the route for CRUD
*
*/
class CRUDRoutesLoader
{
protected $config = [];
public function __construct($config)
{
$this->config = $config;
}
public function load()
{
$collection = new RouteCollection();
foreach ($this->config as $config) {
$collection->addCollection($this->loadConfig($config));
}
return $collection;
}
protected function loadConfig($config): RouteCollection
{
$collection = new RouteCollection();
foreach ($config['actions'] as $name => $action) {
$defaults = [
'_controller' => 'cscrud_'.$config['name'].'_controller'.':'.($action['controller_action'] ?? $name)
];
if ($name === 'index') {
$path = "{_locale}".$config['base_path'];
$route = new Route($path, $defaults);
} elseif ($name === 'new') {
$path = "{_locale}".$config['base_path'].'/'.$name;
$route = new Route($path, $defaults);
} else {
$path = "{_locale}".$config['base_path'].($action['path'] ?? '/{id}/'.$name);
$requirements = $action['requirements'] ?? [
'{id}' => '\d+'
];
$route = new Route($path, $defaults, $requirements);
}
$collection->add('chill_crud_'.$config['name'].'_'.$name, $route);
}
return $collection;
}
}

View File

@ -0,0 +1,66 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2019, 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\CRUD\Templating;
use Chill\MainBundle\CRUD\Resolver\Resolver;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\Extension\AbstractExtension;
use Twig\Environment;
/**
* Twig filters to display data in crud template
*
*/
class TwigCRUDResolver extends AbstractExtension
{
/**
*
* @var Resolver
*/
protected $resolver;
function __construct(Resolver $resolver)
{
$this->resolver = $resolver;
}
public function getFunctions()
{
return [
new TwigFunction('chill_crud_config', [$this, 'getConfig'],
['is_safe' => 'html']),
new TwigFunction('chill_crud_action_exists', [$this, 'hasAction'],
[]),
];
}
public function getConfig($configKey, $crudName, $action = null)
{
return $this->resolver->getConfigValue($configKey, $crudName, $action);
}
public function hasAction($crudName, $action)
{
return $this->resolver->hasAction($crudName, $action);
}
}

View File

@ -14,6 +14,7 @@ use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompile
use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\ACLFlagsCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\GroupingCenterCompilerPass;
use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass;
class ChillMainBundle extends Bundle
@ -31,5 +32,6 @@ class ChillMainBundle extends Bundle
$container->addCompilerPass(new MenuCompilerPass());
$container->addCompilerPass(new ACLFlagsCompilerPass());
$container->addCompilerPass(new GroupingCenterCompilerPass());
$container->addCompilerPass(new RenderEntityCompilerPass());
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Pagination\PaginatorFactory;
/**
*
*
*/
class AdminCountryCRUDController extends CRUDController
{
function __construct(PaginatorFactory $paginator)
{
$this->paginatorFactory = $paginator;
}
}

View File

@ -20,4 +20,62 @@ class DefaultController extends Controller
{
return $this->redirect($this->generateUrl('chill_main_homepage'));
}
}
public function testAction()
{
return $this->render('ChillMainBundle:Tabs:index.html.twig', [
'tabs' => [
'test1' => [
[
'name' => "Link 1",
'content' => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.",
],
[
'name' => "Link 2",
'content' => "Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.",
],
[
'name' => "Link 3",
'content' => "In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.",
],
[
'name' => "Link 4",
'content' => "Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.",
],
[
'name' => "Link 5",
'content' => "Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.",
]
],
'test2' => [
[
'name' => "Link 1",
'link' => "http://localhost",
'content' => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.",
],
[
'name' => "Link 2",
//'link' => "http://localhost",
'content' => "Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.",
],
[
'name' => "Link 3",
//'link' => "http://localhost",
'content' => "In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.",
],
[
'name' => "Link 4",
'link' => "http://localhost",
//'content' => "Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.",
],
[
'name' => "Link 5",
//'link' => "http://localhost",
'content' => "Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.",
]
]
]
]);
}
}

View File

@ -32,6 +32,8 @@ use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray;
use Chill\MainBundle\Doctrine\DQL\Similarity;
use Chill\MainBundle\Doctrine\DQL\OverlapsI;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Chill\MainBundle\Doctrine\DQL\Replace;
/**
@ -118,6 +120,8 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
$loader->load('services/phonenumber.yml');
$loader->load('services/cache.yml');
$loader->load('services/templating.yml');
$this->configureCruds($container, $config['cruds'], $loader);
}
public function getConfiguration(array $config, ContainerBuilder $container)
@ -196,4 +200,54 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
'channels' => array('chill')
));
}
/**
*
* @param ContainerBuilder $container
* @param array $config the config under 'cruds' key
* @return null
*/
protected function configureCruds(ContainerBuilder $container, $config, Loader\YamlFileLoader $loader)
{
if (count($config) === 0) {
return;
}
$loader->load('services/crud.yml');
$container->setParameter('chill_main_crud_route_loader_config', $config);
$definition = new Definition();
$definition
->setClass(\Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader::class)
->addArgument('%chill_main_crud_route_loader_config%')
;
$container->setDefinition('chill_main_crud_route_loader', $definition);
$alreadyExistingNames = [];
foreach ($config as $crudEntry) {
$controller = $crudEntry['controller'];
$controllerServiceName = 'cscrud_'.$crudEntry['name'].'_controller';
$name = $crudEntry['name'];
// check for existing crud names
if (\in_array($name, $alreadyExistingNames)) {
throw new LogicException(sprintf("the name %s is defined twice in CRUD", $name));
}
if (!$container->has($controllerServiceName)) {
$controllerDefinition = new Definition($controller);
$controllerDefinition->addTag('controller.service_arguments');
$controllerDefinition->setAutoconfigured(true);
$controllerDefinition->setClass($crudEntry['controller']);
$container->setDefinition($controllerServiceName, $controllerDefinition);
}
$container->setParameter('chill_main_crud_config_'.$name, $crudEntry);
$container->getDefinition($controllerServiceName)
->addMethodCall('setCrudConfig', ['%chill_main_crud_config_'.$name.'%']);
}
}
}

View File

@ -116,6 +116,58 @@ class Configuration implements ConfigurationInterface
->append($this->addWidgetsConfiguration('homepage', $this->containerBuilder))
->end() // end of widgets/children
->end() // end of widgets
->arrayNode('cruds')
->defaultValue([])
->arrayPrototype()
->children()
->scalarNode('class')->cannotBeEmpty()->isRequired()->end()
->scalarNode('controller')
->cannotBeEmpty()
->defaultValue(\Chill\MainBundle\CRUD\Controller\CRUDController::class)
->end()
->scalarNode('name')->cannotBeEmpty()->isRequired()->end()
->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end()
->scalarNode('base_role')->defaultNull()->end()
->scalarNode('form_class')->defaultNull()->end()
->arrayNode('actions')
->defaultValue([
'edit' => [],
'new' => []
])
->useAttributeAsKey('name')
->arrayPrototype()
->children()
->scalarNode('controller_action')
->defaultNull()
->info('the method name to call in the route. Will be set to the action name if left empty.')
->example("'action'")
->end()
->scalarNode('path')
->defaultNull()
->info('the path that will be **appended** after the base path. Do not forget to add '
. 'arguments for the method. Will be set to the action name, including an `{id}` '
. 'parameter if left empty.')
->example('/{id}/my-action')
->end()
->arrayNode('requirements')
->ignoreExtraKeys(false)
->info('the requirements for the route. Will be set to `[ \'id\' => \'\d+\' ]` if left empty.')
->end()
->scalarNode('role')
->defaultNull()
->info('the role that will be required for this action. Override option `base_role`')
->end()
->scalarNode('template')
->defaultNull()
->info('the template to render the view')
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end() // end of root/children
->end() // end of root
;

View File

@ -20,6 +20,7 @@ namespace Chill\MainBundle\Phonenumber;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\ConnectException;
use Psr\Log\LoggerInterface;
use Psr\Cache\CacheItemPoolInterface;
@ -122,6 +123,24 @@ class PhonenumberHelper
return \in_array($validation, [ 'landline', 'voip' ]);
}
/**
* Return true if the phonenumber is a landline or voip phone. Return always false
* if the validation is not configured.
*
* @param string $phonenumber
* @return bool
*/
public function isValidPhonenumberAny($phonenumber) : bool
{
$validation = $this->performTwilioLookup($phonenumber);
if (NULL === $validation) {
return false;
}
return \in_array($validation, [ 'landline', 'voip', 'mobile' ]);
}
public function format($phonenumber)
{
return $this->performTwilioFormat($phonenumber);
@ -164,6 +183,14 @@ class PhonenumberHelper
"phonenumber" => $phonenumber
]);
return null;
} catch (ConnectException $e) {
$this->logger->error("[phonenumber helper] Could not format number "
. "due to connect error", [
"message" => $e->getMessage(),
"phonenumber" => $phonenumber
]);
return null;
}
@ -212,6 +239,14 @@ class PhonenumberHelper
"phonenumber" => $phonenumber
]);
return null;
} catch (ConnectException $e) {
$this->logger->error("[phonenumber helper] Could not format number "
. "due to connect error", [
"message" => $e->getMessage(),
"phonenumber" => $phonenumber
]);
return null;
}

View File

@ -47,6 +47,6 @@ class Templating extends AbstractExtension
public function formatPhonenumber($phonenumber)
{
return $this->phonenumberHelper->format($phonenumber);
return $this->phonenumberHelper->format($phonenumber) ?? $phonenumber;
}
}

View File

@ -58,12 +58,6 @@ chill_main_admin_central:
chill_main_admin_permissions:
path: /{_locale}/admin/permissions
defaults: {_controller: ChillMainBundle:Admin:indexPermissions }
options:
menus:
admin_section:
order: 200
label: Users and permissions
icons: [key]
chill_main_search:
path: /{_locale}/search.{_format}
@ -88,3 +82,7 @@ login_check:
logout:
path: /logout
chill_main_test:
path: /{_locale}/main/test
defaults: { _controller: ChillMainBundle:Default:test }

View File

@ -2,19 +2,6 @@ parameters:
# cl_chill_main.example.class: Chill\MainBundle\Example
services:
twig_intl:
class: Twig_Extensions_Extension_Intl
tags:
- { name: twig.extension }
twig_date:
class: Twig_Extensions_Extension_Date
arguments:
- "@translator"
tags:
- { name: twig.extension }
chill.main.helper.translatable_string:
class: Chill\MainBundle\Templating\TranslatableStringHelper
arguments:

View File

@ -0,0 +1,16 @@
services:
# Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader:
#
# tags:
# - routing.loader
Chill\MainBundle\CRUD\Resolver\Resolver:
arguments:
$em: '@Doctrine\ORM\EntityManagerInterface'
$crudConfig: '%chill_main_crud_route_loader_config%'
Chill\MainBundle\CRUD\Templating\TwigCRUDResolver:
arguments:
$resolver: '@Chill\MainBundle\CRUD\Resolver\Resolver'
tags:
- { name: twig.extension }

View File

@ -10,3 +10,9 @@ services:
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
tags:
- { name: 'chill.menu_builder' }
Chill\MainBundle\Routing\MenuBuilder\AdminSectionMenuBuilder:
arguments:
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
tags:
- { name: 'chill.menu_builder' }

View File

@ -1,4 +1,21 @@
services:
twig_intl:
class: Twig_Extensions_Extension_Intl
tags:
- { name: twig.extension }
twig_date:
class: Twig_Extensions_Extension_Date
arguments:
- "@translator"
tags:
- { name: twig.extension }
twig_text:
class: Twig_Extensions_Extension_Text
tags:
- { name: twig.extension }
Chill\MainBundle\Templating\ChillTwigHelper:
tags:
- { name: twig.extension }
@ -7,5 +24,9 @@ services:
arguments:
$requestStack: '@Symfony\Component\HttpFoundation\RequestStack'
$originalExtension: '@twig.extension.routing'
tags:
- { name: twig.extension }
Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension:
tags:
- { name: twig.extension }

View File

@ -13,6 +13,10 @@ div.chill-collection {
li.chill-collection__list__entry:nth-last-child(1n+2) {
margin-bottom: 1rem;
}
button.chill-collection__list__remove-entry {
margin-left: 0.5rem;
}
}
button.chill-collection__button--add {

View File

@ -76,7 +76,7 @@ var initializeRemove = function(collection, entry) {
return;
}
button.classList.add('sc-button', 'bt-delete');
button.classList.add('sc-button', 'bt-delete', 'chill-collection__list__remove-entry');
button.textContent = content;
button.addEventListener('click', function(e) {

36
Resources/public/main.js Normal file
View File

@ -0,0 +1,36 @@
// 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 './js/chill.js';
global.chill = chill;
// css
require('./sass/scratch.scss');
require('./css/chillmain.css');
require('./css/pikaday.css');
require('./js/collection/collections.js');
require('./modules/breadcrumb/index.js');
require('./modules/download-report/index.js');
//require('./css/scratch.css');
//require('./css/select2/select2.css');
require('select2/dist/css/select2.css');
require('./modules/select_interactive_loading/index.js');
require('./modules/export-list/export-list.scss');
// img
require('./img/favicon.ico');
require('./img/logo-chill-sans-slogan_white.png');
require('./img/logo-chill-outil-accompagnement_white.png');

View File

@ -17,6 +17,10 @@ body {
position: relative;
height: 90%;
padding-top: 10%;
footer.footer {
position: absolute;
bottom: 0;
}
}
#content:before {
@ -63,10 +67,10 @@ button {
p.forgot-password-link {
text-align: center;
a {
font-weight: 300;
color: #fff;
text-decoration: none;
}
}
}

View File

@ -0,0 +1 @@
require("./show_hide.js");

View File

@ -0,0 +1,126 @@
/**
* Create a control to show or hide values
*
* Possible options are:
*
* - froms: an Element, an Array of Element, or a NodeList. A
* listener will be attached to **all** input of those elements
* and will trigger the check on changes
* - test: a function which will test the element and will return true
* if the content must be shown, false if it must be hidden.
* The function will receive the `froms` as first argument, and the
* event as second argument.
* - container: an Element, an Array of Element, or a Node List. The
* child nodes will be hidden / shown inside this container
* - event_name: the name of the event to listen to. `'change'` by default.
*
* @param object options
*/
var ShowHide = function(options) {
var
froms = typeof options.froms[Symbol.iterator] === "function" ? options.froms : [ options.froms ], //options.froms;
test = options.test,
container = typeof options.container[Symbol.iterator] === "function" ? options.container : [ options.container ],
is_shown = true,
event_name = 'event_name' in options ? options.event_name : 'change',
container_content = [],
debug = 'debug' in options ? options.debug : false,
load_event = 'load_event' in options ? options.load_event : 'load',
id = 'uid' in options ? options.id : Math.random();
var bootstrap = function(event) {
if (debug) {
console.log('debug is activated on this show-hide', this);
}
// keep the content in memory
for (let c of container.values()) {
let contents = [];
for (let el of c.childNodes.values()) {
contents.push(el);
}
container_content.push(contents);
}
// attach the listener on each input
for (let f of froms.values()) {
let inputs = f.querySelectorAll('input');
for (let input of inputs.values()) {
if (debug) {
console.log('attaching event to input', input);
}
input.addEventListener(event_name, function(e) {
onChange(e);
});
}
}
// first launch of the show/hide
onChange(event);
};
var onChange = function (event) {
var result = test(froms, event), me;
if (result === true) {
if (is_shown === false) {
forceShow();
me = new CustomEvent('show-hide-show', { detail: { id: id, container: container, froms: froms } });
window.dispatchEvent(me);
}
} else if (result === false) {
if (is_shown) {
forceHide();
me = new CustomEvent('show-hide-hide', { detail: { id: id, container: container, froms: froms } });
window.dispatchEvent(me);
}
} else {
throw "the result of test is not a boolean";
}
};
var forceHide = function() {
if (debug) {
console.log('force hide');
}
for (let contents of container_content.values()) {
for (let el of contents.values()) {
el.remove();
}
}
is_shown = false;
};
var forceShow = function() {
if (debug) {
console.log('show');
}
for (let i of container_content.keys()) {
var contents = container_content[i];
for (let el of contents.values()) {
container[i].appendChild(el);
}
}
is_shown = true;
};
var forceCompute = function(event) {
onChange(event);
};
if (load_event !== null) {
window.addEventListener('load', bootstrap);
} else {
bootstrap(null);
}
return {
forceHide: forceHide,
forceShow: forceShow,
forceCompute: forceCompute,
};
};
export {ShowHide};

View File

@ -0,0 +1,2 @@
require("./tabs.js");
require("./tabs.scss");

View File

@ -0,0 +1,118 @@
/*
* Remove active class on both elements: link and content
*/
let resetActive = function(links, contents)
{
for (items of [links, contents]) {
items.forEach(function(item) {
if (item.classList.contains('active')) {
item.classList.remove('active');
}
});
}
}
/*
* Count links array and return rank of given link
*/
let countNewActive = function(links, link)
{
let rank = 0;
for (let i = 0; i < links.length; ++i) {
rank++;
if(links[i] == link) {
return rank;
}
}
}
/*
* Set class active on both new elements: link and content
*/
let setNewActive = function(links, contents, rank)
{
if (! links[rank-1]) { rank = 1; }
link = links[rank-1];
link.classList.add('active');
count = 0;
contents.forEach(function(pane) {
count++;
if (rank == count) {
pane.classList.add('active');
}
});
}
/*
* Set height of content pane
*/
let setPaneHeight = function(contents)
{
contents.forEach(function(pane) {
// let computedStyle = getComputedStyle(pane);
// console.log(computedStyle.height);
// comment prendre la hauteur d'une div masquée avec display:none
});
}
/*
* Check if links are defined in controller
* If true, disable javascript listener
*/
let isLinkRef = function(link) {
if (link.getAttribute('href') == "#") {
return false;
}
return true;
}
/*
* Main function
*/
window.addEventListener('load', function()
{
tabParams.forEach(function(unit) {
let tabPanel = document.querySelector('#'+ unit.id );
if (tabPanel) {
let
nav = tabPanel.querySelector('nav'),
tabs = nav.querySelectorAll('ul.nav-tabs li.nav-item'),
links = nav.querySelectorAll('ul.nav-tabs li.nav-item a.nav-link'),
contents = tabPanel.querySelectorAll('div.tab-content div.tab-pane')
;
if (unit.type == 'pill') {
tabPanel.classList.add('pills');
}
if (! unit.initPane) {
unit.initPane = 1;
}
setPaneHeight(contents);
// initial position
setNewActive(links, contents, unit.initPane);
// listen
links.forEach(function(link) {
if (isLinkRef(link) == false) {
link.addEventListener('click', function()
{
resetActive(links, contents);
setNewActive(links, contents, countNewActive(links, link));
});
}
});
}
});
});

View File

@ -0,0 +1,98 @@
$navigation-color: #334d5c;
$body-font-color: #ffffff;
$tab-font-color: #495057;
$border-color: #dee2e6;
$pills-color: #ed9345;
$radius : .25rem;
div.tabs {
margin: 3em;
nav {
ul.nav-tabs {
display: flex;
display: -ms-flexbox;
flex-wrap: wrap;
-ms-flex-wrap: wrap;
list-style: none;
padding-left: 0;
margin-bottom: 0;
li.nav-item {
margin-bottom: -1px;
a.nav-link {
display: block;
padding: .5rem 1rem;
}
}
}
}
div.tab-content {
div.tab-pane {
display: none;
&.fade {
transition: opacity .15s linear;
}
&.active {
display: block;
}
}
}
&:not(.pills) {
nav {
ul.nav-tabs {
border-bottom: 1px solid $border-color;
li.nav-item {
a.nav-link {
border: 1px solid transparent;
border-top-left-radius: $radius;
border-top-right-radius: $radius;
color: $navigation-color;
}
&.show a.nav-link,
& a.nav-link.active {
color: $tab-font-color;
background-color: $body-font-color;
border-color: $border-color $border-color $body-font-color;
}
}
}
}
}
&.pills {
nav {
ul.nav-tabs {
border-bottom: 0;
li.nav-item {
& a.nav-link {
border: 0;
border-radius: $radius;
}
&.show > a.nav-link,
& a.nav-link.active {
color: $body-font-color;
background-color: $pills-color;
}
}
}
}
}
}

View File

@ -4,7 +4,6 @@
@import 'custom/fonts';
@import 'custom/timeline';
@import 'custom/mixins/entity';
@import 'custom/activity';
@import 'custom/report';
@import 'custom/person';
@import 'custom/pagination';

View File

@ -3,12 +3,12 @@
@font-face {
font-family: 'FontAwesome';
src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}');
src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'),
url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'),
url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'),
url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'),
url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg');
src: url('./../../../fonts/fontawesome-webfont.eot?v=#{$fa-version}');
src: url('./../../../fonts/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'),
url('./../../../fonts/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'),
url('./../../../fonts/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'),
url('./../../../fonts/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'),
url('./../../../fonts/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg');
// src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
font-weight: normal;
font-style: normal;

View File

@ -1,7 +0,0 @@
span.entity.entity-activity.activity-reason {
@include entity($chill-pink, white);
}
.activity {
color: $chill-green;
}

View File

@ -10,6 +10,7 @@ ul.record_actions, ul.record_actions_column {
display: flex;
justify-content: flex-end;
padding: 0.5em 0;
flex-wrap: wrap-reverse;
li {
display: inline-block;

View File

@ -1,6 +1,6 @@
@mixin entity($background-color, $color: white) {
font-variant: small-caps;
display: inline;
display: inline-block;
padding: .2em .6em .3em;
font-size: 88%;
font-weight: bold;
@ -11,5 +11,6 @@
border-radius: .25em;
color: $color;
background-color: $background-color;
margin: 0.5em;
}

View File

@ -1,7 +1,7 @@
.sc-button {
margin-bottom: 0.5rem;
&.bt-submit, &.bt-save, &.bt-create, &.bt-new {
&.bt-submit, &.bt-save, &.bt-create, &.bt-new, &.bt-duplicate {
@include button($green, $white);
}
@ -19,6 +19,7 @@
&:not(.change-icon) {
// icons using font-awesome "old way"
&.bt-create::before,
&.bt-save::before,
&.bt-new::before,
@ -31,6 +32,14 @@
font: normal normal normal 14px/1 FontAwesome;
margin-right: 0.5em;
}
// icons using font-awesome "new svg way"
&.bt-duplicate::before {
display: inline-block;
width: 1em;
margin-right: 0.5em;
vertical-align: middle;
}
&.bt-save::before {
// add a floppy
@ -60,6 +69,10 @@
&.bt-show::before, &.bt-view::before {
content: "";
}
&.bt-duplicate::before {
content: url("./copy-solid.svg");
}
}
> i.fa {

View File

@ -0,0 +1,4 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="copy" class="svg-inline--fa fa-copy fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!-- adapted from original work obtained from font-awesome CC-BY 4.0 International - https://fontawesome.com/license Last updated on July 12, 2018 -->
<path fill="white" d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"></path>
</svg>

After

Width:  |  Height:  |  Size: 733 B

View File

@ -25,10 +25,17 @@ label {
abbr {
display: none;
}
// mark the label for empty placeholder
&[for$="_placeholder"] {
font-style: italic;
}
}
.inline-choice {
white-space:nowrap;
display: inline-block;
label {
white-space:normal;
display: inline;
@ -38,6 +45,11 @@ label {
}
}
div.choice-widget-expanded {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
textarea,
//input,
#{$all-text-inputs},

View File

@ -34,6 +34,7 @@ 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 ?"
No value: Aucune information
Edit: Modifier
Update: Mettre à jour
@ -228,4 +229,27 @@ Log in with your new password: Connectez-vous avec votre nouveau mot de passe
# impersonate
Exit impersonation: Retour Administrateur
Impersonate: Mode fantôme
Impersonate mode: Mode fantôme
Impersonate mode: Mode fantôme
crud:
# general items
new:
button_action_form: Créer
link_edit: Modifier
save_and_close: Créer & fermer
save_and_show: Créer & voir
save_and_new: Créer & nouveau
success: Les données ont été créées
edit:
button_action_form: Enregistrer
back_to_view: Voir
save_and_close: Enregistrer & fermer
save_and_show: Enregistrer & voir
success: Les données ont été modifiées
delete:
success: Les données ont été supprimées
link_to_form: Supprimer
default:
success: Les données ont été enregistrées
view:
link_duplicate: Dupliquer

View File

@ -17,3 +17,4 @@ This username or email does not exists: Cet utilisateur ou email n'est pas prés
#phonenumber
This is not a landline phonenumber: Ce numéro n'est pas une ligne fixe valide
This is not a mobile phonenumber: Ce numéro n'est pas un numéro de portable valide
This is not a valid phonenumber: Ce numéro de téléphone n'est pas valide

View File

@ -0,0 +1,37 @@
<div class="{% block crud_content_main_div_class %}grid-10 centered{% endblock %}">
{% block crud_content_header %}
<h1>{{ ('crud.'~crud_name~'.title_delete')|trans({ '%as_string%': entity|chill_entity_render_string }) }}</h1>
{% endblock crud_content_header %}
<p class="message-confirm">{{ ('crud.'~crud_name~'.confirm_message_delete')|trans({ '%as_string%': entity|chill_entity_render_string }) }}</p>
{{ form_start(form) }}
<ul class="record_actions">
{% block content_form_actions_back %}
<li class="cancel">
<a class="sc-button bt-cancel" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_index') }}">
{{ 'Cancel'|trans }}
</a>
</li>
{% endblock %}
{% block content_form_actions_before %}{% endblock %}
{% block content_form_actions_view %}
{% if is_granted(chill_crud_config('role', crud_name, 'view'), entity) %}
<li class="">
<a class="sc-button bt-show" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_view', { 'id': entity.id }) }}">
{{ 'crud.edit.back_to_view'|trans }}
</a>
</li>
{% endif %}
{% endblock %}
{% block content_form_actions_confirm_delete %}
<li>
<button type="submit" class="sc-button bt-delete" value="delete-and-close">{{ ('crud.'~crud_name~'.button_delete')|trans }}</button>
</li>
{% endblock content_form_actions_confirm_delete %}
{% block content_form_actions_after %}{% endblock %}
</ul>
{{ form_end(form) }}
</div>

View File

@ -0,0 +1,63 @@
<div class="{% block crud_content_main_div_class %}grid-10 centered{% endblock %}">
{% block crud_content_header %}
<h1>{{ ('crud.'~crud_name~'.title_edit')|trans }}</h1>
{% endblock crud_content_header %}
{% block crud_content_form %}
{{ form_start(form) }}
{% block crud_content_form_rows %}
{% for f in form %}
{{ form_row(f) }}
{% endfor %}
{% endblock crud_content_form_rows %}
{% block crud_content_form_actions %}
<ul class="record_actions sticky-form-buttons">
{% block content_form_actions_back %}
<li class="cancel">
<a class="sc-button bt-cancel" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_index') }}">
{{ 'Cancel'|trans }}
</a>
</li>
{% endblock %}
{% block content_form_actions_before %}{% endblock %}
{% block content_form_actions_delete %}
{% if chill_crud_action_exists(crud_name, 'delete') %}
{% if is_granted(chill_crud_config('role', crud_name, 'delete'), entity) %}
<li class="">
<a class="sc-button bt-small bt-delete" href="{{ chill_path_add_return_path('chill_crud_'~crud_name~'_delete', { 'id': entity.id }) }}"></a>
</li>
{% endif %}
{% endif %}
{% endblock content_form_actions_delete %}
{% block content_form_actions_view %}
{% if chill_crud_action_exists(crud_name, 'view') %}
{% if is_granted(chill_crud_config('role', crud_name, 'view'), entity) %}
<li class="">
<a class="sc-button bt-small bt-show" href="{{ chill_path_forward_return_path('chill_crud_'~crud_name~'_view', { 'id': entity.id }) }}"></a>
</li>
{% endif %}
{% endif %}
{% endblock content_form_actions_view %}
{% block content_form_actions_save_and_close %}
<li class="">
<button type="submit" name="submit" value="save-and-close" class="sc-button bt-update">
{{ 'crud.edit.save_and_close'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_save_and_show %}
<li class="">
<button type="submit" name="submit" value="save-and-show" class="sc-button bt-update">
{{ 'crud.edit.save_and_show'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_after %}{% endblock %}
</ul>
{% endblock %}
{{ form_end(form) }}
{% endblock %}
</div>

View File

@ -0,0 +1 @@
{{ ('crud.'~crud_name~'.title_edit')|trans }}

View File

@ -0,0 +1 @@
{{ data }}

View File

@ -0,0 +1,48 @@
<div class="grid-10 centered">
{% block index_header %}
<h1>{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}</h1>
{% endblock index_header %}
{% if entities|length == 0 %}
{% block no_existing_entities %}
<p>{{ no_existing_entities_sentences|default('No entities')|trans }}</p>
{% endblock %}
{% else %}
{% block table_entities %}
<table>
<thead>
<tr>
{% block table_entities_thead_tr %}
<th>id</th>
{% endblock %}
</tr>
</thead>
<tbody>
{% block table_entities_tbody %}
{% for entity in entities %}
<tr>
<td>{{ entity.id }}</td>
</tr>
{% endfor %}
{% endblock %}
</tbody>
</table>
{% endblock %}
{% endif %}
<div class="crud_index__pagination">
{{ chill_pagination(paginator) }}
</div>
{% block list_actions %}
<ul class="record_actions sticky-form-buttons">
{% block add_new %}
<li>
<a href="{{ chill_path_add_return_path('chill_crud_' ~ crud_name ~ '_new') }}" class="sc-button bt-new">{{ ('crud.'~crud_name~'.index.add_new')|trans( {'%crud_name%': crud_name} ) }}</a>
</li>
{% endblock %}
</ul>
{% endblock list_actions %}
</div>

View File

@ -0,0 +1,50 @@
<div class="{% block crud_content_main_div_class %}grid-10 centered{% endblock %}">
{% block crud_content_header %}
<h1>{{ ('crud.' ~ crud_name ~ '.title_new')|trans({'%crud_name%' : crud_name }) }}</h1>
{% endblock crud_content_header %}
{% block crud_content_form %}
{{ form_start(form) }}
{% block crud_content_form_rows %}
{% for f in form if f.vars.name != 'submit' %}
{{ form_row(f) }}
{% endfor %}
{% endblock crud_content_form_rows %}
{% block crud_content_form_actions %}
<ul class="record_actions sticky-form-buttons">
{% block content_form_actions_back %}
<li class="cancel">
<a class="sc-button bt-cancel" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_index') }}">
{{ 'Cancel'|trans }}
</a>
</li>
{% endblock %}
{% block content_form_actions_save_and_close %}
<li class="">
<button type="submit" name="submit" value="save-and-close" class="sc-button bt-create">
{{ 'crud.new.save_and_close'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_save_and_show %}
<li class="">
<button type="submit" name="submit" value="save-and-show" class="sc-button bt-create">
{{ 'crud.new.save_and_show'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_save_and_new %}
<li class="">
<button type="submit" name="submit" value="save-and-new" class="sc-button bt-create">
{{ 'crud.new.save_and_new'|trans }}
</button>
</li>
{% endblock %}
</ul>
{% endblock %}
{{ form_end(form) }}
{% endblock %}
</div>

View File

@ -0,0 +1 @@
{{ ('crud.' ~ crud_name ~ '.title_new')|trans({'%crud_name%' : crud_name }) }}

View File

@ -0,0 +1,63 @@
<div class="{% block crud_content_main_div_class %}grid-10 centered{% endblock %}">
{% block crud_content_header %}
<h1>{{ 'crud.%crud_name%.title_view'|trans({'%crud_name%' : crud_name }) }}</h1>
{% endblock crud_content_header %}
{% block crud_content_view %}
{% block crud_content_view_details %}
<dl class="chill_view_data">
<dt>id</dt>
<dd>{{ entity.id|default("No id") }}</dd>
</dl>
{% endblock crud_content_view_details %}
{% block crud_content_view_actions %}
<ul class="record_actions sticky-form-buttons">
{% block content_view_actions_back %}
<li class="cancel">
<a class="sc-button bt-cancel" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_index') }}">
{{ 'Cancel'|trans }}
</a>
</li>
{% endblock %}
{% block content_view_actions_before %}{% endblock %}
{% block content_form_actions_delete %}
{% if chill_crud_action_exists(crud_name, 'delete') %}
{% if is_granted(chill_crud_config('role', crud_name, 'delete'), entity) %}
<li class="">
<a class="sc-button bt-delete" href="{{ chill_path_add_return_path('chill_crud_'~crud_name~'_delete', { 'id': entity.id }) }}">
{{ 'crud.delete.link_to_form'|trans }}
</a>
</li>
{% endif %}
{% endif %}
{% endblock content_form_actions_delete %}
{% block content_view_actions_duplicate_link %}
{% if chill_crud_action_exists(crud_name, 'new') %}
{% if is_granted(chill_crud_config('role', crud_name, 'new'), entity) %}
<li>
<a class="sc-button bt-duplicate" href="{{ chill_path_add_return_path('chill_crud_'~crud_name~'_new', { 'duplicate_id': entity.id, 'duplicate': true }) }}">
{{ 'crud.view.link_duplicate'|trans }}
</a>
</li>
{% endif %}
{% endif %}
{% endblock content_view_actions_duplicate_link %}
{% block content_view_actions_edit_link %}
{% if chill_crud_action_exists(crud_name, 'edit') %}
{% if is_granted(chill_crud_config('role', crud_name, 'edit'), entity) %}
<li>
<a class="sc-button bt-edit" href="{{ chill_path_forward_return_path('chill_crud_'~crud_name~'_edit', { 'id': entity.id }) }}">
{{ 'crud.new.link_edit'|trans }}
</a>
</li>
{% endif %}
{% endif %}
{% endblock content_view_actions_edit_link %}
{% block content_view_actions_after %}{% endblock %}
</ul>
{% endblock crud_content_view_actions %}
{% endblock crud_content_view %}
</div>

View File

@ -0,0 +1 @@
{{ 'crud.%crud_name%.title_view'|trans({'%crud_name%' : crud_name }) }}

View File

@ -0,0 +1,8 @@
{% extends '@ChillMain/layout.html.twig' %}
{% block title %}{{ ('crud.' ~ crud_name ~ '.delete.title')|trans({'%crud_name%': crud_name}) }}{% endblock %}
{% block content %}
{% embed '@ChillMain/CRUD/_delete_content.html.twig' %}
{% endembed %}
{% endblock content %}

View File

@ -0,0 +1,10 @@
{% extends '@ChillMain/layout.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
{% endblock %}
{% block content %}
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
{% endembed %}
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends '@ChillMain/layout.html.twig' %}
{% block title %}{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}{% endblock %}
{% block content %}
{% embed '@ChillMain/CRUD/_index.html.twig' %}
{% endembed %}
{% endblock content %}

View File

@ -0,0 +1,10 @@
{% extends '@ChillMain/layout.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
{% endblock %}
{% block content %}
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
{% endembed %}
{% endblock %}

View File

@ -0,0 +1,10 @@
{% extends '@ChillMain/layout.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_view_title.html.twig') %}
{% endblock %}
{% block content %}
{% embed '@ChillMain/CRUD/_view_content.html.twig' %}
{% endembed %}
{% endblock %}

View File

@ -0,0 +1 @@
{% if value is not empty %}<blockquote class="chill-user-quote">{{ value|localizeddate(date_format, time_format) }}</blockquote>{% else %}<span class="chill-no-data-statement">{{ message|trans }}</span>{% endif %}

View File

@ -0,0 +1 @@
{% if value is not empty %}{{ value|localizeddate(date_format, time_format) }}{% else %}<span class="chill-no-data-statement">{{ message|trans }}</span>{% endif %}

View File

@ -52,7 +52,7 @@
{% block choice_widget_expanded %}
{% spaceless %}
<div {{ block('widget_container_attributes') }}>
<div {{ block('widget_container_attributes') }} class="choice-widget-expanded">
{% for child in form %}
<span class="inline-choice">
{{ form_widget(child) }}

View File

@ -0,0 +1 @@
<footer class="footer"></footer>

View File

@ -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>
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,6 @@
* 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/>.
#}
<!DOCTYPE html>
<html>
<head>
@ -29,7 +28,7 @@
<body>
<div id="content">
{{ include('@ChillMain/Login/_login-logo.html.twig') }}
{% if error is not null %}
<p>{{ error.message|trans }}</p>
{% endif %}
@ -42,11 +41,15 @@
<input type="password" name="_password" />
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}" />
<br/>
<button type="submit" name="login">{{ 'Login'|trans }}</button>
<button type="submit" name="login">{{ 'Login'|trans }}</button>
</form>
<p class="forgot-password-link"><a href="{{ path('password_request_recover') }}">{{ 'Forgot your password ?'|trans }}</a></p>
{{ include('@ChillMain/Login/_footer.html.twig') }}
</div>
<script type="text/javascript" src="{{ asset('build/runtime.js') }}"></script>
</body>
</html>

View File

@ -21,16 +21,18 @@
<a href="{{ path('chill_main_admin_central') }}" class="more">Admin Sections</a>
</div>
<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;">
<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="{{ path(route.key, args ) }}">{{ route.label|trans }}</a>
<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 %}
{% for icon in route.icons %}
{% if menu.extras.icons is defined %}
{% for icon in menu.extras.icons %}
<i class="fa fa-{{ icon }}"></i>
{% endfor %}
{% endif %}
{% endspaceless %}</div>
</div>
</li>

View File

@ -0,0 +1,126 @@
# Fonctionnalité Tabs sur Chill-Main
Version 0.2
(to be translated)
## Description générale
**Tabs** est pensé comme une fonctionnalité paramétrique, qui permet de déployer facilement un ou plusieurs groupes d'onglets, avec des options qui permettent de varier son affichage et son comportement.
Le comportement naturel, ce sont des panneaux qui sont affichés ou cachés en javascript, lorqu'on clique sur l'onglet relatif.
## Controller
```
// in myController.php
public function myPageAction()
{
return $this->render('mytemplate.html.twig', [
'tabs' => [
'myFirstPanel' => [
[ 'name' => "Link 1",
'content' => "content text"
],
[ 'name' => "Link 2",
'content' => "content text for link 2"
]
],
'mySecondPanel' => [
[ 'name' => "Link 1",
'content' => "content text"
],
[ 'name' => "Link 2",
'link' => "http://my.url"
]
]
],
// ...
]);
}
```
#### Description
* On peut définir plusieurs panneaux d'onglets qui s'afficheront dans une même page ;
* Voici la description du tableau passé au template depuis le controller :
* Au départ on rassemble tous les panneaux d'onglets dans une seule clé, nommée ici `tabs` (la variable est utilisée dans le template, mais son nom peut changer);
* Au niveau suivant, chaque clé correspond à l'identifiant(id) d'un panneau d'onglet ;
* Au niveau suivant, chaque onglet est défini par un tableau, qui comprends les clés suivantes :
* la clé `name`, le titre de l'onglet ;
* la clé `content`, le contenu du panneau relatif à l'onglet. A ce stade, si `content` contient un tableau, il vous faudra adapter la boucle twig de la macro (voir ci-dessous) ;
* une clé `link` (facultative)
* Lorsque la clé `link` est définie pour un onglet, le comportement est différent : le lien est suivi et on quitte la page. Un panneau d'onglets peut être mixte, avec certains onglets qui sont en réalité des liens.
## Template
```
{# in mytemplate.html.twig #}
{% block css%}
<link rel="stylesheet" href="{{ asset('build/tabs.css') }}"/>
{% endblock %}
{% block js%}
<script type="text/javascript">
let tabParams = [{
id : 'myFirstPanel',
type: 'tab',
initPane: 2,
disposition: 'horizontal',
fade: true
},
{
id : 'mySecondPanel',
type: 'pill',
initPane: 5,
disposition: 'vertical',
fade: false
}
];
</script>
<script type="text/javascript" src="{{ asset('build/tabs.js') }}"></script>
{% endblock %}
{% import '@ChillMain/Tabs/macro.html.twig' as tabsPanel %}
{% block content %}
{# Display one tabsPanel #}
{{ tabsPanel.displayOne(tabs, 'myFirstPanel') }}
{# Display all tabPanels #}
{{ tabsPanel.display(tabs) }}
{% endblock %}
```
* Il faut commencer par charger la feuille de style `tabs.css` et le script `tabs.js` à l'intérieur des blocs css et js ;
* Avant d'appeler `tabs.js`, on définit un tableau d'options pour le script :
* **id**: string, la clé identifiant du panneau d'onglet (obligatoire)
* **type**: [tab|pill] affecte l'affichage (default: tab)
* **initPane**: entier, numéro de l'onglet à afficher au chargement de la page (default: 1)
* [prévu] **disposition**: [horizontal|vertical] affecte l'affichage (default: horizontal)
* [prévu] **fade**: boolean, transition (default: false)
* Ensuite on peut importer la macro `@ChillMain/Tabs/macro.html.twig` qui permet d'afficher les panneaux d'onglets, soit un par un, soit tous les panneaux ;
* Ou on peut s'inspirer des boucles twig de la macro pour en écrire de nouvelles personnalisées ;
## Limitations
* On ne peut pas afficher plusieurs fois le même panneau dans la même page
## Aller plus loin
* fixer les options disposition et fade
* js, calculer puis fixer la hauteur du panneau suivant le plus long
* controller, avec link, on clique il charge une nouvelle page, avec le même panneau d'onglet ?!
* insérer le initPane dans les urls en paramètres GET &initPane=xx
* quand tab.content n'est pas un string : un bloc de code html, un tableau..

View File

@ -0,0 +1,43 @@
{% extends '@ChillMain/layout.html.twig' %}
{% block title %}Page de test{% endblock %}
{% block css%}
<link rel="stylesheet" href="{{ asset('build/tabs.css') }}"/>
{% endblock %}
{% block js%}
<script type="text/javascript">
let tabParams = [{
id : 'test1',
type: 'tab',
initPane: 2,
disposition: 'horizontal',
fade: true
},
{
id : 'test2',
type: 'pill',
initPane: 5,
disposition: 'vertical',
fade: false
}
];
</script>
<script type="text/javascript" src="{{ asset('build/tabs.js') }}"></script>
{% endblock %}
{% import '@ChillMain/Tabs/macro.html.twig' as tabsPanel %}
{% block content %}
{# Display one tabsPanel
{{ tabsPanel.displayOne(tabs, 'test1') }}
#}
{# Display all tabPanels #}
{{ tabsPanel.display(tabs) }}
{% endblock %}

View File

@ -0,0 +1,36 @@
{#
Display only one panel
#}
{% macro displayOne(tabs, panel) %}
<div class="tabs" id="{{ panel }}">
<nav>
<ul class="nav-tabs" role="tablist">
{% for tab in tabs[panel] %}
<li class="nav-item">
{% set ref = (tab.link is defined )? tab.link : '#' %}
<a class="nav-link" href="{{ ref }}">{{ tab.name }}</a>
</li>
{% endfor %}
</ul>
</nav>
<div class="tab-content">
{% for tab in tabs[panel] %}
<div class="tab-pane fade" role="tabpanel">
{% if tab.content is defined %}
<p>{{ tab.content }}</p>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endmacro %}
{#
Display all panels
#}
{% macro display(tabs) %}
{% import _self as tabsPanel %}
{% for panel,array in tabs %}
{{ tabsPanel.displayOne(tabs, panel) }}
{% endfor %}
{% endmacro %}

View File

@ -142,6 +142,7 @@
{{ include('@ChillMain/Layout/_footer.html.twig') }}
<script type="text/javascript" src="{{ asset('build/runtime.js') }}"></script>
<script type="text/javascript" src="{{ asset('build/chill.js') }}"></script>
<script type="text/javascript">

View File

@ -0,0 +1,62 @@
<?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;
/**
*
*
*/
class AdminSectionMenuBuilder implements LocalMenuBuilderInterface
{
/**
*
* @var AuthorizationCheckerInterface
*/
protected $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
// all the entries below must have ROLE_ADMIN permissions
if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
return;
}
$menu->addChild('Users and permissions', [
'route' => 'chill_main_admin_permissions'
])
->setExtras([
'icons' => ['key'],
'order' => 200
]);
}
public static function getMenuIds(): array
{
return [ 'admin_section' ];
}
}

View File

@ -1,6 +1,22 @@
<?php
/*
*
* Chill is a software for social workers
*
* Copyright (C) 2014-2019, 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\Templating;
@ -33,30 +49,53 @@ class ChillTwigHelper extends AbstractExtension
* - 'default' ;
* - 'blockquote' ;
*
* `DateTimeInterface are also rendered. The date and time format may be set
* using those key in `$options´ parameter:
*
* - `date_format` (default to `'medium'`)
* - `time_format` (default to `'none'`)
*
* @param Environment $twig
* @param string $value
* @param string $value Default to 'No value'. Fallback to default if null
* @param string $message
* @param string $template
* @param array $options
* @return string
*/
public function printOrMessage(
Environment $twig,
$value,
$message = 'No value',
$template = 'default'
$template = 'default',
array $options = []
) {
switch ($template) {
case 'default':
case 'blockquote':
$t = '@ChillMain/Extensions/PrintOrMessage/'.$template.'.html.twig';
break;
default:
$t = $template;
if ($value instanceof \DateTimeInterface) {
$options = \array_merge([
'date_format' => 'medium',
'time_format' => 'none'
], $options);
switch ($template) {
case 'default':
case 'blockquote':
$t = '@ChillMain/Extensions/PrintOrMessage/'.$template.'_date.html.twig';
break;
default:
$t = $template;
}
} else {
switch ($template) {
case 'default':
case 'blockquote':
$t = '@ChillMain/Extensions/PrintOrMessage/'.$template.'.html.twig';
break;
default:
$t = $template;
}
}
return $twig->render($t, [
return $twig->render($t, \array_merge([
'value' => $value,
'message' => $message
]);
'message' => $message ?? 'No value'
], $options));
}
}

View File

@ -1,5 +1,22 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2014-2019, 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\Templating;

View File

@ -0,0 +1,38 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2014-2019, 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\Templating\Entity;
/**
*
*
*/
abstract class AbstractChillEntityRender implements ChillEntityRenderInterface
{
protected function getDefaultOpeningBox($classSuffix): string
{
return '<span class="chill-entity chill-entity__'.$classSuffix.'">';
}
protected function getDefaultClosingBox(): string
{
return '</span>';
}
}

View File

@ -0,0 +1,43 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2014-2019, 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\Templating\Entity;
/**
* Render an entity using `__toString()`
*/
class ChillEntityRender extends AbstractChillEntityRender
{
public function renderBox($entity, array $options): string
{
return $this->getDefaultOpeningBox('default').$entity
.$this->getDefaultClosingBox();
}
public function renderString($entity, array $options): string
{
return (string) $entity;
}
public function supports($entity, array $options): bool
{
return true;
}
}

View File

@ -0,0 +1,96 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2014-2019, 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\Templating\Entity;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
/**
*
*
*/
class ChillEntityRenderExtension extends AbstractExtension
{
/**
*
* @var ChillEntityRenderInterface
*/
protected $renders = [];
/**
*
* @var ChillEntityRender
*/
protected $defaultRender;
public function __construct()
{
$this->defaultRender = new ChillEntityRender();
}
public function getFilters()
{
return [
new TwigFilter('chill_entity_render_string', [$this, 'renderString'], [
'is_safe' => [ 'html' ]
]),
new TwigFilter('chill_entity_render_box', [$this, 'renderBox'], [
'is_safe' => [ 'html' ]
])
];
}
public function renderString($entity, array $options = []): string
{
if (NULL === $entity) {
return '';
}
return $this->getRender($entity, $options)
->renderString($entity, $options);
}
public function renderBox($entity, array $options = []): string
{
if (NULL === $entity) {
return '';
}
return $this->getRender($entity, $options)
->renderBox($entity, $options);
}
public function addRender(ChillEntityRenderInterface $render)
{
$this->renders[] = $render;
}
protected function getRender($entity, $options): ?ChillEntityRenderInterface
{
foreach ($this->renders as $render) {
if ($render->supports($entity, $options)) {
return $render;
}
}
return $this->defaultRender;
}
}

View File

@ -0,0 +1,66 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2014-2019, 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\Templating\Entity;
/**
* Interface to implement which will render an entity in template on a custom
* manner.
*/
interface ChillEntityRenderInterface
{
/**
* Return true if the class support this object for the given options
*
* @param type $entity
* @param array $options
* @return bool
*/
public function supports($entity, array $options): bool;
/**
* Return the entity as a string.
*
* Example: returning the name of a person.
*
* @param object $entity
* @param array $options
* @return string
*/
public function renderString($entity, array $options): string;
/**
* Return the entity in a box
*
* Example: return a person inside a box:
*
* ```html
* <span class="chill-entity">
* <span class="chill-entity__first-name">Roger</span>
* <span class="chill-entity__last-name">Dupont</span>
* </span>
* ```
*
* @param type $entity
* @param array $options
* @return string
*/
public function renderBox($entity, array $options): string;
}

View File

@ -0,0 +1,41 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2014-2019, 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\Templating\Entity;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* Add service tagged with `chill.render_entity` to appropriate service
*
*/
class CompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$extension = $container->getDefinition(ChillEntityRenderExtension::class);
foreach ($container->findTaggedServiceIds('chill.render_entity') as $id => $tags) {
$extension->addMethodCall('addRender', [new Reference($id)]);
}
}
}

View File

@ -1,5 +1,4 @@
<?php
/*
* Chill is a suite of a modules, Chill is a software for social workers
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>

View File

@ -1,5 +1,4 @@
<?php
/*
* Chill is a software for social workers
*

View File

@ -21,7 +21,7 @@ use Symfony\Component\Validator\Constraint;
/**
*
*
* @Annotation
*/
class PhonenumberConstraint extends Constraint
{
@ -29,10 +29,12 @@ class PhonenumberConstraint extends Constraint
public $notLandlineMessage = "This is not a landline phonenumber";
public $notValidMessage = "This is not a valid phonenumber";
/**
* The type of phone: landline (not able to receive sms) or mobile (can receive sms)
*
* @var string 'landline' or 'mobile'
* @var string 'landline', 'mobile' or 'any'
*/
public $type = null;

View File

@ -70,10 +70,14 @@ class ValidPhonenumber extends ConstraintValidator
$isValid = $this->phonenumberHelper->isValidPhonenumberMobile($value);
$message = $constraint->notMobileMessage;
break;
case 'any':
$isValid = $this->phonenumberHelper->isValidPhonenumberAny($value);
$message = $constraint->notValidMessage;
break;
default:
throw new \LogicException(sprintf("This type '%s' is not implemented. "
. "Possible values are 'mobile', 'landline'"), $constraint->type);
. "Possible values are 'mobile', 'landline' or 'any'", $constraint->type));
}
if (FALSE === $isValid) {

View File

@ -1,36 +1,10 @@
// 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/select_interactive_loading/index.js');
require('./Resources/public/modules/export-list/export-list.scss');
// 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');
module.exports = function(encore, entries) {
entries.push(__dirname + '/Resources/public/main.js');
encore.addEntry('login', './vendor/chill-project/main/Resources/public/modules/login_page/index.js');
encore.addEntry('tabs', './vendor/chill-project/main/Resources/public/modules/tabs/index.js');
encore.addAliases({
ShowHide: __dirname + '/Resources/public/modules/show_hide/',
ChillMainSass: __dirname + '/Resources/public/sass'
});
};