<?php
namespace Cms\ContainerBundle\Doctrine;
use Cms\ContainerBundle\Entity\Container;
use Cms\ContainerBundle\Entity\Containers\GenericContainer;
use Cms\ContainerBundle\Entity\Containers\IntranetContainer;
use Cms\ContainerBundle\Entity\Containers\PersonalContainer;
use Cms\ContainerBundle\Entity\Containers\StorageContainer;
use Cms\CoreBundle\Util\Doctrine\EntityRepository;
use Cms\TenantBundle\Entity\Tenant;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\QueryBuilder;
use Platform\SecurityBundle\Entity\Identity\Account;
/**
* Class ContainerRepository
* @package Cms\ContainerBundle\Doctrine
*
* @method Container findExact($id)
* @method Container find($id)
* @method array|Container[] findAll()
*/
class ContainerRepository extends EntityRepository
{
/**
* @param Container $root
* @param string|null $path
* @return array|Container[]
*/
public function findByPath(Container $root, ?string $path): array
{
// normalize path
$path = trim($path, '/'); // trim slashes from front and back
$path = preg_replace('/\/+/', '/', $path); // replace multiple slashes with one
$path = $path ?: null;
// if no path, nothing to return
if ( ! $path) {
return [$root];
}
// break apart the path
$slugs = explode('/', $path);
// start finding the containers
$departments = [
$root, // we include the root as the first department
];
// loop over the slugs
foreach ($slugs as $slug) {
// try to find current one
$department = $this->findOneBy([
'parent' => $departments[count($departments) - 1],
'slug' => $slug,
]);
// if it is not found, the path is bad
if ( ! $department) {
return [];
}
// we did find one, add it to the set
$departments[] = $department;
}
return $departments;
}
/**
* @return Container
* @throws \Exception
*/
public function findOneForRendering()
{
$containers = $this->queryMany($this->createQueryBuilder('container')
->andWhere('container.parent IS NULL')
->orderBy('container.id', 'ASC')
);
if (count($containers) < 1) {
throw new \Exception();
}
foreach ($containers as $container) {
if ($container instanceof GenericContainer) {
return $container;
}
}
return $containers[0];
}
/**
* @param Tenant $tenant
* @param Container $parent
* @return array|Container[]
*/
public function findAllForTenant(Tenant $tenant, Container $parent = null)
{
$qb = $this->createQueryBuilder('containers');
$qb
->andWhere('containers.tenant = :tenant')
->setParameter('tenant', $tenant);
if ($parent !== null) {
$qb
->andWhere('containers.parent = :parent')
->setParameter('parent', $parent);
} else {
$qb->andWhere('containers.parent IS NULL');
}
return $this->queryMany($qb);
}
/**
* TODO: this method has become overloaded and needs refactored and improved
*
* @param Container|null $subtree
* @param array|string[] $types
* @param Account|null $account
* @return QueryBuilder
*/
public function findAllHierarchyQb(Container $subtree = null, array $types = [], ?Account $account = null): QueryBuilder
{
$qb = $this->createQueryBuilder('containers');
if ( ! empty($subtree)) {
$qb
->andWhere('containers.rt = :root')
->setParameter('root', $subtree->getRoot());
} else {
$qb
->leftJoin($this->getClassName(), 'roots', 'WITH', 'containers.rt = roots.id')
->addSelect('CASE WHEN roots INSTANCE OF :generic THEN 0 WHEN roots INSTANCE OF :intranet THEN 1 WHEN roots INSTANCE OF :personal THEN 2 WHEN roots INSTANCE OF :storage THEN 3 ELSE 4 END AS HIDDEN searchOrder')
->setParameter('generic', GenericContainer::DISCR)
->setParameter('intranet', IntranetContainer::DISCR)
->setParameter('personal', PersonalContainer::DISCR)
->setParameter('storage', StorageContainer::DISCR)
->addOrderBy('searchOrder', 'ASC')
->addOrderBy('roots.name', 'ASC');
}
if ( ! empty($types)) {
$ors = [];
foreach ($types as $index => $type) {
$ors[] = $qb->expr()->isInstanceOf('containers', $type);
}
$qb->andWhere(call_user_func_array([$qb->expr(), 'orX'], $ors));
if (in_array(PersonalContainer::class, $types) && ! empty($account)) {
$qb
->andWhere('roots.account = :account')
->setParameter('account', $account);
}
}
$qb
->addOrderBy('containers.rt', 'ASC')
->addOrderBy('containers.lft', 'ASC');
return $qb;
}
/**
* @param Container|null $subtree
* @param array|string[] $types
* @param Account|null $account
* @return array|Container[]
*/
public function findAllHierarchy(Container $subtree = null, array $types = [], ?Account $account = null): array
{
return $this->queryMany($this->findAllHierarchyQb($subtree, $types, $account));
}
/**
* @return array|Container[]
*/
public function findAllRoots()
{
// create builder
$qb = $this->createQueryBuilder('containers');
// filter by site
$qb
->andWhere('containers.parent IS NULL');
// order by left value, then by name
$qb
->addOrderBy('containers.name', 'ASC');
// run
return $this->queryMany($qb);
}
/**
* @param Container $container
* @return array|Container[]
*/
public function findAncestors(Container $container)
{
// create builder
$qb = $this->createQueryBuilder('containers');
// make sure we stay in our root
$qb
->andWhere('containers.rt = :root')
->setParameter('root', $container->getRoot());
// use left/right anomaly to filter
// TODO: if we stay in our root, can we just use the level instead of left/right???
$qb
->andWhere('containers.lft < :left')
->setParameter('left', $container->getLeft())
->andWhere('containers.rgt > :right')
->setParameter('right', $container->getRight());
// order with furthest ancestor first
$qb->addOrderBy('containers.lft', 'ASC');
// run
return $this->queryMany($qb);
}
/**
* @param Container $container
* @return array|Container[]
*/
public function findDescendants(Container $container)
{
// create builder
$qb = $this->createQueryBuilder('containers');
// make sure we stay in our root
$qb
->andWhere('containers.rt = :root')
->setParameter('root', $container->getRoot());
// use left/right anomaly to filter
// TODO: if we stay in our root, can we just use the level instead of left/right???
$qb
->andWhere('containers.lft > :left')
->setParameter('left', $container->getLeft())
->andWhere('containers.rgt < :right')
->setParameter('right', $container->getRight());
// order with furthest ancestor first
$qb->addOrderBy('containers.lft', 'ASC');
// run
return $this->queryMany($qb);
}
/**
* @param Container $container
* @param int $limit
* @param int $page
* @return array
*/
public function findManyChildren(Container $container, $limit = 0, $page = 0)
{
$queryBuilder =
$this
->createQueryBuilder('container')
->andWhere('container.parent = :parent')->setParameter('parent', $container)
->orderBy('container.name', 'ASC');
return $this->queryMany($queryBuilder, $limit, $page);
}
/**
* Get all containers with specified ID or name
*
* @param mixed $value
* @return array|Container[]
*/
public function findAllByIdOrName($value)
{
$value = strval($value);
if (preg_match('/^[1-9]\d*$/', $value) === 1) {
return $this->findBy(array(
'id' => intval($value),
));
}
return $this->findBy(array(
'name' => $value,
));
}
/**
* @param Container $container
* @param string $module
* @return array|Container[]
*/
public function findModuleViewableHierarchy(Container $container, $module)
{
return $this->findViewableHierarchy(
$container,
(new Criteria())
->andWhere(Criteria::expr()->eq(
sprintf(
'containers.moduleFlags.%s',
$module
),
true
))
->orderBy(array(
'containers.name' => 'ASC',
))
);
}
/**
* @param Container $container
* @param string $module
* @return array|Container[]
*/
public function findModuleViewableHierarchySettings(Container $container, $module)
{
return $this->findViewableHierarchy(
$container,
(new Criteria())
->andWhere(Criteria::expr()->eq(
sprintf(
'containers.moduleFlags.%s',
$module
),
true
))
->orderBy(array(
'containers.rt' => 'ASC',
'containers.lft' => 'ASC',
'containers.name' => 'ASC',
))
);
}
/**
* @param Container $container
* @param Criteria $criteria
* @return array|Container[]
*/
public function findViewableHierarchy(Container $container, Criteria $criteria = null)
{
// create builder
$qb = $this->createQueryBuilder('containers');
// make sure we stay in our root
$qb
->andWhere('containers.rt = :root')
->setParameter('root', $container->getRoot());
// use left/right anomaly to filter
// TODO: if we stay in our root, can we just use the level instead of left/right???
$qb
->andWhere('containers.lft >= :left')
->setParameter('left', $container->getLeft())
->andWhere('containers.rgt <= :right')
->setParameter('right', $container->getRight())
->andWhere('containers.hidden = :hidden')
->setParameter('hidden', false)
;
// order with furthest ancestor first
$qb->addOrderBy('containers.lft', 'ASC');
// apply criteria
if ( ! empty($criteria)) {
$qb->resetDQLPart('orderBy');
$qb->addCriteria($criteria);
}
// run
return $this->queryMany($qb);
}
/**
* Soft search and ignoring of missed things
* @param array $ids
* @return Container[]
*/
public function searchContainers($ids = [])
{
$qb = $this->createQueryBuilder('containers')
->andWhere('containers.id IN (:ids)')
->setParameter('ids', $ids);
/** @var Container[] $results */
$results = $this->queryMany($qb);
$fixed = [];
foreach ($results as $result) {
$fixed[$result->getId()] = $result;
}
return $results;
}
}