Merge remote-tracking branch 'origin/master' into issue133_user_current_location

This commit is contained in:
2021-11-22 13:43:22 +01:00
176 changed files with 4626 additions and 3148 deletions

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace Chill\MainBundle\CRUD\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -14,94 +16,77 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Translation\TranslatorInterface;
class AbstractCRUDController extends AbstractController
abstract class AbstractCRUDController extends AbstractController
{
/**
* The crud configuration
* The crud configuration
*
* This configuration si defined by `chill_main['crud']` or `chill_main['apis']`
*
* @var array
*/
protected array $crudConfig = [];
/**
* get the instance of the entity with the given id
*
* @param string $id
* @return object
*
* @throw Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the object is not found
*/
protected function getEntity($action, $id, Request $request): object
protected function getEntity($action, string $id, Request $request): object
{
$e = $this->getDoctrine()
$e = $this
->getDoctrine()
->getRepository($this->getEntityClass())
->find($id);
if (NULL === $e) {
if (null === $e) {
throw $this->createNotFoundException(sprintf("The object %s for id %s is not found", $this->getEntityClass(), $id));
}
return $e;
}
/**
* Create an entity.
*
* @param string $action
* @param Request $request
* @return object
*/
protected function createEntity(string $action, Request $request): object
{
$type = $this->getEntityClass();
return new $type;
$class = $this->getEntityClass();
return new $class;
}
/**
* Count the number of entities
*
* By default, count all entities. You can customize the query by
* By default, count all entities. You can customize the query by
* using the method `customizeQuery`.
*
* @param string $action
* @param Request $request
* @return int
*/
protected function countEntities(string $action, Request $request, $_format): int
{
return $this->buildQueryEntities($action, $request)
->select('COUNT(e)')
->getQuery()
->getSingleScalarResult()
;
->getSingleScalarResult();
}
/**
* Query the entity.
*
*
* By default, get all entities. You can customize the query by using the
* method `customizeQuery`.
*
*
* The method `orderEntity` is called internally to order entities.
*
*
* It returns, by default, a query builder.
*
*/
protected function queryEntities(string $action, Request $request, string $_format, PaginatorInterface $paginator)
{
$query = $this->buildQueryEntities($action, $request)
->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber())
->setMaxResults($paginator->getItemsPerPage());
// allow to order queries and return the new query
return $this->orderQuery($action, $query, $request, $paginator, $_format);
}
/**
* Add ordering fields in the query build by self::queryEntities
*
*/
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
{
@@ -112,14 +97,12 @@ class AbstractCRUDController extends AbstractController
* Build the base query for listing all entities.
*
* This method is used internally by `countEntities` `queryEntities`
*
*
* This base query does not contains any `WHERE` or `SELECT` clauses. You
* can add some by using the method `customizeQuery`.
*
* The alias for the entity is "e".
*
* @param string $action
* @param Request $request
*
* @return QueryBuilder
*/
protected function buildQueryEntities(string $action, Request $request)
@@ -127,8 +110,7 @@ class AbstractCRUDController extends AbstractController
$qb = $this->getDoctrine()->getManager()
->createQueryBuilder()
->select('e')
->from($this->getEntityClass(), 'e')
;
->from($this->getEntityClass(), 'e');
$this->customizeQuery($action, $request, $qb);
@@ -138,55 +120,54 @@ class AbstractCRUDController extends AbstractController
protected function customizeQuery(string $action, Request $request, $query): void {}
/**
* Get the result of the query
* Get the result of the query.
*/
protected function getQueryResult(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query)
protected function getQueryResult(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query)
{
return $query->getQuery()->getResult();
}
protected function onPreIndex(string $action, Request $request, string $_format): ?Response
{
return null;
}
/**
* method used by indexAction
*/
protected function onPreIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator): ?Response
{
{
return null;
}
/**
* method used by indexAction
* Method used by indexAction.
*/
protected function onPreIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator): ?Response
{
return null;
}
/**
* Method used by indexAction.
*/
protected function onPostIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query): ?Response
{
return null;
}
/**
* method used by indexAction
* Method used by indexAction.
*/
protected function onPostIndexFetchQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $entities): ?Response
{
return null;
}
/**
* Get the complete FQDN of the class
*
* @return string the complete fqdn of the class
* Get the FQDN of the class.
*
* @return class-string The FQDN of the class
*/
protected function getEntityClass(): string
{
return $this->crudConfig['class'];
}
/**
* called on post fetch entity
* Called on post fetch entity.
*/
protected function onPostFetchEntity(string $action, Request $request, $entity, $_format): ?Response
{
@@ -194,7 +175,7 @@ class AbstractCRUDController extends AbstractController
}
/**
* Called on post check ACL
* Called on post check ACL.
*/
protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response
{
@@ -203,23 +184,23 @@ class AbstractCRUDController extends AbstractController
/**
* check the acl. Called by every action.
*
* By default, check the role given by `getRoleFor` for the value given in
*
* By default, check the role given by `getRoleFor` for the value given in
* $entity.
*
*
* Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException
* if not accessible.
*
*
* @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException
*/
protected function checkACL(string $action, Request $request, string $_format, $entity = null)
{
// @TODO: Implements abstract getRoleFor method or do it in the interface.
$this->denyAccessUnlessGranted($this->getRoleFor($action, $request, $entity, $_format), $entity);
}
/**
*
* @return string the crud name
* @return string The crud name.
*/
protected function getCrudName(): string
{
@@ -230,7 +211,7 @@ class AbstractCRUDController extends AbstractController
{
return $this->crudConfig['actions'][$action];
}
/**
* Set the crud configuration
*
@@ -241,9 +222,6 @@ class AbstractCRUDController extends AbstractController
$this->crudConfig = $config;
}
/**
* @return PaginatorFactory
*/
protected function getPaginatorFactory(): PaginatorFactory
{
return $this->container->get('chill_main.paginator_factory');
@@ -254,9 +232,6 @@ class AbstractCRUDController extends AbstractController
return $this->get('validator');
}
/**
* @return array
*/
public static function getSubscribedServices(): array
{
return \array_merge(

View File

@@ -3,6 +3,7 @@
namespace Chill\MainBundle;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\MainBundle\Search\SearchApiInterface;
use Chill\MainBundle\Search\SearchInterface;
use Chill\MainBundle\Security\Authorization\ChillVoterInterface;
use Chill\MainBundle\Security\ProvideRoleInterface;
@@ -41,6 +42,8 @@ class ChillMainBundle extends Bundle
->addTag('chill_main.scope_resolver');
$container->registerForAutoconfiguration(ChillEntityRenderInterface::class)
->addTag('chill.render_entity');
$container->registerForAutoconfiguration(SearchApiInterface::class)
->addTag('chill.search_api_provider');
$container->addCompilerPass(new SearchableServicesCompilerPass());
$container->addCompilerPass(new ConfigConsistencyCompilerPass());

View File

@@ -1,7 +1,10 @@
<?php
declare(strict_types=1);
namespace Chill\MainBundle\Command;
use Chill\MainBundle\Repository\UserRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -23,101 +26,51 @@ use League\Csv\Writer;
class ChillImportUsersCommand extends Command
{
/**
*
* @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;
protected EntityManagerInterface $em;
protected ValidatorInterface $validator;
protected LoggerInterface $logger;
protected UserPasswordEncoderInterface $passwordEncoder;
protected UserRepository $userRepository;
protected bool $doChanges = true;
protected OutputInterface $tempOutput;
protected InputInterface $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;
protected array $centers;
protected array $permissionGroups;
protected array $groupCenters;
protected Writer $output;
public function __construct(
EntityManagerInterface $em,
LoggerInterface $logger,
UserPasswordEncoderInterface $passwordEncoder,
ValidatorInterface $validator
ValidatorInterface $validator,
UserRepository $userRepository
) {
$this->em = $em;
$this->passwordEncoder = $passwordEncoder;
$this->validator = $validator;
$this->logger = $logger;
$this->userRepository = $em->getRepository(User::class);
$this->userRepository = $userRepository;
parent::__construct('chill:main:import-users');
}
protected function configure()
{
$this
@@ -126,25 +79,24 @@ class ChillImportUsersCommand extends Command
->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')
;
->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();
}
@@ -152,19 +104,19 @@ class ChillImportUsersCommand extends Command
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( [
@@ -173,35 +125,35 @@ class ChillImportUsersCommand extends Command
$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) {
@@ -211,10 +163,10 @@ class ChillImportUsersCommand extends Command
if ($this->userRepository->countByUsernameOrEmail($data['email']) > 0) {
return true;
}
return false;
}
protected function createUser($offset, $data)
{
$user = new User();
@@ -222,41 +174,41 @@ class ChillImportUsersCommand extends Command
->setEmail(\trim($data['email']))
->setUsername(\trim($data['username']))
->setEnabled(true)
->setPassword($this->passwordEncoder->encodePassword($user,
->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(),
@@ -265,65 +217,58 @@ class ChillImportUsersCommand extends Command
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\" ?",
$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,
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");
}
$this->logger->error('Error while responding to a a question');
$this->tempOutput->writeln('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)) {
@@ -331,36 +276,36 @@ class ChillImportUsersCommand extends Command
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']] =
$this->centers[$r['alias']] =
\array_merge(
$this->centers[$r['alias']] ?? [],
$this->getCenters($r['center']
@@ -368,18 +313,18 @@ class ChillImportUsersCommand extends Command
);
}
}
/**
* 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
*
* 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[]
*/
@@ -387,62 +332,62 @@ class ChillImportUsersCommand extends Command
{
// 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

@@ -1,20 +1,12 @@
<?php
declare(strict_types=1);
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

@@ -137,17 +137,15 @@ class UserController extends CRUDController
]);
}
/**
*
*
* @param User $user
* @return \Symfony\Component\Form\Form
*/
private function createEditPasswordForm(User $user)
private function createEditPasswordForm(User $user): FormInterface
{
return $this->createForm(UserPasswordType::class, null, array(
'user' => $user
))
return $this->createForm(
UserPasswordType::class,
null,
[
'user' => $user
]
)
->add('submit', SubmitType::class, array('label' => 'Change password'))
->remove('actual_password');
}
@@ -314,7 +312,7 @@ class UserController extends CRUDController
{
$user = $this->getUser();
$form = $this->createForm(UserCurrentLocationType::class, $user)
->add('submit', SubmitType::class, ['label' => 'Change current location'])
->add('submit', SubmitType::class, ['label' => 'Save'])
->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {

View File

@@ -1,19 +1,19 @@
<?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/>.
*/
@@ -23,12 +23,11 @@ namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Annotation as Serializer;
/**
* @ORM\Entity
* @ORM\Table(name="centers")
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class Center implements HasCenterInterface
{
@@ -46,9 +45,10 @@ class Center implements HasCenterInterface
* @var string
*
* @ORM\Column(type="string", length=255)
* @Serializer\Groups({"docgen:read"})
*/
private $name;
private string $name = '';
/**
* @var Collection
*
@@ -58,8 +58,8 @@ class Center implements HasCenterInterface
* )
*/
private $groupCenters;
/**
* Center constructor.
*/
@@ -67,7 +67,7 @@ class Center implements HasCenterInterface
{
$this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @return string
*/
@@ -75,7 +75,7 @@ class Center implements HasCenterInterface
{
return $this->name;
}
/**
* @param $name
* @return $this
@@ -85,7 +85,7 @@ class Center implements HasCenterInterface
$this->name = $name;
return $this;
}
/**
* @return int
*/
@@ -93,7 +93,7 @@ class Center implements HasCenterInterface
{
return $this->id;
}
/**
* @return ArrayCollection|Collection
*/
@@ -101,7 +101,7 @@ class Center implements HasCenterInterface
{
return $this->groupCenters;
}
/**
* @param GroupCenter $groupCenter
* @return $this
@@ -111,7 +111,7 @@ class Center implements HasCenterInterface
$this->groupCenters->add($groupCenter);
return $this;
}
/**
* @return string
*/
@@ -119,7 +119,7 @@ class Center implements HasCenterInterface
{
return $this->getName();
}
/**
* @return $this|Center
*/

View File

@@ -1,22 +1,6 @@
<?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/>.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Entity;
@@ -28,38 +12,30 @@ use Doctrine\Common\Collections\ArrayCollection;
* @ORM\Entity
* @ORM\Table(name="role_scopes")
* @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="acl_cache_region")
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class RoleScope
{
/**
* @var integer
*
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
private ?int $id = null;
/**
* @var string
*
* @ORM\Column(type="string", length=255)
*/
private $role;
private ?string $role = null;
/**
* @var Scope
*
* @ORM\ManyToOne(
* targetEntity="Chill\MainBundle\Entity\Scope",
* inversedBy="roleScopes")
* @ORM\JoinColumn(nullable=true, name="scope_id")
* @ORM\Cache(usage="NONSTRICT_READ_WRITE")
*/
private $scope;
private ?Scope $scope = null;
/**
* @var Collection
*
@@ -68,59 +44,37 @@ class RoleScope
* mappedBy="roleScopes")
*/
private $permissionsGroups;
/**
* RoleScope constructor.
*/
public function __construct() {
$this->new = true;
$this->permissionsGroups = new ArrayCollection();
}
/**
* @return int
*/
public function getId()
public function getId(): ?int
{
return $this->id;
}
/**
* @return string
*/
public function getRole()
public function getRole(): ?string
{
return $this->role;
}
/**
* @return Scope
*/
public function getScope()
public function getScope(): ?Scope
{
return $this->scope;
}
/**
* @param type $role
* @return RoleScope
*/
public function setRole($role)
public function setRole(?string $role = null): self
{
$this->role = $role;
return $this;
}
/**
* @param Scope $scope
* @return RoleScope
*/
public function setScope(Scope $scope = null)
public function setScope(?Scope $scope = null): self
{
$this->scope = $scope;
return $this;
}
}

View File

@@ -9,7 +9,7 @@ use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Entity\Location;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Annotation as Serializer;
/**
* User
@@ -17,7 +17,7 @@ use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
* @ORM\Entity
* @ORM\Table(name="users")
* @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="acl_cache_region")
* @DiscriminatorMap(typeProperty="type", mapping={
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
* "user"=User::class
* })
*/
@@ -52,6 +52,7 @@ class User implements AdvancedUserInterface {
/**
* @ORM\Column(type="string", length=200)
* @Serializer\Groups({"docgen:read"})
*/
private string $label = '';
@@ -59,8 +60,9 @@ class User implements AdvancedUserInterface {
* @var string
*
* @ORM\Column(type="string", length=150, nullable=true)
* @Serializer\Groups({"docgen:read"})
*/
private $email;
private ?string $email = null;
/**
* @var string
@@ -124,6 +126,7 @@ class User implements AdvancedUserInterface {
/**
* @var Center|null
* @ORM\ManyToOne(targetEntity=Center::class)
* @Serializer\Groups({"docgen:read"})
*/
private ?Center $mainCenter = null;

View File

@@ -1,50 +1,23 @@
<?php
/*
* Chill is a software for social workers
* Copyright (C) 2016 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/>.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Pagination;
/**
* PageGenerator associated with a Paginator
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* @author Champs Libres <info@champs-libres.coop>
* PageGenerator associated with a Paginator.
*/
class PageGenerator implements \Iterator
{
/**
*
* @var Paginator
*/
protected $paginator;
/**
*
* @var int
*/
protected $current = 1;
public function __construct(Paginator $paginator)
protected Paginator $paginator;
protected int $current = 1;
public function __construct(Paginator $paginator)
{
$this->paginator = $paginator;;
}
public function current()
{
return $this->paginator->getPage($current);
@@ -67,7 +40,7 @@ class PageGenerator implements \Iterator
public function valid()
{
return $this->current > 0
return $this->current > 0
&& $this->current <= $this->paginator->countPages();
}
}

View File

@@ -402,3 +402,11 @@ input.belgian_national_number {
&.daily_counter {}
&.control_digit {}
}
// replace abbr
span.item-key {
font-variant: all-small-caps;
font-size: 90%;
background-color: #0000000a;
//text-decoration: dotted underline;
}

View File

@@ -8,3 +8,43 @@ require('./bootstrap.scss');
import Dropdown from 'bootstrap/js/src/dropdown';
import Modal from 'bootstrap/js/dist/modal';
import Collapse from 'bootstrap/js/src/collapse';
import Carousel from 'bootstrap/js/src/carousel';
//
// ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section
// Initialize options, and show/hide controls in first/last slides
//
let ACHeaderSlider = document.querySelector('#ACHeaderSlider');
if (ACHeaderSlider) {
let controlPrev = ACHeaderSlider.querySelector('button[data-bs-slide="prev"]'),
controlNext = ACHeaderSlider.querySelector('button[data-bs-slide="next"]'),
length = ACHeaderSlider.querySelectorAll('.carousel-item').length,
last = length-1,
carousel = new Carousel(ACHeaderSlider, {
interval: false,
wrap: false,
ride: false,
keyboard: false,
touch: true
})
;
document.addEventListener('DOMContentLoaded', (e) => {
controlNext.classList.remove('visually-hidden');
});
ACHeaderSlider.addEventListener('slid.bs.carousel', (e) => {
//console.log('from slide', e.direction, e.relatedTarget, e.from, e.to );
switch (e.to) {
case 0:
controlPrev.classList.add('visually-hidden');
controlNext.classList.remove('visually-hidden');
break;
case last:
controlPrev.classList.remove('visually-hidden');
controlNext.classList.add('visually-hidden');
break;
default:
controlPrev.classList.remove('visually-hidden');
controlNext.classList.remove('visually-hidden');
}
})
}

View File

@@ -5,7 +5,7 @@
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ installation.name }} - {% block title %}{% endblock %}</title>
<title>{{ installation.name }} - {% block title %}{{ 'Homepage'|trans }}{% endblock %}</title>
<link rel="shortcut icon" href="{{ asset('build/images/favicon.ico') }}" type="image/x-icon">
{{ encore_entry_link_tags('mod_bootstrap') }}
@@ -68,6 +68,9 @@
<button type="submit" class="btn btn-lg btn-warning mt-3">
<i class="fa fa-fw fa-search"></i> {{ 'Search'|trans }}
</button>
<a class="btn btn-lg btn-misc mt-3" href="{{ path('chill_main_advanced_search_list') }}">
<i class="fa fa-fw fa-search"></i> {{ 'Advanced search'|trans }}
</a>
</center>
</form>
</div>

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace Chill\MainBundle\Routing;
use Symfony\Component\Routing\RouteCollection;
@@ -8,43 +10,28 @@ use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* This class permit to build menu from the routing information
* stored in each bundle.
*
* how to must come here FIXME
*
* how to must come here FIXME
*
* @author julien
*/
class MenuComposer
{
/**
*
* @var RouterInterface
*/
private $router;
/**
*
* @var FactoryInterface
*/
private $menuFactory;
/**
*
* @var TranslatorInterface
*/
private $translator;
/**
*
* @var
*/
private $localMenuBuilders = [];
private RouterInterface $router;
private FactoryInterface $menuFactory;
private TranslatorInterface $translator;
private array $localMenuBuilders = [];
private RouteCollection $routeCollection;
function __construct(
RouterInterface $router,
FactoryInterface $menuFactory,
@@ -60,7 +47,7 @@ class MenuComposer
* This function is needed for testing purpose: routeCollection is not
* available as a service (RouterInterface is provided as a service and
* added to this class as paramater in __construct)
*
*
* @param RouteCollection $routeCollection
*/
public function setRouteCollection(RouteCollection $routeCollection)
@@ -71,7 +58,7 @@ class MenuComposer
/**
* Return an array of routes added to $menuId,
* The array is aimed to build route with MenuTwig
*
*
* @param string $menuId
* @param array $parameters see https://redmine.champs-libres.coop/issues/179
* @return array
@@ -83,7 +70,7 @@ class MenuComposer
foreach ($routeCollection->all() as $routeKey => $route) {
if ($route->hasOption('menus')) {
if (array_key_exists($menuId, $route->getOption('menus'))) {
$route = $route->getOption('menus')[$menuId];
@@ -101,12 +88,12 @@ class MenuComposer
return $routes;
}
public function getMenuFor($menuId, array $parameters = array())
{
$routes = $this->getRoutesFor($menuId, $parameters);
$menu = $this->menuFactory->createItem($menuId);
// build menu from routes
foreach ($routes as $order => $route) {
$menu->addChild($this->translator->trans($route['label']), [
@@ -121,24 +108,24 @@ class MenuComposer
])
;
}
if ($this->hasLocalMenuBuilder($menuId)) {
foreach ($this->localMenuBuilders[$menuId] as $builder) {
/* @var $builder LocalMenuBuilderInterface */
$builder->buildMenu($menuId, $menu, $parameters['args']);
}
}
$this->reorderMenu($menu);
return $menu;
}
/**
* recursive function to resolve the order of a array of routes.
* If the order chosen in routing.yml is already in used, find the
* If the order chosen in routing.yml is already in used, find the
* first next order available.
*
*
* @param array $routes the routes previously added
* @param int $order
* @return int
@@ -151,41 +138,41 @@ class MenuComposer
return $order;
}
}
private function reorderMenu(ItemInterface $menu)
private function reorderMenu(ItemInterface $menu)
{
$ordered = [];
$unordered = [];
foreach ($menu->getChildren() as $name => $item) {
$order = $item->getExtra('order');
if ($order !== null) {
$ordered[$this->resolveOrder($ordered, $order)] = $name;
} else {
$unordered = $name;
}
}
ksort($ordered);
$menus = \array_merge(\array_values($ordered), $unordered);
$menu->reorderChildren($menus);
}
public function addLocalMenuBuilder(LocalMenuBuilderInterface $menuBuilder, $menuId)
public function addLocalMenuBuilder(LocalMenuBuilderInterface $menuBuilder, $menuId)
{
$this->localMenuBuilders[$menuId][] = $menuBuilder;
}
/**
* Return true if the menu has at least one builder.
*
*
* This function is a helper to determine if the method `getMenuFor`
* should be used, or `getRouteFor`. The method `getMenuFor` should be used
* if the result is true (it **does** exists at least one menu builder.
*
*
* @param string $menuId
* @return bool
*/

View File

@@ -26,10 +26,10 @@ use Chill\MainBundle\Search\ParsingException;
/**
* This class implements abstract search with most common responses.
*
*
* you should use this abstract class instead of SearchInterface : if the signature of
* search interface change, the generic method will be implemented here.
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*
*/
@@ -37,7 +37,7 @@ abstract class AbstractSearch implements SearchInterface
{
/**
* parse string expected to be a date and transform to a DateTime object
*
*
* @param type $string
* @return \DateTime
* @throws ParsingException if the date is not parseable
@@ -51,14 +51,14 @@ abstract class AbstractSearch implements SearchInterface
. 'not parsable', 0, $ex);
throw $exception;
}
}
/**
* recompose a pattern, retaining only supported terms
*
*
* the outputted string should be used to show users their search
*
*
* @param array $terms
* @param array $supportedTerms
* @param string $domain if your domain is NULL, you should set NULL. You should set used domain instead
@@ -67,35 +67,35 @@ abstract class AbstractSearch implements SearchInterface
protected function recomposePattern(array $terms, array $supportedTerms, $domain = NULL)
{
$recomposed = '';
if ($domain !== NULL)
{
$recomposed .= '@'.$domain.' ';
}
foreach ($supportedTerms as $term) {
if (array_key_exists($term, $terms) && $term !== '_default') {
$recomposed .= ' '.$term.':';
$containsSpace = \strpos($terms[$term], " ") !== false;
if ($containsSpace) {
$recomposed .= "(";
$recomposed .= '"';
}
$recomposed .= (mb_stristr(' ', $terms[$term]) === FALSE) ? $terms[$term] : '('.$terms[$term].')';
if ($containsSpace) {
$recomposed .= ")";
$recomposed .= '"';
}
}
}
if ($terms['_default'] !== '') {
$recomposed .= ' '.$terms['_default'];
}
//strip first character if empty
if (mb_strcut($recomposed, 0, 1) === ' '){
$recomposed = mb_strcut($recomposed, 1);
}
return $recomposed;
}
}
}

View File

@@ -20,19 +20,15 @@ class SearchApi
private EntityManagerInterface $em;
private PaginatorFactory $paginator;
private array $providers = [];
private iterable $providers = [];
public function __construct(
EntityManagerInterface $em,
SearchPersonApiProvider $searchPerson,
ThirdPartyApiSearch $thirdPartyApiSearch,
SearchUserApiProvider $searchUser,
iterable $providers,
PaginatorFactory $paginator
) {
$this->em = $em;
$this->providers[] = $searchPerson;
$this->providers[] = $thirdPartyApiSearch;
$this->providers[] = $searchUser;
$this->providers = $providers;
$this->paginator = $paginator;
}
@@ -68,10 +64,15 @@ class SearchApi
private function findProviders(string $pattern, array $types, array $parameters): array
{
return \array_filter(
$this->providers,
fn($p) => $p->supportsTypes($pattern, $types, $parameters)
);
$providers = [];
foreach ($this->providers as $provider) {
if ($provider->supportsTypes($pattern, $types, $parameters)) {
$providers[] = $provider;
}
}
return $providers;
}
private function countItems($providers, $types, $parameters): int
@@ -82,12 +83,12 @@ class SearchApi
$countNq = $this->em->createNativeQuery($countQuery, $rsmCount);
$countNq->setParameters($parameters);
return $countNq->getSingleScalarResult();
return (int) $countNq->getSingleScalarResult();
}
private function buildCountQuery(array $queries, $types, $parameters)
{
$query = "SELECT COUNT(*) AS count FROM ({union_unordered}) AS sq";
$query = "SELECT SUM(c) AS count FROM ({union_unordered}) AS sq";
$unions = [];
$parameters = [];
@@ -141,17 +142,20 @@ class SearchApi
private function prepareProviders(array $rawResults)
{
$metadatas = [];
$providers = [];
foreach ($rawResults as $r) {
foreach ($this->providers as $k => $p) {
if ($p->supportsResult($r['key'], $r['metadata'])) {
$metadatas[$k][] = $r['metadata'];
$providers[$k] = $p;
break;
}
}
}
foreach ($metadatas as $k => $m) {
$this->providers[$k]->prepare($m);
$providers[$k]->prepare($m);
}
}

View File

@@ -4,6 +4,8 @@ namespace Chill\MainBundle\Search;
class SearchApiQuery
{
private array $select = [];
private array $selectParams = [];
private ?string $selectKey = null;
private array $selectKeyParams = [];
private ?string $jsonbMetadata = null;
@@ -15,6 +17,38 @@ class SearchApiQuery
private array $whereClauses = [];
private array $whereClausesParams = [];
public function addSelectClause(string $select, array $params = []): self
{
$this->select[] = $select;
$this->selectParams = [...$this->selectParams, ...$params];
return $this;
}
public function resetSelectClause(): self
{
$this->select = [];
$this->selectParams = [];
$this->selectKey = null;
$this->selectKeyParams = [];
$this->jsonbMetadata = null;
$this->jsonbMetadataParams = [];
$this->pertinence = null;
$this->pertinenceParams = [];
return $this;
}
public function getSelectClauses(): array
{
return $this->select;
}
public function getSelectParams(): array
{
return $this->selectParams;
}
public function setSelectKey(string $selectKey, array $params = []): self
{
$this->selectKey = $selectKey;
@@ -47,6 +81,16 @@ class SearchApiQuery
return $this;
}
public function getFromClause(): string
{
return $this->fromClause;
}
public function getFromParams(): array
{
return $this->fromClauseParams;
}
/**
* Set the where clause and replace all existing ones.
*
@@ -54,7 +98,7 @@ class SearchApiQuery
public function setWhereClauses(string $whereClause, array $params = []): self
{
$this->whereClauses = [$whereClause];
$this->whereClausesParams = [$params];
$this->whereClausesParams = $params;
return $this;
}
@@ -71,11 +115,53 @@ class SearchApiQuery
public function andWhereClause(string $whereClause, array $params = []): self
{
$this->whereClauses[] = $whereClause;
$this->whereClausesParams[] = $params;
\array_push($this->whereClausesParams, ...$params);
return $this;
}
private function buildSelectParams(bool $count = false): array
{
if ($count) {
return [];
}
$args = $this->getSelectParams();
if (null !== $this->selectKey) {
$args = [...$args, ...$this->selectKeyParams];
}
if (null !== $this->jsonbMetadata) {
$args = [...$args, ...$this->jsonbMetadataParams];
}
if (null !== $this->pertinence) {
$args = [...$args, ...$this->pertinenceParams];
}
return $args;
}
private function buildSelectClause(bool $countOnly = false): string
{
if ($countOnly) {
return 'count(*) AS c';
}
$selects = $this->getSelectClauses();
if (null !== $this->selectKey) {
$selects[] = \strtr("'{key}' AS key", [ '{key}' => $this->selectKey ]);
}
if (null !== $this->jsonbMetadata) {
$selects[] = \strtr('{metadata} AS metadata', [ '{metadata}' => $this->jsonbMetadata]);
}
if (null !== $this->pertinence) {
$selects[] = \strtr('{pertinence} AS pertinence', [ '{pertinence}' => $this->pertinence]);
}
return \implode(', ', $selects);
}
public function buildQuery(bool $countOnly = false): string
{
$isMultiple = count($this->whereClauses);
@@ -87,19 +173,8 @@ class SearchApiQuery
($isMultiple ? ')' : '')
;
if (!$countOnly) {
$select = \strtr("
'{key}' AS key,
{metadata} AS metadata,
{pertinence} AS pertinence
", [
'{key}' => $this->selectKey,
'{metadata}' => $this->jsonbMetadata,
'{pertinence}' => $this->pertinence,
]);
} else {
$select = "1 AS c";
}
$select = $this->buildSelectClause($countOnly);
return \strtr("SELECT
{select}
@@ -116,18 +191,16 @@ class SearchApiQuery
public function buildParameters(bool $countOnly = false): array
{
if (!$countOnly) {
return \array_merge(
$this->selectKeyParams,
$this->jsonbMetadataParams,
$this->pertinenceParams,
$this->fromClauseParams,
\array_merge([], ...$this->whereClausesParams),
);
return [
...$this->buildSelectParams($countOnly),
...$this->fromClauseParams,
...$this->whereClausesParams,
];
} else {
return \array_merge(
$this->fromClauseParams,
\array_merge([], ...$this->whereClausesParams),
);
return [
...$this->fromClauseParams,
...$this->whereClausesParams,
];
}
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace Chill\MainBundle\Search;
use Symfony\Component\Serializer\Annotation as Serializer;
@@ -10,6 +12,8 @@ class SearchApiResult
private $result;
private float $relevance;
public function __construct(float $relevance)
{
$this->relevance = $relevance;
@@ -20,7 +24,7 @@ class SearchApiResult
$this->result = $result;
return $this;
}
}
/**
* @Serializer\Groups({"read"})

View File

@@ -10,10 +10,10 @@ use Chill\MainBundle\Search\HasAdvancedSearchFormInterface;
* installed into the app.
* the service is callable from the container with
* $container->get('chill_main.search_provider')
*
* the syntax for search string is :
* - domain, which begin with `@`. Example: `@person`. Restrict the search to some
* entities. It may exists multiple search provider for the same domain (example:
*
* the syntax for search string is :
* - domain, which begin with `@`. Example: `@person`. Restrict the search to some
* entities. It may exists multiple search provider for the same domain (example:
* a search provider for people which performs regular search, and suggestion search
* with phonetical algorithms
* - terms, which are the terms of the search. There are terms with argument (example :
@@ -25,17 +25,17 @@ class SearchProvider
{
/**
*
*
* @var SearchInterface[]
*/
private $searchServices = array();
/**
*
* @var HasAdvancedSearchForm[]
*/
private $hasAdvancedFormSearchServices = array();
/*
* return search services in an array, ordered by
* the order key (defined in service definition)
@@ -59,7 +59,7 @@ class SearchProvider
return $this->searchServices;
}
public function getHasAdvancedFormSearchServices()
{
//sort the array
@@ -75,7 +75,7 @@ class SearchProvider
/**
* parse the search string to extract domain and terms
*
*
* @param string $pattern
* @return string[] an array where the keys are _domain, _default (residual terms) or term
*/
@@ -95,9 +95,9 @@ class SearchProvider
/**
* Extract the domain of the subject
*
*
* The domain begins with `@`. Example: `@person`, `@report`, ....
*
*
* @param type $subject
* @return string
* @throws ParsingException
@@ -121,14 +121,15 @@ class SearchProvider
private function extractTerms(&$subject)
{
$terms = array();
preg_match_all('/([a-z\-]+):([\w\-]+|\([^\(\r\n]+\))/', $subject, $matches);
$matches = [];
preg_match_all('/([a-z\-]+):(([^"][\S\-]+)|"[^"]*")/', $subject, $matches);
foreach ($matches[2] as $key => $match) {
//remove from search pattern
$this->mustBeExtracted[] = $matches[0][$key];
//strip parenthesis
if (mb_substr($match, 0, 1) === '(' &&
mb_substr($match, mb_strlen($match) - 1) === ')') {
if (mb_substr($match, 0, 1) === '"' &&
mb_substr($match, mb_strlen($match) - 1) === '"') {
$match = trim(mb_substr($match, 1, mb_strlen($match) - 2));
}
$terms[$matches[1][$key]] = $match;
@@ -139,14 +140,14 @@ class SearchProvider
/**
* store string which must be extracted to find default arguments
*
*
* @var string[]
*/
private $mustBeExtracted = array();
/**
* extract default (residual) arguments
*
*
* @param string $subject
* @return string
*/
@@ -158,7 +159,7 @@ class SearchProvider
/**
* search through services which supports domain and give
* results as an array of resultsfrom different SearchInterface
*
*
* @param string $pattern
* @param number $start
* @param number $limit
@@ -167,25 +168,25 @@ class SearchProvider
* @return array of results from different SearchInterface
* @throws UnknowSearchDomainException if the domain is unknow
*/
public function getSearchResults($pattern, $start = 0, $limit = 50,
public function getSearchResults($pattern, $start = 0, $limit = 50,
array $options = array(), $format = 'html')
{
$terms = $this->parse($pattern);
$results = array();
//sort searchServices by order
$sortedSearchServices = array();
foreach($this->searchServices as $service) {
$sortedSearchServices[$service->getOrder()] = $service;
}
if ($terms['_domain'] !== NULL) {
foreach ($sortedSearchServices as $service) {
if ($service->supports($terms['_domain'], $format)) {
$results[] = $service->renderResult($terms, $start, $limit, $options);
}
}
if (count($results) === 0) {
throw new UnknowSearchDomainException($terms['_domain']);
}
@@ -196,24 +197,24 @@ class SearchProvider
}
}
}
//sort array
ksort($results);
return $results;
}
public function getResultByName($pattern, $name, $start = 0, $limit = 50,
array $options = array(), $format = 'html')
array $options = array(), $format = 'html')
{
$terms = $this->parse($pattern);
$search = $this->getByName($name);
if ($terms['_domain'] !== NULL && !$search->supports($terms['_domain'], $format))
{
throw new ParsingException("The domain is not supported for the search name");
}
return $search->renderResult($terms, $start, $limit, $options, $format);
}
@@ -232,16 +233,16 @@ class SearchProvider
throw new UnknowSearchNameException($name);
}
}
/**
* return searchservice with an advanced form, defined in service
* return searchservice with an advanced form, defined in service
* definition.
*
*
* @param string $name
* @return HasAdvancedSearchForm
* @throws UnknowSearchNameException
*/
public function getHasAdvancedFormByName($name)
public function getHasAdvancedFormByName($name)
{
if (\array_key_exists($name, $this->hasAdvancedFormSearchServices)) {
return $this->hasAdvancedFormSearchServices[$name];
@@ -253,7 +254,7 @@ class SearchProvider
public function addSearchService(SearchInterface $service, $name)
{
$this->searchServices[$name] = $service;
if ($service instanceof HasAdvancedSearchFormInterface) {
$this->hasAdvancedFormSearchServices[$name] = $service;
}
@@ -477,7 +478,7 @@ class SearchProvider
$string = strtr($string, $chars);
} /* remove from wordpress: we use only utf 8
* else {
// Assume ISO-8859-1 if not UTF-8
$chars['in'] = chr(128) . chr(131) . chr(138) . chr(142) . chr(154) . chr(158)
. chr(159) . chr(162) . chr(165) . chr(181) . chr(192) . chr(193) . chr(194)

View File

@@ -0,0 +1,36 @@
<?php
namespace Chill\MainBundle\Search\Utils;
use \DateTimeImmutable;
class ExtractDateFromPattern
{
private const DATE_PATTERN = [
["([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))", 'Y-m-d'], // 1981-05-12
["((0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/([12]\d{3}))", 'd/m/Y'], // 15/12/1980
["((0[1-9]|[12]\d|3[01])-(0[1-9]|1[0-2])-([12]\d{3}))", 'd-m-Y'], // 15/12/1980
];
public function extractDates(string $subject): SearchExtractionResult
{
$dates = [];
$filteredSubject = $subject;
foreach (self::DATE_PATTERN as [$pattern, $format]) {
$matches = [];
\preg_match_all($pattern, $filteredSubject, $matches);
foreach ($matches[0] as $match) {
$date = DateTimeImmutable::createFromFormat($format, $match);
if (false !== $date) {
$dates[] = $date;
// filter string: remove what is found
$filteredSubject = \trim(\strtr($filteredSubject, [$match => ""]));
}
}
}
return new SearchExtractionResult($filteredSubject, $dates);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Chill\MainBundle\Search\Utils;
class ExtractPhonenumberFromPattern
{
private const PATTERN = "([\+]{0,1}[0-9\ ]{5,})";
public function extractPhonenumber(string $subject): SearchExtractionResult
{
$matches = [];
\preg_match(self::PATTERN, $subject,$matches);
if (0 < count($matches)) {
$phonenumber = [];
$length = 0;
foreach (\str_split(\trim($matches[0])) as $key => $char) {
switch ($char) {
case '0':
$length++;
if ($key === 0) { $phonenumber[] = '+32'; }
else { $phonenumber[] = $char; }
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
$length++;
$phonenumber[] = $char;
break;
case ' ':
break;
default:
throw new \LogicException("should not match not alnum character");
}
}
if ($length > 5) {
$filtered = \trim(\strtr($subject, [$matches[0] => '']));
return new SearchExtractionResult($filtered, [\implode('', $phonenumber)] );
}
}
return new SearchExtractionResult($subject, []);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Chill\MainBundle\Search\Utils;
class SearchExtractionResult
{
private string $filteredSubject;
private array $found;
public function __construct(string $filteredSubject, array $found)
{
$this->filteredSubject = $filteredSubject;
$this->found = $found;
}
public function getFound(): array
{
return $this->found;
}
public function hasResult(): bool
{
return [] !== $this->found;
}
public function getFilteredSubject(): string
{
return $this->filteredSubject;
}
}

View File

@@ -1,34 +1,17 @@
<?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/>.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Security\Authorization;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Voter for Chill software.
*
* This abstract Voter provide generic methods to handle object specific to Chill
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
abstract class AbstractChillVoter extends Voter implements ChillVoterInterface
{
@@ -39,6 +22,8 @@ abstract class AbstractChillVoter extends Voter implements ChillVoterInterface
. 'getSupportedAttributes and getSupportedClasses methods.',
E_USER_DEPRECATED);
// @TODO: getSupportedAttributes() should be created in here and made abstract or in ChillVoterInterface.
// @TODO: getSupportedClasses() should be created in here and made abstract or in ChillVoterInterface.
return \in_array($attribute, $this->getSupportedAttributes($attribute))
&& \in_array(\get_class($subject), $this->getSupportedClasses());
}
@@ -49,7 +34,7 @@ abstract class AbstractChillVoter extends Voter implements ChillVoterInterface
. 'methods introduced by Symfony 3.0, and do not rely on '
. 'isGranted method', E_USER_DEPRECATED);
// @TODO: isGranted() should be created in here and made abstract or in ChillVoterInterface.
return $this->isGranted($attribute, $subject, $token->getUser());
}
}

View File

@@ -1,48 +1,24 @@
<?php
/*
* Copyright (C) 2018 Julien Fastré <julien.fastre@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Security\Authorization;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Entity\User;
use Symfony\Component\Security\Core\Role\Role;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ChillExportVoter extends Voter
{
const EXPORT = 'chill_export';
/**
*
* @var AuthorizationHelper
*/
protected $authorizationHelper;
public function __construct(AuthorizationHelper $authorizationHelper)
public const EXPORT = 'chill_export';
protected AuthorizationHelperInterface $authorizationHelper;
public function __construct(AuthorizationHelperInterface $authorizationHelper)
{
$this->authorizationHelper = $authorizationHelper;
}
protected function supports($attribute, $subject): bool
{
return $attribute === self::EXPORT;
@@ -53,10 +29,7 @@ class ChillExportVoter extends Voter
if (!$token->getUser() instanceof User) {
return false;
}
$centers = $this->authorizationHelper
->getReachableCenters($token->getUser(), new Role($attribute));
return count($centers) > 0;
return [] !== $this->authorizationHelper->getReachableCenters($token->getUser(), $attribute);
}
}

View File

@@ -1,27 +1,43 @@
<?php
declare(strict_types=1);
namespace Chill\MainBundle\Security\Resolver;
class CenterResolverDispatcher
use Chill\MainBundle\Entity\Center;
/**
* @deprecated Use CenterResolverManager and its interface CenterResolverManagerInterface
*/
final class CenterResolverDispatcher
{
/**
* @var iterabble|CenterResolverInterface[]
* @var CenterResolverInterface[]
*/
private iterable $resolvers = [];
private iterable $resolvers;
public function __construct(iterable $resolvers)
public function __construct(iterable $resolvers = [])
{
$this->resolvers = $resolvers;
}
/**
* @param mixed $entity
* @param array|null $options
* @param object $entity
* @return null|Center|Center[]
*/
public function resolveCenter($entity, ?array $options = [])
{
foreach($this->resolvers as $priority => $resolver) {
trigger_deprecation(
'ChillMainBundle',
'dev-master',
'
Use the service CenterResolverManager through the interface CenterResolverManagerInterface.
The new method "CenterResolverManagerInterface::resolveCenters(): array" is available and the typing
has been improved in order to avoid mixing types.
'
);
foreach($this->resolvers as $resolver) {
if ($resolver->supports($entity, $options)) {
return $resolver->resolveCenter($entity, $options);
}

View File

@@ -6,12 +6,14 @@ use Chill\MainBundle\Entity\Center;
interface CenterResolverInterface
{
/**
* @param object $entity
*/
public function supports($entity, ?array $options = []): bool;
/**
* @param $entity
* @param array|null $options
* @return Center|array|Center[]
* @param object $entity
* @return Center|Center[]
*/
public function resolveCenter($entity, ?array $options = []);

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Chill\MainBundle\Security\Resolver;
use Chill\MainBundle\Entity\Center;
final class CenterResolverManager implements CenterResolverManagerInterface
{
/**
* @var CenterResolverInterface[]
*/
private iterable $resolvers;
public function __construct(iterable $resolvers = [])
{
$this->resolvers = $resolvers;
}
public function resolveCenters($entity, ?array $options = []): array
{
foreach($this->resolvers as $resolver) {
if ($resolver->supports($entity, $options)) {
$resolved = $resolver->resolveCenter($entity, $options);
if (null === $resolved) {
return [];
} elseif ($resolved instanceof Center) {
return [$resolved];
} elseif (\is_array($resolved)) {
return $resolved;
}
throw new \UnexpectedValueException(sprintf("the return type of a %s should be an instance of %s, an array or null. Resolver is %s",
CenterResolverInterface::class, Center::class, get_class($resolver)));
}
}
return [];
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Chill\MainBundle\Security\Resolver;
use Chill\MainBundle\Entity\Center;
interface CenterResolverManagerInterface
{
/**
* @param object $entity
* @return Center[]
*/
public function resolveCenters($entity, ?array $options = []): array;
}

View File

@@ -6,20 +6,21 @@ use Twig\TwigFilter;
final class ResolverTwigExtension extends \Twig\Extension\AbstractExtension
{
private CenterResolverDispatcher $centerResolverDispatcher;
private CenterResolverManagerInterface $centerResolverDispatcher;
private ScopeResolverDispatcher $scopeResolverDispatcher;
/**
* @param CenterResolverDispatcher $centerResolverDispatcher
*/
public function __construct(CenterResolverDispatcher $centerResolverDispatcher)
public function __construct(CenterResolverManagerInterface $centerResolverDispatcher, ScopeResolverDispatcher $scopeResolverDispatcher)
{
$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->scopeResolverDispatcher = $scopeResolverDispatcher;
}
public function getFilters()
{
return [
new TwigFilter('chill_resolve_center', [$this, 'resolveCenter'])
new TwigFilter('chill_resolve_center', [$this, 'resolveCenter']),
new TwigFilter('chill_resolve_scope', [$this, 'resolveScope']),
new TwigFilter('chill_is_scope_concerned', [$this, 'isScopeConcerned']),
];
}
@@ -30,7 +31,26 @@ final class ResolverTwigExtension extends \Twig\Extension\AbstractExtension
*/
public function resolveCenter($entity, ?array $options = [])
{
return $this->centerResolverDispatcher->resolveCenter($entity, $options);
return $this->centerResolverDispatcher->resolveCenters($entity, $options);
}
/**
* @param $entity
* @param array|null $options
* @return bool
*/
public function isScopeConcerned($entity, ?array $options = [])
{
return $this->scopeResolverDispatcher->isConcerned($entity, $options);
}
/**
* @param $entity
* @param array|null $options
* @return array|\Chill\MainBundle\Entity\Scope|\Chill\MainBundle\Entity\Scope[]
*/
public function resolveScope($entity, ?array $options = [])
{
return $this->scopeResolverDispatcher->resolveScope();
}
}

View File

@@ -1,18 +1,18 @@
<?php
/*
*
*
* Copyright (C) 2014-2021, 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/>.
*/
@@ -27,7 +27,7 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
*
*
*
*/
class CenterNormalizer implements NormalizerInterface, DenormalizerInterface
@@ -52,7 +52,7 @@ class CenterNormalizer implements NormalizerInterface, DenormalizerInterface
public function supportsNormalization($data, string $format = null): bool
{
return $data instanceof Center;
return $data instanceof Center && $format === 'json';
}
public function denormalize($data, string $type, string $format = null, array $context = [])

View File

@@ -19,23 +19,78 @@
namespace Chill\MainBundle\Serializer\Normalizer;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
class DateNormalizer implements NormalizerInterface, DenormalizerInterface
class DateNormalizer implements ContextAwareNormalizerInterface, DenormalizerInterface
{
private RequestStack $requestStack;
private ParameterBagInterface $parameterBag;
public function __construct(RequestStack $requestStack, ParameterBagInterface $parameterBag)
{
$this->requestStack = $requestStack;
$this->parameterBag = $parameterBag;
}
public function normalize($date, string $format = null, array $context = array())
{
/** @var \DateTimeInterface $date */
return [
'datetime' => $date->format(\DateTimeInterface::ISO8601)
];
switch($format) {
case 'json':
return [
'datetime' => $date->format(\DateTimeInterface::ISO8601)
];
case 'docgen':
if (null === $date) {
return [
'long' => '', 'short' => ''
];
}
$hasTime = $date->format('His') !== "000000";
$request = $this->requestStack->getCurrentRequest();
$locale = null !== $request ? $request->getLocale() : $this->parameterBag->get('kernel.default_locale');
$formatterLong = \IntlDateFormatter::create(
$locale,
\IntlDateFormatter::LONG,
$hasTime ? \IntlDateFormatter::SHORT: \IntlDateFormatter::NONE
);
$formatterShort = \IntlDateFormatter::create(
$locale,
\IntlDateFormatter::SHORT,
$hasTime ? \IntlDateFormatter::SHORT: \IntlDateFormatter::NONE
);
return [
'short' => $formatterShort->format($date),
'long' => $formatterLong->format($date)
];
}
}
public function supportsNormalization($data, string $format = null): bool
public function supportsNormalization($data, string $format = null, array $context = []): bool
{
return $data instanceof \DateTimeInterface;
if ($format === 'json') {
return $data instanceof \DateTimeInterface;
} elseif ($format === 'docgen') {
return $data instanceof \DateTimeInterface || (
$data === null
&& \array_key_exists('docgen:expects', $context)
&& (
$context['docgen:expects'] === \DateTimeInterface::class
|| $context['docgen:expects'] === \DateTime::class
|| $context['docgen:expects'] === \DateTimeImmutable::class
)
);
}
return false;
}
public function denormalize($data, string $type, string $format = null, array $context = [])

View File

@@ -52,6 +52,6 @@ class UserNormalizer implements NormalizerInterface, NormalizerAwareInterface
public function supportsNormalization($data, string $format = null): bool
{
return $data instanceof User;
return $format === 'json' && $data instanceof User;
}
}

View File

@@ -1,92 +1,56 @@
<?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/>.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Templating;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Translation\Translator;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
*
* This helper helps to find the string in current locale from translatable_strings
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*
*/
class TranslatableStringHelper
final class TranslatableStringHelper implements TranslatableStringHelperInterface
{
/**
*
* @var RequestStack
*/
private $requestStack;
private $fallbackLocales;
public function __construct(RequestStack $requestStack, Translator $translator)
private RequestStack $requestStack;
private TranslatorInterface $translator;
public function __construct(RequestStack $requestStack, TranslatorInterface $translator)
{
$this->requestStack = $requestStack;
$this->fallbackLocales = $translator->getFallbackLocales();
$this->translator = $translator;
}
/**
* return the string in current locale if it exists.
*
* If it does not exists; return the name in the first language available.
*
* Return a blank string if any strings are available.
* Return NULL if $translatableString is NULL
*
* @param array $translatableStrings
* @return string
*/
public function localize(array $translatableStrings)
{
if (NULL === $translatableStrings) {
return NULL;
}
$language = $this->requestStack->getCurrentRequest()->getLocale();
if (isset($translatableStrings[$language])) {
return $translatableStrings[$language];
} else {
foreach ($this->fallbackLocales as $locale) {
if (array_key_exists($locale, $translatableStrings)) {
return $translatableStrings[$locale];
}
}
public function localize(array $translatableStrings): ?string
{
if ([] === $translatableStrings) {
return null;
}
$request = $this->requestStack->getCurrentRequest();
if (null === $request) {
return null;
}
$language = $request->getLocale();
if (array_key_exists($language, $translatableStrings)) {
return $translatableStrings[$language];
}
foreach ($this->translator->getFallbackLocales() as $locale) {
if (array_key_exists($locale, $translatableStrings)) {
return $translatableStrings[$locale];
}
}
// no fallback translation... trying the first available
$langs = array_keys($translatableStrings);
if (count($langs) === 0) {
if ([] === $langs) {
return '';
}
return $translatableStrings[$langs[0]];
return $translatableStrings[$langs[0]];
}
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Chill\MainBundle\Templating;
interface TranslatableStringHelperInterface
{
/**
* Return the string in current locale if it exists.
*
* If it does not exists; return the name in the first language available.
*
* Return a blank string if any strings are available.
*/
public function localize(array $translatableStrings): ?string;
}

View File

@@ -12,7 +12,7 @@ class SearchApiQueryTest extends TestCase
$q = new SearchApiQuery();
$q->setSelectJsonbMetadata('boum')
->setSelectKey('bim')
->setSelectPertinence('1')
->setSelectPertinence('?', ['gamma'])
->setFromClause('badaboum')
->andWhereClause('foo', [ 'alpha' ])
->andWhereClause('bar', [ 'beta' ])
@@ -21,12 +21,12 @@ class SearchApiQueryTest extends TestCase
$query = $q->buildQuery();
$this->assertStringContainsString('(foo) AND (bar)', $query);
$this->assertEquals(['alpha', 'beta'], $q->buildParameters());
$this->assertEquals(['gamma', 'alpha', 'beta'], $q->buildParameters());
$query = $q->buildQuery(true);
$this->assertStringContainsString('(foo) AND (bar)', $query);
$this->assertEquals(['alpha', 'beta'], $q->buildParameters());
$this->assertEquals(['gamma', 'alpha', 'beta'], $q->buildParameters());
}
public function testWithoutWhereClause()
@@ -42,4 +42,20 @@ class SearchApiQueryTest extends TestCase
$this->assertEquals([], $q->buildParameters());
}
public function testBuildParams()
{
$q = new SearchApiQuery();
$q
->addSelectClause('bada', [ 'one', 'two' ])
->addSelectClause('boum', ['three', 'four'])
->setWhereClauses('mywhere', [ 'six', 'seven'])
;
$params = $q->buildParameters();
$this->assertEquals(['six', 'seven'], $q->buildParameters(true));
$this->assertEquals(['one', 'two', 'three', 'four', 'six', 'seven'], $q->buildParameters());
}
}

View File

@@ -26,17 +26,17 @@ use PHPUnit\Framework\TestCase;
class SearchProviderTest extends TestCase
{
/**
*
* @var SearchProvider
* @var SearchProvider
*/
private $search;
public function setUp()
{
$this->search = new SearchProvider();
//add a default service
$this->addSearchService(
$this->createDefaultSearchService('I am default', 10), 'default'
@@ -46,7 +46,7 @@ class SearchProviderTest extends TestCase
$this->createNonDefaultDomainSearchService('I am domain bar', 20, FALSE), 'bar'
);
}
/**
* @expectedException \Chill\MainBundle\Search\UnknowSearchNameException
*/
@@ -54,11 +54,11 @@ class SearchProviderTest extends TestCase
{
$this->search->getByName("invalid name");
}
public function testSimplePattern()
{
$terms = $this->p("@person birthdate:2014-01-02 name:(my name) is not my name");
$terms = $this->p("@person birthdate:2014-01-02 name:\"my name\" is not my name");
$this->assertEquals(array(
'_domain' => 'person',
'birthdate' => '2014-01-02',
@@ -66,40 +66,40 @@ class SearchProviderTest extends TestCase
'name' => 'my name'
), $terms);
}
public function testWithoutDomain()
{
$terms = $this->p('foo:bar residual');
$this->assertEquals(array(
'_domain' => null,
'foo' => 'bar',
'_default' => 'residual'
), $terms);
}
public function testWithoutDefault()
{
$terms = $this->p('@person foo:bar');
$this->assertEquals(array(
'_domain' => 'person',
'foo' => 'bar',
'_default' => ''
), $terms);
}
public function testCapitalLetters()
{
$terms = $this->p('Foo:Bar LOL marCi @PERSON');
$this->assertEquals(array(
'_domain' => 'person',
'_default' => 'lol marci',
'foo' => 'bar'
), $terms);
}
/**
* @expectedException Chill\MainBundle\Search\ParsingException
*/
@@ -107,12 +107,11 @@ class SearchProviderTest extends TestCase
{
$term = $this->p("@person @report");
}
public function testDoubleParenthesis()
{
$terms = $this->p("@papamobile name:(my beautiful name) residual "
. "surname:(i love techno)");
$terms = $this->p('@papamobile name:"my beautiful name" residual surname:"i love techno"');
$this->assertEquals(array(
'_domain' => 'papamobile',
'name' => 'my beautiful name',
@@ -120,65 +119,65 @@ class SearchProviderTest extends TestCase
'surname' => 'i love techno'
), $terms);
}
public function testAccentued()
{
//$this->markTestSkipped('accentued characters must be implemented');
$terms = $this->p('manço bélier aztèque à saloù ê');
$this->assertEquals(array(
'_domain' => NULL,
'_default' => 'manco belier azteque a salou e'
), $terms);
}
public function testAccentuedCapitals()
{
//$this->markTestSkipped('accentued characters must be implemented');
$terms = $this->p('MANÉÀ oÛ lÎ À');
$this->assertEquals(array(
'_domain' => null,
'_default' => 'manea ou li a'
), $terms);
}
public function testTrimInParenthesis()
{
$terms = $this->p('foo:(bar )');
$terms = $this->p('foo:"bar "');
$this->assertEquals(array(
'_domain' => null,
'foo' => 'bar',
'_default' => ''
), $terms);
}
public function testTrimInDefault()
{
$terms = $this->p(' foo bar ');
$this->assertEquals(array(
'_domain' => null,
'_default' => 'foo bar'
), $terms);
}
public function testArgumentNameWithTrait()
{
$terms = $this->p('date-from:2016-05-04');
$this->assertEquals(array(
'_domain' => null,
'date-from' => '2016-05-04',
'_default' => ''
), $terms);
}
/**
* Test the behaviour when no domain is provided in the search pattern :
* Test the behaviour when no domain is provided in the search pattern :
* the default search should be enabled
*/
public function testSearchResultDefault()
@@ -186,12 +185,12 @@ class SearchProviderTest extends TestCase
$response = $this->search->getSearchResults('default search');
//$this->markTestSkipped();
$this->assertEquals(array(
"I am default"
), $response);
), $response);
}
/**
* @expectedException \Chill\MainBundle\Search\UnknowSearchDomainException
*/
@@ -200,49 +199,49 @@ class SearchProviderTest extends TestCase
$response = $this->search->getSearchResults('@unknow domain');
//$this->markTestSkipped();
}
public function testSearchResultDomainSearch()
{
//add a search service which will be supported
$this->addSearchService(
$this->createNonDefaultDomainSearchService("I am domain foo", 100, TRUE), 'foo'
);
$response = $this->search->getSearchResults('@foo default search');
$this->assertEquals(array(
"I am domain foo"
), $response);
}
public function testSearchWithinSpecificSearchName()
{
//add a search service which will be supported
$this->addSearchService(
$this->createNonDefaultDomainSearchService("I am domain foo", 100, TRUE), 'foo'
);
$response = $this->search->getResultByName('@foo search', 'foo');
$this->assertEquals('I am domain foo', $response);
}
/**
* @expectedException \Chill\MainBundle\Search\ParsingException
*/
public function testSearchWithinSpecificSearchNameInConflictWithSupport()
{
$response = $this->search->getResultByName('@foo default search', 'bar');
}
/**
* shortcut for executing parse method
*
*
* @param unknown $pattern
* @return string[]
*/
@@ -250,12 +249,12 @@ class SearchProviderTest extends TestCase
{
return $this->search->parse($pattern);
}
/**
* Add a search service to the chill.main.search_provider
*
*
* Useful for mocking the SearchInterface
*
*
* @param SearchInterface $search
* @param string $name
*/
@@ -264,52 +263,52 @@ class SearchProviderTest extends TestCase
$this->search
->addSearchService($search, $name);
}
private function createDefaultSearchService($result, $order)
{
$mock = $this
->getMockForAbstractClass('Chill\MainBundle\Search\AbstractSearch');
//set the mock as default
$mock->expects($this->any())
->method('isActiveByDefault')
->will($this->returnValue(TRUE));
$mock->expects($this->any())
->method('getOrder')
->will($this->returnValue($order));
//set the return value
$mock->expects($this->any())
->method('renderResult')
->will($this->returnValue($result));
return $mock;
}
private function createNonDefaultDomainSearchService($result, $order, $domain)
{
$mock = $this
->getMockForAbstractClass('Chill\MainBundle\Search\AbstractSearch');
//set the mock as default
$mock->expects($this->any())
->method('isActiveByDefault')
->will($this->returnValue(FALSE));
$mock->expects($this->any())
->method('getOrder')
->will($this->returnValue($order));
$mock->expects($this->any())
->method('supports')
->will($this->returnValue($domain));
//set the return value
$mock->expects($this->any())
->method('renderResult')
->will($this->returnValue($result));
return $mock;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Search\Utils;
use Chill\MainBundle\Search\Utils\ExtractDateFromPattern;
use PHPUnit\Framework\TestCase;
class ExtractDateFromPatternTest extends TestCase
{
/**
* @dataProvider provideSubjects
*/
public function testExtractDate(string $subject, string $filtered, int $count, ...$datesSearched)
{
$extractor = new ExtractDateFromPattern();
$result = $extractor->extractDates($subject);
$this->assertCount($count, $result->getFound());
$this->assertEquals($filtered, $result->getFilteredSubject());
$this->assertContainsOnlyInstancesOf(\DateTimeImmutable::class, $result->getFound());
$dates = \array_map(
function (\DateTimeImmutable $d) {
return $d->format('Y-m-d');
}, $result->getFound()
);
foreach ($datesSearched as $date) {
$this->assertContains($date, $dates);
}
}
public function provideSubjects()
{
yield ["15/06/1981", "", 1, '1981-06-15'];
yield ["15/06/1981 30/12/1987", "", 2, '1981-06-15', '1987-12-30'];
yield ["diallo 15/06/1981", "diallo", 1, '1981-06-15'];
yield ["diallo 31/03/1981", "diallo", 1, '1981-03-31'];
yield ["diallo 15-06-1981", "diallo", 1, '1981-06-15'];
yield ["diallo 1981-12-08", "diallo", 1, '1981-12-08'];
yield ["diallo", "diallo", 0];
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Search\Utils;
use Chill\MainBundle\Search\Utils\ExtractPhonenumberFromPattern;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class ExtractPhonenumberFromPatternTest extends KernelTestCase
{
/**
* @dataProvider provideData
*/
public function testExtract($subject, $expectedCount, $expected, $filteredSubject, $msg)
{
$extractor = new ExtractPhonenumberFromPattern();
$result = $extractor->extractPhonenumber($subject);
$this->assertCount($expectedCount, $result->getFound());
$this->assertEquals($filteredSubject, $result->getFilteredSubject());
$this->assertEquals($expected, $result->getFound());
}
public function provideData()
{
yield ['Diallo', 0, [], 'Diallo', "no phonenumber"];
yield ['Diallo 15/06/2021', 0, [], 'Diallo 15/06/2021', "no phonenumber and a date"];
yield ['Diallo 0486 123 456', 1, ['+32486123456'], 'Diallo', "a phonenumber and a name"];
yield ['Diallo 123 456', 1, ['123456'], 'Diallo', "a number and a name, without leadiing 0"];
yield ['123 456', 1, ['123456'], '', "only phonenumber"];
yield ['0123 456', 1, ['+32123456'], '', "only phonenumber with a leading 0"];
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Serializer\Normalizer;
use Chill\MainBundle\Serializer\Normalizer\DateNormalizer;
use Prophecy\Prophet;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class DateNormalizerTest extends KernelTestCase
{
private Prophet $prophet;
public function setUp()
{
$this->prophet = new Prophet();
}
public function testSupports()
{
$this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new \DateTime(), 'json'));
$this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new \DateTimeImmutable(), 'json'));
$this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new \DateTime(), 'docgen'));
$this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new \DateTimeImmutable(), 'docgen'));
$this->assertTrue($this->buildDateNormalizer()->supportsNormalization(null, 'docgen', ['docgen:expects' => \DateTimeImmutable::class]));
$this->assertTrue($this->buildDateNormalizer()->supportsNormalization(null, 'docgen', ['docgen:expects' => \DateTimeInterface::class]));
$this->assertTrue($this->buildDateNormalizer()->supportsNormalization(null, 'docgen', ['docgen:expects' => \DateTime::class]));
$this->assertFalse($this->buildDateNormalizer()->supportsNormalization(new \stdClass(), 'docgen'));
$this->assertFalse($this->buildDateNormalizer()->supportsNormalization(new \DateTime(), 'xml'));
}
/**
* @dataProvider generateDataNormalize
*/
public function testNormalize($expected, $date, $format, $locale, $msg)
{
$this->assertEquals($expected, $this->buildDateNormalizer($locale)->normalize($date, $format, []), $msg);
}
private function buildDateNormalizer(string $locale = null): DateNormalizer
{
$requestStack = $this->prophet->prophesize(RequestStack::class);
$parameterBag = new ParameterBag();
$parameterBag->set('kernel.default_locale', 'fr');
if ($locale === null) {
$requestStack->getCurrentRequest()->willReturn(null);
} else {
$request = $this->prophet->prophesize(Request::class);
$request->getLocale()->willReturn($locale);
$requestStack->getCurrentRequest()->willReturn($request->reveal());
}
return new DateNormalizer($requestStack->reveal(), $parameterBag);
}
public function generateDataNormalize()
{
$datetime = \DateTime::createFromFormat('Y-m-d H:i:sO', '2021-06-05 15:05:01+02:00');
$date = \DateTime::createFromFormat('Y-m-d H:i:sO', '2021-06-05 00:00:00+02:00');
yield [
['datetime' => '2021-06-05T15:05:01+0200'],
$datetime, 'json', null, 'simple normalization to json'
];
yield [
['long' => '5 juin 2021', 'short' => '05/06/2021'],
$date, 'docgen', 'fr', 'normalization to docgen for a date, with current request'
];
yield [
['long' => '5 juin 2021', 'short' => '05/06/2021'],
$date, 'docgen', null, 'normalization to docgen for a date, without current request'
];
yield [
['long' => '5 juin 2021 à 15:05', 'short' => '05/06/2021 15:05'],
$datetime, 'docgen', null, 'normalization to docgen for a datetime, without current request'
];
yield [
['long' => '', 'short' => ''],
null, 'docgen', null, 'normalization to docgen for a null datetime'
];
}
}

View File

@@ -8,38 +8,29 @@ services:
Chill\MainBundle\Repository\:
resource: '../Repository/'
autowire: true
autoconfigure: true
Chill\MainBundle\Repository\UserACLAwareRepositoryInterface: '@Chill\MainBundle\Repository\UserACLAwareRepository'
Chill\MainBundle\Serializer\Normalizer\:
resource: '../Serializer/Normalizer'
autoconfigure: true
autowire: true
tags:
- { name: 'serializer.normalizer', priority: 64 }
Chill\MainBundle\Form\Type\:
resource: '../Form/Type'
autoconfigure: true
autowire: true
tags:
- { name: form.type }
Chill\MainBundle\Doctrine\Event\:
resource: '../Doctrine/Event/'
autowire: true
tags:
- { name: 'doctrine.event_subscriber' }
chill.main.helper.translatable_string:
class: Chill\MainBundle\Templating\TranslatableStringHelper
arguments:
- "@request_stack"
- "@translator.default"
Chill\MainBundle\Templating\TranslatableStringHelper: '@chill.main.helper.translatable_string'
Chill\MainBundle\Templating\TranslatableStringHelperInterface: '@Chill\MainBundle\Templating\TranslatableStringHelper'
chill.main.twig.translatable_string:
class: Chill\MainBundle\Templating\TranslatableStringTwig

View File

@@ -1,4 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
chill_main.tag_aware_cache:
class: Symfony\Component\Cache\Adapter\TagAwareAdapter
arguments:

View File

@@ -1,10 +1,9 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\Command\ChillImportUsersCommand:
arguments:
$em: '@Doctrine\ORM\EntityManagerInterface'
$logger: '@Psr\Log\LoggerInterface'
$passwordEncoder: '@Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface'
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
tags:
- { name: console.command }

View File

@@ -1,12 +1,13 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\Controller\:
autowire: true
resource: '../../Controller'
tags: ['controller.service_arguments']
Chill\MainBundle\Controller\PasswordController:
autowire: true
arguments:
$chillLogger: '@monolog.logger.chill'
tags: ['controller.service_arguments']
@@ -28,10 +29,6 @@ services:
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
tags: ['controller.service_arguments']
Chill\MainBundle\Controller\UserController:
autowire: true
autoconfigure: true
Chill\MainBundle\Controller\NotificationController:
arguments:
$security: '@Symfony\Component\Security\Core\Security'

View File

@@ -1,4 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader:
arguments:
$crudConfig: '%chill_main_crud_route_loader_config%'

View File

@@ -1,3 +1,7 @@
---
services:
'Chill\MainBundle\Doctrine\Migrations\VersionComparator': ~
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\Doctrine\Migrations\VersionComparator: ~

View File

@@ -1,9 +1,13 @@
services:
_defaults:
autowire: true
autoconfigure: true
chill.main.export_element_validator:
class: Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraintValidator
tags:
- { name: validator.constraint_validator }
# deprecated in favor of spreadsheet_formatter
# chill.main.export.csv_formatter:
# class: Chill\MainBundle\Export\Formatter\CSVFormatter
@@ -11,7 +15,7 @@ services:
# - "@translator"
# tags:
# - { name: chill.export_formatter, alias: 'csv' }
chill.main.export.spreadsheet_formatter:
class: Chill\MainBundle\Export\Formatter\SpreadSheetFormatter
arguments:
@@ -19,7 +23,7 @@ services:
$exportManager: '@Chill\MainBundle\Export\ExportManager'
tags:
- { name: chill.export_formatter, alias: 'spreadsheet' }
chill.main.export.list_formatter:
class: Chill\MainBundle\Export\Formatter\CSVListFormatter
arguments:
@@ -27,7 +31,7 @@ services:
$exportManager: '@Chill\MainBundle\Export\ExportManager'
tags:
- { name: chill.export_formatter, alias: 'csvlist' }
chill.main.export.list_spreadsheet_formatter:
class: Chill\MainBundle\Export\Formatter\SpreadsheetListFormatter
arguments:
@@ -35,7 +39,7 @@ services:
$exportManager: '@Chill\MainBundle\Export\ExportManager'
tags:
- { name: chill.export_formatter, alias: 'spreadlist' }
chill.main.export.pivoted_list_formatter:
class: Chill\MainBundle\Export\Formatter\CSVPivotedListFormatter
arguments:
@@ -43,4 +47,3 @@ services:
$exportManager: '@Chill\MainBundle\Export\ExportManager'
tags:
- { name: chill.export_formatter, alias: 'csv_pivoted_list' }

View File

@@ -1,4 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\DataFixtures\ORM\:
resource: ../../DataFixtures/ORM
tags: [ 'doctrine.fixture.orm' ]

View File

@@ -1,4 +1,7 @@
services:
_defaults:
autowire: true
autoconfigure: true
chill.main.form.type.translatable.string:
class: Chill\MainBundle\Form\Type\TranslatableStringFormType
@@ -39,9 +42,7 @@ services:
tags:
- { name: form.type, alias: select2_chill_language }
Chill\MainBundle\Form\Type\PickCenterType:
autowire: true
autoconfigure: true
Chill\MainBundle\Form\Type\PickCenterType: ~
chill.main.form.type.composed_role_scope:
class: Chill\MainBundle\Form\Type\ComposedRoleScopeType
@@ -62,9 +63,7 @@ services:
tags:
- { name: form.type }
Chill\MainBundle\Form\ChoiceLoader\PostalCodeChoiceLoader:
autowire: true
autoconfigure: true
Chill\MainBundle\Form\ChoiceLoader\PostalCodeChoiceLoader: ~
chill.main.form.type.export:
class: Chill\MainBundle\Form\Type\Export\ExportType
@@ -96,14 +95,10 @@ services:
arguments:
- '@Chill\MainBundle\Export\ExportManager'
Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer:
autowire: true
autoconfigure: true
Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer: ~
chill.main.form.advanced_search_type:
class: Chill\MainBundle\Form\AdvancedSearchType
autowire: true
autoconfigure: true
arguments:
- "@chill_main.search_provider"
tags:
@@ -116,9 +111,7 @@ services:
tags:
- { name: form.type }
Chill\MainBundle\Form\UserType:
autowire: true
autoconfigure: true
Chill\MainBundle\Form\UserType: ~
Chill\MainBundle\Form\PermissionsGroupType:
tags:
@@ -131,10 +124,9 @@ services:
tags:
- { name: form.type }
Chill\MainBundle\Form\Type\PickAddressType: ~
Chill\MainBundle\Form\Type\PickAddressType:
autoconfigure: true
autowire: true
Chill\MainBundle\Form\DataTransform\AddressToIdDataTransformer: ~
Chill\MainBundle\Form\DataTransform\AddressToIdDataTransformer:
autoconfigure: true
@@ -147,3 +139,5 @@ services:
Chill\MainBundle\Form\UserCurrentLocationType:
autowire: true
autoconfigure: true
Chill\MainBundle\Form\Type\LocationFormType: ~

View File

@@ -1,4 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
chill.main.logger:
# a logger to log events from the app (deletion, remove, etc.)
alias: monolog.logger.chill

View File

@@ -1,9 +1,11 @@
services:
Chill\MainBundle\Routing\MenuBuilder\:
resource: '../../Routing/MenuBuilder'
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\Routing\MenuBuilder\:
resource: '../../Routing/MenuBuilder'
Chill\MainBundle\Routing\MenuBuilder\UserMenuBuilder:
arguments:
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'

View File

@@ -1,4 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\Notification\Mailer:
arguments:
$logger: '@Psr\Log\LoggerInterface'
@@ -9,6 +13,4 @@ services:
$translator: '@Symfony\Component\Translation\TranslatorInterface'
$routeParameters: '%chill_main.notifications%'
Chill\MainBundle\Notification\NotificationRenderer:
autoconfigure: true
autowire: true
Chill\MainBundle\Notification\NotificationRenderer: ~

View File

@@ -1,9 +1,11 @@
services:
_defaults:
autowire: true
autoconfigure: true
chill_main.paginator_factory:
class: Chill\MainBundle\Pagination\PaginatorFactory
public: true
autowire: true
autoconfigure: true
arguments:
- "@request_stack"
- "@router"

View File

@@ -1,19 +1,21 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\Phonenumber\PhonenumberHelper:
arguments:
$logger: '@Psr\Log\LoggerInterface'
$config: '%chill_main.phone_helper%'
$cachePool: '@cache.user_data'
Chill\MainBundle\Phonenumber\Templating:
arguments:
$phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper'
tags:
- { name: twig.extension }
Chill\MainBundle\Validation\Validator\ValidPhonenumber:
arguments:
$logger: '@Psr\Log\LoggerInterface'
$phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper'
tags:
- { name: validator.constraint_validator }

View File

@@ -1,10 +1,13 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\Redis\RedisConnectionFactory:
arguments:
$parameters: "%chill_main.redis%"
tags:
- { name: kernel.event_subcriber }
Chill\MainBundle\Redis\ChillRedis:
factory: [ '@Chill\MainBundle\Redis\RedisConnectionFactory', 'create' ]

View File

@@ -1,4 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
chill.main.menu_composer:
class: Chill\MainBundle\Routing\MenuComposer
arguments:
@@ -6,7 +10,7 @@ services:
- '@Knp\Menu\FactoryInterface'
- '@Symfony\Component\Translation\TranslatorInterface'
Chill\MainBundle\Routing\MenuComposer: '@chill.main.menu_composer'
chill.main.routes_loader:
class: Chill\MainBundle\Routing\Loader\ChillRoutesLoader
arguments:

View File

@@ -1,4 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
chill_main.search_provider:
class: Chill\MainBundle\Search\SearchProvider
@@ -7,8 +11,13 @@ services:
Chill\MainBundle\Search\SearchApi:
autowire: true
autoconfigure: true
arguments:
$providers: !tagged_iterator chill.search_api_provider
Chill\MainBundle\Search\Entity\:
resource: '../../Search/Entity'
Chill\MainBundle\Search\Utils\:
autowire: true
autoconfigure: true
resource: '../../Search/Entity'
resource: './../Search/Utils/'

View File

@@ -3,46 +3,35 @@ services:
autowire: true
autoconfigure: true
# do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Resolver\CenterResolverDispatcher:
arguments:
- !tagged_iterator chill_main.center_resolver
Chill\MainBundle\Security\Resolver\CenterResolverManager:
arguments:
- !tagged_iterator chill_main.center_resolver
Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface: '@Chill\MainBundle\Security\Resolver\CenterResolverManager'
Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher:
arguments:
- !tagged_iterator chill_main.scope_resolver
# do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Resolver\DefaultCenterResolver:
autoconfigure: true
autowire: true
Chill\MainBundle\Security\Resolver\DefaultCenterResolver: ~
Chill\MainBundle\Security\Resolver\DefaultScopeResolver:
autoconfigure: true
autowire: true
Chill\MainBundle\Security\Resolver\DefaultScopeResolver: ~
# do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Resolver\ResolverTwigExtension:
autoconfigure: true
autowire: true
Chill\MainBundle\Security\Resolver\ResolverTwigExtension: ~
# do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory:
autowire: true
Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory: ~
# do not autowire the directory Security/Resolver
Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface: '@Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory'
chill.main.security.authorization.helper:
class: Chill\MainBundle\Security\Authorization\AuthorizationHelper
autowire: true
autoconfigure: true
Chill\MainBundle\Security\Authorization\AuthorizationHelper: '@chill.main.security.authorization.helper'
Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface: '@chill.main.security.authorization.helper'
Chill\MainBundle\Security\ParentRoleHelper:
autowire: true
autoconfigure: true
Chill\MainBundle\Security\ParentRoleHelper: ~
chill.main.role_provider:
class: Chill\MainBundle\Security\RoleProvider
@@ -54,20 +43,16 @@ services:
Symfony\Component\Security\Core\User\UserProviderInterface: "@chill.main.user_provider"
Chill\MainBundle\Security\Authorization\ChillExportVoter:
arguments:
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
tags:
- { name: security.voter }
Chill\MainBundle\Security\PasswordRecover\TokenManager:
arguments:
$secret: '%kernel.secret%'
$logger: '@Psr\Log\LoggerInterface'
Chill\MainBundle\Security\PasswordRecover\RecoverPasswordHelper:
arguments:
$tokenManager: '@Chill\MainBundle\Security\PasswordRecover\TokenManager'
$urlGenerator: '@Symfony\Component\Routing\Generator\UrlGeneratorInterface'
$mailer: '@Chill\MainBundle\Notification\Mailer'
$routeParameters: "%chill_main.notifications%"
@@ -80,11 +65,9 @@ services:
Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker:
arguments:
$chillRedis: '@Chill\MainBundle\Redis\ChillRedis'
$logger: '@Psr\Log\LoggerInterface'
Chill\MainBundle\Security\PasswordRecover\PasswordRecoverVoter:
arguments:
$locker: '@Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker'
$requestStack: '@Symfony\Component\HttpFoundation\RequestStack'
tags:
- { name: security.voter }

View File

@@ -1,11 +1,13 @@
---
services:
_defaults:
autowire: true
autoconfigure: true
# note: the autowiring for serializers and normalizers is declared
# into ../services.yaml
Chill\MainBundle\Serializer\Normalizer\DoctrineExistingEntityNormalizer:
autowire: true
tags:
- { name: 'serializer.normalizer', priority: 8 }

View File

@@ -1,4 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
# twig_intl:
# class: Twig_Extensions_Extension_Intl
# tags:
@@ -32,8 +36,6 @@ services:
- { name: twig.extension }
Chill\MainBundle\Templating\Entity\CommentRender:
autoconfigure: true
autowire: true
tags:
- { name: 'chill.render_entity' }
@@ -41,17 +43,11 @@ services:
tags:
- { name: twig.extension }
Chill\MainBundle\Templating\Entity\AddressRender:
autoconfigure: true
autowire: true
Chill\MainBundle\Templating\Entity\AddressRender: ~
Chill\MainBundle\Templating\Entity\UserRender:
autoconfigure: true
autowire: true
Chill\MainBundle\Templating\Entity\UserRender: ~
Chill\MainBundle\Templating\Listing\:
resource: './../../Templating/Listing'
autoconfigure: true
autowire: true
Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface: '@Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory'

View File

@@ -1,4 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
chill_main.timeline_builder:
class: Chill\MainBundle\Timeline\TimelineBuilder
arguments:

View File

@@ -1,11 +1,15 @@
services:
_defaults:
autowire: true
autoconfigure: true
chill_main.validator_user_circle_consistency:
class: Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistencyValidator
arguments:
- "@chill.main.security.authorization.helper"
tags:
- { name: "validator.constraint_validator" }
Chill\MainBundle\Validation\Validator\UserUniqueEmailAndUsername:
arguments:
$em: '@Doctrine\ORM\EntityManagerInterface'

View File

@@ -1,2 +1,6 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\Templating\UI\CountNotificationUser: ~

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20211119173554 extends AbstractMigration
{
public function getDescription(): string
{
return 'remove comment on deprecated json_array type';
}
public function up(Schema $schema): void
{
$columns = [
'users.attributes'
];
foreach ($columns as $col) {
$this->addSql("COMMENT ON COLUMN $col IS NULL");
}
}
public function down(Schema $schema): void
{
$this->throwIrreversibleMigrationException();
}
}

View File

@@ -86,6 +86,10 @@ address more:
Create a new address: Créer une nouvelle adresse
Create an address: Créer une adresse
Update address: Modifier l'adresse
City or postal code: Ville ou code postal
# contact
Part of the phonenumber: Partie du numéro de téléphone
#serach
Your search is empty. Please provide search terms.: La recherche est vide. Merci de fournir des termes de recherche.
@@ -196,7 +200,7 @@ Location: Localisation
Location type list: Liste des types de localisation
Create a new location type: Créer un nouveau type de localisation
Available for users: Disponible aux utilisateurs
Address required: Adresse requise?
Address required: Adresse requise?
Contact data: Données de contact?
optional: optionnel
required: requis