diff --git a/.gitignore b/.gitignore
index 4b827b7fb..c71baeafc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ Tests/Fixtures/App/app/DoctrineMigrations/*
Test/Fixtures/App/app/DoctrineMigrations/*
Test/Fixtures/App/app/cache/*
Test/Fixtures/App/app/config/parameters.yml
+/nbproject/private/
\ No newline at end of file
diff --git a/Export/Aggregator/ReasonAggregator.php b/Export/Aggregator/ReasonAggregator.php
index 5698d4071..c6504ae7b 100644
--- a/Export/Aggregator/ReasonAggregator.php
+++ b/Export/Aggregator/ReasonAggregator.php
@@ -24,6 +24,8 @@ use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Export\AggregatorInterface;
use Symfony\Component\Security\Core\Role\Role;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
+use Doctrine\ORM\EntityRepository;
+use Chill\MainBundle\Templating\TranslatableStringHelper;
/**
*
@@ -32,26 +34,65 @@ use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
*/
class ReasonAggregator implements AggregatorInterface
{
+ /**
+ *
+ * @var EntityRepository
+ */
+ protected $categoryRepository;
+
+ /**
+ *
+ * @var EntityRepository
+ */
+ protected $reasonRepository;
+
+ /**
+ *
+ * @var TranslatableStringHelper
+ */
+ protected $stringHelper;
+
+ public function __construct(
+ EntityRepository $categoryRepository,
+ EntityRepository $reasonRepository,
+ TranslatableStringHelper $stringHelper
+ ) {
+ $this->categoryRepository = $categoryRepository;
+ $this->reasonRepository = $reasonRepository;
+ $this->stringHelper = $stringHelper;
+ }
+
public function alterQuery(QueryBuilder $qb, $data)
{
// add select element
- if ($data['level'] === 'reason') {
- $elem = 'reason.id';
- $alias = 'activity_reason_id';
- } elseif ($data['level'] === 'category') {
+ if ($data['level'] === 'reasons') {
+ $elem = 'reasons.id';
+ $alias = 'activity_reasons_id';
+ } elseif ($data['level'] === 'categories') {
$elem = 'category.id';
- $alias = 'activity_category_id';
+ $alias = 'activity_categories_id';
} else {
- throw new \RuntimeException('the data provided are not recognised');
+ throw new \RuntimeException('the data provided are not recognized');
}
$qb->addSelect($elem.' as '.$alias);
+ // make a jointure only if needed
+ // add a join to reasons only if needed
+ if (array_key_exists('activity', $qb->getDQLPart('join'))) {
+ // we want to avoid multiple join on same object
+ if (!$this->checkJoinAlreadyDefined($qb->getDQLPart('join')['activity'], 'reasons')) {
+ $qb->add('join', new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons'));
+ }
+ } else {
+ $qb->join('activity.reasons', 'reasons');
+ }
- // make a jointure
- $qb->join('activity.reason', 'reason');
// join category if necessary
- if ($alias === 'activity_category_id') {
- $qb->join('reason.category', 'category');
+ if ($alias === 'activity_categories_id') {
+ // add join only if needed
+ if (!$this->checkJoinAlreadyDefined($qb->getDQLPart('join')['activity'], 'category')) {
+ $qb->join('reasons.category', 'category');
+ }
}
// add the "group by" part
@@ -63,6 +104,24 @@ class ReasonAggregator implements AggregatorInterface
$qb->groupBy($alias);
}
}
+
+ /**
+ * Check if a join between Activity and another alias
+ *
+ * @param Join[] $joins
+ * @param string $alias the alias to search for
+ * @return boolean
+ */
+ private function checkJoinAlreadyDefined(array $joins, $alias)
+ {
+ foreach ($joins as $join) {
+ if ($join->getAlias() === $alias) {
+ return true;
+ }
+ }
+
+ return false;
+ }
public function applyOn()
{
@@ -73,12 +132,13 @@ class ReasonAggregator implements AggregatorInterface
{
$builder->add('level', 'choice', array(
'choices' => array(
- 'By reason' => 'reason',
- 'By category of reason' => 'category'
+ 'By reason' => 'reasons',
+ 'By category of reason' => 'categories'
),
'choices_as_values' => true,
'multiple' => false,
- 'expanded' => true
+ 'expanded' => true,
+ 'label' => 'Reason\'s level'
));
}
@@ -94,17 +154,54 @@ class ReasonAggregator implements AggregatorInterface
public function getLabels($key, array $values, $data)
{
- return array_combine($values, $values);
+ // for performance reason, we load data from db only once
+ switch ($data['level']) {
+ case 'reasons':
+ $this->reasonRepository->findBy(array('id' => $values));
+ break;
+ case 'categories':
+ $this->categoryRepository->findBy(array('id' => $values));
+ break;
+ default:
+ throw new \RuntimeException(sprintf("the level data '%s' is invalid",
+ $data['level']));
+ }
+
+ return function($value) use ($data) {
+ if ($value === '_header') {
+ return $data['level'] === 'reasons' ?
+ 'Group by reasons'
+ :
+ 'Group by categories of reason'
+ ;
+ }
+
+ switch ($data['level']) {
+ case 'reasons':
+ $n = $this->reasonRepository->find($value)
+ ->getName()
+ ;
+ break;
+ case 'categories':
+ $n = $this->categoryRepository->find($value)
+ ->getName()
+ ;
+ break;
+ // no need for a default : the default was already set above
+ }
+
+ return $this->stringHelper->localize($n);
+ };
}
public function getQueryKeys($data)
{
// add select element
- if ($data['level'] === 'reason') {
- return array('activity_reason_id');
- } elseif ($data['level'] === 'category') {
- return array ('activity_category_id');
+ if ($data['level'] === 'reasons') {
+ return array('activity_reasons_id');
+ } elseif ($data['level'] === 'categories') {
+ return array ('activity_categories_id');
} else {
throw new \RuntimeException('the data provided are not recognised');
}
diff --git a/Export/Export/CountActivity.php b/Export/Export/CountActivity.php
index 57ce19369..cdab3dd74 100644
--- a/Export/Export/CountActivity.php
+++ b/Export/Export/CountActivity.php
@@ -102,10 +102,13 @@ class CountActivity implements ExportInterface
throw new \LogicException("the key $key is not used by this export");
}
- $labels = array_combine($values, $values);
- $labels['_header'] = 'Number of activities';
-
- return $labels;
+ return function($value) {
+ return $value === '_header' ?
+ 'Number of activities'
+ :
+ $value
+ ;
+ };
}
public function getQueryKeys($data)
diff --git a/Export/Filter/ActivityReasonFilter.php b/Export/Filter/ActivityReasonFilter.php
index 9f2059fbe..32b05b01e 100644
--- a/Export/Filter/ActivityReasonFilter.php
+++ b/Export/Filter/ActivityReasonFilter.php
@@ -28,6 +28,8 @@ use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr;
use Symfony\Component\Security\Core\Role\Role;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
+use Doctrine\ORM\EntityRepository;
+use Doctrine\ORM\Query\Expr\Join;
/**
*
@@ -40,18 +42,38 @@ class ActivityReasonFilter implements FilterInterface
*
* @var TranslatableStringHelper
*/
- public $translatableStringHelper;
+ protected $translatableStringHelper;
- public function __construct(TranslatableStringHelper $helper)
- {
+ /**
+ * The repository for activity reasons
+ *
+ * @var EntityRepository
+ */
+ protected $reasonRepository;
+
+ public function __construct(
+ TranslatableStringHelper $helper,
+ EntityRepository $reasonRepository
+ ) {
$this->translatableStringHelper = $helper;
+ $this->reasonRepository = $reasonRepository;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
- $clause = $qb->expr()->in('activity.reason', ':selected_activity_reasons');
+ $join = $qb->getDQLPart('join');
+ $clause = $qb->expr()->in('reasons', ':selected_activity_reasons');
+ // add a join to reasons only if needed
+ if (array_key_exists('activity', $join)) {
+ // we want to avoid multiple join on same object
+ if (!$this->checkJoinAlreadyDefined($join['activity'])) {
+ $qb->add('join', new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons'));
+ }
+ } else {
+ $qb->join('activity.reasons', 'reasons');
+ }
if ($where instanceof Expr\Andx) {
$where->add($clause);
@@ -62,6 +84,23 @@ class ActivityReasonFilter implements FilterInterface
$qb->add('where', $where);
$qb->setParameter('selected_activity_reasons', $data['reasons']);
}
+
+ /**
+ * Check if a join between Activity and Reason is already defined
+ *
+ * @param Join[] $joins
+ * @return boolean
+ */
+ private function checkJoinAlreadyDefined(array $joins)
+ {
+ foreach ($joins as $join) {
+ if ($join->getAlias() === 'reasons') {
+ return true;
+ }
+ }
+
+ return false;
+ }
public function applyOn()
{
@@ -95,4 +134,18 @@ class ActivityReasonFilter implements FilterInterface
{
return new Role(ActivityVoter::SEE);
}
+
+ public function describeAction($data, $format = 'string')
+ {
+ // collect all the reasons'name used in this filter in one array
+ $reasonsNames = array_map(
+ function(ActivityReason $r) {
+ return "\"".$this->translatableStringHelper->localize($r->getName())."\"";
+ },
+ $this->reasonRepository->findBy(array('id' => $data['reasons']->toArray()))
+ );
+
+ return array("Filtered by reasons: only %list%",
+ ["%list%" => implode(", ", $reasonsNames)]);
+ }
}
diff --git a/Resources/config/services/export.yml b/Resources/config/services/export.yml
index 94875c3c2..26b69ebab 100644
--- a/Resources/config/services/export.yml
+++ b/Resources/config/services/export.yml
@@ -10,10 +10,15 @@ services:
class: Chill\ActivityBundle\Export\Filter\ActivityReasonFilter
arguments:
- "@chill.main.helper.translatable_string"
+ - "@chill_activity.repository.reason"
tags:
- { name: chill.export_filter, alias: 'activity_reason_filter' }
chill.activity.export.reason_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ReasonAggregator
+ arguments:
+ - "@chill_activity.repository.reason_category"
+ - "@chill_activity.repository.reason"
+ - "@chill.main.helper.translatable_string"
tags:
- { name: chill.export_aggregator, alias: activity_reason }
diff --git a/Resources/config/services/repositories.yml b/Resources/config/services/repositories.yml
index 967c67bc3..2867782b4 100644
--- a/Resources/config/services/repositories.yml
+++ b/Resources/config/services/repositories.yml
@@ -4,3 +4,15 @@ services:
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments:
- 'Chill\ActivityBundle\Entity\ActivityType'
+
+ chill_activity.repository.reason:
+ class: Doctrine\ORM\EntityRepository
+ factory: ['@doctrine.orm.entity_manager', getRepository]
+ arguments:
+ - 'Chill\ActivityBundle\Entity\ActivityReason'
+
+ chill_activity.repository.reason_category:
+ class: Doctrine\ORM\EntityRepository
+ factory: ['@doctrine.orm.entity_manager', getRepository]
+ arguments:
+ - 'Chill\ActivityBundle\Entity\ActivityReasonCategory'
diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml
index cfa46f634..dfa72bb7a 100644
--- a/Resources/translations/messages.fr.yml
+++ b/Resources/translations/messages.fr.yml
@@ -98,3 +98,13 @@ The activity has been successfully removed.: L'activité a été supprimée.
# exports
Count activities: Nombre d'activités
Count activities by various parameters.: Compte le nombre d'activités enregistrées en fonction de différents paramètres.
+
+#filters
+Filter by reason: Filtrer par sujet d'activité
+'Filtered by reasons: only %list%': 'Filtré par sujet: seulement %list%'
+
+#aggregators
+Aggregate by activity reason: Aggréger par sujet de l'activité
+By reason: Par sujet
+By category of reason: Par catégorie de sujet
+Reason's level: Niveau du sujet
diff --git a/Tests/Export/Aggregator/ReasonAggregatorTest.php b/Tests/Export/Aggregator/ReasonAggregatorTest.php
new file mode 100644
index 000000000..f508fcde2
--- /dev/null
+++ b/Tests/Export/Aggregator/ReasonAggregatorTest.php
@@ -0,0 +1,93 @@
+
+ *
+ * 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 .
+ */
+
+namespace Chill\ActivityBundle\Tests\Aggregator;
+
+use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class ReasonAggregatorTest extends AbstractAggregatorTest
+{
+ /**
+ *
+ * @var \Chill\ActivityBundle\Export\Aggregator\ReasonAggregator
+ */
+ private $aggregator;
+
+ public function setUp()
+ {
+ static::bootKernel();
+
+ $container = static::$kernel->getContainer();
+
+ $this->aggregator = $container->get('chill.activity.export.reason_aggregator');
+
+ // add a fake request with a default locale (used in translatable string)
+ $prophet = new \Prophecy\Prophet;
+ $request = $prophet->prophesize();
+ $request->willExtend(\Symfony\Component\HttpFoundation\Request::class);
+ $request->getLocale()->willReturn('fr');
+
+ $container->get('request_stack')
+ ->push($request->reveal());
+ }
+
+ public function getAggregator()
+ {
+ return $this->aggregator;
+ }
+
+ public function getFormData()
+ {
+ return array(
+ array('level' => 'reasons'),
+ array('level' => 'categories')
+ );
+ }
+
+ public function getQueryBuilders()
+ {
+ if (static::$kernel === null) {
+ static::bootKernel();
+ }
+
+ $em = static::$kernel->getContainer()
+ ->get('doctrine.orm.entity_manager');
+
+ return array(
+ $em->createQueryBuilder()
+ ->select('count(activity.id)')
+ ->from('ChillActivityBundle:Activity', 'activity'),
+ $em->createQueryBuilder()
+ ->select('count(activity.id)')
+ ->from('ChillActivityBundle:Activity', 'activity')
+ ->join('activity.reasons', 'reasons'),
+ $em->createQueryBuilder()
+ ->select('count(activity.id)')
+ ->from('ChillActivityBundle:Activity', 'activity')
+ ->join('activity.reasons', 'reasons')
+ ->join('reasons.category', 'category')
+ );
+ }
+
+}
diff --git a/Tests/Export/Export/CountActivityTest.php b/Tests/Export/Export/CountActivityTest.php
new file mode 100644
index 000000000..4f9918fd0
--- /dev/null
+++ b/Tests/Export/Export/CountActivityTest.php
@@ -0,0 +1,50 @@
+
+ */
+class CountActivityTest extends AbstractExportTest
+{
+ /**
+ *
+ * @var
+ */
+ private $export;
+
+ public function setUp()
+ {
+ static::bootKernel();
+
+ /* @var $container \Symfony\Component\DependencyInjection\ContainerInterface */
+ $container = self::$kernel->getContainer();
+
+ $this->export = $container->get('chill.activity.export.count_activity');
+ }
+
+ public function getExport()
+ {
+ return $this->export;
+ }
+
+ public function getFormData()
+ {
+ return array(
+ array()
+ );
+ }
+
+ public function getModifiersCombination()
+ {
+ return array(
+ array('activity'),
+ array('activity', 'person')
+ );
+ }
+
+}
diff --git a/Tests/Export/Filter/ActivityReasonFilterTest.php b/Tests/Export/Filter/ActivityReasonFilterTest.php
new file mode 100644
index 000000000..1c4ca4c21
--- /dev/null
+++ b/Tests/Export/Filter/ActivityReasonFilterTest.php
@@ -0,0 +1,109 @@
+
+ *
+ * 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 .
+ */
+
+namespace Chill\ActivityBundle\Tests\Filter;
+
+use Chill\MainBundle\Test\Export\AbstractFilterTest;
+use Doctrine\Common\Collections\ArrayCollection;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class ActivityReasonFilterTest extends AbstractFilterTest
+{
+ /**
+ *
+ * @var \Chill\PersonBundle\Export\Filter\GenderFilter
+ */
+ private $filter;
+
+ public function setUp()
+ {
+ static::bootKernel();
+
+ $container = static::$kernel->getContainer();
+
+ $this->filter = $container->get('chill.activity.export.reason_filter');
+
+ // add a fake request with a default locale (used in translatable string)
+ $prophet = new \Prophecy\Prophet;
+ $request = $prophet->prophesize();
+ $request->willExtend(\Symfony\Component\HttpFoundation\Request::class);
+ $request->getLocale()->willReturn('fr');
+
+ $container->get('request_stack')
+ ->push($request->reveal());
+ }
+
+
+ public function getFilter()
+ {
+ return $this->filter;
+ }
+
+ public function getFormData()
+ {
+ if (static::$kernel === null) {
+ static::bootKernel();
+ }
+
+ $em = static::$kernel->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ;
+
+ $reasons = $em->createQuery("SELECT reason "
+ . "FROM ChillActivityBundle:ActivityReason reason")
+ ->getResult();
+
+ // generate an array of 5 different combination of results
+ for ($i=0; $i < 5; $i++) {
+ $r[] = array('reasons' => new ArrayCollection(array_splice($reasons, ($i + 1) * -1)));
+ }
+
+ return $r;
+ }
+
+ public function getQueryBuilders()
+ {
+ if (static::$kernel === null) {
+ static::bootKernel();
+ }
+
+ $em = static::$kernel->getContainer()
+ ->get('doctrine.orm.entity_manager');
+
+ return array(
+ $em->createQueryBuilder()
+ ->select('count(activity.id)')
+ ->from('ChillActivityBundle:Activity', 'activity'),
+ $em->createQueryBuilder()
+ ->select('count(activity.id)')
+ ->from('ChillActivityBundle:Activity', 'activity')
+ ->join('activity.reasons', 'reasons'),
+ $em->createQueryBuilder()
+ ->select('count(activity.id)')
+ ->from('ChillActivityBundle:Activity', 'activity')
+ ->join('activity.reasons', 'reasons')
+ ->join('reasons.category', 'category')
+ );
+ }
+
+}