Merge remote-tracking branch 'ChillMain/master'

This commit is contained in:
Julien Fastré 2021-03-18 12:46:45 +01:00
commit 9b97d10ec4
717 changed files with 90404 additions and 0 deletions

34
src/Bundle/ChillMain/.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# MacOS
.DS_Store
# Bootstrap
app/bootstrap*
# Symfony directories
vendor/*
*/logs/*
*/cache/*
web/uploads/*
web/bundles/*
# Configuration files
app/config/parameters.ini
app/config/parameters.yml
Tests/Fixtures/App/config/parameters.yml
#composer
composer.lock
#sass-cache
Resources/assets/gumpy/.sass-cache
Resources/public/stylesheets/sass
*~
Resources/.sass-cache/
Resources/bower_components/
Resources/node_modules/
/nbproject/private/
# Yarn lock
Resources/package-lock.json
Resources/yarn.lock

View File

@ -0,0 +1,65 @@
.test_definition: &test_definition
services:
- chill/database:latest
before_script:
- composer config github-oauth.github.com $GITHUB_TOKEN
- composer install
- cp Resources/test/Fixtures/App/app/config/parameters.gitlab-ci.yml Resources/test/Fixtures/App/app/config/parameters.yml
- php Resources/test/Fixtures/App/app/console --env=test cache:warmup
- php Resources/test/Fixtures/App/app/console doctrine:migrations:migrate --env=test --no-interaction
- php Resources/test/Fixtures/App/app/console doctrine:fixtures:load --env=test --no-interaction
stages:
- deploy
- test
- build-doc
- deploy-doc
test:php-7.2:
stage: test
<<: *test_definition
image: chill/ci-image:php-7.2
script: APP_ENV=test vendor/bin/phpunit
# deploy documentation
api-doc-build:
stage: build-doc
environment: api-doc
image: chill/ci-image:php-7.2
before_script:
- mkdir api-doc
script: apigen generate --destination api-doc/$CI_BUILD_REF_NAME/$CI_PROJECT_NAME
artifacts:
paths:
- "api-doc/"
name: api
expire_in: '2h'
only:
- master
- tags
api-doc-deploy:
stage: deploy-doc
image: pallet/swiftclient:latest
before_script:
# test that CONTAINER_API variable is set
- if [ -z ${CONTAINER_API+x} ]; then echo "Please set CONTAINER_API variable"; exit -1; fi
# go to api-doc to have and url with PROJECT/BUILD
- cd api-doc
# upload, and keep files during 1 year
script: "swift upload --header \"X-Delete-After: 31536000\" $CONTAINER_API $CI_BUILD_REF_NAME/$CI_PROJECT_NAME"
only:
- master
- tags
deploy-packagist:
stage: deploy
image: chill/ci-image:php-7.2
before_script:
# test that PACKAGIST USERNAME and PACKAGIST_TOKEN variable are set
- if [ -z ${PACKAGIST_USERNAME+x} ]; then echo "Please set PACKAGIST_USERNAME variable"; exit -1; fi
- if [ -z ${PACKAGIST_TOKEN+x} ]; then echo "Please set PACKAGIST_TOKEN variable"; exit -1; fi
script:
- STATUSCODE=$(curl -XPOST -H'content-type:application/json' "https://packagist.org/api/update-package?username=$PACKAGIST_USERNAME&apiToken=$PACKAGIST_TOKEN" -d"{\"repository\":{\"url\":\"$CI_PROJECT_URL.git\"}}" --silent --output /dev/stderr --write-out "%{http_code}")
- if [ $STATUSCODE = "202" ]; then exit 0; else exit $STATUSCODE; fi

View File

@ -0,0 +1,157 @@
Version 1.5.1
=============
- add email to users on fixtures ;
- spare ressource with recursive trigger on inserting user ;
- fix error when no flags are used during edit and creation of permission group ;
Version 1.5.2
=============
- allow to filters users shown by `UserPickerType` based on flags. This flags do an additional filter based on the flags assigned in permissions groups;
- add a method to filters users by permissions groups flags in `UserRepository`
Version 1.5.3
=============
- fix error when interval is hour only
Version 1.5.4
=============
- layout of page "list exports"
- create function "SIMILARITY" (see [posgtgresql documentation](https://www.postgresql.org/docs/9.6/static/pgtrgm.html))
- create function "OVERLAPSI", which will detect period of date overlapping, replacing NULL date by infinity or -infinity (see [postgresql page for date time function and operators](https://www.postgresql.org/docs/9.6/static/functions-datetime.html))
- add repository for Center class
Version 1.5.5
=============
- add margin of 0.5rem beyond buttons ;
- add a spreadsheet formatter (format xlsx, ods, csv) for lists
- add possibility to generate DirectExport: exports without formatters, filters and aggregators ;
- add api for grouping centers ;
- select centers as grouped on step "pick centers" in exports ;
Version 1.5.6
=============
- fix long url in report download. The exports parameters are now stored in redis.
- add an option to allow address to be empty if street or postcode is not set. Used for embedding address in another form, when address is not required.
Version 1.5.7
=============
- insert the title of the export inside the "download" page ;
- add twig helper "print_or_message" ;
- add twig helper for routing ;
- add css layout for boxes ;
- collect supplementary query parameters in SearchController ;
Version 1.5.8
=============
- allow to remove interval units from DateInterval
Version 1.5.9
=============
- add optionnal impersonate feature (if firewall option switch_user is true) ;
Version 1.5.10
==============
- allow to group export in UI
Version 1.5.11
==============
- improve return path functions and filters ;
Version 1.5.12
==============
- make the redirection to admin temporarily: some admin experienced cache problems (403 error) when they switched from one admin account to a non-admin one ;
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;
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 ;
Version 1.5.19
==============
- [address] add a "homeless" characteristic to addresses ;
Version 1.5.20
==============
- [CRUD] make index query more abstract.
Improve build and count query in default index action to be customized
in one dedicated method.
Version 1.5.21
==============
- [Export list] improve alignment of last line
- [CRUD] Forward query parameters when pushing button "save and new" in "create" page;
- [Show/hide] Take selects input into account;
Version 1.5.23
==============
- [address] allow to add custom fields to addresses
Version 1.5.24
==============
- [bugfix] add missing migration files

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
{
}

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

@ -0,0 +1,27 @@
<?php
/*
*
*/
namespace Chill\MainBundle\Center;
/**
* Interface to declare a groups of centers.
*
* This interface is used to declare a groups of centers in
* `Chill\MainBundle\Form\Export\PickCenterType`.
*
*/
interface GroupingCenterInterface
{
/**
* @return string[]
*/
public function getGroups($authorizedCenters = null): array;
/**
*
* @param string $group
* @return \Chill\MainBundle\Entity\Center[]
*/
public function getCentersForGroup($group);
}

View File

@ -0,0 +1,37 @@
<?php
namespace Chill\MainBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Chill\MainBundle\DependencyInjection\SearchableServicesCompilerPass;
use Chill\MainBundle\DependencyInjection\ConfigConsistencyCompilerPass;
use Chill\MainBundle\DependencyInjection\TimelineCompilerClass;
use Chill\MainBundle\DependencyInjection\RoleProvidersCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\ExportsCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass;
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
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new SearchableServicesCompilerPass());
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
$container->addCompilerPass(new TimelineCompilerClass());
$container->addCompilerPass(new RoleProvidersCompilerPass());
$container->addCompilerPass(new ExportsCompilerPass());
$container->addCompilerPass(new WidgetsCompilerPass());
$container->addCompilerPass(new NotificationCounterCompilerPass());
$container->addCompilerPass(new MenuCompilerPass());
$container->addCompilerPass(new ACLFlagsCompilerPass());
$container->addCompilerPass(new GroupingCenterCompilerPass());
$container->addCompilerPass(new RenderEntityCompilerPass());
}
}

View File

@ -0,0 +1,448 @@
<?php
namespace Chill\MainBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use League\Csv\Reader;
use Doctrine\ORM\EntityManagerInterface;
use Chill\MainBundle\Entity\Center;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Chill\MainBundle\Entity\User;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Chill\MainBundle\Entity\GroupCenter;
use Chill\MainBundle\Entity\PermissionsGroup;
use Symfony\Component\Console\Question\ChoiceQuestion;
use League\Csv\Writer;
class ChillImportUsersCommand extends ContainerAwareCommand
{
/**
*
* @var EntityManagerInterface
*/
protected $em;
/**
*
* @var ValidatorInterface
*/
protected $validator;
/**
*
* @var LoggerInterface
*/
protected $logger;
/**
*
* @var UserPasswordEncoderInterface
*/
protected $passwordEncoder;
/**
*
* @var \Chill\MainBundle\Repository\UserRepository
*/
protected $userRepository;
/**
*
* @var bool
*/
protected $doChanges = true;
/**
*
* @var OutputInterface
*/
protected $tempOutput;
/**
*
* @var InputInterface
*/
protected $tempInput;
/**
* Centers and aliases.
*
* key are aliases, values are an array of centers
*
* @var array
*/
protected $centers = [];
/**
*
* @var array
*/
protected $permissionGroups = [];
/**
*
* @var array
*/
protected $groupCenters = [];
/**
*
* @var Writer
*/
protected $output = null;
public function __construct(
EntityManagerInterface $em,
LoggerInterface $logger,
UserPasswordEncoderInterface $passwordEncoder,
ValidatorInterface $validator
) {
$this->em = $em;
$this->passwordEncoder = $passwordEncoder;
$this->validator = $validator;
$this->logger = $logger;
$this->userRepository = $em->getRepository(User::class);
parent::__construct('chill:main:import-users');
}
protected function configure()
{
$this
->setDescription('Import users from csv file')
->setHelp("Import users from a csv file. Users are added to centers contained in the file. Headers are used to detect columns. Adding to multiple centers can be done by using a `grouping centers` file, which will group multiple centers into a signle alias, used in 'centers' column.")
->addArgument('csvfile', InputArgument::REQUIRED, 'Path to the csv file. Columns are: `username`, `email`, `center` (can contain alias), `permission group`')
->addOption('grouping-centers', null, InputOption::VALUE_OPTIONAL, 'Path to a csv file to aggregate multiple centers into a single alias')
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Do not commit the changes')
->addOption('csv-dump', null, InputOption::VALUE_REQUIRED, 'A path to dump a summary of the created file')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->tempOutput = $output;
$this->tempInput = $input;
if ($input->getOption('dry-run')) {
$this->doChanges = false;
}
$this->prepareWriter();
if ($input->hasOption('grouping-centers')) {
$this->prepareGroupingCenters();
}
try {
$this->loadUsers();
}
catch(\Exception $e) {
throw $e;
}
}
protected function prepareWriter()
{
$this->output = $output = Writer::createFromPath($this->tempInput
->getOption('csv-dump'), 'a+');
$output->insertOne([
'email',
'username',
'id'
]);
}
protected function appendUserToFile(User $user)
{
$this->output->insertOne( [
$user->getEmail(),
$user->getUsername(),
$user->getId()
]);
}
protected function loadUsers()
{
$reader = Reader::createFromPath($this->tempInput->getArgument('csvfile'));
$reader->setHeaderOffset(0);
foreach ($reader->getRecords() as $line => $r) {
$this->logger->debug("starting handling new line", [
'line' => $line
]);
if ($this->doesUserExists($r)) {
$this->tempOutput->writeln(sprintf("User with username '%s' already "
. "exists, skipping", $r["username"]));
$this->logger->info("One user already exists, skipping creation", [
'username_in_file' => $r['username'],
'email_in_file' => $r['email'],
'line' => $line
]);
continue;
}
$user = $this->createUser($line, $r);
$this->appendUserToFile($user);
}
}
protected function doesUserExists($data)
{
if ($this->userRepository->countByUsernameOrEmail($data['username']) > 0) {
return true;
}
if ($this->userRepository->countByUsernameOrEmail($data['email']) > 0) {
return true;
}
return false;
}
protected function createUser($offset, $data)
{
$user = new User();
$user
->setEmail(\trim($data['email']))
->setUsername(\trim($data['username']))
->setEnabled(true)
->setPassword($this->passwordEncoder->encodePassword($user,
\bin2hex(\random_bytes(32))))
;
$errors = $this->validator->validate($user);
if ($errors->count() > 0) {
$errorMessages = $this->concatenateViolations($errors);
$this->tempOutput->writeln(sprintf("%d errors found with user with username \"%s\" at line %d", $errors->count(), $data['username'], $offset));
$this->tempOutput->writeln($errorMessages);
throw new \RuntimeException("Found errors while creating an user. "
. "Watch messages in command output");
}
$pgs = $this->getPermissionGroup($data['permission group']);
$centers = $this->getCenters($data['center']);
foreach($pgs as $pg) {
foreach ($centers as $center) {
$groupcenter = $this->createOrGetGroupCenter($center, $pg);
if (FALSE === $user->getGroupCenters()->contains($groupcenter)) {
$user->addGroupCenter($groupcenter);
}
}
}
if ($this->doChanges) {
$this->em->persist($user);
$this->em->flush();
}
$this->logger->notice("Create user", [
'username' => $user->getUsername(),
'id' => $user->getId(),
'nb_of_groupCenters' => $user->getGroupCenters()->count()
]);
return $user;
}
protected function getPermissionGroup($alias)
{
if (\array_key_exists($alias, $this->permissionGroups)) {
return $this->permissionGroups[$alias];
}
$permissionGroupsByName = [];
foreach($this->em->getRepository(PermissionsGroup::class)
->findAll() as $permissionGroup) {
$permissionGroupsByName[$permissionGroup->getName()] = $permissionGroup;
}
if (count($permissionGroupsByName) === 0) {
throw new \RuntimeException("no permission groups found. Create them "
. "before importing users");
}
$question = new ChoiceQuestion("To which permission groups associate with \"$alias\" ?",
\array_keys($permissionGroupsByName));
$question
->setMultiselect(true)
->setAutocompleterValues(\array_keys($permissionGroupsByName))
->setNormalizer(function($value) {
if (NULL === $value) { return ''; }
return \trim($value);
})
;
$helper = $this->getHelper('question');
$keys = $helper->ask($this->tempInput, $this->tempOutput, $question);
$this->tempOutput->writeln("You have chosen ".\implode(", ", $keys));
if ($helper->ask($this->tempInput, $this->tempOutput,
new ConfirmationQuestion("Are you sure ?", true))) {
foreach ($keys as $key) {
$this->permissionGroups[$alias][] = $permissionGroupsByName[$key];
}
return $this->permissionGroups[$alias];
} else {
$this->logger->error("Error while responding to a a question");
$this->tempOutput("Ok, I accept, but I do not know what to do. Please try again.");
throw new \RuntimeException("Error while responding to a question");
}
}
/**
*
* @param Center $center
* @param \Chill\MainBundle\Command\PermissionGroup $pg
* @return GroupCenter
*/
protected function createOrGetGroupCenter(Center $center, PermissionsGroup $pg): GroupCenter
{
if (\array_key_exists($center->getId(), $this->groupCenters)) {
if (\array_key_exists($pg->getId(), $this->groupCenters[$center->getId()])) {
return $this->groupCenters[$center->getId()][$pg->getId()];
}
}
$repository = $this->em->getRepository(GroupCenter::class);
$groupCenter = $repository->findOneBy(array(
'center' => $center,
'permissionsGroup' => $pg
));
if ($groupCenter === NULL) {
$groupCenter = new GroupCenter();
$groupCenter
->setCenter($center)
->setPermissionsGroup($pg)
;
$this->em->persist($groupCenter);
}
$this->groupCenters[$center->getId()][$pg->getId()] = $groupCenter;
return $groupCenter;
}
protected function prepareGroupingCenters()
{
$reader = Reader::createFromPath($this->tempInput->getOption('grouping-centers'));
$reader->setHeaderOffset(0);
foreach ($reader->getRecords() as $r) {
$this->centers[$r['alias']] =
\array_merge(
$this->centers[$r['alias']] ?? [],
$this->getCenters($r['center']
)
);
}
}
/**
* return a list of centers matching the name of alias.
*
* If the name match one center, this center is returned in an array.
*
* If the name match an alias, the centers corresponding to the alias are
* returned in an array.
*
* If the center is not found or alias is not created, a new center is created
* and suggested to user
*
* @param string $name the name of the center or the alias regrouping center
* @return Center[]
*/
protected function getCenters($name)
{
// sanitize
$name = \trim($name);
if (\array_key_exists($name, $this->centers)) {
return $this->centers[$name];
}
// search for a center with given name
$center = $this->em->getRepository(Center::class)
->findOneByName($name);
if ($center instanceof Center) {
$this->centers[$name] = [$center];
return $this->centers[$name];
}
// suggest and create
$center = (new Center())
->setName($name);
$this->tempOutput->writeln("Center with name \"$name\" not found.");
$qFormatter = $this->getHelper('question');
$question = new ConfirmationQuestion("Create a center with name \"$name\" ?", true);
if ($qFormatter->ask($this->tempInput, $this->tempOutput, $question)) {
$this->centers[$name] = [ $center ];
$errors = $this->validator->validate($center);
if ($errors->count() > 0) {
$errorMessages = $this->concatenateViolations($errors);
$this->tempOutput->writeln(sprintf("%d errors found with center with name \"%s\"", $errors->count(), $name));
$this->tempOutput->writeln($errorMessages);
throw new \RuntimeException("Found errors while creating one center. "
. "Watch messages in command output");
}
$this->em->persist($center);
return $this->centers[$name];
}
return null;
}
protected function concatenateViolations(ConstraintViolationListInterface $list)
{
$str = [];
foreach ($list as $e) {
/* @var $e \Symfony\Component\Validator\ConstraintViolationInterface */
$str[] = $e->getMessage();
}
return \implode(";", $str);
}
}

View File

