<?php
namespace Cms\TagBundle\Doctrine;
use Cms\CoreBundle\Model\Search\SearchableTrait;
use Cms\TagBundle\Entity\Tag;
use Cms\CoreBundle\Util\Doctrine\EntityRepository;
/**
* Class TagRepository
* @package Cms\TagBundle\Doctrine
*
* @method Tag findExact($id)
*/
class TagRepository extends EntityRepository
{
use SearchableTrait;
const RELATION_TABLE_REGEX = '#^cms__modules__\w+?__\w+?__\w+?__tag$#';
/**
* @param Tag $tag
* @param $tenantId
* @return bool
*/
public function isUniqueTag(Tag $tag, $tenantId)
{
$builder = $this->createQueryBuilder('tags');
$builder
->andWhere('tags.name = :name')
->setParameter('name', $tag->getName())
;
if ($tenantId) {
$builder
->andWhere('tags.tenant = :tenant')
->setParameter('tenant', $tenantId)
;
}
if ($tag->getId()) {
$builder
->andWhere('tags.id != :id')
->setParameter('id', $tag->getId())
;
}
return is_null($this->queryOne($builder));
}
/**
* Merges one tag into another
*
* @param Tag $source Tag that will be merged and removed
* @param Tag|integer $target
* return $this
*/
public function mergeTag(Tag $source, $target)
{
if ($target instanceof Tag) {
$target = $target->getId();
}
$connection = $this->getEntityManager()->getConnection();
$connection->beginTransaction();
foreach ($this->getTagRelationTables() as $table) {
$this->mergeTable($table, $source->getId(), $target);
}
$this->getEntityManager()->delete($source);
$connection->commit();
}
/**
* Returns list of tag relation tables
* @return []
*/
protected function getTagRelationTables()
{
$result = [];
$tables = $this->getEntityManager()->getConnection()->getSchemaManager()->listTables();
foreach ($tables as $table) {
if (preg_match(self::RELATION_TABLE_REGEX, $table->getName())) {
$result[] = $table->getName();
}
}
return $result;
}
/**
* @param string $table
* @param integer $sourceId
* @param integer $targetId
* @todo reduce requests
*/
public function mergeTable($table, $sourceId, $targetId)
{
$connection = $this->getEntityManager()->getConnection();
// At first get list of proxy id from target and use it for delete statement to avoid key duplication
$query = $connection->prepare('SELECT item_id FROM '.$table.' WHERE tag_id = :id');
$query->execute(array('id' => $targetId));
$targetRelations = $query->fetchAll(\PDO::FETCH_COLUMN);
if (count($targetRelations)) {
// System updates only records that do not have relation with target tag.
// System uses IN condition to avoid primary key duplication.
// Records that have relation with target tag deleted inside mergeTag method due to cascade deletion.
$query = $connection->prepare(
'UPDATE '.$table.' SET tag_id = :targetId
WHERE tag_id = :sourceId AND item_id NOT IN ('.implode(',', $targetRelations).')'
);
$query->execute(array('targetId' => $targetId, 'sourceId' => $sourceId));
}
}
/**
* Find all tags with root level
*
* @param int $limit
* @param int $page
*
* @return array|Tag[]
*/
public function findAllRoots($limit = 0, $page = 0)
{
return $this->queryMany(
$this->createQueryBuilder('tags')
->andWhere('tags.parent IS NULL')
->addOrderBy('tags.name', 'ASC'),
$limit,
$page
);
}
/**
* Find all tags without current
*
* @param integer|null $tagId
* @return array
*/
public function parentTagsQueryBuilder($tagId = null)
{
$qb = $this->createQueryBuilder('tags');
if ($tagId) {
$qb->where('tags.id != :tagId')->setParameter('tagId', $tagId);
}
return $qb;
}
/**
* @param string $name
* @return Tag
* @throws \Exception
*/
public function findOneByName($name)
{
if (empty(trim($name))) {
throw new \Exception();
}
return $this->queryOne(
$this->createQueryBuilder('tag')
->andWhere('tag.name = :name')
->setParameter('name', $name)
);
}
}