diff --git a/Doctrine/Phpcr/PublishWorkflowQueryFilter.php b/Doctrine/Phpcr/PublishWorkflowQueryFilter.php new file mode 100644 index 00000000..b80ba1f8 --- /dev/null +++ b/Doctrine/Phpcr/PublishWorkflowQueryFilter.php @@ -0,0 +1,154 @@ + + */ +class PublishWorkflowQueryFilter +{ + private $skipClasses = array(); + + /** + * Hashmap of FQN => field configuration as per configureFields(). + * + * @var array + */ + private $fieldMap = array(); + + /** + * Mark a class to be skipped from filtering + * + * @param string $class FQN + */ + public function skipClass($class) + { + $this->skipClasses[$class] = true; + } + + /** + * The default map if not set for a specific class is + * + * publishable => publishable + * publishStartDate => publishStartDate + * publishEndDate => publishEndDate + * + * @param string $class FQN + * @param array $map Hashmap with the field names to use for filtering + */ + public function configureFields($class, array $map) + { + $this->fieldMap[$class] = $map; + } + + /** + * Update query to limit results to published documents. + * + * @param QueryBuilder $queryBuilder + */ + public function filterQuery(QueryBuilder $queryBuilder) + { + /** @var From $from */ + $from = $queryBuilder->getChildOfType(AbstractNode::NT_FROM); + $map = $this->extractAlias($from); + foreach ($map as $alias => $options) { + if (isset($this->skipClasses[$options['class']])) { + continue; + } + + if ($options['publishable']) { + $queryBuilder + ->andWhere() + ->eq() + ->field($this->getFieldName('publishable', $alias, $options)) + ->literal(true) + ; + } + + if ($options['publish_time_period']) { + $queryBuilder + ->andWhere() + ->andX() + ->orX() + // TODO how to check for IS NULL? + ->not()->fieldIsset($this->getFieldName('publishStartDate', $alias, $options))->end() + ->lte() + ->field($this->getFieldName('publishStartDate', $alias, $options)) + ->literal(new \DateTime()) + ->end() + ->end() + ->andX() + ->orX() + ->not()->fieldIsset($this->getFieldName('publishEndDate', $alias, $options))->end() + ->gte() + ->field($this->getFieldName('publishEndDate', $alias, $options)) + ->literal(new \DateTime()) + ->end() + ->end() + ->end() + ; + } + } + } + + private function extractAlias(AbstractNode $source) + { + if ($source instanceof SourceDocument) { + $class = $source->getDocumentFqn(); + return array( + $source->getAlias() => array( + 'class' => $class, + 'publishable' => is_subclass_of($class, 'Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishableReadInterface'), + 'publish_time_period' => is_subclass_of($class, 'Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishTimePeriodReadInterface'), + ) + ); + } + + if ($source instanceof From) { + return $this->extractAlias($source->getChildOfType(AbstractNode::NT_SOURCE)); + } + + if (!$source instanceof SourceJoin) { + throw new \Exception(sprintf('Source of class %s is not implemented', get_class($source))); + } + + $map = $this->extractAlias($source->getChildOfType(AbstractNode::NT_SOURCE_JOIN_LEFT)); + return array_merge( + $map, + $this->extractAlias($source->getChildOfType(AbstractNode::NT_SOURCE_JOIN_RIGHT)) + ); + } + + private function getFieldName($field, $alias, $options) + { + return $alias.'.'.(isset($this->fieldMap[$options['class']][$field]) + ? $this->fieldMap[$options['class']][$field] + : $field + ); + } +} diff --git a/Tests/Functional/PublishWorkflow/QueryFilterTest.php b/Tests/Functional/PublishWorkflow/QueryFilterTest.php new file mode 100644 index 00000000..cd9fffb9 --- /dev/null +++ b/Tests/Functional/PublishWorkflow/QueryFilterTest.php @@ -0,0 +1,87 @@ +db('PHPCR')->loadFixtures(array('\Symfony\Cmf\Bundle\CoreBundle\Tests\Resources\DataFixture\LoadPublishableData')); + + /** @var DocumentManagerInterface $dm */ + $dm = $this->db('PHPCR')->getOm(); + $this->qb = $dm->createQueryBuilder(); + $this->qb->fromDocument($this->documentClass, 'a'); + $documents = $this->qb->getQuery()->getResult(); + $this->assertCount(3, $documents); + + $this->assertTrue(isset($documents['/published'])); + $this->assertTrue(isset($documents['/unpublishable'])); + $this->assertTrue(isset($documents['/timeperiod'])); + } + + public function testQueryFilter() + { + $filter = new PublishWorkflowQueryFilter(); +// $filter->filterQuery($this->qb); + $this->qb + ->where() + ->lte() + ->field('a.publishStartDate') + ->literal(new \DateTime()); +var_dump($this->qb->getQuery()->getStatement()); + $documents = $this->qb->getQuery()->getResult(); + $this->assertCount(1, $documents); +var_dump(array_keys($documents->toArray())); + $this->assertTrue(isset($documents['/published'])); + } + + public function testSkip() + { + $filter = new PublishWorkflowQueryFilter(); + $filter->skipClass($this->documentClass); + $filter->filterQuery($this->qb); + $documents = $this->qb->getQuery()->getResult(); + $this->assertCount(3, $documents); + + $this->assertTrue(isset($documents['/published'])); + $this->assertTrue(isset($documents['/unpublishable'])); + $this->assertTrue(isset($documents['/timeperiod'])); + } + + /** + * This test is relying on the fact that publish start date may be null + */ + public function testMap() + { + $filter = new PublishWorkflowQueryFilter(); + $filter->configureFields($this->documentClass, array('publishStartDate' => 'foo')); + $filter->filterQuery($this->qb); + $documents = $this->qb->getQuery()->getResult(); + $this->assertCount(2, $documents); + + $this->assertTrue(isset($documents['/published'])); + $this->assertTrue(isset($documents['/timeperiod'])); + } +} diff --git a/Tests/Resources/DataFixture/LoadPublishableData.php b/Tests/Resources/DataFixture/LoadPublishableData.php new file mode 100644 index 00000000..33069023 --- /dev/null +++ b/Tests/Resources/DataFixture/LoadPublishableData.php @@ -0,0 +1,44 @@ +id = '/published'; + $manager->persist($doc); + + $doc = new Publishable(false); + $doc->id = '/unpublishable'; + $manager->persist($doc); + + $doc = new Publishable(true, new \DateTime('tomorrow')); + $doc->id = '/timeperiod'; + $manager->persist($doc); + + $manager->flush(); + } +} diff --git a/Tests/Resources/Document/Publishable.php b/Tests/Resources/Document/Publishable.php new file mode 100644 index 00000000..6e82193c --- /dev/null +++ b/Tests/Resources/Document/Publishable.php @@ -0,0 +1,65 @@ +publishable = $publishable; + $this->publishStartDate = $start; + $this->publishEndDate = $end; + } + + public function getId() + { + return $this->id; + } + + public function isPublishable() + { + return $this->publishable; + } + + public function getPublishStartDate() + { + return $this->publishStartDate; + } + + public function getPublishEndDate() + { + return $this->publishEndDate; + } +}