@ -0,0 +1,198 @@
<?php
namespace Chill\MainBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use League\Csv\Reader;
use Psr\Log\LoggerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Notification\Mailer;
use Chill\MainBundle\Security\PasswordRecover\RecoverPasswordHelper;
use Chill\MainBundle\Security\PasswordRecover\PasswordRecoverEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class ChillUserSendRenewPasswordCodeCommand extends ContainerAwareCommand
{
/**
*
* @var LoggerInterface
*/
protected $logger;
/**
*
* @var EntityManagerInterface
*/
protected $em;
/**
*
* @var Mailer
*/
protected $mailer;
/**
*
* @var RecoverPasswordHelper
*/
protected $recoverPasswordHelper;
/**
*
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The current input interface
*
* @var InputInterface
*/
private $input;
/**
* The current output interface
*
* @var OutputInterface
*/
private $output;
public function __construct(
LoggerInterface $logger,
EntityManagerInterface $em,
RecoverPasswordHelper $recoverPasswordHelper,
EventDispatcherInterface $eventDispatcher)
{
$this->logger = $logger;
$this->em = $em;
$this->recoverPasswordHelper = $recoverPasswordHelper;
$this->eventDispatcher = $eventDispatcher;
parent::__construct();
}
protected function configure()
{
$this
->setName('chill:user:send-password-recover-code')
->setDescription('Send a message with code to recover password')
->addArgument('csvfile', InputArgument::REQUIRED, 'CSV file with the list of users')
->addOption('template', null, InputOption::VALUE_REQUIRED, 'Template for email')
->addOption('expiration', null, InputOption::VALUE_REQUIRED, 'Expiration of the link, as an unix timestamp')
->addOption('subject', null, InputOption::VALUE_REQUIRED, 'Subject of the email', 'Recover your password')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->output = $output;
$reader = $this->getReader();
foreach($reader->getRecords() as $offset => $r) {
$user = $this->getUser($r);
if ($user === null) {
$this->onUserNotFound($r, $offset);
continue;
}
$this->sendRecoverCode($user);
}
}
protected function sendRecoverCode(User $user)
{
if (empty($user->getEmail())) {
$this->logger->alert("User without email", [
'user_id' => $user->getId(),
'username' => $user->getUsername()
]);
return;
}
$template = $this->input->getOption('template');
$expiration = \DateTime::createFromFormat('U',
$this->input->getOption('expiration'));
$this->recoverPasswordHelper
->sendRecoverEmail(
$user,
$expiration,
$template,
[ 'expiration' => $expiration],
false,
[ '_locale' => 'fr' ],
$this->input->getOption('subject')
);
}
protected function onUserNotFound($row, $offset)
{
$this->logger->alert('User not found', \array_merge([
'offset' => $offset
], $row));
}
protected function getUser($row)
{
/* @var $userRepository \Chill\MainBundle\Repository\UserRepository */
$userRepository = $this->em->getRepository(User::class);
try {
if (\array_key_exists('email', $row)) {
return $userRepository->findOneByUsernameOrEmail(\trim($row['email']));
}
} catch (\Doctrine\ORM\NoResultException $e) {
// continue, we will try username
}
try {
if (\array_key_exists('username', $row)) {
return $userRepository->findOneByUsernameOrEmail(\trim($row['username']));
}
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
/**
*
* @return Reader
* @throws \Exception
*/
protected function getReader()
{
try {
$reader = Reader::createFromPath($this->input->getArgument('csvfile'));
} catch (\Exception $e) {
$this->logger->error("The csv file could not be read", [
'path' => $this->input->getArgument('csvfile')
]);
throw $e;
}
$reader->setHeaderOffset(0);
$headers = $reader->getHeader();
if (FALSE === \in_array('username', $headers)
&& FALSE === \in_array('email', $headers)) {
throw new \InvalidArgumentException("The csv file does not have an "
. "username or email header");
}
return $reader;
}
}

View File

@ -0,0 +1,124 @@
<?php
/*
* Copyright (C) 2014, 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\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Intl\Intl;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/*
* Load or update the languages entities command
*/
class LoadAndUpdateLanguagesCommand extends ContainerAwareCommand
{
// The regional version of language are language with _ in the code
// This array contains regional code to not exclude
private $regionalVersionToInclude = ["ro_MD"];
// Array of ancien languages (to exclude)
private $ancientToExclude = ["ang", "egy", "fro", "goh", "grc", "la", "non", "peo", "pro", "sga",
"dum", "enm", "frm", "gmh", "mga", "akk", "phn", "zxx", "got", "und"];
const INCLUDE_REGIONAL_VERSION = 'include_regional';
const INCLUDE_ANCIENT = 'include_ancient';
/*
* (non-PHPdoc)
* @see \Symfony\Component\Console\Command\Command::configure()
*/
protected function configure()
{
$this
->setName('chill:main:languages:populate')
->setDescription('Load or update languages in db. This command does not delete existing '.
'languages, but will update names according to available languages')
->addOption(
self::INCLUDE_REGIONAL_VERSION,
null,
InputOption::VALUE_NONE,
'Include the regional languages. The regional languages are languages with code containing _ excepted '
. implode(',', $this->regionalVersionToInclude) . '.')
->addOption(
self::INCLUDE_ANCIENT,
null,
InputOption::VALUE_NONE,
'Include the ancient languages that are languages with code '
. implode(', ', $this->ancientToExclude) . '.')
;
}
/*
* (non-PHPdoc)
* @see \Symfony\Component\Console\Command\Command::execute()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$chillAvailableLanguages = $this->getContainer()->getParameter('chill_main.available_languages');
$languageBundle = Intl::getLanguageBundle();
$languages = array();
foreach ($chillAvailableLanguages as $avLang) {
$languages[$avLang] = $languageBundle->getLanguageNames($avLang);
}
$languageCodes = array_keys($languages[$chillAvailableLanguages[0]]);
foreach ($languageCodes as $code) {
$excludeCode = (
(
! $input->getOption(self::INCLUDE_REGIONAL_VERSION)
and strpos($code, '_')
and !in_array($code, $this->regionalVersionToInclude)
) or (
! $input->getOption(self::INCLUDE_ANCIENT)
and in_array($code, $this->ancientToExclude)
)
);
$langageDB = $em->getRepository('ChillMainBundle:Language')->find($code);
if(! $excludeCode) {
if (! $langageDB) {
$langageDB = new \Chill\MainBundle\Entity\Language();
$langageDB->setId($code);
$em->persist($langageDB);
}
$avLangNames = array();
foreach ($chillAvailableLanguages as $avLang) {
$avLangNames[$avLang] = $languages[$avLang][$code];
}
$langageDB->setName($avLangNames);
} else {
if($langageDB) {
$em->remove($langageDB);
}
echo "Code excluded : ".$code." - ".$languageBundle->getLanguageName($code)."\n";
}
}
$em->flush();
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Chill\MainBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Intl\Intl;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
*
* @author Julien Fastré <julien.fastre@champs-libres.coop
*
*/
class LoadCountriesCommand extends ContainerAwareCommand
{
/*
* (non-PHPdoc)
* @see \Symfony\Component\Console\Command\Command::configure()
*/
protected function configure()
{
$this->setName('chill:main:countries:populate')
->setDescription('Load or update countries in db. This command does not delete existing countries, '.
'but will update names according to available languages');
}
/*
* (non-PHPdoc)
* @see \Symfony\Component\Console\Command\Command::execute()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$countries = static::prepareCountryList($this->getContainer()->getParameter('chill_main.available_languages'));
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
foreach($countries as $country) {
$countryStored = $em->getRepository('ChillMainBundle:Country')
->findOneBy(array('countryCode' => $country->getCountryCode()));
if (NULL === $countryStored) {
$em->persist($country);
} else {
$countryStored->setName($country->getName());
}
}
$em->flush();
}
public static function prepareCountryList($languages)
{
$regionBundle = Intl::getRegionBundle();
foreach ($languages as $language) {
$countries[$language] = $regionBundle->getCountryNames($language);
}
$countryEntities = array();
foreach ($countries[$languages[0]] as $countryCode => $name) {
$names = array();
foreach ($languages as $language) {
$names[$language] = $countries[$language][$countryCode];
}
$country = new \Chill\MainBundle\Entity\Country();
$country->setName($names)->setCountryCode($countryCode);
$countryEntities[] = $country;
}
return $countryEntities;
}
}

View File

@ -0,0 +1,199 @@
<?php
/*
* Copyright (C) 2016 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\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Filesystem\Filesystem;
use Chill\MainBundle\Entity\PostalCode;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class LoadPostalCodesCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('chill:main:postal-code:populate')
->setDescription("Add the postal code from a csv file.")
->setHelp("This script will try to avoid existing postal code "
. "using the postal code and name. \n"
. "The CSV file must have the following columns: "
. "postal code, label, country code."
. "The CSV file should not have any header row.")
->addArgument('csv_file', InputArgument::REQUIRED, "the path to "
. "the csv file. See the help for specifications.")
->addOption(
'delimiter',
'd',
InputOption::VALUE_OPTIONAL,
"The delimiter character of the csv file",
",")
->addOption(
'enclosure',
null,
InputOption::VALUE_OPTIONAL,
"The enclosure character of the csv file",
'"'
)
->addOption(
'escape',
null,
InputOption::VALUE_OPTIONAL,
"The escape character of the csv file",
"\\"
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
try {
$csv = $this->getCSVResource($input);
} catch (\RuntimeException $e) {
$output->writeln('<error>Error during opening the csv file : '.
$e->getMessage().'</error>');
}
if ($output->getVerbosity() === OutputInterface::VERBOSITY_VERY_VERBOSE) {
$output->writeln('The content of the file is ...');
$output->write(file_get_contents($input->getArgument('csv_file')));
}
$num = 0;
$line = 0;
while (($row = fgetcsv(
$csv,
0,
$input->getOption('delimiter'),
$input->getOption('enclosure'),
$input->getOption('escape'))) !== false) {
try{
$this->addPostalCode($row, $output);
$num++;
} catch (ExistingPostalCodeException $ex) {
$output->writeln('<warning> on line '.$line.' : '.$ex->getMessage().'</warning>');
} catch (CountryCodeNotFoundException $ex) {
$output->writeln('<warning> on line '.$line.' : '.$ex->getMessage().'</warning>');
} catch (PostalCodeNotValidException $ex) {
$output->writeln('<warning> on line '.$line.' : '.$ex->getMessage().'</warning>');
}
$line ++;
}
$this->getContainer()->get('doctrine.orm.entity_manager')->flush();
$output->writeln('<info>'.$num.' were added !</info>');
}
private function getCSVResource(InputInterface $input)
{
$fs = new Filesystem();
$filename = $input->getArgument('csv_file');
if (!$fs->exists($filename)) {
throw new \RuntimeException("The file does not exists or you do not "
. "have the right to read it.");
}
$resource = fopen($filename, 'r');
if ($resource == FALSE) {
throw new \RuntimeException("The file '$filename' could not be opened.");
}
return $resource;
}
private function addPostalCode($row, OutputInterface $output)
{
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
$output->writeln('handling row: '. $row[0].' | '. $row[1].' | '. $row[2]);
}
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$country = $em
->getRepository('ChillMainBundle:Country')
->findOneBy(array('countryCode' => $row[2]));
if ($country === NULL) {
throw new CountryCodeNotFoundException(sprintf("The country with code %s is not found. Aborting to insert postal code with %s - %s",
$row[2], $row[0], $row[1]));
}
// try to find an existing postal code
$existingPC = $em
->getRepository('ChillMainBundle:PostalCode')
->findBy(array('code' => $row[0], 'name' => $row[1]));
if (count($existingPC) > 0) {
throw new ExistingPostalCodeException(sprintf("A postal code with code : %s and name : %s already exists, skipping",
$row[0], $row[1]));
}
$postalCode = (new PostalCode())
->setCode($row[0])
->setName($row[1])
->setCountry($country)
;
$errors = $this->getContainer()->get('validator')->validate($postalCode);
if ($errors->count() == 0) {
$em->persist($postalCode);
} else {
$msg = "";
foreach ($errors as $error) {
$msg .= " ".$error->getMessage();
}
throw new PostalCodeNotValidException($msg);
}
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
$output->writeln(sprintf('Creating postal code with code: %s, name: %s, countryCode: %s',
$postalCode->getCode(), $postalCode->getName(), $postalCode->getCountry()->getCountryCode()));
}
}
}
class ExistingPostalCodeException extends \Exception
{
}
class CountryCodeNotFoundException extends \Exception
{
}
class PostalCodeNotValidException extends \Exception
{
}

View File

@ -0,0 +1,80 @@
<?php
/*
* Chill is a software for social workers
* Copyright (C) 2014 Champs-Libres Coopérative <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\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Chill\MainBundle\Entity\User;
/**
* Description of SetPasswordCommand
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class SetPasswordCommand extends ContainerAwareCommand
{
public function configure()
{
$this->setName('chill:user:set_password')
->setDescription('set a password to user')
->addArgument('username', InputArgument::REQUIRED, 'the user\'s '
. 'username you want to change password')
->addArgument('password', InputArgument::OPTIONAL, 'the new password')
;
}
public function execute(InputInterface $input, OutputInterface $output)
{
$user = $this->_getUser($input->getArgument('username'));
if ($user === NULL) {
throw new \LogicException("The user with username '".
$input->getArgument('username')."' is not found");
}
$password = $input->getArgument('password');
if ($password === NULL) {
$dialog = $this->getHelperSet()->get('dialog');
$password = $dialog->askHiddenResponse($output, "<question>the new password :"
. "</question>");
}
$this->_setPassword($user, $password);
}
public function _getUser($username)
{
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
return $em->getRepository('ChillMainBundle:User')
->findOneBy(array('username' => $username));
}
public function _setPassword(User $user, $password)
{
$encoder = $this->getContainer()->get('security.encoder_factory')
->getEncoder($user);
$user->setPassword($encoder->encodePassword($password, $user->getSalt()));
$this->getContainer()->get('doctrine.orm.entity_manager')
->flush($user);
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2014, 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;
/**
*
*
* @author julien.fastre@champs-libres.coop
* @author marc@champs-libres.coop
*/
class AdminController extends Controller {
public function indexAction($menu = 'admin',
$header_title = 'views.Main.admin.index.header_title',
$page_title = 'views.Main.admin.index.page_title') {
return $this->render('ChillMainBundle:Admin:layout.html.twig');
}
public function indexPermissionsAction()
{
return $this->render('ChillMainBundle:Admin:layout_permissions.html.twig');
}
public function configurationWarningsAction()
{
$alertManager = $this->get('chill_main.configuration_alert_manager');
}
}

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

@ -0,0 +1,178 @@
<?php
namespace Chill\MainBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Form\CenterType;
/**
* Center controller.
*
*/
class CenterController extends Controller
{
/**
* Lists all Center entities.
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('ChillMainBundle:Center')->findAll();
return $this->render('ChillMainBundle:Center:index.html.twig', array(
'entities' => $entities,
));
}
/**
* Creates a new Center entity.
*
*/
public function createAction(Request $request)
{
$center = new Center();
$form = $this->createCreateForm($center);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($center);
$em->flush();
return $this->redirect($this->generateUrl('admin_center_show', array('id' => $center->getId())));
}
return $this->render('ChillMainBundle:Center:new.html.twig', array(
'entity' => $center,
'form' => $form->createView(),
));
}
/**
* Creates a form to create a Center entity.
*
* @param Center $center The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Center $center)
{
$form = $this->createForm(CenterType::class, $center, array(
'action' => $this->generateUrl('admin_center_create'),
'method' => 'POST',
));
$form->add('submit', SubmitType::class, array('label' => 'Create'));
return $form;
}
/**
* Displays a form to create a new Center entity.
*
*/
public function newAction()
{
$center = new Center();
$form = $this->createCreateForm($center);
return $this->render('ChillMainBundle:Center:new.html.twig', array(
'entity' => $center,
'form' => $form->createView(),
));
}
/**
* Finds and displays a Center entity.
*
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$center = $em->getRepository('ChillMainBundle:Center')->find($id);
if (!$center) {
throw $this->createNotFoundException('Unable to find Center entity.');
}
return $this->render('ChillMainBundle:Center:show.html.twig', array(
'entity' => $center
));
}
/**
* Displays a form to edit an existing Center entity.
*
*/
public function editAction($id)
{
$em = $this->getDoctrine()->getManager();
$center = $em->getRepository('ChillMainBundle:Center')->find($id);
if (!$center) {
throw $this->createNotFoundException('Unable to find Center entity.');
}
$editForm = $this->createEditForm($center);
return $this->render('ChillMainBundle:Center:edit.html.twig', array(
'entity' => $center,
'edit_form' => $editForm->createView()
));
}
/**
* Creates a form to edit a Center entity.
*
* @param Center $center The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(Center $center)
{
$form = $this->createForm(CenterType::class, $center, array(
'action' => $this->generateUrl('admin_center_update', array('id' => $center->getId())),
'method' => 'PUT',
));
$form->add('submit', SubmitType::class, array('label' => 'Update'));
return $form;
}
/**
* Edits an existing Center entity.
*
*/
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$center = $em->getRepository('ChillMainBundle:Center')->find($id);
if (!$center) {
throw $this->createNotFoundException('Unable to find Center entity.');
}
$editForm = $this->createEditForm($center);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('admin_center_edit', array('id' => $id)));
}
return $this->render('ChillMainBundle:Center:edit.html.twig', array(
'entity' => $center,
'edit_form' => $editForm->createView()
));
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace Chill\MainBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
if ($this->isGranted('ROLE_ADMIN')) {
return $this->redirectToRoute('chill_main_admin_central', [], 302);
}
return $this->render('ChillMainBundle::layout.html.twig');
}
public function indexWithoutLocaleAction()
{
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

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

View File

@ -0,0 +1,43 @@
<?php
namespace Chill\MainBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class LoginController extends Controller
{
/**
*
* @var AuthenticationUtils
*/
protected $helper;
public function __construct(AuthenticationUtils $helper)
{
$this->helper = $helper;
}
/**
* Show a login form
*
* @param Request $request
* @return Response
*/
public function loginAction(Request $request)
{
return $this->render('ChillMainBundle:Login:login.html.twig', array(
'last_username' => $this->helper->getLastUsername(),
'error' => $this->helper->getLastAuthenticationError()
));
}
public function LoginCheckAction(Request $request)
{
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Chill\MainBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MenuController extends Controller
{
public function writeMenuAction($menu, $layout, $activeRouteKey = null, array $args = array() )
{
return $this->render($layout, array(
'menu_composer' => $this->get('chill.main.menu_composer'),
'menu' => $menu,
'args' => $args,
'activeRouteKey' => $activeRouteKey
));
}
}

View File

@ -0,0 +1,311 @@
<?php
namespace Chill\MainBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\MainBundle\Form\UserPasswordType;
use Chill\MainBundle\Entity\User;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Chill\MainBundle\Security\PasswordRecover\RecoverPasswordHelper;
use Symfony\Component\HttpFoundation\Response;
use Chill\MainBundle\Security\PasswordRecover\TokenManager;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Chill\MainBundle\Security\PasswordRecover\PasswordRecoverEvent;
use Chill\MainBundle\Security\PasswordRecover\PasswordRecoverVoter;
class PasswordController extends Controller
{
/**
*
* @var UserPasswordEncoderInterface
*/
protected $passwordEncoder;
/**
*
* @var TranslatorInterface
*/
protected $translator;
/**
*
* @var LoggerInterface
*/
protected $chillLogger;
/**
*
* @var RecoverPasswordHelper
*/
protected $recoverPasswordHelper;
/**
*
* @var TokenManager
*/
protected $tokenManager;
/**
*
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
public function __construct(
LoggerInterface $chillLogger,
UserPasswordEncoderInterface $passwordEncoder,
RecoverPasswordHelper $recoverPasswordHelper,
TokenManager $tokenManager,
TranslatorInterface $translator,
EventDispatcherInterface $eventDispatcher
) {
$this->chillLogger = $chillLogger;
$this->passwordEncoder = $passwordEncoder;
$this->translator = $translator;
$this->tokenManager = $tokenManager;
$this->recoverPasswordHelper = $recoverPasswordHelper;
$this->eventDispatcher = $eventDispatcher;
}
/**
*
* @param Request $request
* @return Response
*/
public function UserPasswordAction(Request $request)
{
// get authentified user
$user = $this->getUser();
// create a form for password_encoder
$form = $this->passwordForm($user);
// process the form
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$password = $form->get('new_password')->getData();
// logging for prod
$this
->chillLogger
->notice(
'update password for an user',
array(
'method' => $request->getMethod(),
'user' => $user->getUsername()
)
);
$user->setPassword($this->passwordEncoder->encodePassword($user, $password));
$em = $this->getDoctrine()->getManager();
$em->flush();
$this->addFlash('success', $this->translator->trans('Password successfully updated!'));
return $this->redirectToRoute('change_my_password');
}
// render into a template
return $this->render('ChillMainBundle:Password:password.html.twig', array(
'form' => $form->createView()
));
}
/**
*
*
* @param User $user
* @return \Symfony\Component\Form\Form
*/
private function passwordForm(User $user)
{
return $this
->createForm(
UserPasswordType::class,
[],
[ 'user' => $user ]
)
->add('submit', SubmitType::class, array('label' => 'Change password'))
;
}
public function recoverAction(Request $request)
{
if (FALSE === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) {
return (new Response($this->translator->trans("You are not allowed "
. "to try to recover password, due to mitigating possible "
. "attack. Try to contact your system administrator"), Response::HTTP_FORBIDDEN));
}
$query = $request->query;
$username = $query->get(TokenManager::USERNAME_CANONICAL);
$hash = $query->getAlnum(TokenManager::HASH);
$token = $query->getAlnum(TokenManager::TOKEN);
$timestamp = $query->getInt(TokenManager::TIMESTAMP);
$user = $this->getDoctrine()->getRepository(User::class)
->findOneByUsernameCanonical($username);
if (NULL === $user) {
$this->eventDispatcher->dispatch(PasswordRecoverEvent::INVALID_TOKEN,
new PasswordRecoverEvent($token, null, $request->getClientIp()));
throw $this->createNotFoundException(sprintf('User %s not found', $username));
}
if (TRUE !== $this->tokenManager->verify($hash, $token, $user, $timestamp)) {
$this->eventDispatcher->dispatch(PasswordRecoverEvent::INVALID_TOKEN,
new PasswordRecoverEvent($token, $user, $request->getClientIp()));
return new Response("Invalid token", Response::HTTP_FORBIDDEN);
}
$form = $this->passwordForm($user);
$form->remove('actual_password');
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$password = $form->get('new_password')->getData();
$user->setPassword($this->passwordEncoder->encodePassword($user, $password));
// logging for prod
$this
->chillLogger
->notice(
'setting new password for user',
array(
'user' => $user->getUsername()
)
);
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('password_request_recover_changed');
}
return $this->render('@ChillMain/Password/recover_password_form.html.twig', [
'form' => $form->createView()
]);
}
public function changeConfirmedAction()
{
return $this->render('@ChillMain/Password/recover_password_changed.html.twig');
}
public function requestRecoverAction(Request $request)
{
if (FALSE === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) {
return (new Response($this->translator->trans("You are not allowed "
. "to try to recover password, due to mitigating possible "
. "attack. Try to contact your system administrator"), Response::HTTP_FORBIDDEN));
}
$form = $this->requestRecoverForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/* @var $qb \Doctrine\ORM\QueryBuilder */
$qb = $this->getDoctrine()->getManager()
->createQueryBuilder();
$qb->select('u')
->from(User::class, 'u')
->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))'))
->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))' ))
->setParameter('pattern', $form->get('username_or_email')->getData())
;
$user = $qb->getQuery()->getSingleResult();
if (empty($user->getEmail())) {
$this->addFlash('error', $this->translator->trans('This account does not have an email address. '
. 'Please ask your administrator to renew your password.'));
} else {
if (FALSE === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN, $user)) {
return (new Response($this->translator->trans("You are not allowed "
. "to try to recover password, due to mitigating possible "
. "attack. Try to contact your system administrator"), Response::HTTP_FORBIDDEN));
}
$this->recoverPasswordHelper->sendRecoverEmail($user,
(new \DateTimeImmutable('now'))->add(new \DateInterval('PT30M')));
// logging for prod
$this
->chillLogger
->notice(
'Sending an email for password recovering',
array(
'user' => $user->getUsername()
)
);
$this->eventDispatcher->dispatch(
PasswordRecoverEvent::ASK_TOKEN_SUCCESS,
new PasswordRecoverEvent(null, $user, $request->getClientIp())
);
return $this->redirectToRoute('password_request_recover_confirm');
}
} elseif ($form->isSubmitted() && FALSE === $form->isValid()) {
$this->eventDispatcher->dispatch(
PasswordRecoverEvent::ASK_TOKEN_INVALID_FORM,
new PasswordRecoverEvent(null, null, $request->getClientIp())
);
}
return $this->render('@ChillMain/Password/request_recover_password.html.twig', [
'form' => $form->createView()
]);
}
public function requestRecoverConfirmAction()
{
return $this->render('@ChillMain/Password/request_recover_password_confirm.html.twig');
}
protected function requestRecoverForm()
{
$builder = $this->createFormBuilder();
$builder
->add('username_or_email', TextType::class, [
'label' => 'Username or email',
'constraints' => [
new Callback([
'callback' => function($pattern, ExecutionContextInterface $context, $payload) {
$qb = $this->getDoctrine()->getManager()
->createQueryBuilder();
$qb->select('COUNT(u)')
->from(User::class, 'u')
->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))'))
->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))' ))
->setParameter('pattern', $pattern)
;
if ((int) $qb->getQuery()->getSingleScalarResult() !== 1) {
$context->addViolation('This username or email does not exists');
}
}
])
]
])
->add('submit', SubmitType::class, [
'label' => 'Request recover'
]);
return $builder->getForm();
}
}

View File

@ -0,0 +1,509 @@
<?php
namespace Chill\MainBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\MainBundle\Entity\RoleScope;
use Chill\MainBundle\Entity\PermissionsGroup;
use Chill\MainBundle\Form\PermissionsGroupType;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Role\RoleInterface;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Form\Type\ComposedRoleScopeType;
/**
* PermissionsGroup controller.
*
*/
class PermissionsGroupController extends Controller
{
/**
* Lists all PermissionsGroup entities.
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('ChillMainBundle:PermissionsGroup')->findAll();
return $this->render('ChillMainBundle:PermissionsGroup:index.html.twig', array(
'entities' => $entities,
));
}
/**
* Creates a new PermissionsGroup entity.
*
*/
public function createAction(Request $request)
{
$permissionsGroup = new PermissionsGroup();
$form = $this->createCreateForm($permissionsGroup);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($permissionsGroup);
$em->flush();
return $this->redirect($this->generateUrl('admin_permissionsgroup_edit',
array('id' => $permissionsGroup->getId())));
}
return $this->render('ChillMainBundle:PermissionsGroup:new.html.twig', array(
'entity' => $permissionsGroup,
'form' => $form->createView(),
));
}
/**
* Creates a form to create a PermissionsGroup entity.
*
* @param PermissionsGroup $permissionsGroup The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(PermissionsGroup $permissionsGroup)
{
$form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, array(
'action' => $this->generateUrl('admin_permissionsgroup_create'),
'method' => 'POST',
));
$form->add('submit', SubmitType::class, array('label' => 'Create'));
return $form;
}
/**
* Displays a form to create a new PermissionsGroup entity.
*
*/
public function newAction()
{
$permissionsGroup = new PermissionsGroup();
$form = $this->createCreateForm($permissionsGroup);
return $this->render('ChillMainBundle:PermissionsGroup:new.html.twig', array(
'entity' => $permissionsGroup,
'form' => $form->createView(),
));
}
/**
* Finds and displays a PermissionsGroup entity.
*
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($id);
if (!$permissionsGroup) {
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
}
$translatableStringHelper = $this->get('chill.main.helper.translatable_string');
$roleScopes = $permissionsGroup->getRoleScopes()->toArray();
// sort $roleScopes by name
usort($roleScopes,
function(RoleScope $a, RoleScope $b) use ($translatableStringHelper) {
if ($a->getScope() === NULL) {
return 1;
}
if ($b->getScope() === NULL) {
return +1;
}
return strcmp(
$translatableStringHelper->localize($a->getScope()->getName()),
$translatableStringHelper->localize($b->getScope()->getName())
);
});
// sort role scope by title
/* @var $roleProvider \Chill\MainBundle\Security\RoleProvider */
$roleProvider = $this->get('chill.main.role_provider');
$roleScopesSorted = array();
foreach($roleScopes as $roleScope) {
/* @var $roleScope RoleScope */
$title = $roleProvider->getRoleTitle($roleScope->getRole());
$roleScopesSorted[$title][] = $roleScope;
}
ksort($roleScopesSorted);
return $this->render('ChillMainBundle:PermissionsGroup:show.html.twig', array(
'entity' => $permissionsGroup,
'role_scopes_sorted' => $roleScopesSorted,
'expanded_roles' => $this->getExpandedRoles($roleScopes)
));
}
/**
* expand roleScopes to be easily shown in template
*
* @param array $roleScopes
* @return array
*/
private function getExpandedRoles(array $roleScopes)
{
$expandedRoles = array();
foreach ($roleScopes as $roleScope) {
if (!array_key_exists($roleScope->getRole(), $expandedRoles)) {
$expandedRoles[$roleScope->getRole()] =
array_map(
function(RoleInterface $role) {
return $role->getRole();
},
$this->get('security.role_hierarchy')
->getReachableRoles(
array(new Role($roleScope->getRole()))
)
);
}
}
return $expandedRoles;
}
/**
* Displays a form to edit an existing PermissionsGroup entity.
*
*/
public function editAction($id)
{
$em = $this->getDoctrine()->getManager();
$permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($id);
if (!$permissionsGroup) {
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
}
// create all the forms
$editForm = $this->createEditForm($permissionsGroup);
$deleteRoleScopesForm = array();
foreach ($permissionsGroup->getRoleScopes() as $roleScope) {
$deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm(
$permissionsGroup, $roleScope);
}
$addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup);
// sort role scope by title
/* @var $roleProvider \Chill\MainBundle\Security\RoleProvider */
$roleProvider = $this->get('chill.main.role_provider');
$roleScopesSorted = array();
foreach($permissionsGroup->getRoleScopes()->toArray() as $roleScope) {
/* @var $roleScope RoleScope */
$title = $roleProvider->getRoleTitle($roleScope->getRole());
$roleScopesSorted[$title][] = $roleScope;
}
ksort($roleScopesSorted);
return $this->render('ChillMainBundle:PermissionsGroup:edit.html.twig', array(
'entity' => $permissionsGroup,
'role_scopes_sorted' => $roleScopesSorted,
'edit_form' => $editForm->createView(),
'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()),
'delete_role_scopes_form' => array_map( function($form) {
return $form->createView();
}, $deleteRoleScopesForm),
'add_role_scopes_form' => $addRoleScopesForm->createView()
));
}
/**
* Creates a form to edit a PermissionsGroup entity.
*
* @param PermissionsGroup $permissionsGroup The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(PermissionsGroup $permissionsGroup)
{
$form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, array(
'action' => $this->generateUrl('admin_permissionsgroup_update', array('id' => $permissionsGroup->getId())),
'method' => 'PUT',
));
$form->add('submit', SubmitType::class, array('label' => 'Update'));
return $form;
}
/**
* Edits an existing PermissionsGroup entity.
*
*/
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$permissionsGroup = $em
->getRepository('ChillMainBundle:PermissionsGroup')
->find($id);
if (!$permissionsGroup) {
throw $this->createNotFoundException('Unable to find Permissions'
. 'Group entity.');
}
$editForm = $this->createEditForm($permissionsGroup);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', array('id' => $id)));
}
$deleteRoleScopesForm = array();
foreach ($permissionsGroup->getRoleScopes() as $roleScope) {
$deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm(
$permissionsGroup, $roleScope);
}
$addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup);
// sort role scope by title
/* @var $roleProvider \Chill\MainBundle\Security\RoleProvider */
$roleProvider = $this->get('chill.main.role_provider');
$roleScopesSorted = array();
foreach($permissionsGroup->getRoleScopes()->toArray() as $roleScope) {
/* @var $roleScope RoleScope */
$title = $roleProvider->getRoleTitle($roleScope->getRole());
$roleScopesSorted[$title][] = $roleScope;
}
ksort($roleScopesSorted);
return $this->render('ChillMainBundle:PermissionsGroup:edit.html.twig', array(
'entity' => $permissionsGroup,
'role_scopes_sorted' => $roleScopesSorted,
'edit_form' => $editForm->createView(),
'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()),
'delete_role_scopes_form' => array_map( function($form) {
return $form->createView();
}, $deleteRoleScopesForm),
'add_role_scopes_form' => $addRoleScopesForm->createView()
));
}
/**
* get a role scope by his parameters. The role scope is persisted if it
* doesn't exists in database.
*
* @param Scope $scope
* @param string $role
* @return RoleScope
*/
protected function getPersistentRoleScopeBy($role, Scope $scope = null)
{
$em = $this->getDoctrine()->getManager();
$roleScope = $em->getRepository('ChillMainBundle:RoleScope')
->findOneBy(array('role' => $role, 'scope' => $scope));
if ($roleScope === NULL) {
$roleScope = (new RoleScope())
->setRole($role)
->setScope($scope)
;
$em->persist($roleScope);
}
return $roleScope;
}
/**
* remove an association between permissionsGroup and roleScope
*
* @param int $pgid permissionsGroup id
* @param int $rsid roleScope id
* @return redirection to edit form
*/
public function deleteLinkRoleScopeAction($pgid, $rsid)
{
$em = $this->getDoctrine()->getManager();
$permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($pgid);
$roleScope = $em->getRepository('ChillMainBundle:RoleScope')->find($rsid);
if (!$permissionsGroup) {
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
}
if (!$roleScope) {
throw $this->createNotFoundException('Unable to find RoleScope entity');
}
try {
$permissionsGroup->removeRoleScope($roleScope);
} catch (\RuntimeException $ex) {
$this->addFlash('notice',
$this->get('translator')->trans("The role '%role%' and circle "
. "'%scope%' is not associated with this permission group", array(
'%role%' => $this->get('translator')->trans($roleScope->getRole()),
'%scope%' => $this->get('chill.main.helper.translatable_string')
->localize($roleScope->getScope()->getName())
)));
return $this->redirect($this->generateUrl('admin_permissionsgroup_edit',
array('id' => $pgid)));
}
$em->flush();
if ($roleScope->getScope() !== NULL ) {
$this->addFlash('notice',
$this->get('translator')->trans("The role '%role%' on circle "
. "'%scope%' has been removed", array(
'%role%' => $this->get('translator')->trans($roleScope->getRole()),
'%scope%' => $this->get('chill.main.helper.translatable_string')
->localize($roleScope->getScope()->getName())
)));
} else {
$this->addFlash('notice',
$this->get('translator')->trans("The role '%role%' has been removed", array(
'%role%' => $this->get('translator')->trans($roleScope->getRole())
)));
}
return $this->redirect($this->generateUrl('admin_permissionsgroup_edit',
array('id' => $pgid)));
}
/**
*
* @param Request $request
* @param int $id
* @return Respon
* @throws type
*/
public function addLinkRoleScopeAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($id);
if (!$permissionsGroup) {
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
}
$form = $this->createAddRoleScopeForm($permissionsGroup);
$form->handleRequest($request);
if ($form->isValid()) {
$roleScope = $this->getPersistentRoleScopeBy(
$form['composed_role_scope']->getData()->getRole(),
$form['composed_role_scope']->getData()->getScope()
);
$permissionsGroup->addRoleScope($roleScope);
$violations = $this->get('validator')->validate($permissionsGroup);
if ($violations->count() === 0) {
$em->flush();
$this->addFlash('notice',
$this->get('translator')->trans("The permissions have been added"));
return $this->redirect($this->generateUrl('admin_permissionsgroup_edit',
array('id' => $id)));
} else {
foreach($violations as $error) {
$this->addFlash('error', $error->getMessage());
}
}
} else {
foreach ($form->getErrors() as $error) {
$this->addFlash('error', $error->getMessage());
}
}
$editForm = $this->createEditForm($permissionsGroup);
$deleteRoleScopesForm = array();
foreach ($permissionsGroup->getRoleScopes() as $roleScope) {
$deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm(
$permissionsGroup, $roleScope);
}
$addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup);
// sort role scope by title
/* @var $roleProvider \Chill\MainBundle\Security\RoleProvider */
$roleProvider = $this->get('chill.main.role_provider');
$roleScopesSorted = array();
foreach($permissionsGroup->getRoleScopes()->toArray() as $roleScope) {
/* @var $roleScope RoleScope */
$title = $roleProvider->getRoleTitle($roleScope->getRole());
$roleScopesSorted[$title][] = $roleScope;
}
ksort($roleScopesSorted);
return $this->render('ChillMainBundle:PermissionsGroup:edit.html.twig', array(
'entity' => $permissionsGroup,
'edit_form' => $editForm->createView(),
'role_scopes_sorted' => $roleScopesSorted,
'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()),
'delete_role_scopes_form' => array_map( function($form) {
return $form->createView();
}, $deleteRoleScopesForm),
'add_role_scopes_form' => $addRoleScopesForm->createView()
));
}
/**
* Creates a form to delete a link to roleScope.
*
* @param mixed $permissionsGroup The entity id
*
* @return \Symfony\Component\Form\Form The form
*/
private function createDeleteRoleScopeForm(PermissionsGroup $permissionsGroup,
RoleScope $roleScope)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('admin_permissionsgroup_delete_role_scope',
array('pgid' => $permissionsGroup->getId(), 'rsid' => $roleScope->getId())))
->setMethod('DELETE')
->add('submit', SubmitType::class, array('label' => 'Delete'))
->getForm()
;
}
/**
* creates a form to add a role scope to permissionsgroup
*
* @param PermissionsGroup $permissionsGroup
* @return \Symfony\Component\Form\Form The form
*/
private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('admin_permissionsgroup_add_role_scope',
array('id' => $permissionsGroup->getId())))
->setMethod('PUT')
->add('composed_role_scope', ComposedRoleScopeType::class)
->add('submit', SubmitType::class, array('label' => 'Add permission'))
->getForm()
;
}
}

