chill-bundles/Timeline/TimelineBuilder.php
Julien Fastré 8ac41a15b6 Merge branch add_acl ref #263
Squashed commit of the following:

commit e1236655e1514fd207818aeb57789eca0d949453
Merge: c0b349b fb15bd3
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Tue Jun 30 09:51:35 2015 +0200

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

    In order to prepare merging of add_acl to master

    Conflicts:
    	composer.json

commit c0b349bb5f31fe79c84f82d4dd6658c9e90ef728
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Tue Jun 30 00:40:13 2015 +0200

    fix infos in composer.json

    [ci skip]

commit 106bbf56a5060efd2a89232f278692eeb57e3092
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Mon Jun 29 22:58:48 2015 +0200

    add username and password to client auth options

    [ci skip]

commit c4990972711850616aa1426394884223d63b504f
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Mon Jun 29 22:22:20 2015 +0200

    fix quoting in timelinebuilder

commit 1db7cbea5a0fb0e8d396f8c9d8dc01240b47e96f
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 25 22:43:46 2015 +0200

    remove data_class to allow edit form

commit 7c999279310b5e2b9ecef8a9b5001c71910a822d
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Jun 24 22:33:42 2015 +0200

    fix doc for AppendScopeChoiceTypeTrait

commit 839d4c43bf6f463e705b47d5dbc0cdf7853db0b6
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Jun 24 22:30:13 2015 +0200

    refactor: move scope field to a trait

    Example usage :

      ```
      class AbcType extends Symfony\Component\Form\AbstractType
      {
          use AppendScopeChoiceTypeTrait;
          protected $authorizationHelper;
          protected $translatableStringHelper;
          protected $user;

          public function __construct(AuthorizationHelper $helper,
            TokenStorageInterface $tokenStorage,
            TranslatableStringHelper $translatableStringHelper)
          {
              $this->authorizationHelper = $helper;
              $this->user = $tokenStorage->getToken()->getUser();
              $this->translatableStringHelper = $translatableStringHelper;
          }

          public function buildForm(FormBuilder $builder, array $options)
          {
                // ... add your form there

              // append the scope using FormEvents: PRE_SET_DATA
              $this->appendScopeChoices($builder, $options['role'],
                   $options['center'], $this->user,
                   $this->authorizationHelper,
                   $this->translatableStringHelper);
           }

           public function configureOptions(OptionsResolver $resolver)
           {
                   // ... add your options

              // add an option 'role' and 'center' to your form (optional)
              $this->appendScopeChoicesOptions($resolver);
           }

       }
      ```

    [ci skip]

commit a1ac530f343146eee12b5982e4e6fceb6dc1da66
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Jun 24 00:24:30 2015 +0200

    remove unused statements

commit 74f0a4ce5dfdfa4f8fc39ce1cfe726d945e9bdec
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Jun 24 00:09:47 2015 +0200

    add missing unused statement

commit b3a49f2de8758b51c57af6ff0437f30b2fdef72e
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Tue Jun 23 23:58:57 2015 +0200

    remove ScopeType strategy and fix autorization helper

commit aaa70b5eeae76b0950110f51b8274e08bef10576
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Tue Jun 23 21:35:06 2015 +0200

    create scope type

commit 8f5b2b23c9448b8c8e752e46bdd7f9f721054a98
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 19 18:13:54 2015 +0200

    add getREachableCenter method + test on Autho.Helper

commit ab2ccb8c287f9aef12912ea9b7f5dc4998209d77
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 19 17:27:22 2015 +0200

    remove debug information [ci skip]

commit 9d481c07966a5d769d1418bb366bd6b1ea9d4f76
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 19 17:24:57 2015 +0200

    fix role hierarchy

    Now we test effectively that a user has access, not that a role may grant access

commit f4b17d0ae398fd6c26d4907377761e9f15d36a90
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 18 02:08:55 2015 +0200

    fix trait conflicts

    traits does not share the same instance of prophet any more

commit baac8ce97ba0acd4c4fe6f03278f7a0d30da28ad
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 18 01:56:39 2015 +0200

    try to fix trait error in zend strict mode (used by travis)

commit 7b9fa4b14bde72c7036a29c03a0c6cca3c7e6c74
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 18 01:24:09 2015 +0200

    fix error on trait hierarchy (should be)

