adding a basic search page, which create event list

ref #10
ref #18
This commit is contained in:
Julien Fastré 2016-03-22 17:01:25 +01:00
parent b89ed5d534
commit 55a2c66793
10 changed files with 402 additions and 7 deletions

View File

@ -0,0 +1,84 @@
<?php
/*
* Copyright (C) 2016 Julien Fastré <julien.fastre@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Chill\EventBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup;
use Chill\MainBundle\Entity\RoleScope;
use Chill\MainBundle\DataFixtures\ORM\LoadScopes;
use Doctrine\Common\Persistence\ObjectManager;
/**
* Add roles to existing groups
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* @author Champs Libres <info@champs-libres.coop>
*/
class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface
{
public function load(ObjectManager $manager)
{
foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) {
$permissionsGroup = $this->getReference($permissionsGroupRef);
foreach (LoadScopes::$references as $scopeRef){
$scope = $this->getReference($scopeRef);
//create permission group
switch ($permissionsGroup->getName()) {
case 'social':
if ($scope->getName()['en'] === 'administrative') {
break 2; // we do not want any power on administrative
}
break;
case 'administrative':
case 'direction':
if (in_array($scope->getName()['en'], array('administrative', 'social'))) {
break 2; // we do not want any power on social or administrative
}
break;
}
printf("Adding CHILL_EVENT_UPDATE & CHILL_EVENT_CREATE to %s "
. "permission group, scope '%s' \n",
$permissionsGroup->getName(), $scope->getName()['en']);
$roleScopeUpdate = (new RoleScope())
->setRole('CHILL_EVENT_UPDATE')
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeUpdate);
$roleScopeCreate = (new RoleScope())
->setRole('CHILL_EVENT_CREATE')
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeCreate);
$manager->persist($roleScopeUpdate);
$manager->persist($roleScopeCreate);
}
}
$manager->flush();
}
public function getOrder()
{
return 30011;
}
}

View File

@ -7,6 +7,7 @@ use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Chill\EventBundle\Security\Authorization\EventVoter;
/**
* This is the class that loads and manages your bundle configuration
@ -23,8 +24,11 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
$loader = new Loader\YamlFileLoader($container,
new FileLocator(__DIR__.'/../Resources/config/services'));
$loader->load('repositories.yml');
$loader->load('search.yml');
$loader->load('authorization.yml');
}
/* (non-PHPdoc)
@ -32,7 +36,17 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
*/
public function prepend(ContainerBuilder $container)
{
$this->prependAuthorization($container);
$this->prependRoute($container);
}
/**
* add route to route loader for chill
*
* @param ContainerBuilder $container
*/
protected function prependRoute(ContainerBuilder $container)
{
//add routes for custom bundle
$container->prependExtensionConfig('chill_main', array(
'routing' => array(
@ -42,4 +56,20 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
)
));
}
/**
* add authorization hierarchy
*
* @param ContainerBuilder $container
*/
protected function prependAuthorization(ContainerBuilder $container)
{
$container->prependExtensionConfig('security', array(
'role_hierarchy' => array(
EventVoter::SEE_DETAILS => array(EventVoter::SEE),
EventVoter::UPDATE => array(EventVoter::SEE_DETAILS),
EventVoter::CREATE => array(EventVoter::SEE_DETAILS)
)
));
}
}

View File

@ -1,4 +0,0 @@
services:
# chill_event.example:
# class: Chill\EventBundle\Example
# arguments: [@service_id, "plain_value", %parameter%]

View File

@ -0,0 +1,8 @@
services:
chill_event.event_voter:
class: Chill\EventBundle\Security\Authorization\EventVoter
arguments:
- "@chill.main.security.authorization.helper"
tags:
- { name: chill.role }
- { name: security.voter }

View File

@ -0,0 +1,6 @@
services:
chill_group.repository.event:
class: Doctrine\ORM\EntityRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments:
- 'Chill\EventBundle\Entity\Event'

View File

@ -0,0 +1,11 @@
services:
chill_event.search_events:
class: Chill\EventBundle\Search\EventSearch
arguments:
- "@security.token_storage"
- "@chill_group.repository.event"
- "@chill.main.security.authorization.helper"
- "@templating"
tags:
- { name: chill.search, alias: 'event_regular' }

View File

@ -0,0 +1,11 @@
#events
Name: Nom
Date: Date
Event type : Type d'événement
See: Voir
#search
Event search: Recherche d'événements
'%total% events match the search %pattern%' : '{0} Aucun événement ne correspond aux termes de recherche "%pattern%" | {1} Un événement a été trouvé par la recherche "%pattern%" | ]1,Inf] %total% événements correspondent aux termes de recherche "%pattern%".'

View File