View 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 ] ]);
}
}

View File

@ -0,0 +1,179 @@
<?php
namespace Chill\MainBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Form\ScopeType;
/**
* Scope controller.
*
*/
class ScopeController extends Controller
{
/**
* Lists all Scope entities.
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('ChillMainBundle:Scope')->findAll();
return $this->render('ChillMainBundle:Scope:index.html.twig', array(
'entities' => $entities,
));
}
/**
* Creates a new Scope entity.
*
*/
public function createAction(Request $request)
{
$scope = new Scope();
$form = $this->createCreateForm($scope);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($scope);
$em->flush();
return $this->redirect($this->generateUrl('admin_scope_show', array('id' => $scope->getId())));
}
return $this->render('ChillMainBundle:Scope:new.html.twig', array(
'entity' => $scope,
'form' => $form->createView(),
));
}
/**
* Creates a form to create a Scope entity.
*
* @param Scope $scope The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Scope $scope)
{
$form = $this->createForm(ScopeType::class, $scope, array(
'action' => $this->generateUrl('admin_scope_create'),
'method' => 'POST',
));
$form->add('submit', SubmitType::class, array('label' => 'Create'));
return $form;
}
/**
* Displays a form to create a new Scope entity.
*
*/
public function newAction()
{
$scope = new Scope();
$form = $this->createCreateForm($scope);
return $this->render('ChillMainBundle:Scope:new.html.twig', array(
'entity' => $scope,
'form' => $form->createView(),
));
}
/**
* Finds and displays a Scope entity.
*
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$scope = $em->getRepository('ChillMainBundle:Scope')->find($id);
if (!$scope) {
throw $this->createNotFoundException('Unable to find Scope entity.');
}
return $this->render('ChillMainBundle:Scope:show.html.twig', array(
'entity' => $scope
));
}
/**
* Displays a form to edit an existing Scope entity.
*
*/
public function editAction($id)
{
$em = $this->getDoctrine()->getManager();
$scope = $em->getRepository('ChillMainBundle:Scope')->find($id);
if (!$scope) {
throw $this->createNotFoundException('Unable to find Scope entity.');
}
$editForm = $this->createEditForm($scope);
return $this->render('ChillMainBundle:Scope:edit.html.twig', array(
'entity' => $scope,
'edit_form' => $editForm->createView(),
));
}
/**
* Creates a form to edit a Scope entity.
*
* @param Scope $scope The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(Scope $scope)
{
$form = $this->createForm(ScopeType::class, $scope, array(
'action' => $this->generateUrl('admin_scope_update', array('id' => $scope->getId())),
'method' => 'PUT',
));
$form->add('submit', SubmitType::class, array('label' => 'Update'));
return $form;
}
/**
* Edits an existing Scope entity.
*
*/
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$scope = $em->getRepository('ChillMainBundle:Scope')->find($id);
if (!$scope) {
throw $this->createNotFoundException('Unable to find Scope entity.');
}
$editForm = $this->createEditForm($scope);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('admin_scope_edit', array('id' => $id)));
}
return $this->render('ChillMainBundle:Scope:edit.html.twig', array(
'entity' => $scope,
'edit_form' => $editForm->createView()
));
}
}

View File

@ -0,0 +1,215 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2014, 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 Chill\MainBundle\Search\UnknowSearchDomainException;
use Chill\MainBundle\Search\UnknowSearchNameException;
use Chill\MainBundle\Search\ParsingException;
use Chill\MainBundle\Search\SearchInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
*
*
* @author julien.fastre@champs-libres.coop
* @author marc@champs-libres.coop
*/
class SearchController extends Controller
{
public function searchAction(Request $request, $_format)
{
$pattern = $request->query->get('q', '');
if ($pattern === ''){
switch($_format) {
case 'html':
return $this->render('ChillMainBundle:Search:error.html.twig',
array(
'message' => $this->get('translator')->trans("Your search is empty. "
. "Please provide search terms."),
'pattern' => $pattern
));
case 'json':
return new JsonResponse([
'results' => [],
'pagination' => [ 'more' => false ]
]);
}
}
$name = $request->query->get('name', NULL);
try {
if ($name === NULL) {
if ($_format === 'json') {
return new JsonResponse('Currently, we still do not aggregate results '
. 'from different providers', JsonResponse::HTTP_BAD_REQUEST);
}
// no specific search selected. Rendering result in "preview" mode
$results = $this->get('chill.main.search_provider')
->getSearchResults(
$pattern,
0,
5,
array(SearchInterface::SEARCH_PREVIEW_OPTION => true)
);
} else {
// we want search on a specific search provider. Display full results.
// create a paginator to get the startPage and stopPage
/* @var $paginatorFactory \Chill\MainBundle\Pagination\PaginatorFactory */
$paginatorFactory = $this->get('chill_main.paginator_factory');
$results = [$this->get('chill.main.search_provider')
->getResultByName(
$pattern,
$name,
$paginatorFactory->getCurrentPageFirstItemNumber(),
$paginatorFactory->getCurrentItemsPerPage(),
array(
SearchInterface::SEARCH_PREVIEW_OPTION => false,
SearchInterface::REQUEST_QUERY_PARAMETERS => $request
->get(SearchInterface::REQUEST_QUERY_KEY_ADD_PARAMETERS, [])
),
$_format
)];
if ($_format === 'json') {
return new JsonResponse(\reset($results));
}
}
} catch (UnknowSearchDomainException $ex) {
return $this->render('ChillMainBundle:Search:error.html.twig',
array(
"message" => $this->get('translator')->trans("The domain %domain% "
. "is unknow. Please check your search.", array('%domain%' => $ex->getDomain())),
'pattern' => $pattern
));
} catch (UnknowSearchNameException $ex) {
throw $this->createNotFoundException("The name ".$ex->getName()." is not found");
} catch (ParsingException $ex) {
return $this->render('ChillMainBundle:Search:error.html.twig',
array(
"message" => $this->get('translator')->trans('Invalid terms').
": ".$this->get('translator')->trans($ex->getMessage()),
'pattern' => $pattern
));
}
return $this->render('ChillMainBundle:Search:list.html.twig',
array('results' => $results, 'pattern' => $pattern)
);
}
public function advancedSearchListAction(Request $request)
{
/* @var $variable Chill\MainBundle\Search\SearchProvider */
$searchProvider = $this->get('chill.main.search_provider');
$advancedSearchProviders = $searchProvider
->getHasAdvancedFormSearchServices();
if(\count($advancedSearchProviders) === 1) {
\reset($advancedSearchProviders);
return $this->redirectToRoute('chill_main_advanced_search', [
'name' => \key($advancedSearchProviders)
]);
}
return $this->render('ChillMainBundle:Search:choose_list.html.twig');
}
public function advancedSearchAction($name, Request $request)
{
try {
/* @var $variable Chill\MainBundle\Search\SearchProvider */
$searchProvider = $this->get('chill.main.search_provider');
/* @var $variable Chill\MainBundle\Search\HasAdvancedSearchFormInterface */
$search = $this->get('chill.main.search_provider')
->getHasAdvancedFormByName($name);
} catch (\Chill\MainBundle\Search\UnknowSearchNameException $e) {
throw $this->createNotFoundException("no advanced search for "
. "$name");
}
if ($request->query->has('q')) {
$data = $search->convertTermsToFormData($searchProvider->parse(
$request->query->get('q')));
}
$form = $this->createAdvancedSearchForm($name, $data ?? []);
if ($request->isMethod(Request::METHOD_POST)) {
$form->handleRequest($request);
if ($form->isValid()) {
$pattern = $this->get('chill.main.search_provider')
->getHasAdvancedFormByName($name)
->convertFormDataToQuery($form->getData());
return $this->redirectToRoute('chill_main_search', [
'q' => $pattern, 'name' => $name
]);
}
}
return $this->render('ChillMainBundle:Search:advanced_search.html.twig',
[
'form' => $form->createView(),
'name' => $name,
'title' => $search->getAdvancedSearchTitle()
]);
}
protected function createAdvancedSearchForm($name, array $data = [])
{
$builder = $this
->get('form.factory')
->createNamedBuilder(
null,
FormType::class,
$data,
[ 'method' => Request::METHOD_POST ]
);
$this->get('chill.main.search_provider')
->getHasAdvancedFormByName($name)
->buildForm($builder)
;
$builder->add('submit', SubmitType::class, [
'label' => 'Search'
]);
return $builder->getForm();
}
}

View 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
]);
}
}

View File

@ -0,0 +1,435 @@
<?php
namespace Chill\MainBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\UserType;
use Chill\MainBundle\Entity\GroupCenter;
use Chill\MainBundle\Form\Type\ComposedGroupCenterType;
use Chill\MainBundle\Form\UserPasswordType;
/**
* User controller.
*
*/
class UserController extends Controller
{
const FORM_GROUP_CENTER_COMPOSED = 'composed_groupcenter';
/**
* Lists all User entities.
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->createQuery('SELECT u FROM ChillMainBundle:User u '
. 'ORDER BY u.username')
->getResult();
return $this->render('ChillMainBundle:User:index.html.twig', array(
'entities' => $entities,
));
}
/**
* Creates a new User entity.
*
*/
public function createAction(Request $request)
{
$user = new User();
$form = $this->createCreateForm($user);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$user->setPassword($this->get('security.password_encoder')
->encodePassword($user, $form['plainPassword']->getData()));
$em->persist($user);
$em->flush();
return $this->redirect($this->generateUrl('admin_user_show', array('id' => $user->getId())));
}
return $this->render('ChillMainBundle:User:new.html.twig', array(
'entity' => $user,
'form' => $form->createView(),
));
}
/**
* Creates a form to create a User entity.
*
* @param User $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(User $entity)
{
$form = $this->createForm(UserType::class, $entity, array(
'action' => $this->generateUrl('admin_user_create'),
'method' => 'POST',
'is_creation' => true
));
$form->add('submit', SubmitType::class, array('label' => 'Create'));
return $form;
}
/**
* Displays a form to create a new User entity.
*
*/
public function newAction()
{
$user = new User();
$form = $this->createCreateForm($user);
return $this->render('ChillMainBundle:User:new.html.twig', array(
'entity' => $user,
'form' => $form->createView(),
));
}
/**
* Finds and displays a User entity.
*
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('ChillMainBundle:User')->find($id);
if (!$user) {
throw $this->createNotFoundException('Unable to find User entity.');
}
return $this->render('ChillMainBundle:User:show.html.twig', array(
'entity' => $user,
));
}
/**
* Displays a form to edit an existing User entity.
*
*/
public function editAction($id)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('ChillMainBundle:User')->find($id);
if (!$user) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$editForm = $this->createEditForm($user);
return $this->render('ChillMainBundle:User:edit.html.twig', array(
'entity' => $user,
'edit_form' => $editForm->createView(),
'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user)->createView(),
'delete_groupcenter_form' => array_map(
function(\Symfony\Component\Form\Form $form) {
return $form->createView();
},
iterator_to_array($this->getDeleteLinkGroupCenterByUser($user), true))
));
}
/**
* Displays a form to edit the user password.
*
*/
public function editPasswordAction($id)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('ChillMainBundle:User')->find($id);
if (!$user) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$editForm = $this->createEditPasswordForm($user);
return $this->render('ChillMainBundle:User:edit_password.html.twig', array(
'entity' => $user,
'edit_form' => $editForm->createView()
));
}
/**
*
*
* @param User $user
* @return \Symfony\Component\Form\Form
*/
private function createEditPasswordForm(User $user)
{
return $this->createForm(UserPasswordType::class, null, array(
'action' =>
$this->generateUrl('admin_user_update_password', array('id' => $user->getId())),
'method' => 'PUT',
'user' => $user
))
->add('submit', SubmitType::class, array('label' => 'Change password'))
->remove('actual_password')
;
}
public function deleteLinkGroupCenterAction($uid, $gcid)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('ChillMainBundle:User')->find($uid);
if (!$user) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$groupCenter = $em->getRepository('ChillMainBundle:GroupCenter')
->find($gcid);
if (!$groupCenter) {
throw $this->createNotFoundException('Unable to find groupCenter entity');
}
try {
$user->removeGroupCenter($groupCenter);
} catch (\RuntimeException $ex) {
$this->addFlash('error', $this->get('translator')->trans($ex->getMessage()));
return $this->redirect($this->generateUrl('admin_user_edit', array('id' => $uid)));
}
$em->flush();
$this->addFlash('success', $this->get('translator')
->trans('The permissions where removed.'));
return $this->redirect($this->generateUrl('admin_user_edit', array('id' => $uid)));
}
public function addLinkGroupCenterAction(Request $request, $uid)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('ChillMainBundle:User')->find($uid);
if (!$user) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$form = $this->createAddLinkGroupCenterForm($user);
$form->handleRequest($request);
if ($form->isValid()) {
$groupCenter = $this->getPersistedGroupCenter(
$form[self::FORM_GROUP_CENTER_COMPOSED]->getData());
$user->addGroupCenter($groupCenter);
if ($this->get('validator')->validate($user)->count() === 0) {
$em->flush();
$this->addFlash('success', $this->get('translator')->trans('The '
. 'permissions have been successfully added to the user'));
return $this->redirect($this->generateUrl('admin_user_edit',
array('id' => $uid)));
} else {
foreach($this->get('validator')->validate($user) as $error)
$this->addFlash('error', $error->getMessage());
}
}
return $this->render('ChillMainBundle:User:edit.html.twig', array(
'entity' => $user,
'edit_form' => $this->createEditForm($user)->createView(),
'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user)->createView(),
'delete_groupcenter_form' => array_map(
function(\Symfony\Component\Form\Form $form) {
return $form->createView();
},
iterator_to_array($this->getDeleteLinkGroupCenterByUser($user), true))
));
}
private function getPersistedGroupCenter(GroupCenter $groupCenter)
{
$em = $this->getDoctrine()->getManager();
$groupCenterManaged = $em->getRepository('ChillMainBundle:GroupCenter')
->findOneBy(array(
'center' => $groupCenter->getCenter(),
'permissionsGroup' => $groupCenter->getPermissionsGroup()
));
if (!$groupCenterManaged) {
$em->persist($groupCenter);
return $groupCenter;
}
return $groupCenterManaged;
}
/**
* Creates a form to edit a User entity.
*
* @param User $user The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(User $user)
{
$form = $this->createForm(UserType::class, $user, array(
'action' => $this->generateUrl('admin_user_update', array('id' => $user->getId())),
'method' => 'PUT',
));
$form->add('submit', SubmitType::class, array('label' => 'Update'));
return $form;
}
/**
* Edits an existing User entity.
*
*/
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('ChillMainBundle:User')->find($id);
if (!$user) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$editForm = $this->createEditForm($user);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('admin_user_edit', array('id' => $id)));
}
return $this->render('ChillMainBundle:User:edit.html.twig', array(
'entity' => $user,
'edit_form' => $editForm->createView(),
'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user)->createView(),
'delete_groupcenter_form' => array_map(
function(\Symfony\Component\Form\Form $form) {
return $form->createView();
},
iterator_to_array($this->getDeleteLinkGroupCenterByUser($user), true))
));
}
/**
* Edits the user password
*
*/
public function updatePasswordAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('ChillMainBundle:User')->find($id);
if (!$user) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$editForm = $this->createEditPasswordForm($user);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$password = $editForm->get('new_password')->getData();
// logging for prod
$this->get('logger')->info('update password for an user', [
'by' => $this->getUser()->getUsername(),
'user' => $user->getUsername()
]);
$user->setPassword($this->get('security.password_encoder')
->encodePassword($user, $password));
$em->flush();
$this->addFlash('success', $this->get('translator')->trans('Password successfully updated!'));
return $this->redirect($this->generateUrl('admin_user_edit', array('id' => $id)));
}
return $this->render('ChillMainBundle:User:edit_password.html.twig', array(
'entity' => $user,
'edit_form' => $editForm->createView(),
));
}
/**
* Creates a form to delete a link to a GroupCenter
*
* @param mixed $permissionsGroup The entity id
*
* @return \Symfony\Component\Form\Form The form
*/
private function createDeleteLinkGroupCenterForm(User $user, GroupCenter $groupCenter)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('admin_user_delete_group_center',
array('uid' => $user->getId(), 'gcid' => $groupCenter->getId())))
->setMethod('DELETE')
->add('submit', SubmitType::class, array('label' => 'Delete'))
->getForm()
;
}
/**
* create a form to add a link to a groupcenter
*
* @param User $user
* @return \Symfony\Component\Form\Form
*/
private function createAddLinkGroupCenterForm(User $user)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('admin_user_add_group_center',
array('uid' => $user->getId())))
->setMethod('POST')
->add(self::FORM_GROUP_CENTER_COMPOSED, ComposedGroupCenterType::class)
->add('submit', SubmitType::class, array('label' => 'Add a new groupCenter'))
->getForm()
;
}
/**
*
* @param User $user
*/
private function getDeleteLinkGroupCenterByUser(User $user)
{
foreach ($user->getGroupCenters() as $groupCenter) {
yield $groupCenter->getId() => $this
->createDeleteLinkGroupCenterForm($user, $groupCenter);
}
}
}

View File