commit f8b3451089f7017653bf8d280b640356df6a1841
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 18 01:03:04 2015 +0200

    add userHasAccess method to AuthorizationHelper

    This method may be used in voter to check access.

    It supports both hasCenterInterface and HasScopeInterface and check all
    required permission.

commit 9ad9f624a0aa73ca2e639fd68fe4d5559da2bbd7
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Jun 18 01:01:16 2015 +0200

    add utilities to generate prophesized entities

    entities
    - User (with permissions)
    - Center
    - Scope
    may be  generated by trait/methods

    This ease test writing about acl

commit 16008b9e64bb7f551319abea494db1c1c5a12b82
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 12 22:42:46 2015 +0200

    add test to CenterType

commit 55e2c64aba9714caf09df8dbc9595980826b296a
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 12 20:50:05 2015 +0200

    first implementation of test on CenterType

    [TRANSFER][ci skip]

commit 548fb24927cc470794a51d81f1ba6af52237363f
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Jun 10 21:15:52 2015 +0200

    add center type

    the center type is hidden if the current user can reach only one center,
    and is `entity` type if the user can reach multiple centers

commit 024e3ef8d969d25560406864d86768a260ef4402
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Mon Jun 8 00:28:51 2015 +0200

    add first impl of Access Model

    - first classes and interfaces
    - authorizationHelper + test
    - rewrite loadUser to have multi-center

commit bc5ae70c83c39a0a738e78313e33020fc284f456
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sun Jun 7 22:11:13 2015 +0200

    make deprecations message not fail tests

commit ab9308ed62e45171e9d355ec033b0862b9274e07
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sat Jun 6 10:19:19 2015 +0200

    introducting phpunit-bridge to handle deprecation warnings

commit 5b7a43c4d058af58578c30120670c5be5b11cfd8
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 12:49:23 2015 +0200

    fix options resolver deprecation

commit a5b4e5743f790c16dfd04e5068c5ecca9c2b1583
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 12:36:17 2015 +0200

    remove warning about deprecation in phpunit

commit 56621767936df1ea1293c49ae06380d7735c8d6c
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 11:59:50 2015 +0200

    fix pattern deprecation in routing/test

    pattern=> path

commit 17d40fc5294b245d7377572a8c918abab2484985
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 11:40:59 2015 +0200

    fix deprecation of pattern in routing

    pattern => path in routing

commit 6a33752c6439bd9dbc707df010ce1d8765eeb5fe
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 11:21:18 2015 +0200

    fix twig.form.resources deprecation

    the new key is twig.form_themes

commit adf03eb819f2d443f87d7b744f59a5adf51d2b31
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Jun 5 10:56:51 2015 +0200

    switch to symfony 2.7 [ci-skip]
2015-06-30 14:38:08 +02:00

263 lines
8.4 KiB
PHP