@ -0,0 +1,43 @@
<h2>{{ 'Event search'|trans }}</h2>
<p>{% transchoice total with { '%pattern%' : pattern } %}%total% events match the search %pattern%{% endtranschoice %}</p>
{% if events|length > 0 %}
<table>
<thead>
<tr>
<th class="chill-red">{{ 'Name'|trans }}</th>
<th class="chill-green">{{ 'Date'|trans }}</th>
<th class="chill-orange">{{ 'Event type'|trans }}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr>
<td>{{ event.label }}</td>
<td>{{ event.date|localizeddate('long', 'none') }}</td>
<td>{{ event.type.label|localize_translatable_string }}</td>
<td>
<ul class="record_actions">
<li>
{% if is_granted('CHILL_EVENT_SEE_DETAILS', event) %}
<a href="#" class="sc-button btn-action">
{{ 'See'|trans }}
</a>
{% endif %}
{% if is_granted('CHILL_EVENT_UPDATE', event) %}
<a href="#" class="sc-button btn-edit">
{{ 'Edit'|trans }}
</a>
{% endif %}
</li>
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}

143
Search/EventSearch.php Normal file
View File

@ -0,0 +1,143 @@
<?php
namespace Chill\EventBundle\Search;
use Chill\MainBundle\Search\AbstractSearch;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Symfony\Component\Templating\EngineInterface as TemplatingEngine;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Role\Role;
/**
* Search within Events.
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* @author Champs Libres <info@champs-libres.coop>
*/
class EventSearch extends AbstractSearch
{
/**
*
* @var EntityRepository
*/
private $er;
/**
*
* @var \Chill\MainBundle\Entity\User
*/
private $user;
/**
*
* @var AuthorizationHelper
*/
private $helper;
/**
*
* @var TemplatingEngine
*/
private $templating;
public function __construct(
TokenStorageInterface $tokenStorage,
EntityRepository $eventRepository,
AuthorizationHelper $authorizationHelper,
TemplatingEngine $templating
)
{
$this->user = $tokenStorage->getToken()->getUser();
$this->er = $eventRepository;
$this->helper = $authorizationHelper;
$this->templating = $templating;
}
public function supports($domain)
{
return 'event' === $domain or 'events' === $domain;
}
public function isActiveByDefault()
{
return false;
}
public function getOrder()
{
return 3000;
}
public function renderResult(array $terms, $start = 0, $limit = 50, array $options = array())
{
return $this->templating->render('ChillEventBundle:Event:list.html.twig',
array(
'events' => $this->search($terms, $start, $limit, $options),
'pattern' => $this->recomposePattern($terms, array(), $terms['_domain']),
'total' => $this->count($terms)
));
}
protected function search(array $terms, $start, $limit, $options)
{
$qb = $this->er->createQueryBuilder('e');
$qb->select('e');
$this->composeQuery($qb, $terms)
->setMaxResults($limit)
->setFirstResult($start)
->orderBy('e.date', 'DESC')
;
return $qb->getQuery()->getResult();
}
protected function count(array $terms)
{
$qb = $this->er->createQueryBuilder('e');
$qb->select('COUNT(e)');
$this->composeQuery($qb, $terms)
;
return $qb->getQuery()->getSingleScalarResult();
}
protected function composeQuery(QueryBuilder &$qb, $terms)
{
// add security clauses
$reachableCenters = $this->helper
->getReachableCenters($this->user, new Role('CHILL_EVENT_SEE'));
if (count($reachableCenters) === 0) {
// add a clause to block all events
$where = $qb->expr()->isNull('e.center');
$qb->andWhere($where);
} else {
$n = 0;
$orWhere = $qb->expr()->orX();
foreach ($reachableCenters as $center) {
$circles = $this->helper->getReachableScopes($this->user,
new Role('CHILL_EVENT_SEE'), $center);
$where = $qb->expr()->andX(
$qb->expr()->eq('e.center', ':center_'.$n),
$qb->expr()->in('e.circle', ':circle_'.$n)
);
$qb->setParameter('center_'.$n, $center);
$qb->setParameter('circle_'.$n, $circles);
$orWhere->add($where);
}
$qb->andWhere($orWhere);
}
return $qb;
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace Chill\EventBundle\Security\Authorization;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\ProvideRoleInterface;
use Chill\EventBundle\Entity\Event;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
/**
* Description of EventVoter
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* @author Champs Libres <info@champs-libres.coop>
*/
class EventVoter extends AbstractChillVoter implements ProvideRoleInterface
{
const SEE = 'CHILL_EVENT_SEE';
const SEE_DETAILS = 'CHILL_EVENT_SEE_DETAILS';
const CREATE = 'CHILL_EVENT_CREATE';
const UPDATE = 'CHILL_EVENT_UPDATE';
protected $authorizationHelper;
public function __construct(AuthorizationHelper $helper)
{
$this->authorizationHelper = $helper;
}
protected function getSupportedAttributes()
{
return array(self::SEE, self::SEE_DETAILS,
self::CREATE, self::UPDATE);
}
protected function getSupportedClasses()
{
return array(Event::class);
}
protected function isGranted($attribute, $event, $user = null)
{
if (!$user instanceof User) {
return false;
}
return $this->helper->userHasAccess($user, $event, $attribute);
}
public function getRoles()
{
return $this->getSupportedAttributes();
}
public function getRolesWithoutScope()
{
return null;
}
}