@ -0,0 +1,68 @@
<?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>
*
* 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\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Chill\MainBundle\Entity\Center;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class LoadCenters extends AbstractFixture implements OrderedFixtureInterface
{
public function getOrder()
{
return 100;
}
public static $centers = array(
array(
'name' => 'Center A',
'ref' => 'centerA'
),
array(
'name' => 'Center B',
'ref' => 'centerB'
)
);
public static $refs = array();
public function load(ObjectManager $manager)
{
foreach (static::$centers as $new) {
$centerA = new Center();
$centerA->setName($new['name']);
$manager->persist($centerA);
$this->addReference($new['ref'], $centerA);
static::$refs[] = $new['ref'];
}
$manager->flush();
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Chill\MainBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Chill\MainBundle\Command\LoadCountriesCommand;
/**
* Load countries into database
*
* @author Julien Fastré <julien arobase fastre point info>
*/
class LoadCountries extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface {
/**
*
* @var ContainerInterface
*/
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function getOrder() {
return 20;
}
public function load(ObjectManager $manager) {
echo "loading countries... \n";
$languages = $this->container->getParameter('chill_main.available_languages');
foreach (LoadCountriesCommand::prepareCountryList($languages) as $country){
$manager->persist($country);
}
$manager->flush();
}
}

View File

@ -0,0 +1,63 @@
<?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>
*
* 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\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Chill\MainBundle\Entity\GroupCenter;
use Chill\MainBundle\DataFixtures\ORM\LoadCenters;
use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class LoadGroupCenters extends AbstractFixture implements OrderedFixtureInterface
{
public function getOrder()
{
return 500;
}
public static $refs = array();
public function load(ObjectManager $manager)
{
foreach (LoadCenters::$refs as $centerRef) {
foreach (LoadPermissionsGroup::$refs as $permissionGroupRef) {
$GroupCenter = new GroupCenter();
$GroupCenter->setCenter($this->getReference($centerRef));
$GroupCenter->setPermissionsGroup($this->getReference($permissionGroupRef));
$manager->persist($GroupCenter);
$reference = $centerRef.'_'.$permissionGroupRef;
$this->addReference($reference, $GroupCenter);
static::$refs[] = $reference;
echo "Creating $reference... \n";
}
}
$manager->flush();
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace Chill\MainBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Intl\Intl;
use Chill\MainBundle\Entity\Language;
/**
* Load languages into database
*
* @author Julien Fastré <julien arobase fastre point info>
*/
class LoadLanguages extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface
{
// The regional version of language are language with _ in the code
// This array contains regional code to not exclude
private $regionalVersionToInclude = ["ro_MD"];
// Array of ancien languages (to exclude)
private $ancientToExclude = ["ang", "egy", "fro", "goh", "grc", "la", "non", "peo", "pro", "sga",
"dum", "enm", "frm", "gmh", "mga", "akk", "phn", "zxx", "got", "und"];
/**
*
* @var ContainerInterface
*/
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function getOrder() {
return 10;
}
public function load(ObjectManager $manager) {
echo "loading languages... \n";
foreach (Intl::getLanguageBundle()->getLanguageNames() as $code => $language) {
if (
!in_array($code, $this->regionalVersionToInclude)
&&
!in_array($code, $this->ancientToExclude)
) {
$lang = (new Language())
->setId($code)
->setName($this->prepareName($code))
;
$manager->persist($lang);
}
}
$manager->flush();
}
/**
* prepare names for languages
*
* @param string $languageCode
* @return string[] languages name indexed by available language code
*/
private function prepareName($languageCode) {
foreach ($this->container->getParameter('chill_main.available_languages') as $lang) {
$names[$lang] = Intl::getLanguageBundle()->getLanguageName($languageCode);
}
return $names;
}
}

View File

@ -0,0 +1,87 @@
<?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>
*
* 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\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Chill\MainBundle\Entity\PermissionsGroup;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class LoadPermissionsGroup extends AbstractFixture implements OrderedFixtureInterface
{
public function getOrder()
{
return 400;
}
public static $permissionGroup = array(
array(
'name' => 'social',
'role_scopes' => array(
'role_scope_CHILL_FOO_EDIT_social',
'role_scope_CHILL_FOO_SEE_administrative',
"role_scope_CHILL_FOO_EDIT_all"
)
),
array(
'name' => 'administrative',
'role_scopes' => array(
"role_scope_CHILL_FOO_SEE_social",
"role_scope_CHILL_FOO_EDIT_administrative",
"role_scope_CHILL_FOO_EDIT_all"
)
),
array(
'name' => 'direction',
'role_scopes' => array(
"role_scope_CHILL_FOO_EDIT_all",
"role_scope_CHILL_FOO_SEE_DETAILS_social",
"role_scope_CHILL_FOO_SEE_DETAILS_administrative"
)
)
);
public static $refs = array();
public function load(ObjectManager $manager)
{
foreach (static::$permissionGroup as $new) {
$permissionGroup = new PermissionsGroup();
$permissionGroup->setName($new['name']);
foreach ($new['role_scopes'] as $roleScopeRef) {
$permissionGroup->addRoleScope($this->getReference($roleScopeRef));
}
$manager->persist($permissionGroup);
$reference = 'permission_group_'.$new['name'];
echo "Creating $reference \n";
$this->setReference($reference, $permissionGroup);
static::$refs[] = $reference;
}
$manager->flush();
}
}

View File

@ -0,0 +1,105 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2014-2016, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <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\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Chill\MainBundle\Entity\PostalCode;
/**
* Description of LoadPostalCodes
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* @author Champs Libres <info@champs-libres.coop>
*/
class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface
{
public function getOrder()
{
return 50;
}
public static $refs = array();
public function load(ObjectManager $manager)
{
$lines = str_getcsv(self::$codes, "\n");
$belgium = $manager->getRepository('ChillMainBundle:Country')
->findOneBy(array('countryCode' => 'BE'));
foreach($lines as $line) {
$code = str_getcsv($line);
$c = new PostalCode();
$c->setCountry($belgium)
->setCode($code[0])
->setName(\ucwords(\strtolower($code[2])))
;
$manager->persist($c);
$ref = 'postal_code_'.$code[0];
if (! $this->hasReference($ref)) {
$this->addReference($ref, $c);
self::$refs[] = $ref;
}
}
$manager->flush();
}
private static $codes = <<<EOF
1000,BRUXELLES,BRUXELLES,Bruxelles
1020,Laeken,BRUXELLES,Bruxelles
1030,SCHAERBEEK,SCHAERBEEK,Bruxelles
1040,ETTERBEEK,ETTERBEEK,Bruxelles
1050,IXELLES,IXELLES,Bruxelles
1060,SAINT-GILLES,SAINT-GILLES,Bruxelles
1070,ANDERLECHT,ANDERLECHT,Bruxelles
1080,MOLENBEEK-SAINT-JEAN,MOLENBEEK-SAINT-JEAN,Bruxelles
1081,KOEKELBERG,KOEKELBERG,Bruxelles
1082,BERCHEM-SAINTE-AGATHE,BERCHEM-SAINTE-AGATHE,Bruxelles
1083,GANSHOREN,GANSHOREN,Bruxelles
1090,JETTE,JETTE,Bruxelles
1120,Neder-Over-Heembeek,BRUXELLES,Bruxelles
1130,Haren,BRUXELLES,Bruxelles
1140,EVERE,EVERE,Bruxelles
1150,WOLUWE-SAINT-PIERRE,WOLUWE-SAINT-PIERRE,Bruxelles
1160,AUDERGHEM,AUDERGHEM,Bruxelles
1170,WATERMAEL-BOITSFORT,WATERMAEL-BOITSFORT,Bruxelles
1180,UCCLE,UCCLE,Bruxelles
1190,FOREST,FOREST,Bruxelles
1200,WOLUWE-SAINT-LAMBERT,WOLUWE-SAINT-LAMBERT,Bruxelles
1210,SAINT-JOSSE-TEN-NOODE,SAINT-JOSSE-TEN-NOODE,Bruxelles
1300,Limal,WAVRE,Brabant-Wallon
1300,WAVRE,WAVRE,Brabant-Wallon
1301,Bierges,WAVRE,Brabant-Wallon
1310,LA HULPE,LA HULPE,Brabant-Wallon
1315,Glimes,INCOURT,Brabant-Wallon
1315,INCOURT,INCOURT,Brabant-Wallon
1315,Opprebais,INCOURT,Brabant-Wallon
1315,Piètrebais,INCOURT,Brabant-Wallon
1315,Roux-Miroir,INCOURT,Brabant-Wallon
1320,BEAUVECHAIN,BEAUVECHAIN,Brabant-Wallon
EOF;
}

View File

@ -0,0 +1,86 @@
<?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>
*
* 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\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Chill\MainBundle\Entity\RoleScope;
use Chill\MainBundle\DataFixtures\ORM\LoadScopes;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class LoadRoleScopes extends AbstractFixture implements OrderedFixtureInterface
{
public function getOrder()
{
return 300;
}
public static $permissions = array(
'CHILL_FOO_SEE' => array(
'names' => array(
'fr' => 'voir foo',
'en' => 'see foo',
'nl' => 'zie foo'
)
),
'CHILL_FOO_SEE_DETAILS' => array(
'names' => array(
'fr' => 'voir foo avec détails',
'en' => 'see foo with details',
'nl' => 'zie foo in details'
)
),
'CHILL_FOO_EDIT' => array(
'names' => array(
'fr' => 'modifier foo',
'en' => 'edit foo',
'nl' => 'editie foo'
)
)
);
public static $references = array();
public function load(ObjectManager $manager)
{
foreach (static::$permissions as $key => $permission) {
foreach(LoadScopes::$references as $scopeReference) {
$roleScope = new RoleScope();
$roleScope->setRole($key)
->setScope($this->getReference($scopeReference))
;
$reference = 'role_scope_'.$key.'_'.$this->getReference($scopeReference)->getName()['en'];
echo "Creating $reference \n";
$this->addReference($reference, $roleScope);
$manager->persist($roleScope);
static::$references[] = $reference;
}
}
$manager->flush();
}
}

View File

@ -0,0 +1,79 @@
<?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>
*
* 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\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
/**
* Create scopes
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class LoadScopes extends AbstractFixture implements OrderedFixtureInterface
{
public function getOrder()
{
return 200;
}
public $scopes = array(
array(
'names' => array(
'fr' => 'tous',
'en' => 'all',
'nl' => 'algemeen'
),
),
array(
'names' => array(
'fr' => 'social',
'en' => 'social',
'nl' => 'sociaal'
)
),
array(
'names' => array(
'fr' => 'administratif',
'en' => 'administrative',
'nl' => 'administratief'
)
)
);
public static $references = array();
public function load(ObjectManager $manager)
{
foreach ($this->scopes as $new) {
$scope = new \Chill\MainBundle\Entity\Scope();
$scope->setName($new['names']);
$manager->persist($scope);
$reference = 'scope_'.$new['names']['en'];
$this->addReference($reference, $scope);
static::$references[] = $reference;
}
$manager->flush();
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace Chill\MainBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Chill\MainBundle\DataFixtures\ORM\LoadCenters;
use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup;
use Chill\MainBundle\Entity\User;
/**
* Load fixtures users into database
*
* create a user for each permission_group and center.
* username and password are identicals.
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class LoadUsers extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
/**
*
* @var ContainerInterface
*/
private $container;
public function getOrder()
{
return 1000;
}
public static $refs = array(
'center a_social' => array(
'groupCenterRefs' => ['centerA_permission_group_social']
),
'center a_administrative' => array(
'groupCenterRefs' => ['centerA_permission_group_administrative']
),
'center a_direction' => array(
'groupCenterRefs' => ['centerA_permission_group_direction']
),
'center b_social' => array(
'groupCenterRefs' => ['centerB_permission_group_social']
),
'center b_administrative' => array(
'groupCenterRefs' => ['centerB_permission_group_administrative']
),
'center b_direction' => array(
'groupCenterRefs' => ['centerB_permission_group_direction']
),
'multi_center' => array(
'groupCenterRefs' => ['centerA_permission_group_social',
'centerB_permission_group_social']
)
);
public function load(ObjectManager $manager)
{
foreach (self::$refs as $username => $params) {
$user = new User();
$user
->setUsername($username)
->setPassword(
$this->container->get('security.encoder_factory')
->getEncoder($user)
->encodePassword('password', $user->getSalt())
)
->setEmail(sprintf("%s@chill.social", \str_replace(' ', '', $username)))
;
foreach ($params['groupCenterRefs'] as $groupCenterRef) {
$user->addGroupCenter($this->getReference($groupCenterRef));
}
echo 'Creating user ' . $username ."... \n";
$manager->persist($user);
$this->addReference($username, $user);
}
$manager->flush();
}
public function setContainer(ContainerInterface $container = null)
{
if (NULL === $container) {
throw new \LogicException('$container should not be null');
}
$this->container = $container;
}
}

View File

@ -0,0 +1,253 @@
<?php
/*
* Copyright (C) 2014-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\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Chill\MainBundle\DependencyInjection\Configuration;
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
use Chill\MainBundle\Doctrine\DQL\Unaccent;
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;
/**
* This class load config for chillMainExtension.
*/
class ChillMainExtension extends Extension implements PrependExtensionInterface,
Widget\HasWidgetFactoriesExtensionInterface
{
/**
* widget factory
*
* @var WidgetFactoryInterface[]
*/
protected $widgetFactories = array();
public function addWidgetFactory(WidgetFactoryInterface $factory)
{
$this->widgetFactories[] = $factory;
}
/**
*
* @return WidgetFactoryInterface[]
*/
public function getWidgetFactories()
{
return $this->widgetFactories;
}
/**
* {@inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
// configuration for main bundle
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('chill_main.installation_name',
$config['installation_name']);
$container->setParameter('chill_main.available_languages',
$config['available_languages']);
$container->setParameter('chill_main.routing.resources',
$config['routing']['resources']);
$container->setParameter('chill_main.pagination.item_per_page',
$config['pagination']['item_per_page']);
$container->setParameter('chill_main.notifications',
$config['notifications']);
$container->setParameter('chill_main.redis',
$config['redis']);
$container->setParameter('chill_main.phone_helper',
$config['phone_helper'] ?? []);
// add the key 'widget' without the key 'enable'
$container->setParameter('chill_main.widgets',
isset($config['widgets']['homepage']) ?
array('homepage' => $config['widgets']['homepage']):
array()
);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
$loader->load('services/logger.yml');
$loader->load('services/repositories.yml');
$loader->load('services/pagination.yml');
$loader->load('services/export.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');
$loader->load('services/notification.yml');
$loader->load('services/redis.yml');
$loader->load('services/command.yml');
$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)
{
return new Configuration($this->widgetFactories, $container);
}
public function prepend(ContainerBuilder $container)
{
$bundles = $container->getParameter('kernel.bundles');
//add ChillMain to assetic-enabled bundles
if (!isset($bundles['AsseticBundle'])) {
throw new MissingBundleException('AsseticBundle');
}
$asseticConfig = $container->getExtensionConfig('assetic');
$asseticConfig['bundles'][] = 'ChillMainBundle';
$container->prependExtensionConfig('assetic',
array('bundles' => array('ChillMainBundle')));
//add installation_name and date_format to globals
$chillMainConfig = $container->getExtensionConfig($this->getAlias());
$config = $this->processConfiguration($this
->getConfiguration($chillMainConfig, $container), $chillMainConfig);
$twigConfig = array(
'globals' => array(
'installation' => array(
'name' => $config['installation_name']),
'available_languages' => $config['available_languages']
),
'form_themes' => array('ChillMainBundle:Form:fields.html.twig')
);
$container->prependExtensionConfig('twig', $twigConfig);
//add DQL function to ORM (default entity_manager)
$container->prependExtensionConfig('doctrine', array(
'orm' => array(
'dql' => array(
'string_functions' => array(
'unaccent' => Unaccent::class,
'GET_JSON_FIELD_BY_KEY' => GetJsonFieldByKey::class,
'AGGREGATE' => JsonAggregate::class,
'REPLACE' => Replace::class,
),
'numeric_functions' => [
'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class,
'SIMILARITY' => Similarity::class,
'OVERLAPSI' => OverlapsI::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
$container->prependExtensionConfig('chill_main', array(
'routing' => array(
'resources' => array(
'@ChillMainBundle/Resources/config/routing.yml'
)
)
));
//add a channel to log app events
$container->prependExtensionConfig('monolog', array(
'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

@ -0,0 +1,39 @@
<?php
/*
*/
namespace Chill\MainBundle\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Chill\MainBundle\Form\PermissionsGroupType;
use Symfony\Component\DependencyInjection\Reference;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ACLFlagsCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$permissionGroupType = $container->getDefinition(PermissionsGroupType::class);
foreach($container->findTaggedServiceIds('chill_main.flags') as $id => $tags) {
$reference = new Reference($id);
foreach ($tags as $tag) {
switch($tag['scope']) {
case PermissionsGroupType::FLAG_SCOPE:
$permissionGroupType->addMethodCall('addFlagProvider', [ $reference ]);
break;
default:
throw new \LogicalException(sprintf(
"This tag 'scope' is not implemented: %s, on service with id %s", $tag['scope'], $id)
);
}
}
}
}
}

View File

@ -0,0 +1,210 @@
<?php
/*
* Copyright (C) 2015 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\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
/**
* Compiles the services tagged with :
*
* - chill.export
* - chill.export_formatter
* - chill.export_aggregator
* - chill.export_filter
* - chill.export_elements_provider
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ExportsCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('chill.main.export_manager')) {
throw new \LogicException('service chill.main.export_manager '
. 'is not defined. It is required by ExportsCompilerPass');
}
$chillManagerDefinition = $container->getDefinition(
'chill.main.export_manager'
);
$this->compileExports($chillManagerDefinition, $container);
$this->compileFilters($chillManagerDefinition, $container);
$this->compileAggregators($chillManagerDefinition, $container);
$this->compileFormatters($chillManagerDefinition, $container);
$this->compileExportElementsProvider($chillManagerDefinition, $container);
}
private function compileExports(Definition $chillManagerDefinition,
ContainerBuilder $container)
{
$taggedServices = $container->findTaggedServiceIds(
'chill.export'
);
$knownAliases = array();
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
if (!isset($attributes["alias"])) {
throw new \LogicException("the 'alias' attribute is missing in your ".
"service '$id' definition");
}
if (array_search($attributes["alias"], $knownAliases)) {
throw new \LogicException("There is already a chill.export service with alias "
.$attributes["alias"].". Choose another alias.");
}
$knownAliases[] = $attributes["alias"];
$chillManagerDefinition->addMethodCall(
'addExport',
array(new Reference($id), $attributes["alias"])
);
}
}
}
private function compileFilters(Definition $chillManagerDefinition,
ContainerBuilder $container)
{
$taggedServices = $container->findTaggedServiceIds(
'chill.export_filter'
);
$knownAliases = array();
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
if (!isset($attributes["alias"])) {
throw new \LogicException("the 'alias' attribute is missing in your ".
"service '$id' definition");
}
if (array_search($attributes["alias"], $knownAliases)) {
throw new \LogicException("There is already a chill.export_filter service with alias "
.$attributes["alias"].". Choose another alias.");
}
$knownAliases[] = $attributes["alias"];
$chillManagerDefinition->addMethodCall(
'addFilter',
array(new Reference($id), $attributes["alias"])
);
}
}
}
private function compileAggregators(Definition $chillManagerDefinition,
ContainerBuilder $container)
{
$taggedServices = $container->findTaggedServiceIds(
'chill.export_aggregator'
);
$knownAliases = array();
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
if (!isset($attributes["alias"])) {
throw new \LogicException("the 'alias' attribute is missing in your ".
"service '$id' definition");
}
if (array_search($attributes["alias"], $knownAliases)) {
throw new \LogicException("There is already a chill.export_aggregator service with alias "
.$attributes["alias"].". Choose another alias.");
}
$knownAliases[] = $attributes["alias"];
$chillManagerDefinition->addMethodCall(
'addAggregator',
array(new Reference($id), $attributes["alias"])
);
}
}
}
private function compileFormatters(Definition $chillManagerDefinition,
ContainerBuilder $container)
{
$taggedServices = $container->findTaggedServiceIds(
'chill.export_formatter'
);
$knownAliases = array();
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
if (!isset($attributes["alias"])) {
throw new \LogicException("the 'alias' attribute is missing in your ".
"service '$id' definition");
}
if (array_search($attributes["alias"], $knownAliases)) {
throw new \LogicException("There is already a chill.export_formatter service with alias "
.$attributes["alias"].". Choose another alias.");
}
$knownAliases[] = $attributes["alias"];
$chillManagerDefinition->addMethodCall(
'addFormatter',
array(new Reference($id), $attributes["alias"])
);
}
}
}
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"])
);
}
}
}
}

View File

@ -0,0 +1,46 @@
<?php
/*
* Copyright (C) 2019 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\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
use Chill\MainBundle\Form\Type\Export\PickCenterType;
/**
*
*
*
*/
class GroupingCenterCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (FALSE === $container->hasDefinition('chill.main.form.pick_centers_type')) {
throw new \LogicException("The service chill.main.form.pick_centers_type does "
. "not exists in container");
}
$pickCenterType = $container->getDefinition('chill.main.form.pick_centers_type');
foreach ($container->findTaggedServiceIds('chill.grouping_center') as $serviceId => $tagged) {
$pickCenterType->addMethodCall('addGroupingCenter',
[ new Reference($serviceId) ]);
}
}
}

View 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]);
}
}
}
}

View 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\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)]);
}
}
}

View File

@ -0,0 +1,34 @@
<?php
/*
* Copyright (C) 2016 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\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass;
/**
* Compile the service definition to register widgets.
*
*/
class WidgetsCompilerPass extends AbstractWidgetsCompilerPass {
public function process(ContainerBuilder $container)
{
$this->doProcess($container, 'chill_main', 'chill_main.widgets');
}
}

View File

@ -0,0 +1,61 @@
<?php
/*
* Chill is a software for social workers
* Copyright (C) 2015 Champs-Libres Coopérative <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;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Description of ConfigConsistencyCompilerPass
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ConfigConsistencyCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$availableLanguages = $container
->getParameter('chill_main.available_languages');
$methodCallsTranslator = $container
->findDefinition('translator.default')
->getMethodCalls();
$fallbackLocales = array();
foreach($methodCallsTranslator as $call) {
if ($call[0] === 'setFallbackLocales') {
$fallbackLocales = array_merge($fallbackLocales,
$call[1][0]);
}
}
if (count($fallbackLocales) === 0) {
throw new \LogicException('the fallback locale are not defined. '
. 'The framework config should not allow this.');
}
$diff = array_diff($fallbackLocales, $availableLanguages);
if (count($diff) > 0) {
throw new \RuntimeException(sprintf('The chill_main.available_languages'
. ' parameter does not contains fallback locales. The languages %s'
. ' are missing.', implode(', ', $diff)));
}
}
}

View File

@ -0,0 +1,178 @@
<?php
namespace Chill\MainBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Configure the main bundle
*/
class Configuration implements ConfigurationInterface
{
use AddWidgetConfigurationTrait;
/**
*
* @var ContainerBuilder
*/
private $containerBuilder;
public function __construct(array $widgetFactories = array(),
ContainerBuilder $containerBuilder)
{
$this->setWidgetFactories($widgetFactories);
$this->containerBuilder = $containerBuilder;
}
/**
* {@inheritDoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('chill_main');
$rootNode
->children()
->scalarNode('installation_name')
->cannotBeEmpty()
->defaultValue('Chill')
->end() // end of scalar 'installation_name'
->arrayNode('available_languages')
->defaultValue(array('fr'))
->prototype('scalar')->end()
->end() // end of array 'available_languages'
->arrayNode('routing')
->children()
->arrayNode('resources')
->prototype('scalar')->end()
->end() // end of array 'resources'
->end() // end of children
->end() // end of array node 'routing'
->arrayNode('pagination')
->canBeDisabled()
->children()
->integerNode('item_per_page')
->info('The number of item to show in the page result, by default')
->min(1)
->defaultValue(50)
->end() // end of integer 'item_per_page'
->end() // end of children
->end() // end of pagination
->arrayNode('notifications')
->children()
->scalarNode('from_email')
->cannotBeEmpty()
->end()
->scalarNode('from_name')
->cannotBeEmpty()
->end()
->enumNode('scheme')
->cannotBeEmpty()
->values(['http', 'https'])
->defaultValue('https')
->end()
->scalarNode('host')
->cannotBeEmpty()
->end()
->end()
->end() // end of notifications
->arrayNode('phone_helper')
->canBeUnset()
->children()
->scalarNode('twilio_sid')
->defaultNull()
->end()
->scalarNode('twilio_secret')
->defaultNull()
->end()
->end()
->end()
->arrayNode('redis')
->children()
->scalarNode('host')
->cannotBeEmpty()
->end()
->scalarNode('port')
->defaultValue(6379)
->end()
->scalarNode('timeout')
->defaultValue(1)
->end()
->end()
->end()
->arrayNode('widgets')
->canBeEnabled()
->canBeUnset()
->children()
->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
;
return $treeBuilder;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Chill\MainBundle\DependencyInjection;
use Exception;
/**
* Description of MissingBundleException
*
* @author julien
*/
class MissingBundleException extends Exception {
public function __construct($missingBundleName) {
$message = "The bundle $missingBundleName is missing.";
parent::__construct($message, 500);
}
}

View File

@ -0,0 +1,57 @@
<?php
/*
* Copyright (C) 2015 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\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class RoleProvidersCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('chill.main.role_provider')) {
throw new \LogicException('service chill.main.role_provider '
. 'is not defined. It is required by RoleProviderCompilerPass');
}
$definition = $container->getDefinition(
'chill.main.role_provider'
);
$taggedServices = $container->findTaggedServiceIds(
'chill.role'
);
foreach ($taggedServices as $id => $tagAttributes) {
$definition->addMethodCall(
'addProvider',
array(new Reference($id))
);
}
}
}

View File

@ -0,0 +1,73 @@
<?php
/*
* Chill is a software for social workers
*
* Copyright (C) 2014, 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\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class SearchableServicesCompilerPass implements CompilerPassInterface
{
/*
* (non-PHPdoc)
* @see \Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface::process()
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('chill.main.search_provider')) {
throw new \LogicException('service chill.main.search_provider '
. 'is not defined.');
}
$definition = $container->getDefinition(
'chill.main.search_provider'
);
$taggedServices = $container->findTaggedServiceIds(
'chill.search'
);
$knownAliases = array();
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
if (!isset($attributes["alias"])) {
throw new \LogicException("the 'name' attribute is missing in your ".
"service '$id' definition");
}
if (array_search($attributes["alias"], $knownAliases)) {
throw new \LogicException("There is already a chill.search service with alias "
.$attributes["alias"].". Choose another alias.");
}
$knownAliases[] = $attributes["alias"];
$definition->addMethodCall(
'addSearchService',
array(new Reference($id), $attributes["alias"])
);
}
}
}
}

View File

@ -0,0 +1,65 @@
<?php
/*
* Copyright (C) 2015 Champs-Libres Coopérative <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;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Add services taggued with `name: chill.timeline` to
* timeline_builder service definition
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class TimelineCompilerClass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('chill.main.timeline_builder')) {
throw new \LogicException('service chill.main.timeline_builder '
. 'is not defined.');
}
$definition = $container->getDefinition(
'chill.main.timeline_builder'
);
$taggedServices = $container->findTaggedServiceIds(
'chill.timeline'
);
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
if (!isset($attributes["context"])) {
throw new \LogicException("the 'context' attribute is missing in your ".
"service '$id' definition");
}
$definition->addMethodCall(
'addProvider',
array($attributes["context"], $id)
);
}
}
}
}

View File

@ -0,0 +1,388 @@
<?php
/*
* Copyright (C) 2016 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\DependencyInjection\Widget;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Chill\MainBundle\DependencyInjection\Widget\HasWidgetFactoriesExtensionInterface;
/**
* Compile the configurations and inject required service into container.
*
* The widgets are services tagged with :
*
* ```
* { name: chill_widget, alias: my_alias, place: my_place }
* ```
*
* Or, if the tag does not exist or if you need to add some config to your
* service depending on the config, you should use a `WidgetFactory` (see
* `WidgetFactoryInterface`.
*
* To reuse this compiler pass, simple execute the doProcess metho in your
* compiler. Example :
*
* ```
* namespace Chill\MainBundle\DependencyInjection\CompilerPass;
*
* use Symfony\Component\DependencyInjection\ContainerBuilder;
* use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass;
* class WidgetsCompilerPass extends AbstractWidgetsCompilerPass {
*
* public function process(ContainerBuilder $container)
* {
* $this->doProcess($container, 'chill_main', 'chill_main.widgets');
* }
* }
* ```
*
*
*/
abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
{
private $widgetServices = array();
/**
*
* @var WidgetFactoryInterface[]
*/
private $widgetFactories;
/**
* The service which will manage the widgets
*
* @var string
*/
const WIDGET_MANAGER = 'chill.main.twig.widget';
/**
* the method wich register the widget into give service.
*/
const WIDGET_MANAGER_METHOD_REGISTER = 'addWidget';
/**
* the value of the `name` key in service definitions's tag
*
* @var string
*/
const WIDGET_SERVICE_TAG_NAME = 'chill_widget';
/**
* the key used to collect the alias in the service definition's tag.
* the alias must be
* injected into the configuration under 'alias' key.
*
* @var string
*/
const WIDGET_SERVICE_TAG_ALIAS = 'alias';
/**
* the key used to collect the authorized place in the service definition's tag
*
* @var string
*/
const WIDGET_SERVICE_TAG_PLACES = 'place';
/**
* the key to use to order widget for a given place
*/
const WIDGET_CONFIG_ORDER = 'order';
/**
* the key to use to identify widget for a given place
*/
const WIDGET_CONFIG_ALIAS = 'widget_alias';
/**
* process the configuration and the container to add the widget available
*
* @param ContainerBuilder $container
* @param string $extension the extension of your bundle
* @param string $containerWidgetConfigParameterName the key under which we can use the widget configuration
* @throws \LogicException
* @throws \UnexpectedValueException if the given extension does not implement HasWidgetExtensionInterface
* @throws \InvalidConfigurationException if there are errors in the config
*/
public function doProcess(ContainerBuilder $container, $extension,
$containerWidgetConfigParameterName)
{
if (!$container->hasDefinition(self::WIDGET_MANAGER)) {
throw new \LogicException("the service ".self::WIDGET_MANAGER." should".
" be present. It is required by ".self::class);
}
$managerDefinition = $container->getDefinition(self::WIDGET_MANAGER);
// collect the widget factories
/* @var $extensionClass HasWidgetFactoriesExtensionInterface */
$extensionClass = $container->getExtension($extension);
// throw an error if extension does not implement HasWidgetFactoriesExtensionInterface
if (!$extensionClass instanceof HasWidgetFactoriesExtensionInterface) {
throw new \UnexpectedValueException(sprintf("The extension for %s "
. "do not implements %s. You should implement %s on %s",
$extension,
HasWidgetFactoriesExtensionInterface::class,
HasWidgetFactoriesExtensionInterface::class,
get_class($extensionClass)));
}
$this->widgetFactories = $extensionClass->getWidgetFactories();
// collect the availabled tagged services
$this->collectTaggedServices($container);
// collect the widgets and their config :
$widgetParameters = $container->getParameter($containerWidgetConfigParameterName);
// and add them to the delegated_block
foreach($widgetParameters as $place => $widgets) {
foreach ($widgets as $param) {
$alias = $param[self::WIDGET_CONFIG_ALIAS];
// check that the service exists
if (!array_key_exists($alias, $this->widgetServices)) {
throw new InvalidConfigurationException(sprintf("The alias %s".
" is not defined.", $alias));
}
// check that the widget is allowed at this place
if (!$this->isPlaceAllowedForWidget($place, $alias, $container)) {
throw new \InvalidConfigurationException(sprintf(
"The widget with alias %s is not allowed at place %s",
$alias,
$place
));
}
// get the order, eventually corrected
$order = $this->cacheAndGetOrdering($place, $param[self::WIDGET_CONFIG_ORDER]);
// register the widget with config to the service, using the method
// `addWidget`
if ($this->widgetServices[$alias] instanceof WidgetFactoryInterface) {
/* @var $factory WidgetFactoryInterface */
$factory = $this->widgetServices[$alias];
// get the config (under the key which equals to widget_alias
$config = isset($param[$factory->getWidgetAlias()]) ?
$param[$factory->getWidgetAlias()] : array();
// register the service into the container
$serviceId =$this->registerServiceIntoContainer($container,
$factory, $place, $order, $config);
$managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER,
array(
$place,
$order,
new Reference($serviceId),
$config
));
} else {
$managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER,
array(
$place,
$order,
new Reference($this->widgetServices[$alias]),
array() // the config is alway an empty array
));
}
}
}
}
/**
* register the service into container.
*
* @param ContainerBuilder $container
* @param WidgetFactoryInterface $factory
* @param string $place
* @param float $order
* @param array $config
* @return string the id of the new service
*/
protected function registerServiceIntoContainer(
ContainerBuilder $container,
WidgetFactoryInterface $factory,
$place,
$order,
array $config
) {
$serviceId = $factory->getServiceId($container, $place, $order, $config);
$definition = $factory->createDefinition($container, $place,
$order, $config);
$container->setDefinition($serviceId, $definition);
return $serviceId;
}
/**
* cache of ordering by place.
*
* @internal used by function cacheAndGetOrdering
* @var array
*/
private $cacheOrdering = array();
/**
* check if the ordering has already be used for the given $place and,
* if yes, correct the ordering by incrementation of 1 until the ordering
* has not be used.
*
* recursive method.
*
* @param string $place
* @param float $ordering
* @return float
*/
private function cacheAndGetOrdering($place, $ordering) {
// create a key in the cache array if not exists
if (!array_key_exists($place, $this->cacheOrdering)) {
$this->cacheOrdering[$place] = array();
}
// check if the order exists
if (array_search($ordering, $this->cacheOrdering[$place])) {
// if the order exists, increment of 1 and try again
return $this->cacheAndGetOrdering($place, $ordering + 1);
} else {
// cache the ordering
$this->cacheOrdering[$place][] = $ordering;
return $ordering;
}
}
/**
* get the places where the service is allowed
*
* @param Definition $definition
* @return unknown
*/
private function isPlaceAllowedForWidget($place, $widgetAlias, ContainerBuilder $container)
{
if ($this->widgetServices[$widgetAlias] instanceof WidgetFactoryInterface) {
if (in_array($place, $this->widgetServices[$widgetAlias]
->getAllowedPlaces())) {
return true;
}
} else {
$definition = $container->findDefinition($this->widgetServices[$widgetAlias]);
foreach($definition->getTag(self::WIDGET_SERVICE_TAG_NAME) as $attrs) {
$placeValue = $attrs[self::WIDGET_SERVICE_TAG_PLACES];
if ($placeValue === $place) {
return true;
}
}
}
return false;
}
/**
* This method collect all service tagged with `self::WIDGET_SERVICE_TAG`, and
* add also the widget defined by factories
*
* This method also check that the service is correctly tagged with `alias` and
* `places`, or the factory give a correct alias and more than one place.
*
* @param ContainerBuilder $container
* @throws InvalidConfigurationException
* @throws InvalidArgumentException
*/
protected function collectTaggedServices(ContainerBuilder $container)
{
// first, check the service tagged in service definition
foreach ($container->findTaggedServiceIds(self::WIDGET_SERVICE_TAG_NAME) as $id => $attrs) {
foreach ($attrs as $attr) {
// check the alias is set
if (!isset($attr[self::WIDGET_SERVICE_TAG_ALIAS])) {
throw new InvalidConfigurationException("you should add an ".self::WIDGET_SERVICE_TAG_ALIAS.
" key on the service ".$id);
}
// check the place is set
if (!isset($attr[self::WIDGET_SERVICE_TAG_PLACES])) {
throw new InvalidConfigurationException(sprintf(
"You should add a %s key on the service %s",
self::WIDGET_SERVICE_TAG_PLACES,
$id
));
}
// check the alias does not exists yet
if (array_key_exists($attr[self::WIDGET_SERVICE_TAG_ALIAS], $this->widgetServices)) {
throw new InvalidArgumentException("a service has already be defined with the ".
self::WIDGET_SERVICE_TAG_ALIAS." ".$attr[self::WIDGET_SERVICE_TAG_ALIAS]);
}
// register the service as available
$this->widgetServices[$attr[self::WIDGET_SERVICE_TAG_ALIAS]] = $id;
}
}
// add the services defined by factories
foreach($this->widgetFactories as $factory) {
/* @var $factory WidgetFactoryInterface */
$alias = $factory->getWidgetAlias();
// check the alias is not empty
if (empty($alias)) {
throw new \LogicException(sprintf(
"the widget factory %s returns an empty alias",
get_class($factory)));
}
// check the places are not empty
if (!is_array($factory->getAllowedPlaces())) {
throw new \UnexpectedValueException("the method 'getAllowedPlaces' "
. "should return a non-empty array. Unexpected value on ".
get_class($factory));
}
if (count($factory->getAllowedPlaces()) == 0) {
throw new \LengthException("The method 'getAllowedPlaces' should "
. "return a non-empty array, but returned 0 elements on ".
get_class($factory).'::getAllowedPlaces()');
}
// check the alias does not exists yet
if (array_key_exists($alias, $this->widgetServices)) {
throw new InvalidArgumentException("a service has already be defined with the ".
self::WIDGET_SERVICE_TAG_ALIAS." ".$alias);
}
// register the factory as available
$this->widgetServices[$factory->getWidgetAlias()] = $factory;
}
}
}