<?php
/*
* Copyright (C) 2015 Champs-Libres Coopérative <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Chill\MainBundle\Timeline;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
/**
* Build timeline
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class TimelineBuilder implements ContainerAwareInterface
{
use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
/**
*
* @var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
*
* @var string references to providers services
*/
private $providers = array();
/**
* return an HTML string with timeline
*
* This function must be called from controller
*
* @example https://redmine.champs-libres.coop/projects/chillperson/repository/revisions/bd2e1b1808f73e39532e9538413025df5487cad0/entry/Controller/TimelinePersonController.php#L47 the implementation in person bundle
*
* @param string $context
* @param array $args arguments defined by the bundle which create the context
* @param int $page first page = 0
* @param int $number number of items by page
* @return string an HTML representation, must be included using `|raw` filter
*/
public function getTimelineHTML($context, array $args, $page = 0, $number = 20)
{
$query = $this->buildUnionQuery($context, $args, $page, $number);
$fetched = $this->runQuery($query);
$entitiesByKey = $this->getEntities($fetched, $context);
return $this->render($fetched, $entitiesByKey, $context, $args);
}
/**
* add a provider id
*
* @internal This function is called by the TimelineCompilerClass
*
* @param string $context the context of the service
* @param string $id the
*/
public function addProvider($context, $id)
{
$this->providers[$context][] = $id;
}
/**
* Get providers by context
*
* @param string $context
* @return TimelineProviderInterface[]
*/
public function getProvidersByContext($context)
{
$providers = array();
foreach($this->providers[$context] as $providerId) {
$providers[] = $this->container->get($providerId);
}
return $providers;
}
/**
* build the UNION query with all providers
*
* @uses self::buildSelectQuery to build individual SELECT queries
*
* @param string $context
* @param mixed $args
* @param int $page
* @param int $number
* @return string
* @throws \LogicException if no builder have been defined for this context
*/
private function buildUnionQuery($context, array $args, $page, $number)
{
//throw an exception if no provider have been defined for this context
if (!array_key_exists($context, $this->providers)) {
throw new \LogicException(sprintf('No builders have been defined for "%s"'
. ' context', $context));
}
//append SELECT queries with UNION keyword between them
$union = '';
foreach($this->getProvidersByContext($context) as $provider) {
$select = $this->buildSelectQuery($provider, $context, $args);
$append = ($union === '') ? $select : ' UNION '.$select;
$union .= $append;
}
//add ORDER BY clause and LIMIT
$union .= sprintf(' ORDER BY date LIMIT %d OFFSET %d',
$number, $page * $number);
return $union;
}
/**
* return the SQL SELECT query as a string,
*
* @uses TimelineProfiderInterface::fetchQuery use the fetchQuery function
* @param \Chill\MainBundle\Timeline\TimelineProviderInterface $provider
* @param string $context
* @param mixed[] $args
* @return string
*/
private function buildSelectQuery(TimelineProviderInterface $provider, $context, array $args)
{
$data = $provider->fetchQuery($context, $args);
return sprintf(
'SELECT %s AS id, '
. '%s AS "date", '
. "'%s' AS type "
. 'FROM %s '
. 'WHERE %s',
$data['id'],
$data['date'],
$data['type'],
$data['FROM'],
$data['WHERE']);
}
/**
* run the UNION query and return result as an array
*
* @param string $query
* @return array
*/
private function runQuery($query)
{
$resultSetMapping = (new ResultSetMapping())
->addScalarResult('id', 'id')
->addScalarResult('type', 'type')
->addScalarResult('date', 'date');
return $this->em->createNativeQuery($query, $resultSetMapping)
->getArrayResult();
}
/**
*
* @param array $queriedIds
* @param string $context
* @return array with the form array($type => [$entity, $entity, $entity])
*/
private function getEntities(array $queriedIds, $context)
{
//gather entities by type to pass all id with same type to the TimelineProvider.
$idsByType = array();
foreach($queriedIds as $result) {
$idsByType[$result['type']][] = $result['id'];
}
//fetch entities from providers
$entitiesByType = array();
foreach ($idsByType as $type => $ids) {
//iterate providers for current context
foreach($this->getProvidersByContext($context) as $provider) {
if ($provider->supportsType($type)) {
$entitiesByType[$type] = $provider->getEntities($ids);
break; //we assume that providers have unique keys => we break the loop
}
}
}
return $entitiesByType;
}
/**
* render the timeline as HTML
*
* @param array $fetched
* @param array $entitiesByType
* @param string $context
* @param mixed[] $args
* @return string the HTML representation of the timeline
*/
private function render(array $fetched, array $entitiesByType, $context, array $args)
{
//add results to a pretty array
$timelineEntries = array();
foreach ($fetched as $result) {
$data = $this->getTemplateData(
$result['type'],
$entitiesByType[$result['type']][$result['id']], //the entity
$context,
$args);
$timelineEntry['date'] = new \DateTime($result['date']);
$timelineEntry['template'] = $data['template'];
$timelineEntry['template_data'] = $data['template_data'];
$timelineEntries[] = $timelineEntry;
}
return $this->container->get('templating')
->render('ChillMainBundle:Timeline:index.html.twig', array(
'results' => $timelineEntries
));
}
/**
* get the template data from the provider for the given entity, by type.
*
* @param string $type
* @param mixed $entity
* @param string $context
* @param mixed[] $args
* @return array the template data fetched from the provider
*/
private function getTemplateData($type, $entity, $context, array $args)
{
foreach($this->getProvidersByContext($context) as $provider) {
if ($provider->supportsType($type)) {
return $provider->getEntityTemplate($entity, $context, $args);
}
}
}
}