View File

@ -0,0 +1,252 @@
<?php
/*
* Copyright (C) 2016 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\DependencyInjection\Widget;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass as WidgetsCompilerPass;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* This trait allow to add automatic configuration for widget inside your config.
*
* Usage
* ======
*
* 1. Register widget factories
* ----------------------------
*
* Add widget factories, using `setWidgetFactories`
*
* Example :
*
* ```
* use Symfony\Component\DependencyInjection\ContainerBuilder;
* use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
*
* class MyConfig
* {
*
* use addWidgetConfigurationTrait;
*
* public function __construct(array $widgetFactories = array(), ContainerBuilder $container)
* {
* $this->setWidgetFactories($widgetFactories);
* // will be used on next step
* $this->container = $container;
* }
*
* }
* ```
*
*
*
* 2. add widget config to your config
* -----------------------------------
*
* ```
* use Symfony\Component\DependencyInjection\ContainerBuilder;
* use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
* use Symfony\Component\Config\Definition\Builder\TreeBuilder;
*
* class MyConfig
* {
*
* use addWidgetConfigurationTrait;
*
* private $container;
*
* public function __construct(array $widgetFactories = array(), ContainerBuilder $container)
* {
* $this->setWidgetFactories($widgetFactories);
* $this->container;
* }
*
* public function getConfigTreeBuilder()
* {
* $treeBuilder = new TreeBuilder();
* $root = $treeBuilder->root('my_root');
*
* $root->children()
* ->arrayNode('widgets')
* ->canBeDisabled()
* ->children()
* ->append($this->addWidgetsConfiguration('homepage', $this->container))
* ->end()
* ->end()
* ;
*
* return $treeBuilder;
* }
*
* }
* ```
*
* the above code will add to the config :
*
* ```
* widgets:
* enabled: true
*
* # register widgets on place "homepage"
* homepage:
* order: ~ # Required
* widget_alias: ~ # One of "person_list"; "add_person", Required
* person_list:
* # options for the person_list
* ```
*
*
*/
trait AddWidgetConfigurationTrait
{
/**
* @param WidgetFactoryInterface[]
*/
private $widgetFactories;
/**
*
* @param WidgetFactoryInterface[] $widgetFactories
*/
public function setWidgetFactories(array $widgetFactories)
{
$this->widgetFactories = $widgetFactories;
}
/**
* @return WidgetFactoryInterface[]
*/
public function getWidgetFactories()
{
return $this->widgetFactories;
}
/**
* add configuration nodes for the widget at the given place.
*
* @param type $place
* @param ContainerBuilder $containerBuilder
* @return type
*/
protected function addWidgetsConfiguration($place, ContainerBuilder $containerBuilder)
{
$treeBuilder = new TreeBuilder();
$root = $treeBuilder->root($place)
->canBeUnset()
->info('register widgets on place "'.$place.'"');
// if no childen, return the root
if (count(iterator_to_array($this->filterWidgetByPlace($place))) === 0) {
return $root;
}
$prototypeChildren = $root->prototype('array')->children();
$prototypeChildren
->floatNode(WidgetsCompilerPass::WIDGET_CONFIG_ORDER)
->isRequired()
->info("the ordering of the widget. May be a number with decimal")
->example("10.58")
->end()
->scalarNode(WidgetsCompilerPass::WIDGET_CONFIG_ALIAS)
// this node is scalar: when the configuration is build, the
// tagged services are not available. But when the config reference
// is build, the services are avaialble => we add the possible aliases
// in the info.
->info("the widget alias (see your installed bundles config). "
. "Possible values are (maybe incomplete) : ".
\implode(", ", $this->getWidgetAliasesbyPlace($place, $containerBuilder)))
->isRequired()
->end()
;
// adding the possible config on each widget under the widget_alias
foreach ($this->filterWidgetByPlace($place) as $factory) {
$builder = new TreeBuilder();
$widgetOptionsRoot = $builder->root($factory->getWidgetAlias());
$widgetOptionsRoot->canBeUnset()
->info(sprintf('the configuration for the widget "%s" (only required if this widget is set in widget_alias)',
$factory->getWidgetAlias()));
$factory->configureOptions($place, $widgetOptionsRoot->children());
$prototypeChildren->append($widgetOptionsRoot);
}
return $root;
}
/**
* get all widget factories for the given place.
*
* @param string $place
* @return \Generator a generator containing a widget factory
*/
protected function filterWidgetByPlace($place)
{
foreach($this->widgetFactories as $factory) {
if (in_array($place, $factory->getAllowedPlaces())) {
yield $factory;
}
}
}
/**
* get the all possible aliases for the given place. This method
* search within service tags and widget factories
*
* **Note** that services are not available when the config is build: the whole
* aliases will be checked in compiler pass, or when the command
* `config:dump-reference` is runned.
*
* @param type $place
* @param ContainerBuilder $containerBuilder
* @return type
* @throws InvalidConfigurationException if a service's tag does not have the "alias" key
*/
protected function getWidgetAliasesbyPlace($place, ContainerBuilder $containerBuilder)
{
$result = array();
foreach ($this->filterWidgetByPlace($place) as $factory) {
$result[] = $factory->getWidgetAlias();
}
// append the aliases added without factory
foreach ($containerBuilder
->findTaggedServiceIds(WidgetsCompilerPass::WIDGET_SERVICE_TAG_NAME)
as $serviceId => $tags) {
foreach ($tags as $tag) {
// throw an error if no alias in definition
if (!array_key_exists(WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS, $tag)) {
throw new InvalidConfigurationException(sprintf(
"The service with id %s does not have any %d key",
$serviceId,
WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS
));
}
// add the key to the possible results
$result[] = $tag[WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS];
}
}
return $result;
}
}

View File

@ -0,0 +1,57 @@
<?php
/*
* Copyright (C) 2016 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\DependencyInjection\Widget\Factory;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Allow to easily create WidgetFactory.
*
* Extending this factory, the widget will be created using the already defined
* service created "as other services" in your configuration (the most frequent
* way is using `services.yml` file.
*
* If you need to create different service based upon place, position or
* definition, you should implements directly
* `Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface`
*
*
*/
abstract class AbstractWidgetFactory implements WidgetFactoryInterface
{
/**
*
* {@inheritdoc}
*
* Will create the definition by returning the definition from the `services.yml`
* file (or `services.xml` or `what-you-want.yml`).
*
* @see \Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface::createDefinition()
*/
public function createDefinition(ContainerBuilder $containerBuilder, $place, $order, array $config)
{
return $containerBuilder->getDefinition($this
->getServiceId($containerBuilder, $place, $order, $config)
);
}
}

View File

@ -0,0 +1,103 @@
<?php
/*
* Copyright (C) 2016 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\DependencyInjection\Widget\Factory;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Factory for creating configuration of widgets.
*
* When you need a widget with some configuration, you should implements this
* interface on a factory. The factory will add configuration to the bundle
* giving the places for your widget.
*
* Using this interface, **you do not need** to define the service in your
* container configuration (`services.yml` files).
*
* Once the class is created, you should inject the factory inside the container
* at compile time, in your `Bundle` class :
*
*
* ```
* namespace Chill\PersonBundle;
*
* use Symfony\Component\HttpKernel\Bundle\Bundle;
* use Symfony\Component\DependencyInjection\ContainerBuilder;
* use Chill\PersonBundle\Widget\PersonListWidgetFactory;
*
* class ChillPersonBundle extends Bundle
* {
* public function build(ContainerBuilder $container)
* {
* parent::build($container);
* // register a widget factory into chill_main :
* $container->getExtension('chill_main')
* ->addWidgetFactory(new PersonListWidgetFactory());
* }
* }
* ```
*
*
*
*/
interface WidgetFactoryInterface
{
/**
* configure options for the widget. Those options will be added in
* configuration in the bundle where the widget will be used.
*
* @param type $place
* @param NodeBuilder $node
*/
public function configureOptions($place, NodeBuilder $node);
/**
* get the widget alias. This alias will be used in configuration (`config.yml`)
*/
public function getWidgetAlias();
/**
* Create a definition for the service which will render the widget.
*
* (Note: you can define the service by yourself, as other services,
* using the `AbstractWidgetFactory`)
*
* @param ContainerBuilder $containerBuilder
* @param type $place
* @param type $order
* @param array $config
*/
public function createDefinition(ContainerBuilder $containerBuilder, $place, $order, array $config);
/**
* return the service id to build the widget.
*
* @param ContainerBuilder $containerBuilder
* @param string $place
* @param float $order
* @param array $config
*
* @return string the service definition
*
*/
public function getServiceId(ContainerBuilder $containerBuilder, $place, $order, array $config);
}

View File

@ -0,0 +1,43 @@
<?php
/*
* Copyright (C) 2016 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\DependencyInjection\Widget;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
/**
* Register widget factories to an extension.
*
*/
interface HasWidgetFactoriesExtensionInterface {
/**
* register a widget factory
*
* @param \Chill\MainBundle\DependencyInjection\Widget\WidgetFactoryInterface $factory
*/
public function addWidgetFactory(WidgetFactoryInterface $factory);
/**
* get all the widget factories registered for this extension
*
* @return WidgetFactoryInterface[]
*/
public function getWidgetFactories();
}

View File

@ -0,0 +1,53 @@
<?php
/*
* Copyright (C) 2017 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\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 GetJsonFieldByKey extends FunctionNode
{
private $expr1;
private $expr2;
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->expr1 = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->expr2 = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(SqlWalker $sqlWalker)
{
return sprintf(
'(%s->%s)',
$this->expr1->dispatch($sqlWalker),
$this->expr2->dispatch($sqlWalker)
);
}
}

View File

@ -0,0 +1,49 @@
<?php
/*
* Copyright (C) 2017 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\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
/**
* Return an aggregation of values in a json representation, as a string.
*
* Internally, this function use the postgresql `jsonb_agg` function. Using
* json allow to aggregate data from different types without have to cast them.
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class JsonAggregate extends FunctionNode
{
private $expr;
public function getSql(SqlWalker $sqlWalker)
{
return sprintf('jsonb_agg(%s)', $this->expr->dispatch($sqlWalker));
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->expr = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View 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);
}
}

View File

@ -0,0 +1,110 @@
<?php
/*
* Chill is a software for social workers
* Copyright (C) 2018 Champs-Libres Coopérative <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\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
/**
* DQL function for OVERLAPS function in postgresql
*
* If a value is null in period start, it will be replaced by -infinity.
* If a value is null in period end, it will be replaced by infinity
*
*/
class OverlapsI extends FunctionNode
{
private $firstPeriodStart;
private $firstPeriodEnd;
private $secondPeriodStart;
private $secondPeriodEnd;
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return '('
.$this->makeCase($sqlWalker, $this->firstPeriodStart, 'start').', '
.$this->makeCase($sqlWalker, $this->firstPeriodEnd, 'end').
') OVERLAPS ('
.$this->makeCase($sqlWalker, $this->secondPeriodStart, 'start').', '
.$this->makeCase($sqlWalker, $this->secondPeriodEnd, 'end').')'
;
}
protected function makeCase($sqlWalker, $part, $position)
{
//return $part->dispatch($sqlWalker);
switch ($position) {
case 'start' :
$p = '-infinity';
break;
case 'end':
$p = 'infinity';
break;
}
if ($part instanceof \Doctrine\ORM\Query\AST\PathExpression) {
return 'CASE WHEN '
.' '.$part->dispatch($sqlWalker).' IS NOT NULL '
. 'THEN '.
$part->dispatch($sqlWalker)
. ' ELSE '.
"'".$p."'::date "
. 'END';
} else {
return 'CASE WHEN '
.' '.$part->dispatch($sqlWalker).'::date IS NOT NULL '
. 'THEN '.
$part->dispatch($sqlWalker)
. '::date ELSE '.
"'".$p."'::date "
. 'END';
}
}
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstPeriodStart = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->firstPeriodEnd = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->secondPeriodStart = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->secondPeriodEnd = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@ -0,0 +1,64 @@
<?php
/*
* Chill is a software for social workers
* Copyright (C) 2019 Champs-Libres Coopérative <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\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
/**
*
*
*/
class Replace extends FunctionNode
{
protected $string;
protected $from;
protected $to;
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker): string
{
return 'REPLACE('.
$this->string->dispatch($sqlWalker).
', '.
$this->from->dispatch($sqlWalker).
', '.
$this->to->dispatch($sqlWalker).
')';
}
public function parse(\Doctrine\ORM\Query\Parser $parser): void
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->string = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->from = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->to = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@ -0,0 +1,54 @@
<?php
/*
* Chill is a software for social workers
* Copyright (C) 2018 Champs-Libres Coopérative <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\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
/**
*
*
*
*/
class Similarity extends FunctionNode
{
private $firstPart;
private $secondPart;
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'SIMILARITY('.$this->firstPart->dispatch($sqlWalker).
', ' . $this->secondPart->dispatch($sqlWalker) .")";
}
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstPart = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->secondPart = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@ -0,0 +1,53 @@
<?php
/*
* Chill is a software for social workers
* Copyright (C) 2015 Champs-Libres Coopérative <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\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
/**
* Unaccent string using postgresql extension unaccent :
* http://www.postgresql.org/docs/current/static/unaccent.html
*
* Usage : StringFunction UNACCENT(string)
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class Unaccent extends FunctionNode
{
private $string;
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'UNACCENT(' . $this->string->dispatch($sqlWalker) .")";
}
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->string = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View 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})/', $current) === 1) {
$tExploded = explode(':', $current);
$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);
}
}

View File

@ -0,0 +1,269 @@
<?php
namespace Chill\MainBundle\Entity;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* Address
*/
class Address
{
/**
* @var integer
*/
private $id;
/**
* @var string
*/
private $streetAddress1 = '';
/**
* @var string
*/
private $streetAddress2 = '';
/**
* @var \Chill\MainBundle\Entity\PostalCode
*/
private $postcode;
/**
* Indicates when the address starts validation. Used to build an history
* of address. By default, the current date.
*
* @var \DateTime
*/
private $validFrom;
/**
* True if the address is a "no address", aka homeless person, ...
*
* @var bool
*/
private $isNoAddress = false;
/**
* A list of metadata, added by customizable fields
*
* @var array
*/
private $customs = [];
public function __construct()
{
$this->validFrom = new \DateTime();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set streetAddress1
*
* @param string $streetAddress1
*
* @return Address
*/
public function setStreetAddress1($streetAddress1)
{
$this->streetAddress1 = $streetAddress1 === NULL ? '' : $streetAddress1;
return $this;
}
/**
* Get streetAddress1
*
* @return string
*/
public function getStreetAddress1()
{
return $this->streetAddress1;
}
/**
* Set streetAddress2
*
* @param string $streetAddress2
*
* @return Address
*/
public function setStreetAddress2($streetAddress2)
{
$this->streetAddress2 = $streetAddress2 === NULL ? '' : $streetAddress2;
return $this;
}
/**
* Get streetAddress2
*
* @return string
*/
public function getStreetAddress2()
{
return $this->streetAddress2;
}
/**
* Set postcode
*
* @param \Chill\MainBundle\Entity\PostalCode $postcode
*
* @return Address
*/
public function setPostcode(\Chill\MainBundle\Entity\PostalCode $postcode = null)
{
$this->postcode = $postcode;
return $this;
}
/**
* Get postcode
*
* @return \Chill\MainBundle\Entity\PostalCode
*/
public function getPostcode()
{
return $this->postcode;
}
/**
*
* @return \DateTime
*/
public function getValidFrom()
{
return $this->validFrom;
}
/**
*
* @param \DateTime $validFrom
* @return \Chill\MainBundle\Entity\Address
*/
public function setValidFrom(\DateTime $validFrom)
{
$this->validFrom = $validFrom;
return $this;
}
/**
* get "isNoAddress"
*
* Indicate true if the address is a fake address (homeless, ...)
*
* @return bool
*/
public function getIsNoAddress(): bool
{
return $this->isNoAddress;
}
public function isNoAddress(): bool
{
return $this->getIsNoAddress();
}
/**
* set Is No Address
*
* Indicate true if the address is a fake address (homeless, ...)
*
* @param bool $isNoAddress
* @return $this
*/
public function setIsNoAddress(bool $isNoAddress)
{
$this->isNoAddress = $isNoAddress;
return $this;
}
/**
* Get customs informations in the address
*
* @return array
*/
public function getCustoms(): array
{
return $this->customs;
}
/**
* Store custom informations in the address
*
* @param array $customs
* @return $this
*/
public function setCustoms(array $customs): self
{
$this->customs = $customs;
return $this;
}
/**
* Validate the address.
*
* Check that:
*
* * if the address is not home address:
* * the postal code is present
* * the valid from is not null
* * the address street 1 is greater than 2
*
* @param ExecutionContextInterface $context
* @param array $payload
*/
public function validate(ExecutionContextInterface $context, $payload)
{
if (!$this->getValidFrom() instanceof \DateTime) {
$context
->buildViolation("address.date-should-be-set")
->atPath('validFrom')
->addViolation();
}
if ($this->isNoAddress()) {
return;
}
if (empty($this->getStreetAddress1())) {
$context
->buildViolation("address.street1-should-be-set")
->atPath('streetAddress1')
->addViolation();
}
if (!$this->getPostcode() instanceof PostalCode) {
$context
->buildViolation("address.postcode-should-be-set")
->atPath('postCode')
->addViolation();
}
}
public static function createFromAddress(Address $original) : Address
{
return (new Address())
->setPostcode($original->getPostcode())
->setStreetAddress1($original->getStreetAddress1())
->setStreetAddress2($original->getStreetAddress2())
->setValidFrom($original->getValidFrom())
;
}
}

View File

@ -0,0 +1,90 @@
<?php
/*
*
* Copyright (C) 2014, 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\Entity;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class Center implements HasCenterInterface
{
/**
*
* @var string
*/
private $name;
/**
*
* @var int
*/
private $id;
/**
*
* @var \Doctrine\Common\Collections\Collection
*/
private $groupCenters;
public function __construct()
{
$this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getId()
{
return $this->id;
}
public function getGroupCenters()
{
return $this->groupCenters;
}
public function addGroupCenter(GroupCenter $groupCenter)
{
$this->groupCenters->add($groupCenter);
return $this;
}
public function __toString()
{
return $this->getName();
}
public function getCenter()
{
return $this;
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Chill\MainBundle\Entity;
/**
* Country
*/
class Country
{
/**
* @var integer
*/
private $id;
/**
* @var string
*/
private $name;
/**
*
* @var string
*/
private $countryCode;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* @param string $name
* @return Country
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
public function __toString() {
return $this->getName();
}
/**
*
* @return the string
*/
public function getCountryCode()
{
return $this->countryCode;
}
/**
*
* @param string $countryCode
*/
public function setCountryCode($countryCode)
{
$this->countryCode = $countryCode;
return $this;
}
}

View File

@ -0,0 +1,119 @@
<?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>
*
* 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\Entity;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\PermissionsGroup;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class GroupCenter
{
/**
*
* @var int
*/
private $id;
/**
*
* @var Center
*/
private $center;
/**
*
* @var Collection
*/
private $users;
/**
*
* @var PermissionsGroup
*/
private $permissionsGroup;
public function __construct()
{
$this->permissionsGroup = new ArrayCollection();
$this->users = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
/**
*
* @return Center
*/
public function getCenter()
{
return $this->center;
}
/**
*
* @param Center $center
* @return \Chill\MainBundle\Entity\GroupCenter
*/
public function setCenter(Center $center)
{
$this->center = $center;
return $this;
}
public function getUsers()
{
return $this->users;
}
/**
*
* @return PermissionGroup
*/
public function getPermissionsGroup()
{
return $this->permissionsGroup;
}
/**
*
* @param \Chill\MainBundle\Entity\PermissionsGroup $permissionGroup
* @return \Chill\MainBundle\Entity\GroupCenter
*/
public function setPermissionsGroup(PermissionsGroup $permissionsGroup)
{
$this->permissionsGroup = $permissionsGroup;
return $this;
}
}

View File

@ -0,0 +1,36 @@
<?php
/*
* Copyright (C) 2015 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\Entity;
/**
* Interface for entities which may be linked to a center
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface HasCenterInterface
{
/**
* the linked center
*
* @return Center
*/
public function getCenter();
}

View File

@ -0,0 +1,35 @@
<?php
/*
* Copyright (C) 2015 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\Entity;
/**
* Interface for entities which have a scop
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface HasScopeInterface
{
/**
* Return the linked scope
*
* @return Scope
*/
public function getScope();
}

View File

@ -0,0 +1,82 @@
<?php
/*
*
* Copyright (C) 2014, 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\Entity;
/**
* Language
*/
class Language
{
/**
* @var string
*/
private $id;
/**
* @var string array
*/
private $name;
/**
* Get id
*
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* Set id
*
* @param string $id
* @return Language
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Set name
*
* @param string array $name
* @return Language
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string array
*/
public function getName()
{
return $this->name;
}
}

View File

@ -0,0 +1,154 @@
<?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>
*
* 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\Entity;
use Chill\MainBundle\Entity\RoleScope;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class PermissionsGroup
{
/**
*
* @var int
*/
private $id;
/**
*
* @var string
*/
private $name;
/**
*
* @var string[]
*/
private $flags = [];
/**
*
* @var \Doctrine\Common\Collections\Collection
*/
private $roleScopes;
/**
*
* @var \Doctrine\Common\Collections\Collection
*/
private $groupCenters;
public function __construct()
{
$this->roleScopes = new \Doctrine\Common\Collections\ArrayCollection();
$this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getId()
{
return $this->id;
}
/**
*
* @return string
*/
public function getName()
{
return $this->name;
}
public function getRoleScopes()
{
return $this->roleScopes;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
*
* @param RoleScope $roleScope
*/
public function addRoleScope(RoleScope $roleScope)
{
$this->roleScopes->add($roleScope);
}
/**
*
* @param RoleScope $roleScope
* @throws \RuntimeException if the roleScope could not be removed.
*/
public function removeRoleScope(RoleScope $roleScope)
{
$result = $this->roleScopes->removeElement($roleScope);
if ($result === FALSE) {
throw new \RuntimeException(sprintf("The roleScope '%s' could not be removed, "
. "aborting.", spl_object_hash($roleScope)));
}
}
public function getFlags()
{
return $this->flags;
}
public function setFlags(array $flags)
{
$this->flags = $flags;
return $this;
}
/**
* Test that a role scope is associated only once with the permission group
*
* @param ExecutionContextInterface $context
*/
public function isRoleScopePresentOnce(ExecutionContextInterface $context)
{
$roleScopesId = array_map(function(RoleScope $roleScope) {
return $roleScope->getId();
},
$this->getRoleScopes()->toArray());
$countedIds = array_count_values($roleScopesId);
foreach ($countedIds as $id => $nb) {
if ($nb > 1) {
$context->buildViolation("A permission is already present "
. "for the same role and scope")
->addViolation();
}
}
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace Chill\MainBundle\Entity;
/**
* PostalCode
*/
class PostalCode
{
/**
* @var integer
*/
private $id;
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $code;
/**
* @var \Chill\MainBundle\Entity\Country
*/
private $country;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* @param string $name
*
* @return PostalCode
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set code
*
* @param string $code
*
* @return PostalCode
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* Get code
*
* @return string
*/
public function getCode()
{
return $this->code;
}
/**
* Set country
*
* @param \Chill\MainBundle\Entity\Country $country
*
* @return PostalCode
*/
public function setCountry(\Chill\MainBundle\Entity\Country $country = null)
{
$this->country = $country;
return $this;
}
/**
* Get country
*
* @return \Chill\MainBundle\Entity\Country
*/
public function getCountry()
{
return $this->country;
}
}

View File

@ -0,0 +1,105 @@
<?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>
*
* 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\Entity;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class RoleScope
{
/**
*
* @var int
*/
private $id;
/**
*
* @var string
*/
private $role;
/**
*
* @var Scope
*/
private $scope;
/**
*
* @var \Doctrine\Common\Collections\Collection
*/
private $permissionsGroups;
public function __construct() {
$this->new = true;
$this->permissionsGroups = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getId()
{
return $this->id;
}
/**
*
* @return string
*/
public function getRole()
{
return $this->role;
}
/**
*
* @return Scope
*/
public function getScope()
{
return $this->scope;
}
/**
*
* @param type $role
* @return \Chill\MainBundle\Entity\RoleScope
*/
public function setRole($role)
{
$this->role = $role;
return $this;
}
/**
*
* @param \Chill\MainBundle\Entity\Scope $scope
* @return \Chill\MainBundle\Entity\RoleScope
*/
public function setScope(Scope $scope = null)
{
$this->scope = $scope;
return $this;
}
}

View File

@ -0,0 +1,93 @@
<?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>
*
* 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\Entity;
use Chill\MainBundle\Entity\RoleScope;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class Scope
{
/**
*
* @var int
*/
private $id;
/**
* translatable names
*
* @var array
*/
private $name = array();
/**
*
* @var \Doctrine\Common\Collections\Collection
*/
private $roleScopes;
public function __construct()
{
$this->roleScopes = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
*
* @return array
*/
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getRoleScopes()
{
return $this->roleScopes;
}
public function addRoleScope(RoleScope $roleScope)
{
$this->roleScopes->add($roleScope);
}
}

View File

@ -0,0 +1,288 @@
<?php
namespace Chill\MainBundle\Entity;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* User
*/
class User implements AdvancedUserInterface {
/**
* @var integer
*/
protected $id;
/**
* @var string
*/
private $username;
/**
*
* @var string
*/
private $usernameCanonical;
/**
*
* @var string
*/
private $email;
/**
*
* @var string
*/
private $emailCanonical;
/**
*
* @var string
*/
private $password;
/**
*
* @var string
* @internal must be set to null if we use bcrypt
*/
private $salt = null;
/**
*
* @var boolean
*/
private $locked = true;
/**
*
* @var boolean
*/
private $enabled = true;
/**
*
* @var Collection
*/
private $groupCenters;
public function __construct()
{
$this->groupCenters = new ArrayCollection();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set username
*
* @param string $name
* @return Agent
*/
public function setUsername($name)
{
$this->username = $name;
return $this;
}
public function __toString() {
return $this->getUsername();
}
public function eraseCredentials()
{
}
/**
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
public function getRoles()
{
return array('ROLE_USER');
}
public function getSalt()
{
return $this->salt;
}
public function getUsername()
{
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)
{
$this->password = $password;
return $this;
}
function setSalt($salt)
{
$this->salt = $salt;
return $this;
}
/**
* {@inheritdoc}
*
* @return boolean
*/
public function isAccountNonExpired()
{
return true;
}
/**
* {@inheritdoc}
*
*/
public function isAccountNonLocked()
{
return $this->locked;
}
/**
* {@inheritdoc}
*
* @return boolean
*/
public function isCredentialsNonExpired()
{
return true;
}
/**
* {@inheritdoc}
*
* @return boolean
*/
public function isEnabled()
{
return $this->enabled;
}
/**
*
* @param bool $enabled
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
return $this;
}
/**
*
* @return GroupCenter[]
*/
public function getGroupCenters()
{
return $this->groupCenters;
}
/**
*
* @param \Chill\MainBundle\Entity\GroupCenter $groupCenter
* @return \Chill\MainBundle\Entity\User
*/
public function addGroupCenter(GroupCenter $groupCenter)
{
$this->groupCenters->add($groupCenter);
return $this;
}
/**
*
* @param \Chill\MainBundle\Entity\GroupCenter $groupCenter
* @throws \RuntimeException if the groupCenter is not in the collection
*/
public function removeGroupCenter(GroupCenter $groupCenter)
{
if ($this->groupCenters->removeElement($groupCenter) === FALSE) {
throw new \RuntimeException(sprintf("The groupCenter could not be removed, "
. "it seems not to be associated with the user. Aborting."));
}
}
/**
* This function check that groupCenter are present only once. The validator
* use this function to avoid a user to be associated to the same groupCenter
* more than once.
*/
public function isGroupCenterPresentOnce(ExecutionContextInterface $context)
{
$groupCentersIds = array();
foreach ($this->getGroupCenters() as $groupCenter) {
if (in_array($groupCenter->getId(), $groupCentersIds)) {
$context->buildViolation("The user has already those permissions")
->addViolation();
} else {
$groupCentersIds[] = $groupCenter->getId();
}
}
}
}

View File

@ -0,0 +1,90 @@
<?php
/*
* Copyright (C) 2015 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\Export;
/**
* Interface for Aggregators.
*
* Aggregators gather result of a query. Most of the time, it will add
* a GROUP BY clause.
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface AggregatorInterface extends ModifierInterface
{
/**
* give the list of keys the current export added to the queryBuilder in
* self::initiateQuery
*
* Example: if your query builder will contains `SELECT count(id) AS count_id ...`,
* this function will return `array('count_id')`.
*
* @param mixed[] $data the data from the export's form (added by self::buildForm)
*/
public function getQueryKeys($data);
/**
* get a callable which will be able to transform the results into
* viewable and understable string.
*
* The callable will have only one argument: the `value` to translate.
*
* The callable should also be able to return a key `_header`, which
* will contains the header of the column.
*
* The string returned **must** be already translated if necessary,
* **with an exception** for the string returned for `_header`.
*
* Example :
*
* ```
* protected $translator;
*
* public function getLabels($key, array $values, $data)
* {
* return function($value) {
* case $value
* {
* case '_header' :
* return 'my header not translated';
* case true:
* return $this->translator->trans('true');
* case false:
* return $this->translator->trans('false');
* default:
* // this should not happens !
* throw new \LogicException();
* }
* }
* ```
*
* **Note:** Why each string must be translated with an exception for
* the `_header` ? For performance reasons: most of the value will be number
* which do not need to be translated, or value already translated in
* database. But the header must be, in every case, translated.
*
* @param string $key The column key, as added in the query
* @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR')
* @param mixed $data The data from the export's form (as defined in `buildForm`
* @return \Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }`
*/
public function getLabels($key, array $values, $data);
}

View File

@ -0,0 +1,15 @@
<?php
/*
*
*/
namespace Chill\MainBundle\Export;
use Symfony\Component\HttpFoundation\Response;
/**
*
*/
interface DirectExportInterface extends ExportElementInterface
{
}

View File

@ -0,0 +1,47 @@
<?php
/*
* Copyright (C) 2016 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\Export;
use Symfony\Component\Form\FormBuilderInterface;
/**
* The common methods between different object used to build export (i.e. : ExportInterface,
* FilterInterface, AggregatorInterface)
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface ExportElementInterface
{
/**
* get a title, which will be used in UI (and translated)
*
* @return string
*/
public function getTitle();
/**
* Add a form to collect data from the user.
*
* @param FormBuilderInterface $builder
*/
public function buildForm(FormBuilderInterface $builder);
}

View File

@ -0,0 +1,46 @@
<?php
/*
* Copyright (C) 2017 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;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* Add a validation method to validate data collected by Export Element
*
* Implements this interface on other `ExportElementInterface` to validate
* the form submitted by users.
*
* **note**: When this interface is implemented on filters or aggregators,
* the form is validated **only** if the filter/aggregator is `enabled` by the
* user.
*
* @link http://symfony.com/doc/current/validation/custom_constraint.html#class-constraint-validator Example of building violations
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface ExportElementValidatedInterface
{
/**
* validate the form's data and, if required, build a contraint
* violation on the data.
*
* @param mixed $data the data, as returned by the user
* @param ExecutionContextInterface $context
*/
public function validateForm($data, ExecutionContextInterface $context);
}

View 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();
}

View File

@ -0,0 +1,165 @@
<?php
/*
* Copyright (C) 2015 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\Export;
use Doctrine\ORM\QueryBuilder;
/**
* Interface for Export.
*
* An export is a class which will initiate a query for an export.
*
* **Note** : the report implementing this class will be modified by
* both filters **and** aggregators. If the report does not support
* aggregation, use `ListInterface`.
*
* @example Chill\PersonBundle\Export\CountPerson an example of implementation
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface ExportInterface extends ExportElementInterface
{
/**
* Return the Export's type. This will inform _on what_ export will apply.
* Most of the type, it will be a string which references an entity.
*
* Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE
*
* @return string
*/
public function getType();
/**
* A description, which will be used in the UI to explain what the export does.
* This description will be translated.
*
* @return string
*/
public function getDescription();
/**
* The initial query, which will be modified by ModifiersInterface
* (i.e. AggregatorInterface, FilterInterface).
*
* This query should take into account the `$acl` and restrict result only to
* what the user is allowed to see. (Do not show personal data the user
* is not allowed to see).
*
* The returned object should be an instance of QueryBuilder or NativeQuery.
*
* @param array $requiredModifiers
* @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )`
* @param array $data the data from the form, if any
* @return QueryBuilder|\Doctrine\ORM\NativeQuery the query to execute.
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = array());
/**
* Inform which ModifiersInterface (i.e. AggregatorInterface, FilterInterface)
* are allowed. The modifiers should be an array of types the _modifier_ apply on
* (@see ModifiersInterface::applyOn()).
*
* @return string[]
*/
public function supportsModifiers();
/**
* Return the required Role to execute the Export.
*
* @return \Symfony\Component\Security\Core\Role\Role
*
*/
public function requiredRole();
/**
* Return which formatter type is allowed for this report.
*
* @return string[]
*/
public function getAllowedFormattersTypes();
/**
* give the list of keys the current export added to the queryBuilder in
* self::initiateQuery
*
* Example: if your query builder will contains `SELECT count(id) AS count_id ...`,
* this function will return `array('count_id')`.
*
* @param mixed[] $data the data from the export's form (added by self::buildForm)
*/
public function getQueryKeys($data);
/**
* Return the results of the query builder.
*
* @param QueryBuilder|\Doctrine\ORM\NativeQuery $query
* @param mixed[] $data the data from the export's fomr (added by self::buildForm)
* @return mixed[] an array of results
*/
public function getResult($query, $data);
/**
* transform the results to viewable and understable string.
*
* The callable will have only one argument: the `value` to translate.
*
* The callable should also be able to return a key `_header`, which
* will contains the header of the column.
*
* The string returned **must** be already translated if necessary,
* **with an exception** for the string returned for `_header`.
*
* Example :
*
* ```
* protected $translator;
*
* public function getLabels($key, array $values, $data)
* {
* return function($value) {
* case $value
* {
* case '_header' :
* return 'my header not translated';
* case true:
* return $this->translator->trans('true');
* case false:
* return $this->translator->trans('false');
* default:
* // this should not happens !
* throw new \LogicException();
* }
* }
* ```
*
* **Note:** Why each string must be translated with an exception for
* the `_header` ? For performance reasons: most of the value will be number
* which do not need to be translated, or value already translated in
* database. But the header must be, in every case, translated.
*
*
* @param string $key The column key, as added in the query
* @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR')
* @param mixed $data The data from the export's form (as defined in `buildForm`
* @return \Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }`
*/
public function getLabels($key, array $values, $data);
}

View File

@ -0,0 +1,754 @@
<?php
/*
* Copyright (C) 2015 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\Export;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Symfony\Component\HttpFoundation\Response;
use Psr\Log\LoggerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Chill\MainBundle\Form\Type\Export\PickCenterType;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Export\ListInterface;
/**
* Collects all agregators, filters and export from
* the installed bundle, and performs the export logic.
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ExportManager
{
/**
* The collected filters, injected by DI
*
* @var FilterInterface[]
*/
private $filters = array();
/**
* The collected aggregators, injected by DI
*
* @var AggregatorInterface[]
*/
private $aggregators = array();
/**
* Collected Exports, injected by DI
*
* @var ExportInterface[]
*/
private $exports = array();
/**
* Collected Formatters, injected by DI
*
* @var FormatterInterface[]
*/
private $formatters = array();
/**
* a logger
*
* @var LoggerInterface
*/
private $logger;
/**
*
* @var EntityManagerInterface
*/
private $em;
/**
*
* @var AuthorizationChecker
*/
private $authorizationChecker;
/**
*
* @var AuthorizationHelper
*/
private $authorizationHelper;
/**
*
* @var \Symfony\Component\Security\Core\User\UserInterface
*/
private $user;
public function __construct(
LoggerInterface $logger,
EntityManagerInterface $em,
AuthorizationCheckerInterface $authorizationChecker,
AuthorizationHelper $authorizationHelper,
TokenStorageInterface $tokenStorage)
{
$this->logger = $logger;
$this->em = $em;
$this->authorizationChecker = $authorizationChecker;
$this->authorizationHelper = $authorizationHelper;
$this->user = $tokenStorage->getToken()->getUser();
}
/**
* add a Filter
*
* @internal Normally used by the dependency injection
*
* @param FilterInterface $filter
* @param string $alias
*/
public function addFilter(FilterInterface $filter, $alias)
{
$this->filters[$alias] = $filter;
}
/**
* add an aggregator
*
* @internal used by DI
*
* @param AggregatorInterface $aggregator
* @param string $alias
*/
public function addAggregator(AggregatorInterface $aggregator, $alias)
{
$this->aggregators[$alias] = $aggregator;
}
/**
* add an export
*
* @internal used by DI
*
* @param ExportInterface|DirectExportInterface $export
* @param type $alias
*/
public function addExport($export, $alias)
{
if ($export instanceof ExportInterface || $export instanceof DirectExportInterface) {
$this->exports[$alias] = $export;
} else {
throw new \InvalidArgumentException(sprintf("The export with alias %s "
. "does not implements %s or %s.", $alias, ExportInterface::class,
DirectExportInterface::class));
}
}
/**
* add a formatter
*
* @internal used by DI
*
* @param FormatterInterface $formatter
* @param type $alias
*/
public function addFormatter(FormatterInterface $formatter, $alias)
{
$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
*/
public function getExistingExportsTypes()
{
$existingTypes = array();
foreach($this->exports as $export) {
if (!in_array($export->getType(), $existingTypes)) {
array_push($existingTypes, $export->getType());
}
}
return $existingTypes;
}
/**
* Return all exports. The exports's alias are the array's keys.
*
* @param boolean $whereUserIsGranted if true (default), restrict to user which are granted the right to execute the export
* @return ExportInterface[] an array where export's alias are keys
*/
public function getExports($whereUserIsGranted = true)
{
foreach ($this->exports as $alias => $export) {
if ($whereUserIsGranted) {
if ($this->isGrantedForElement($export, null, null)) {
yield $alias => $export;
}
} else {
yield $alias => $export;
}
}
}
/**
* Get all exports grouped in an array.
*
* @param bool $whereUserIsGranted
* @return array where keys are the groups's name and value is an array of exports
*/
public function getExportsGrouped($whereUserIsGranted = true): array
{
$groups = [ '_' => [] ];
foreach ($this->getExports($whereUserIsGranted) as $alias => $export) {
if ($export instanceof GroupedExportInterface) {
$groups[$export->getGroup()][$alias] = $export;
} else {
$groups['_'][$alias] = $export;
}
}
return $groups;
}
/**
* Return an export by his alias
*
* @param string $alias
* @return ExportInterface
* @throws \RuntimeException
*/
public function getExport($alias)
{
if (!array_key_exists($alias, $this->exports)) {
throw new \RuntimeException("The export with alias $alias is not known.");
}
return $this->exports[$alias];
}
/**
*
* @param string $alias
* @return FilterInterface
* @throws \RuntimeException if the filter is not known
*/
public function getFilter($alias)
{
if (!array_key_exists($alias, $this->filters)) {
throw new \RuntimeException("The filter with alias $alias is not known.");
}
return $this->filters[$alias];
}
/**
* get all filters
*
* @param \Generator $aliases
*/
public function getFilters(array $aliases)
{
foreach($aliases as $alias) {
yield $alias => $this->getFilter($alias);
}
}
/**
*
* @param string $alias
* @return AggregatorInterface
* @throws \RuntimeException if the aggregator is not known
*/
public function getAggregator($alias)
{
if (!array_key_exists($alias, $this->aggregators)) {
throw new \RuntimeException("The aggregator with alias $alias is not known.");
}
return $this->aggregators[$alias];
}
public function getAggregators(array $aliases)
{
foreach ($aliases as $alias) {
yield $alias => $this->getAggregator($alias);
}
}
public function getFormatter($alias)
{
if (!array_key_exists($alias, $this->formatters)) {
throw new \RuntimeException("The formatter with alias $alias is not known.");
}
return $this->formatters[$alias];
}
/**
* Get all formatters which supports one of the given types.
*
*
* @param array $types
* @return \Generator
*/
public function getFormattersByTypes(array $types)
{
foreach ($this->formatters as $alias => $formatter) {
if (in_array($formatter->getType(), $types)) {
yield $alias => $formatter;
}
}
}
/**
* Return a \Generator containing filter which support type. If `$centers` is
* not null, restrict the given filters to the center the user have access to.
*
* if $centers is null, the function will returns all filters where the user
* has access in every centers he can reach (if the user can use the filter F in
* center A, but not in center B, the filter F will not be returned)
*
* @param \Chill\MainBundle\Entity\Center[] $centers the centers where the user have access to
* @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias
*/
public function &getFiltersApplyingOn(ExportInterface $export, array $centers = null)
{
foreach ($this->filters as $alias => $filter) {
if (in_array($filter->applyOn(), $export->supportsModifiers())
&& $this->isGrantedForElement($filter, $export, $centers)) {
yield $alias => $filter;
}
}
}
/**
* Return true if the current user has access to the ExportElement for every
* center, false if the user hasn't access to element for at least one center.
*
* @param \Chill\MainBundle\Export\ExportElementInterface $element
* @param ExportInterface|DirectExportInterface $export
* @param array|null $centers, if null, the function take into account all the reachables centers for the current user and the role given by element::requiredRole
* @return boolean
*/
public function isGrantedForElement(ExportElementInterface $element, ExportElementInterface $export = NULL, array $centers = null)
{
if ($element instanceof ExportInterface || $element instanceof DirectExportInterface) {
$role = $element->requiredRole();
} elseif ($element instanceof ModifierInterface ) {
if (is_null($element->addRole())) {
if (is_null($export)) {
throw new \LogicException("The export should not be null: as the "
. "ModifierInstance element is not an export, we should "
. "be aware of the export to determine which role is required");
} else {
$role = $export->requiredRole();
}
} else {
$role = $element->addRole();
}
} else {
throw new \LogicException("The element is not an ModifiersInterface or "
. "an ExportInterface.");
}
if ($centers === null) {
$centers = $this->authorizationHelper->getReachableCenters($this->user,
$role);
}
if (count($centers) === 0) {
return false;
}
foreach($centers as $center) {
if ($this->authorizationChecker->isGranted($role->getRole(), $center) === false) {
//debugging
$this->logger->debug('user has no access to element', array(
'method' => __METHOD__,
'type' => get_class($element),
'center' => $center->getName(),
'role' => $role->getRole()
));
return false;
}
}
return true;
}
/**
* Return a \Generator containing aggregators supported by the given export
*
* @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array
* @return AggregatorInterface[] a \Generator that contains aggretagors. The key is the filter's alias
*/
public function &getAggregatorsApplyingOn(ExportInterface $export, array $centers = null)
{
if ($export instanceof ListInterface) {
return;
}
foreach ($this->aggregators as $alias => $aggregator) {
if (in_array($aggregator->applyOn(), $export->supportsModifiers()) &&
$this->isGrantedForElement($aggregator, $export, $centers)) {
yield $alias => $aggregator;
}
}
}
/**
* Generate a response which contains the requested data.
*
* @param string $exportAlias
* @param mixed[] $data
* @return Response
*/
public function generate($exportAlias, array $pickedCentersData, array $data, array $formatterData)
{
$export = $this->getExport($exportAlias);
//$qb = $this->em->createQueryBuilder();
$centers = $this->getPickedCenters($pickedCentersData);
if ($export instanceof DirectExportInterface) {
return $export->generate(
$this->buildCenterReachableScopes($centers, $export),
$data[ExportType::EXPORT_KEY]
);
}
$query = $export->initiateQuery(
$this->retrieveUsedModifiers($data),
$this->buildCenterReachableScopes($centers, $export),
$data[ExportType::EXPORT_KEY]
);
if ($query instanceof \Doctrine\ORM\NativeQuery) {
// throw an error if the export require other modifier, which is
// not allowed when the export return a `NativeQuery`
if (count($export->supportsModifiers()) > 0) {
throw new \LogicException("The export with alias `$exportAlias` return "
. "a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not "
. "allowed. Either the method `supportsModifiers` should return an empty "
. "array, or return a `Doctrine\ORM\QueryBuilder`");
}
} elseif ($query instanceof QueryBuilder) {
//handle filters
$this->handleFilters($export, $query, $data[ExportType::FILTER_KEY], $centers);
//handle aggregators
$this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers);
$this->logger->debug('current query is '.$query->getDQL(), array(
'class' => self::class, 'function' => __FUNCTION__
));
} else {
throw new \UnexpectedValueException("The method `intiateQuery` should return "
. "a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` "
. "object.");
}
$result = $export->getResult($query, $data[ExportType::EXPORT_KEY]);
if (!is_iterable($result)) {
throw new \UnexpectedValueException(
sprintf(
'The result of the export should be an iterable, %s given',
gettype($result)
)
);
}
/* @var $formatter FormatterInterface */
$formatter = $this->getFormatter($this->getFormatterAlias($data));
$filtersData = array();
$aggregatorsData = array();
if ($query instanceof QueryBuilder) {
$aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]);
foreach($aggregators as $alias => $aggregator) {
$aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form'];
}
}
$filters = $this->retrieveUsedFilters($data[ExportType::FILTER_KEY]);
foreach($filters as $alias => $filter) {
$filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form'];
}
return $formatter->getResponse(
$result,
$formatterData,
$exportAlias,
$data[ExportType::EXPORT_KEY],
$filtersData,
$aggregatorsData);
}
/**
* build the array required for defining centers and circles in the initiate
* queries of ExportElementsInterfaces
*
* @param \Chill\MainBundle\Entity\Center[] $centers
*/
private function buildCenterReachableScopes(array $centers, ExportElementInterface $element) {
$r = array();
foreach($centers as $center) {
$r[] = array(
'center' => $center,
'circles' => $this->authorizationHelper->getReachableScopes($this->user,
$element->requiredRole(), $center)
);
}
return $r;
}
/**
* get the aggregators typse used in the form export data
*
* @param array $data the data from the export form
* @return string[]
*/
public function getUsedAggregatorsAliases(array $data)
{
$aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]);
return array_keys(iterator_to_array($aggregators));
}
/**
* get the formatter alias from the form export data
*
* @param array $data the data from the export form
* @string the formatter alias
*/
public function getFormatterAlias(array $data)
{
return $data[ExportType::PICK_FORMATTER_KEY]['alias'];
}
/**
* Get the Center picked by the user for this export. The data are
* extracted from the PickCenterType data
*
* @param array $data the data from a PickCenterType
* @return \Chill\MainBundle\Entity\Center[] the picked center
*/
public function getPickedCenters(array $data)
{
return $data;
}
/**
* parse the data to retrieve the used filters and aggregators
*
* @param mixed $data
* @return string[]
*/
private function retrieveUsedModifiers($data)
{
if ($data === null) {
return [];
}
$usedTypes = array_merge(
$this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]),
$this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY])
);
$this->logger->debug('Required types are '.implode(', ', $usedTypes),
array('class' => self::class, 'function' => __FUNCTION__));
return array_unique($usedTypes);
}
/**
* Retrieve the filter used in this export.
*
* @param mixed $data the data from the `filters` key of the ExportType
* @return array an array with types
*/
private function retrieveUsedFiltersType($data)
{
if ($data === null) {
return [];
}
$usedTypes = array();
foreach($data as $alias => $filterData) {
if ($filterData['enabled'] == true){
$filter = $this->getFilter($alias);
if (!in_array($filter->applyOn(), $usedTypes)) {
array_push($usedTypes, $filter->applyOn());
}
}
}
return $usedTypes;
}
/**
*
* @param mixed $data
* @return string[]
*/
private function retrieveUsedAggregatorsType($data)
{
if ($data === null) {
return [];
}
$usedTypes = array();
foreach($this->retrieveUsedAggregators($data) as $alias => $aggregator) {
if (!in_array($aggregator->applyOn(), $usedTypes)) {
array_push($usedTypes, $aggregator->applyOn());
}
}
return $usedTypes;
}
/**
*
* @param mixed $data
* @return AggregatorInterface[]
*/
private function retrieveUsedAggregators($data)
{
if ($data === null) {
return [];
}
foreach ($data as $alias => $aggregatorData) {
if ($aggregatorData['enabled'] === true){
yield $alias => $this->getAggregator($alias);
}
}
}
/**
*
* @param type $data the data from the filter key of the ExportType
*/
private function retrieveUsedFilters($data)
{
if ($data === null) {
return [];
}
foreach ($data as $alias => $filterData) {
if ($filterData['enabled'] === true) {
yield $alias => $this->getFilter($alias);
}
}
}
/**
* alter the query with selected filters.
*
* This function check the acl.
*
* @param ExportInterface $export
* @param QueryBuilder $qb
* @param mixed $data the data under the initial 'filters' data
* @param \Chill\MainBundle\Entity\Center[] $centers the picked centers
* @throw UnauthorizedHttpException if the user is not authorized
*/
private function handleFilters(
ExportInterface $export,
QueryBuilder $qb,
$data,
array $centers)
{
$filters = $this->retrieveUsedFilters($data);
foreach($filters as $alias => $filter) {
if ($this->isGrantedForElement($filter, $export, $centers) === false) {
throw new UnauthorizedHttpException("You are not authorized to "
. "use the filter ".$filter->getTitle());
}
$formData = $data[$alias];
$this->logger->debug('alter query by filter '.$alias, array(
'class' => self::class, 'function' => __FUNCTION__
));
$filter->alterQuery($qb, $formData['form']);
}
}
/**
* Alter the query with selected aggregators
*
* Check for acl. If an user is not authorized to see an aggregator, throw an
* UnauthorizedException.
*
* @param ExportInterface $export
* @param QueryBuilder $qb
* @param type $data
* @param \Chill\MainBundle\Entity\Center[] $centers the picked centers
* @throw UnauthorizedHttpException if the user is not authorized
*/
private function handleAggregators(
ExportInterface $export,
QueryBuilder $qb,
$data,
array $center)
{
$aggregators = $this->retrieveUsedAggregators($data);
foreach ($aggregators as $alias => $aggregator) {
$formData = $data[$alias];
$aggregator->alterQuery($qb, $formData['form']);
}
}
}

View File

@ -0,0 +1,70 @@
<?php
/*
* Copyright (C) 2015 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\Export;
/**
* Interface for filters.
*
* Filter will filter result on the query initiated by Export. Most of the time,
* it will add a `WHERE` clause on this query.
*
* Filters should not add column in `SELECT` clause.
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface FilterInterface extends ModifierInterface
{
const STRING_FORMAT = 'string';
/**
* Describe the filtering action.
*
* The description will be inserted in report to remains to the user the
* filters applyied on this report.
*
* Should return a statement about the filtering action, like :
* "filtering by date: from xxxx-xx-xx to xxxx-xx-xx" or
* "filtering by nationality: only Germany, France"
*
* The format will be determined by the $format. Currently, only 'string' is
* supported, later some 'html' will be added. The filter should always
* implement the 'string' format and fallback to it if other format are used.
*
* If no i18n is necessery, or if the filter translate the string by himself,
* this function should return a string. If the filter does not do any translation,
* the return parameter should be an array, where
*
* - the first element is the string,
* - and the second an array of parameters (may be an empty array)
* - the 3rd the domain (optional)
* - the 4th the locale (optional)
*
* Example: `array('my string with %parameter%', ['%parameter%' => 'good news'], 'mydomain', 'mylocale')`
*
* @param array $data
* @param string $format the format
* @return string|array a string with the data or, if translatable, an array where first element is string, second elements is an array of arguments
*/
public function describeAction($data, $format = 'string');
}

View File

@ -0,0 +1,465 @@
<?php
/*
* Copyright (C) 2016 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\Export\Formatter;
use Chill\MainBundle\Export\ExportInterface;
use Symfony\Component\HttpFoundation\Response;
use Chill\MainBundle\Export\FormatterInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Export\ExportManager;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* @deprecated this formatter is not used any more.
*/
class CSVFormatter implements FormatterInterface
{
/**
*
* @var TranslatorInterface
*/
protected $translator;
protected $result;
protected $formatterData;
protected $export;
protected $aggregators;
protected $exportData;
protected $aggregatorsData;
protected $filtersData;
protected $labels;
/**
*
* @var ExportManager
*/
protected $exportManager;
public function __construct(TranslatorInterface $translator,
ExportManager $manager)
{
$this->translator = $translator;
$this->exportManager = $manager;
}
public function getType()
{
return 'tabular';
}
public function getName()
{
return 'Comma separated values (CSV)';
}
/**
*
* @uses appendAggregatorForm
* @param FormBuilderInterface $builder
* @param type $exportAlias
* @param array $aggregatorAliases
*/
public function buildForm(FormBuilderInterface $builder, $exportAlias, array $aggregatorAliases)
{
$aggregators = $this->exportManager->getAggregators($aggregatorAliases);
$nb = count($aggregatorAliases);
foreach ($aggregators as $alias => $aggregator) {
$builderAggregator = $builder->create($alias, FormType::class, array(
'label' => $aggregator->getTitle(),
'block_name' => '_aggregator_placement_csv_formatter'
));
$this->appendAggregatorForm($builderAggregator, $nb);
$builder->add($builderAggregator);
}
}
/**
* append a form line by aggregator on the formatter form.
*
* This form allow to choose the aggregator position (row or column) and
* the ordering
*
* @param FormBuilderInterface $builder
* @param string $nbAggregators
*/
private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators)
{
$builder->add('order', ChoiceType::class, array(
'choices' => array_combine(
range(1, $nbAggregators),
range(1, $nbAggregators)
),
'choices_as_values' => true,
'multiple' => false,
'expanded' => false
));
$builder->add('position', ChoiceType::class, array(
'choices' => array(
'row' => 'r',
'column' => 'c'
),
'choices_as_values' => true,
'multiple' => false,
'expanded' => false
));
}
public function getResponse(
$result,
$formatterData,
$exportAlias,
array $exportData,
array $filtersData,
array $aggregatorsData
) {
$this->result = $result;
$this->orderingHeaders($formatterData);
$this->export = $this->exportManager->getExport($exportAlias);
$this->aggregators = iterator_to_array($this->exportManager
->getAggregators(array_keys($aggregatorsData)));
$this->exportData = $exportData;
$this->aggregatorsData = $aggregatorsData;
$this->labels = $this->gatherLabels();
$this->filtersData = $filtersData;
$response = new Response();
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
//$response->headers->set('Content-Disposition','attachment; filename="export.csv"');
$response->setContent($this->generateContent());
return $response;
}
/**
* ordering aggregators, preserving key association.
*
* This function do not mind about position.
*
* If two aggregators have the same order, the second given will be placed
* after. This is not significant for the first ordering.
*
* @param type $formatterData
* @return type
*/
protected function orderingHeaders($formatterData)
{
$this->formatterData = $formatterData;
uasort($this->formatterData, function($a, $b) {
return ($a['order'] <= $b['order'] ? -1 : 1);
});
}
protected function generateContent()
{
$rowKeysNb = count($this->getRowHeaders());
$columnKeysNb = count($this->getColumnHeaders());
$resultsKeysNb = count($this->export->getQueryKeys($this->exportData));
$results = $this->getOrderedResults();
/* @var $columnHeaders string[] the column headers associations */
$columnHeaders = array();
/* @var $data string[] the data of the csv file */
$contentData = array();
$content = array();
function findColumnPosition(&$columnHeaders, $columnToFind) {
$i = 0;
foreach($columnHeaders as $set) {
if ($set === $columnToFind) {
return $i;
}
$i++;
}
//we didn't find it, adding the column
$columnHeaders[] = $columnToFind;
return $i++;
}
// create a file pointer connected to the output stream
$output = fopen('php://output', 'w');
//title
fputcsv($output, array($this->translator->trans($this->export->getTitle())));
//blank line
fputcsv($output, array(""));
// add filtering description
foreach($this->gatherFiltersDescriptions() as $desc) {
fputcsv($output, array($desc));
}
// blank line
fputcsv($output, array(""));
// iterate on result to : 1. populate row headers, 2. populate column headers, 3. add result
foreach ($results as $row) {
$rowHeaders = array_slice($row, 0, $rowKeysNb);
//first line : we create line and adding them row headers
if (!isset($line)) {
$line = array_slice($row, 0, $rowKeysNb);
}
// do we have to create a new line ? if the rows are equals, continue on the line, else create a next line
if (array_slice($line, 0, $rowKeysNb) !== $rowHeaders) {
$contentData[] = $line;
$line = array_slice($row, 0, $rowKeysNb);
}
// add the column headers
/* @var $columns string[] the column for this row */
$columns = array_slice($row, $rowKeysNb, $columnKeysNb);
$columnPosition = findColumnPosition($columnHeaders, $columns);
//fill with blank at the position given by the columnPosition + nbRowHeaders
for ($i=0; $i < $columnPosition; $i++) {
if (!isset($line[$rowKeysNb + $i])) {
$line[$rowKeysNb + $i] = "";
}
}
$resultData = array_slice($row, $resultsKeysNb*-1);
foreach($resultData as $data) {
$line[] = $data;
}
}
// we add the last line
$contentData[] = $line;
//column title headers
for ($i=0; $i < $columnKeysNb; $i++) {
$line = array_fill(0, $rowKeysNb, '');
foreach($columnHeaders as $set) {
$line[] = $set[$i];
}
$content[] = $line;
}
//row title headers
$headerLine = array();
foreach($this->getRowHeaders() as $headerKey) {
$headerLine[] = array_key_exists('_header', $this->labels[$headerKey]) ?
$this->labels[$headerKey]['_header'] : '';
}
foreach($this->export->getQueryKeys($this->exportData) as $key) {
$headerLine[] = array_key_exists('_header', $this->labels[$key]) ?
$this->labels[$key]['_header'] : '';
}
fputcsv($output, $headerLine);
unset($headerLine); //free memory
//generate CSV
foreach($content as $line) {
fputcsv($output, $line);
}
foreach($contentData as $line) {
fputcsv($output, $line);
}
$text = stream_get_contents($output);
fclose($output);
return $text;
}
private function getOrderedResults()
{
$r = array();
$results = $this->result;
$labels = $this->labels;
$rowKeys = $this->getRowHeaders();
$columnKeys = $this->getColumnHeaders();
$resultsKeys = $this->export->getQueryKeys($this->exportData);
$headers = array_merge($rowKeys, $columnKeys);
foreach ($results as $row) {
$line = array();
foreach ($headers as $key) {
$line[] = call_user_func($labels[$key], $row[$key]);
}
//append result
foreach ($resultsKeys as $key) {
$line[] = call_user_func($labels[$key], $row[$key]);
}
$r[] = $line;
}
array_multisort($r);
return $r;
}
protected function getRowHeaders()
{
return $this->getPositionnalHeaders('r');
}
protected function getColumnHeaders()
{
return $this->getPositionnalHeaders('c');
}
/**
*
* @param string $position may be 'c' (column) or 'r' (row)
* @return string[]
* @throws \RuntimeException
*/
private function getPositionnalHeaders($position)
{
$headers = array();
foreach($this->formatterData as $alias => $data) {
if (!array_key_exists($alias, $this->aggregatorsData)) {
throw new \RuntimeException("the formatter wants to use the "
. "aggregator with alias $alias, but the export do not "
. "contains data about it");
}
$aggregator = $this->aggregators[$alias];
if ($data['position'] === $position) {
$headers = array_merge($headers, $aggregator->getQueryKeys($this->aggregatorsData[$alias]));
}
}
return $headers;
}
/**
*
* @param mixed $result
* @param \Chill\MainBundle\Export\AggregatorInterface[] $aggregators
*/
protected function gatherLabels()
{
return array_merge(
$this->gatherLabelsFromAggregators(),
$this->gatherLabelsFromExport()
);
}
protected function gatherLabelsFromAggregators()
{
$labels = array();
/* @var $aggretator \Chill\MainBundle\Export\AggregatorInterface */
foreach ($this->aggregators as $alias => $aggregator) {
$keys = $aggregator->getQueryKeys($this->aggregatorsData[$alias]);
// gather data in an array
foreach($keys as $key) {
$values = array_map(function($row) use ($key, $alias) {
if (!array_key_exists($key, $row)) {
throw new \LogicException("the key '".$key."' is declared by "
. "the aggregator with alias '".$alias."' but is not "
. "present in results");
}
return $row[$key];
}, $this->result);
$labels[$key] = $aggregator->getLabels($key, array_unique($values),
$this->aggregatorsData[$alias]);
}
}
return $labels;
}
protected function gatherLabelsFromExport()
{
$labels = array();
$export = $this->export;
$keys = $this->export->getQueryKeys($this->exportData);
foreach($keys as $key) {
$values = array_map(function($row) use ($key, $export) {
if (!array_key_exists($key, $row)) {
throw new \LogicException("the key '".$key."' is declared by "
. "the export with title '".$export->getTitle()."' but is not "
. "present in results");
}
return $row[$key];
}, $this->result);
$labels[$key] = $this->export->getLabels($key, array_unique($values),
$this->exportData);
}
return $labels;
}
public function gatherFiltersDescriptions()
{
$descriptions = array();
foreach($this->filtersData as $key => $filterData) {
$statement = $this->exportManager
->getFilter($key)
->describeAction($filterData);
if ($statement === null) {
continue;
}
if (is_array($statement)) {
$descriptions[] = $this->translator->trans(
$statement[0],
$statement[1],
isset($statement[2]) ? $statement[2] : null,
isset($statement[3]) ? $statement[3] : null);
} else {
$descriptions[] = $statement;
}
}
return $descriptions;
}
}

View File

@ -0,0 +1,238 @@
<?php
/*
* Copyright (C) 2016 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\Export\Formatter;
use Symfony\Component\HttpFoundation\Response;
use Chill\MainBundle\Export\FormatterInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Export\ExportManager;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
/**
* Create a CSV List for the export
*
* @author Champs-Libres <info@champs-libres.coop>
*/
class CSVListFormatter implements FormatterInterface
{
/**
* This variable cache the labels internally
*
* @var string[]
*/
protected $labelsCache = null;
protected $result = null;
protected $exportAlias = null;
protected $exportData = null;
protected $formatterData = null;
/**
*
* @var ExportManager
*/
protected $exportManager;
/**
*
* @var TranslatorInterface
*/
protected $translator;
public function __construct(TranslatorInterface $translator, ExportManager $manager)
{
$this->translator = $translator;
$this->exportManager = $manager;
}
public function getType()
{
return FormatterInterface::TYPE_LIST;
}
public function getName()
{
return 'CSV vertical list';
}
/**
* build a form, which will be used to collect data required for the execution
* of this formatter.
*
* @uses appendAggregatorForm
* @param FormBuilderInterface $builder
* @param type $exportAlias
* @param array $aggregatorAliases
*/
public function buildForm(
FormBuilderInterface $builder,
$exportAlias,
array $aggregatorAliases
){
$builder->add('numerotation', ChoiceType::class, array(
'choices' => array(
'yes' => true,
'no' => false
),
'expanded' => true,
'multiple' => false,
'label' => "Add a number on first column",
'choices_as_values' => true,
'data' => true
));
}
/**
* Generate a response from the data collected on differents ExportElementInterface
*
* @param mixed[] $result The result, as given by the ExportInterface
* @param mixed[] $formatterData collected from the current form
* @param string $exportAlias the id of the current export
* @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data
* @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data
* @return \Symfony\Component\HttpFoundation\Response The response to be shown
*/
public function getResponse(
$result,
$formatterData,
$exportAlias,
array $exportData,
array $filtersData,
array $aggregatorsData
) {
$this->result = $result;
$this->exportAlias = $exportAlias;
$this->exportData = $exportData;
$this->formatterData = $formatterData;
$output = fopen('php://output', 'w');
$this->prepareHeaders($output);
$i = 1;
foreach ($result as $row) {
$line = array();
if ($this->formatterData['numerotation'] === true) {
$line[] = $i;
}
foreach ($row as $key => $value) {
$line[] = $this->getLabel($key, $value);
}
fputcsv($output, $line);
$i++;
}
$csvContent = stream_get_contents($output);
fclose($output);
$response = new Response();
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
//$response->headers->set('Content-Disposition','attachment; filename="export.csv"');
$response->setContent($csvContent);
return $response;
}
/**
* add the headers to the csv file
*
* @param resource $output
*/
protected function prepareHeaders($output)
{
$keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData);
// we want to keep the order of the first row. So we will iterate on the first row of the results
$first_row = count($this->result) > 0 ? $this->result[0] : array();
$header_line = array();
if ($this->formatterData['numerotation'] === true) {
$header_line[] = $this->translator->trans('Number');
}
foreach ($first_row as $key => $value) {
$header_line[] = $this->translator->trans(
$this->getLabel($key, '_header'));
}
if (count($header_line) > 0) {
fputcsv($output, $header_line);
}
}
/**
* Give the label corresponding to the given key and value.
*
* @param string $key
* @param string $value
* @return string
* @throws \LogicException if the label is not found
*/
protected function getLabel($key, $value)
{
if ($this->labelsCache === null) {
$this->prepareCacheLabels();
}
if (!\array_key_exists($key, $this->labelsCache)){
throw new \OutOfBoundsException(sprintf("The key \"%s\" "
. "is not present in the list of keys handled by "
. "this query. Check your `getKeys` and `getLabels` "
. "methods. Available keys are %s.", $key,
\implode(", ", \array_keys($this->labelsCache))));
}
return $this->labelsCache[$key]($value);
}
/**
* Prepare the label cache which will be used by getLabel. This function
* should be called only once in the generation lifecycle.
*/
protected function prepareCacheLabels()
{
$export = $this->exportManager->getExport($this->exportAlias);
$keys = $export->getQueryKeys($this->exportData);
foreach($keys as $key) {
// get an array with all values for this key if possible
$values = \array_map(function ($v) use ($key) { return $v[$key]; }, $this->result);
// store the label in the labelsCache property
$this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData);
}
}
}

View File

@ -0,0 +1,229 @@
<?php
/*
* Copyright (C) 2016 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\Export\Formatter;
use Symfony\Component\HttpFoundation\Response;
use Chill\MainBundle\Export\FormatterInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Export\ExportManager;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
/**
* Create a CSV List for the export where the header are printed on the
* first column, and the result goes from left to right.
*
* @author Champs-Libres <info@champs-libres.coop>
*/
class CSVPivotedListFormatter implements FormatterInterface
{
/**
* This variable cache the labels internally
*
* @var string[]
*/
protected $labelsCache = null;
protected $result = null;
protected $exportAlias = null;
protected $exportData = null;
protected $formatterData = null;
/**
*
* @var ExportManager
*/
protected $exportManager;
/**
*
* @var TranslatorInterface
*/
protected $translator;
public function __construct(TranslatorInterface $translator, ExportManager $manager)
{
$this->translator = $translator;
$this->exportManager = $manager;
}
public function getType()
{
return FormatterInterface::TYPE_LIST;
}
public function getName()
{
return 'CSV horizontal list';
}
/**
* build a form, which will be used to collect data required for the execution
* of this formatter.
*
* @uses appendAggregatorForm
* @param FormBuilderInterface $builder
* @param type $exportAlias
* @param array $aggregatorAliases
*/
public function buildForm(
FormBuilderInterface $builder,
$exportAlias,
array $aggregatorAliases
){
$builder->add('numerotation', ChoiceType::class, array(
'choices' => array(
'yes' => true,
'no' => false
),
'expanded' => true,
'multiple' => false,
'label' => "Add a number on first column",
'choices_as_values' => true,
'data' => true
));
}
/**
* Generate a response from the data collected on differents ExportElementInterface
*
* @param mixed[] $result The result, as given by the ExportInterface
* @param mixed[] $formatterData collected from the current form
* @param string $exportAlias the id of the current export
* @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data
* @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data
* @return \Symfony\Component\HttpFoundation\Response The response to be shown
*/
public function getResponse(
$result,
$formatterData,
$exportAlias,
array $exportData,
array $filtersData,
array $aggregatorsData
) {
$this->result = $result;
$this->exportAlias = $exportAlias;
$this->exportData = $exportData;
$this->formatterData = $formatterData;
$output = fopen('php://output', 'w');
$i = 1;
$lines = array();
$this->prepareHeaders($lines);
foreach ($result as $row) {
$j = 0;
if ($this->formatterData['numerotation'] === true) {
$lines[$j][] = $i;
$j++;
}
foreach ($row as $key => $value) {
$lines[$j][] = $this->getLabel($key, $value);
$j++;
}
$i++;
}
//adding the lines to the csv output
foreach($lines as $line) {
fputcsv($output, $line);
}
$csvContent = stream_get_contents($output);
fclose($output);
$response = new Response();
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
$response->headers->set('Content-Disposition','attachment; filename="export.csv"');
$response->setContent($csvContent);
return $response;
}
/**
* add the headers to lines array
*
* @param array $lines the lines where the header will be added
*/
protected function prepareHeaders(array &$lines)
{
$keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData);
// we want to keep the order of the first row. So we will iterate on the first row of the results
$first_row = count($this->result) > 0 ? $this->result[0] : array();
$header_line = array();
if ($this->formatterData['numerotation'] === true) {
$lines[] = array($this->translator->trans('Number'));
}
foreach ($first_row as $key => $value) {
$lines[] = array($this->getLabel($key, '_header'));
}
}
/**
* Give the label corresponding to the given key and value.
*
* @param string $key
* @param string $value
* @return string
* @throws \LogicException if the label is not found
*/
protected function getLabel($key, $value)
{
if ($this->labelsCache === null) {
$this->prepareCacheLabels();
}
return $this->labelsCache[$key]($value);
}
/**
* Prepare the label cache which will be used by getLabel. This function
* should be called only once in the generation lifecycle.
*/
protected function prepareCacheLabels()
{
$export = $this->exportManager->getExport($this->exportAlias);
$keys = $export->getQueryKeys($this->exportData);
foreach($keys as $key) {
// get an array with all values for this key if possible
$values = \array_map(function ($v) use ($key) { return $v[$key]; }, $this->result);
// store the label in the labelsCache property
$this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData);
}
}
}

View File

@ -0,0 +1,585 @@
<?php
/*
* Copyright (C) 2017 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\Formatter;
use Symfony\Component\HttpFoundation\Response;
use Chill\MainBundle\Export\FormatterInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Export\ExportManager;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class SpreadSheetFormatter implements FormatterInterface
{
/**
*
* @var TranslatorInterface
*/
protected $translator;
/**
*
* @var ExportManager
*/
protected $exportManager;
/**
* The result, as returned by the export
*
* replaced when `getResponse` is called.
*
* @var type
*/
protected $result;
/**
*
* replaced when `getResponse` is called.
*
* @var type
*/
protected $formatterData;
/**
* The export
*
* replaced when `getResponse` is called.
*
* @var \Chill\MainBundle\Export\ExportInterface
*/
protected $export;
/**
*
* replaced when `getResponse` is called.
*
* @var type
*/
//protected $aggregators;
/**
* array containing value of export form
*
* replaced when `getResponse` is called.
*
* @var array
*/
protected $exportData;
/**
* an array where keys are the aggregators aliases and
* values are the data
*
* replaced when `getResponse` is called.
*
* @var type
*/
protected $aggregatorsData;
/**
*
* replaced when `getResponse` is called.
*
* @var array
*/
protected $filtersData;
/**
*
* replaced when `getResponse` is called.
*
* @var array
*/
//protected $labels;
/**
* temporary file to store spreadsheet
*
* @var string
*/
protected $tempfile;
/**
* cache for displayable result.
*
* This cache is reset when `getResponse` is called.
*
* The array's keys are the keys in the raw result, and
* values are the callable which will transform the raw result to
* displayable result.
*
* @var array
*/
private $cacheDisplayableResult;
/**
* Whethe `cacheDisplayableResult` is initialized or not.
*
* @var boolean
*/
private $cacheDisplayableResultIsInitialized = false;
public function __construct(TranslatorInterface $translatorInterface, ExportManager $exportManager)
{
$this->translator = $translatorInterface;
$this->exportManager = $exportManager;
}
public function buildForm(
FormBuilderInterface $builder,
$exportAlias,
array $aggregatorAliases
) {
// choosing between formats
$builder->add('format', ChoiceType::class, array(
'choices' => array(
'OpenDocument Format (.ods) (LibreOffice, ...)' => 'ods',
'Microsoft Excel 2007-2013 XML (.xlsx) (Microsoft Excel, LibreOffice)' => 'xlsx',
'Comma separated values (.csv)' => 'csv'
),
'choices_as_values' => true,
'placeholder' => 'Choose the format'
));
// ordering aggregators
$aggregators = $this->exportManager->getAggregators($aggregatorAliases);
$nb = count($aggregatorAliases);
foreach ($aggregators as $alias => $aggregator) {
$builderAggregator = $builder->create($alias, FormType::class, array(
'label' => $aggregator->getTitle(),
'block_name' => '_aggregator_placement_spreadsheet_formatter'
));
$this->appendAggregatorForm($builderAggregator, $nb);
$builder->add($builderAggregator);
}
}
/**
* append a form line by aggregator on the formatter form.
*
* This form allow to choose the aggregator position (row or column) and
* the ordering
*
* @param FormBuilderInterface $builder
* @param string $nbAggregators
*/
private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators)
{
$builder->add('order', ChoiceType::class, array(
'choices' => array_combine(
range(1, $nbAggregators),
range(1, $nbAggregators)
),
'choices_as_values' => true,
'multiple' => false,
'expanded' => false
));
}
public function getName()
{
return 'SpreadSheet (xlsx, ods)';
}
public function getResponse(
$result,
$formatterData,
$exportAlias,
array $exportData,
array $filtersData,
array $aggregatorsData
) {
// store all data when the process is initiated
$this->result = $result;
$this->formatterData = $formatterData;
$this->export = $this->exportManager->getExport($exportAlias);
$this->exportData = $exportData;
$this->filtersData = $filtersData;
$this->aggregatorsData = $aggregatorsData;
// reset cache
$this->cacheDisplayableResult = array();
$this->cacheDisplayableResultIsInitialized = false;
$response = new Response();
$response->headers->set('Content-Type',
$this->getContentType($this->formatterData['format']));
$this->tempfile = \tempnam(\sys_get_temp_dir(), '');
$this->generatecontent();
$f = \fopen($this->tempfile, 'r');
$response->setContent(\stream_get_contents($f));
fclose($f);
// remove the temp file from disk
\unlink($this->tempfile);
return $response;
}
/**
* Generate the content and write it to php://temp
*/
protected function generateContent()
{
list($spreadsheet, $worksheet) = $this->createSpreadsheet();
$this->addTitleToWorkSheet($worksheet);
$line = $this->addFiltersDescription($worksheet);
// at this point, we are going to sort retsults for an easier manipulation
list($sortedResult, $exportKeys, $aggregatorKeys, $globalKeys) =
$this->sortResult();
$line = $this->addHeaders($worksheet, $globalKeys, $line);
$line = $this->addContentTable($worksheet, $sortedResult, $line);
switch ($this->formatterData['format'])
{
case 'ods':
$writer = \PhpOffice\PhpSpreadsheet\IOFactory
::createWriter($spreadsheet, 'Ods');
break;
case 'xlsx':
$writer = \PhpOffice\PhpSpreadsheet\IOFactory
::createWriter($spreadsheet, 'Xlsx');
break;
case 'csv':
$writer = \PhpOffice\PhpSpreadsheet\IOFactory
::createWriter($spreadsheet, 'Csv');
break;
default:
// this should not happen
// throw an exception to ensure that the error is catched
throw new \LogicException();
}
$writer->save($this->tempfile);
}
/**
* Create a spreadsheet and a working worksheet
*
* @return array where 1st member is spreadsheet, 2nd is worksheet
*/
protected function createSpreadsheet()
{
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
// setting the worksheet title and code name
$worksheet
->setTitle($this->getTitle())
->setCodeName('result');
return [$spreadsheet, $worksheet];
}
/**
* Add the title to the worksheet and merge the cell containing
* the title
*
* @param Worksheet $worksheet
*/
protected function addTitleToWorkSheet(Worksheet &$worksheet)
{
$worksheet->setCellValue('A1', $this->getTitle());
$worksheet->mergeCells('A1:G1');
}
/**
* Add filter description since line 3.
*
* return the line number after the last description
*
* @param Worksheet $worksheet
* @return int the line number after the last description
*/
protected function addFiltersDescription(Worksheet &$worksheet)
{
$line = 3;
foreach ($this->filtersData as $alias => $data) {
$filter = $this->exportManager->getFilter($alias);
$description = $filter->describeAction($data, 'string');
if (is_array($description)) {
$description = $this->translator
->trans(
$description[0],
isset($description[1]) ? $description[1] : []
);
}
$worksheet->setCellValue('A'.$line, $description);
$line ++;
}
return $line;
}
/**
* sort the results, and return an array where :
* - 0 => sorted results
* - 1 => export keys
* - 2 => aggregator keys
* - 3 => global keys (aggregator keys and export keys)
*
*
* Example, assuming that the result contains two aggregator keys :
*
* array in result :
*
* ```
* array(
* array( //row 1
* 'export_result' => 1,
* 'aggregator_1' => 2,
* 'aggregator_2' => 3
* ),
* array( // row 2
* 'export_result' => 4,
* 'aggregator_1' => 5,
* 'aggregator_2' => 6
* )
* )
* ```
*
* the sorted result will be :
*
* ```
* array(
* array( 2, 3, 1 ),
* array( 5, 6, 4 )
* )
* ```
*
*/
protected function sortResult()
{
// get the keys for each row
$exportKeys = $this->export->getQueryKeys($this->exportData);
$aggregatorKeys = $this->getAggregatorKeysSorted();
$globalKeys = \array_merge($aggregatorKeys, $exportKeys);
$sortedResult = \array_map(function ($row) use ($globalKeys) {
$newRow = array();
foreach ($globalKeys as $key) {
$newRow[] = $this->getDisplayableResult($key, $row[$key]);
}
return $newRow;
}, $this->result);
\array_multisort($sortedResult);
return array($sortedResult, $exportKeys, $aggregatorKeys, $globalKeys);
}
/**
* get an array of aggregator keys. The keys are sorted as asked
* by user in the form.
*
* @return string[] an array containing the keys of aggregators
*/
protected function getAggregatorKeysSorted()
{
// empty array for aggregators keys
$keys = array();
// this association between key and aggregator alias will be used
// during sorting
$aggregatorKeyAssociation = array();
foreach ($this->aggregatorsData as $alias => $data) {
$aggregator = $this->exportManager->getAggregator($alias);
$aggregatorsKeys = $aggregator->getQueryKeys($data);
// append the keys from aggregator to the $keys existing array
$keys = \array_merge($keys, $aggregatorsKeys);
// append the key with the alias, which will be use later for sorting
foreach ($aggregatorsKeys as $key) {
$aggregatorKeyAssociation[$key] = $alias;
}
}
// sort the result using the form
usort($keys, function ($a, $b) use ($aggregatorKeyAssociation) {
$A = $this->formatterData[$aggregatorKeyAssociation[$a]]['order'];
$B = $this->formatterData[$aggregatorKeyAssociation[$b]]['order'];
if ($A === $B) {
return 0;
} elseif ($A > $B) {
return 1;
} else {
return -1;
}
});
return $keys;
}
/**
* add headers to worksheet
*
* return the line number where the next content (i.e. result) should
* be appended.
*
* @param Worksheet $worksheet
* @param array $aggregatorKeys
* @param array $exportKeys
* @param int $line
* @return int
*/
protected function addHeaders(
Worksheet &$worksheet,
array $globalKeys,
$line
) {
// get the displayable form of headers
$displayables = array();
foreach ($globalKeys as $key) {
$displayables[] = $this->translator->trans(
$this->getDisplayableResult($key, '_header')
);
}
// add headers on worksheet
$worksheet->fromArray(
$displayables,
NULL,
'A'.$line);
return $line + 1;
}
protected function addContentTable(Worksheet $worksheet,
$sortedResults,
$line
) {
$worksheet->fromArray(
$sortedResults,
NULL,
'A'.$line);
return $line + count($sortedResults) + 1;
}
protected function getTitle()
{
return $this->translator->trans($this->export->getTitle());
}
/**
* Get the displayable result.
*
* @param string $key
* @param string $value
* @return string
*/
protected function getDisplayableResult($key, $value)
{
if ($this->cacheDisplayableResultIsInitialized === false) {
$this->initializeCache($key);
}
return call_user_func($this->cacheDisplayableResult[$key], $value);
}
protected function initializeCache($key)
{
/*
* this function follows the following steps :
*
* 1. associate all keys used in result with their export element
* (export or aggregator) and data;
* 2. associate all keys used in result with all the possible values :
* this array will be necessary to call `getLabels`
* 3. store the `callable` in an associative array, in cache
*/
// 1. create an associative array with key and export / aggregator
$keysExportElementAssociation = array();
// keys for export
foreach ($this->export->getQueryKeys($this->exportData) as $key) {
$keysExportElementAssociation[$key] = [$this->export,
$this->exportData];
}
// keys for aggregator
foreach ($this->aggregatorsData as $alias => $data) {
$aggregator = $this->exportManager->getAggregator($alias);
foreach ($aggregator->getQueryKeys($data) as $key) {
$keysExportElementAssociation[$key] = [$aggregator, $data];
}
}
// 2. collect all the keys before iteration
$keys = array_keys($keysExportElementAssociation);
$allValues = array();
// store all the values in an array
foreach ($this->result as $row) {
foreach ($keys as $key) {
$allValues[$key][] = $row[$key];
}
}
// 3. iterate on `keysExportElementAssociation` to store the callable
// in cache
foreach ($keysExportElementAssociation as $key => list($element, $data)) {
$this->cacheDisplayableResult[$key] =
$element->getLabels($key, \array_unique($allValues[$key]), $data);
}
// the cache is initialized !
$this->cacheDisplayableResultIsInitialized = true;
}
protected function getContentType($format)
{
switch ($format)
{
case 'csv':
return 'text/csv';
case 'ods':
return 'application/vnd.oasis.opendocument.spreadsheet';
case 'xlsx':
return 'application/vnd.openxmlformats-officedocument.'
. 'spreadsheetml.sheet';
}
}
public function getType()
{
return 'tabular';
}
}

View File

@ -0,0 +1,287 @@
<?php
/*
* Copyright (C) 2016 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\Export\Formatter;
use Symfony\Component\HttpFoundation\Response;
use Chill\MainBundle\Export\FormatterInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Export\ExportManager;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
/**
* Create a CSV List for the export
*
* @author Champs-Libres <info@champs-libres.coop>
*/
class SpreadsheetListFormatter implements FormatterInterface
{
/**
* This variable cache the labels internally
*
* @var string[]
*/
protected $labelsCache = null;
protected $result = null;
protected $exportAlias = null;
protected $exportData = null;
protected $formatterData = null;
/**
*
* @var ExportManager
*/
protected $exportManager;
/**
*
* @var TranslatorInterface
*/
protected $translator;
public function __construct(TranslatorInterface $translator, ExportManager $manager)
{
$this->translator = $translator;
$this->exportManager = $manager;
}
public function getType()
{
return FormatterInterface::TYPE_LIST;
}
/**
* build a form, which will be used to collect data required for the execution
* of this formatter.
*
* @uses appendAggregatorForm
* @param FormBuilderInterface $builder
* @param type $exportAlias
* @param array $aggregatorAliases
*/
public function buildForm(
FormBuilderInterface $builder,
$exportAlias,
array $aggregatorAliases
){
$builder
->add('format', ChoiceType::class, array(
'choices' => array(
'OpenDocument Format (.ods) (LibreOffice, ...)' => 'ods',
'Microsoft Excel 2007-2013 XML (.xlsx) (Microsoft Excel, LibreOffice)' => 'xlsx'
),
'choices_as_values' => true,
'placeholder' => 'Choose the format'
))
->add('numerotation', ChoiceType::class, array(
'choices' => array(
'yes' => true,
'no' => false
),
'expanded' => true,
'multiple' => false,
'label' => "Add a number on first column",
'choices_as_values' => true,
'data' => true
));
}
public function getName()
{
return 'Spreadsheet list formatter (.xlsx, .ods)';
}
/**
* Generate a response from the data collected on differents ExportElementInterface
*
* @param mixed[] $result The result, as given by the ExportInterface
* @param mixed[] $formatterData collected from the current form
* @param string $exportAlias the id of the current export
* @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data
* @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data
* @return \Symfony\Component\HttpFoundation\Response The response to be shown
*/
public function getResponse(
$result,
$formatterData,
$exportAlias,
array $exportData,
array $filtersData,
array $aggregatorsData
) {
$this->result = $result;
$this->exportAlias = $exportAlias;
$this->exportData = $exportData;
$this->formatterData = $formatterData;
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$this->prepareHeaders($worksheet);
$i = 1;
foreach ($result as $row) {
$line = array();
if ($this->formatterData['numerotation'] === true) {
$worksheet->setCellValue('A'.($i+1), (string) $i);
}
$a = $this->formatterData['numerotation'] ? 'B' : 'A';
foreach ($row as $key => $value) {
$row = $a.($i+1);
if ($value instanceof \DateTimeInterface) {
$worksheet->setCellValue($row, Date::PHPToExcel($value));
$worksheet->getStyle($row)
->getNumberFormat()
->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY);
} else {
$worksheet->setCellValue($row, $this->getLabel($key, $value));
}
$a ++;
}
$i++;
}
switch ($this->formatterData['format'])
{
case 'ods':
$writer = \PhpOffice\PhpSpreadsheet\IOFactory
::createWriter($spreadsheet, 'Ods');
$contentType = "application/vnd.oasis.opendocument.spreadsheet";
break;
case 'xlsx':
$writer = \PhpOffice\PhpSpreadsheet\IOFactory
::createWriter($spreadsheet, 'Xlsx');
$contentType = 'application/vnd.openxmlformats-officedocument.'
. 'spreadsheetml.sheet';
break;
case 'csv':
$writer = \PhpOffice\PhpSpreadsheet\IOFactory
::createWriter($spreadsheet, 'Csv');
$contentType = 'text/csv';
break;
default:
// this should not happen
// throw an exception to ensure that the error is catched
throw new \OutOfBoundsException("The format ".$this->formatterData['format'].
" is not supported");
}
$response = new Response();
$response->headers->set('content-type', $contentType);
$tempfile = \tempnam(\sys_get_temp_dir(), '');
$writer->save($tempfile);
$f = \fopen($tempfile, 'r');
$response->setContent(\stream_get_contents($f));
fclose($f);
// remove the temp file from disk
\unlink($tempfile);
return $response;
}
/**
* add the headers to the csv file
*
* @param Worksheet $worksheet
*/
protected function prepareHeaders(Worksheet $worksheet)
{
$keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData);
// we want to keep the order of the first row. So we will iterate on the first row of the results
$first_row = count($this->result) > 0 ? $this->result[0] : array();
$header_line = array();
if ($this->formatterData['numerotation'] === true) {
$header_line[] = $this->translator->trans('Number');
}
foreach ($first_row as $key => $value) {
$header_line[] = $this->translator->trans(
$this->getLabel($key, '_header'));
}
if (count($header_line) > 0) {
$worksheet->fromArray($header_line, NULL, 'A1');
}
}
/**
* Give the label corresponding to the given key and value.
*
* @param string $key
* @param string $value
* @return string
* @throws \LogicException if the label is not found
*/
protected function getLabel($key, $value)
{
if ($this->labelsCache === null) {
$this->prepareCacheLabels();
}
if (!\array_key_exists($key, $this->labelsCache)){
throw new \OutOfBoundsException(sprintf("The key \"%s\" "
. "is not present in the list of keys handled by "
. "this query. Check your `getKeys` and `getLabels` "
. "methods. Available keys are %s.", $key,
\implode(", ", \array_keys($this->labelsCache))));
}
return $this->labelsCache[$key]($value);
}
/**
* Prepare the label cache which will be used by getLabel. This function
* should be called only once in the generation lifecycle.
*/
protected function prepareCacheLabels()
{
$export = $this->exportManager->getExport($this->exportAlias);
$keys = $export->getQueryKeys($this->exportData);
foreach($keys as $key) {
// get an array with all values for this key if possible
$values = \array_map(function ($v) use ($key) { return $v[$key]; }, $this->result);
// store the label in the labelsCache property
$this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData);
}
}
}

View File

@ -0,0 +1,71 @@
<?php
/*
* Copyright (C) 2015 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\Export;
use Symfony\Component\Form\FormBuilderInterface;
/**
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface FormatterInterface
{
const TYPE_TABULAR = 'tabular';
const TYPE_LIST = 'list';
public function getType();
public function getName();
/**
* build a form, which will be used to collect data required for the execution
* of this formatter.
*
* @uses appendAggregatorForm
* @param FormBuilderInterface $builder
* @param String $exportAlias Alias of the export which is being executed. An export gets the data and implements the \Chill\MainBundle\Export\ExportInterface
* @param Array(String) $aggregatorAliases Array of the aliases of the aggregators. An aggregator do the "group by" on the data. $aggregatorAliases
*/
public function buildForm(
FormBuilderInterface $builder,
$exportAlias,
array $aggregatorAliases
);
/**
* Generate a response from the data collected on differents ExportElementInterface
*
* @param mixed[] $result The result, as given by the ExportInterface
* @param mixed[] $formatterData collected from the current form
* @param string $exportAlias the id of the current export
* @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data
* @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data
* @return \Symfony\Component\HttpFoundation\Response The response to be shown
*/
public function getResponse(
$result,
$formatterData,
$exportAlias,
array $exportData,
array $filtersData,
array $aggregatorsData
);
}

View File

@ -0,0 +1,16 @@
<?php
/*
*
*/
namespace Chill\MainBundle\Export;
/**
* Add a grouping option to exports.
*
* **usage**: the exports will be grouped under the key given by the `getGroup`
* method.
*/
interface GroupedExportInterface
{
public function getGroup(): string;
}

View File

@ -0,0 +1,17 @@
<?php
namespace Chill\MainBundle\Export;
/**
* Define methods to export list.
*
* This interface is a specification of export interface
* and should be used when the export does not supports aggregators
* (and list does not support aggregation on their data).
*
* When used, the `ExportManager` will not handle aggregator for this class.
*/
interface ListInterface extends ExportInterface
{
}

View File

@ -0,0 +1,58 @@
<?php
/*
* Copyright (C) 2016 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\Export;
use Doctrine\ORM\QueryBuilder;
/**
* Modifiers modify the export's query.
*
* Known subclasses : AggregatorInterface and FilterInterface
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
interface ModifierInterface extends ExportElementInterface
{
/**
* The role required for executing this Modifier
*
* If null, will used the ExportInterface::requiredRole role from
* the current executing export.
*
* @return NULL|\Symfony\Component\Security\Core\Role\Role A role required to execute this ModifiersInterface
*/
public function addRole();
/**
* On which type of Export this ModifiersInterface may apply.
*
* @return string the type on which the Modifiers apply
*/
public function applyOn();
/**
* Alter the query initiated by the export, to add the required statements
* (`GROUP BY`, `SELECT`, `WHERE`)
*
* @param QueryBuilder $qb the QueryBuilder initiated by the Export (and eventually modified by other Modifiers)
* @param mixed[] $data the data from the Form (builded by buildForm)
*/
public function alterQuery(QueryBuilder $qb, $data);
}

View File

@ -0,0 +1,59 @@
<?php
/*
* Copyright (C) 2017 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;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Chill\MainBundle\Search\SearchProvider;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class AdvancedSearchType extends AbstractType
{
/**
*
* @var SearchProvider
*/
protected $searchProvider;
public function __construct(SearchProvider $searchProvider)
{
$this->searchProvider = $searchProvider;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->searchProvider
->getHasAdvancedFormByName($options['search_service'])
->createSearchForm($builder)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setRequired('search_service')
->setAllowedTypes('search_service', [ 'string' ])
;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Chill\MainBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class CenterType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
;
}
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Chill\MainBundle\Entity\Center'
));
}
/**
* @return string
*/
public function getBlockPrefix()
{
return 'chill_mainbundle_center';
}
}

View File

@ -0,0 +1,89 @@
<?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 = null) use ($value) {
return \call_user_func($value, $pc);
});
return $list;
}
public function loadChoicesForValues(array $values, $value = null)
{
$choices = [];
foreach($values as $value) {
if (empty($value)) {
$choices[] = null;
} else {
$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;
}
}

View File

@ -0,0 +1,121 @@
<?php
/*
* Copyright (C) 2019 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\DataMapper;
use Symfony\Component\Form\DataMapperInterface;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\PostalCode;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
/**
* Add a data mapper to Address.
*
* If the address is incomplete, the data mapper returns null
*/
class AddressDataMapper implements DataMapperInterface
{
/**
*
* @param Address $address
* @param \Iterator $forms
*/
public function mapDataToForms($address, $forms)
{
if (NULL === $address) {
return;
}
if (!$address instanceof Address) {
throw new UnexpectedTypeException($address, Address::class);
}
foreach ($forms as $key => $form) {
/** @var FormInterface $form */
switch ($key) {
case 'streetAddress1':
$form->setData($address->getStreetAddress1());
break;
case 'streetAddress2':
$form->setData($address->getStreetAddress2());
break;
case 'postCode':
$form->setData($address->getPostcode());
break;
case 'validFrom':
$form->setData($address->getValidFrom());
break;
case 'isNoAddress':
$form->setData($address->isNoAddress());
break;
default:
break;
}
}
}
/**
*
* @param \Iterator $forms
* @param Address $address
*/
public function mapFormsToData($forms, &$address)
{
if (!$address instanceof Address) {
$address = new Address();
}
$isNoAddress = false;
foreach ($forms as $key => $form) {
if ($key === 'isNoAddress') {
$isNoAddress = $form->get('isNoAddress')->getData();
}
}
foreach ($forms as $key => $form) {
/** @var FormInterface $form */
switch($key) {
case 'postCode':
if (!$form->getData() instanceof PostalCode && !$isNoAddress) {
$address = null;
return;
}
$address->setPostcode($form->getData());
break;
case 'streetAddress1':
if (empty($form->getData()) && !$isNoAddress) {
$address = null;
return;
}
$address->setStreetAddress1($form->getData());
break;
case 'streetAddress2':
$address->setStreetAddress2($form->getData());
break;
case 'validFrom':
$address->setValidFrom($form->getData());
break;
case 'isNoAddress':
$address->setIsNoAddress($form->getData());
break;
default:
break;
}
}
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Chill\MainBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Chill\MainBundle\Form\Utils\PermissionsGroupFlagProvider;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class PermissionsGroupType extends AbstractType
{
/**
*
* @var PermissionsGroupFlagProvider[]
*/
protected $flagProviders = [];
const FLAG_SCOPE = 'permissions_group';
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
;
$flags = $this->getFlags();
if (count($flags) > 0) {
$builder
->add('flags', ChoiceType::class, [
'choices' => \array_combine($flags, $flags),
'multiple' => true,
'expanded' => true,
'required' => false
]);
}
}
/**
*
* @return array
*/
protected function getFlags(): array
{
$flags = [];
foreach ($this->flagProviders as $flagProvider) {
$flags = \array_merge($flags, $flagProvider->getPermissionsGroupFlags());
}
return $flags;
}
public function addFlagProvider(PermissionsGroupFlagProvider $provider)
{
$this->flagProviders[] = $provider;
}
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Chill\MainBundle\Entity\PermissionsGroup'
));
}
/**
* @return string
*/
public function getBlockPrefix()
{
return 'chill_mainbundle_permissionsgroup';
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Chill\MainBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
class ScopeType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TranslatableStringFormType::class)
;
}
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Chill\MainBundle\Entity\Scope'
));
}
/**
* @return string
*/
public function getBlockPrefix()
{
return 'chill_mainbundle_scope';
}
}

Some files were not shown because too many files have changed in this diff Show More