at the end of the day, it was inevitable
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle;
|
||||
|
||||
use CacheBundle\DependencyInjection\Compiler\CollectFeedFetchersCompilerPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* Class CacheBundle
|
||||
* @package CacheBundle
|
||||
*/
|
||||
class CacheBundle extends Bundle
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the bundle.
|
||||
*
|
||||
* It is only ever called once when the cache is empty.
|
||||
*
|
||||
* @param ContainerBuilder $container A ContainerBuilder instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function build(ContainerBuilder $container)
|
||||
{
|
||||
$container->addCompilerPass(new CollectFeedFetchersCompilerPass());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle;
|
||||
|
||||
/**
|
||||
* Class CacheBundleServices
|
||||
* @package CacheBundle
|
||||
*/
|
||||
abstract class CacheBundleServices
|
||||
{
|
||||
|
||||
/**
|
||||
* Cache storage.
|
||||
*
|
||||
* Implements {@see \CacheBundle\Cache\CacheInterface} interface.
|
||||
*/
|
||||
const CACHE = 'app.feed_manager';
|
||||
|
||||
/**
|
||||
* Source cache storage.
|
||||
*
|
||||
* Implements {@see \CacheBundle\Cache\SourceCacheInterface} interface.
|
||||
*/
|
||||
const SOURCE_CACHE = 'app.source_manager';
|
||||
|
||||
/**
|
||||
* Feed fetcher factory.
|
||||
*
|
||||
* Implements {@see \CacheBundle\Feed\Fetcher\Factory\FeedFetcherFactoryInterface}
|
||||
* interface.
|
||||
*/
|
||||
const FEED_FETCHER_FACTORY = 'cache.feed_fetcher_factory';
|
||||
|
||||
/**
|
||||
* Comment manager.
|
||||
*
|
||||
* Implements {@see \CacheBundle\Document\Extractor\DocumentContentExtractorInterface}
|
||||
* interface.
|
||||
*/
|
||||
const DOCUMENT_CONTENT_EXTRACTOR = 'cache.document_content_extractor';
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Command;
|
||||
|
||||
use CacheBundle\Entity\Page;
|
||||
use CacheBundle\Entity\Query\SimpleQuery;
|
||||
use CacheBundle\Repository\SimpleQueryRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class RemoveOldQueriesCommand
|
||||
* @package CacheBundle\Command
|
||||
*/
|
||||
class RemoveOldQueriesCommand extends Command
|
||||
{
|
||||
|
||||
const NAME = 'socialhose:query:remove_old';
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* RemoveOldQueriesCommand constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface instance.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
parent::__construct(self::NAME);
|
||||
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the current command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setDescription('Remove old queries from cache');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the current command.
|
||||
*
|
||||
* This method is not abstract because you can use this class
|
||||
* as a concrete class. In this case, instead of defining the
|
||||
* execute() method, you set the code to execute by passing
|
||||
* a Closure to the setCode() method.
|
||||
*
|
||||
* @param InputInterface $input An InputInterface instance.
|
||||
* @param OutputInterface $output An OutputInterface instance.
|
||||
*
|
||||
* @return null|integer null or 0 if everything went fine, or an error code.
|
||||
*
|
||||
* @throws \LogicException When this abstract method is not implemented.
|
||||
*
|
||||
* @see setCode()
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
/** @var SimpleQueryRepository $queryRepository */
|
||||
$queryRepository = $this->em->getRepository(SimpleQuery::class);
|
||||
/** @var EntityRepository $pageRepository */
|
||||
$pageRepository = $this->em->getRepository(Page::class);
|
||||
|
||||
$expr = $this->em->getExpressionBuilder();
|
||||
|
||||
$ids = $queryRepository->getOld();
|
||||
$pageRepository->createQueryBuilder('Page')
|
||||
->delete()
|
||||
->where($expr->in('Page.query', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$queryRepository->createQueryBuilder('Query')
|
||||
->delete()
|
||||
->where($expr->in('Query.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Comment\Manager;
|
||||
|
||||
use CacheBundle\Entity\Comment;
|
||||
use CacheBundle\Entity\Document;
|
||||
use CacheBundle\Repository\CommentRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* Class CommentManager
|
||||
* @package CacheBundle\Comment\Manager
|
||||
*/
|
||||
class CommentManager implements CommentManagerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* CommentManager constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface instance.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new comment to specified entity.
|
||||
*
|
||||
* @param Comment $comment A Comment entity instance.
|
||||
* @param Document $document A Document entity instance.
|
||||
*
|
||||
* @return Comment
|
||||
*/
|
||||
public function addComment(Comment $comment, Document $document)
|
||||
{
|
||||
$comment->setDocument($document);
|
||||
$document->incCommentsCount();
|
||||
|
||||
$this->em->persist($document);
|
||||
$this->em->persist($comment);
|
||||
$this->em->flush();
|
||||
|
||||
/** @var CommentRepository $repository */
|
||||
$repository = $this->em->getRepository(Comment::class);
|
||||
$repository->updateCommentMarks($document->getId(), self::NEW_COMMENT_POOL_SIZE);
|
||||
|
||||
return $comment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Comment\Manager;
|
||||
|
||||
use CacheBundle\Entity\Comment;
|
||||
use CacheBundle\Entity\Document;
|
||||
|
||||
/**
|
||||
* Interface CommentManagerInterface
|
||||
* @package CacheBundle\Comment\Manager
|
||||
*/
|
||||
interface CommentManagerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Size of new comments pool.
|
||||
*/
|
||||
const NEW_COMMENT_POOL_SIZE = 1;
|
||||
|
||||
/**
|
||||
* Add new comment to specified entity.
|
||||
*
|
||||
* @param Comment $comment A Comment entity instance.
|
||||
* @param Document $document A Document entity instance.
|
||||
*
|
||||
* @return Comment
|
||||
*/
|
||||
public function addComment(Comment $comment, Document $document);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\DTO;
|
||||
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use IndexBundle\Filter\FilterInterface;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class AnalyticDTO
|
||||
*
|
||||
* Contains all data which is used for creating Analytic and AnalyticContext entities.
|
||||
*
|
||||
* @package CacheBundle\DTO
|
||||
*/
|
||||
class AnalyticDTO
|
||||
{
|
||||
|
||||
/**
|
||||
* Used feeds as source of data for analyzes.
|
||||
*
|
||||
* @var AbstractFeed[]
|
||||
*/
|
||||
public $feeds;
|
||||
|
||||
/**
|
||||
* Analytic owner.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $owner;
|
||||
|
||||
|
||||
/**
|
||||
* Additional filters for data from feeds.
|
||||
*
|
||||
* @var FilterInterface[]
|
||||
*/
|
||||
public $filters;
|
||||
|
||||
/**
|
||||
* Additional filters as is it's passed from frontend.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $rawFilters;
|
||||
|
||||
/**
|
||||
* AnalyticDTO constructor.
|
||||
*
|
||||
* @param array $feeds Used feeds as source of data for
|
||||
* analyzes.
|
||||
* @param User $owner Analytic owner.
|
||||
|
||||
* @param FilterInterface[] $filters Additional filters for data from feeds.
|
||||
* @param array $rawFilters Additional filters as is it's passed
|
||||
* from frontend.
|
||||
*/
|
||||
public function __construct(
|
||||
array $feeds = [],
|
||||
User $owner = null,
|
||||
array $filters = [],
|
||||
array $rawFilters = []
|
||||
) {
|
||||
$this->feeds = $feeds;
|
||||
$this->owner = $owner;
|
||||
$this->filters = $filters;
|
||||
$this->rawFilters = $rawFilters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\DependencyInjection\Compiler;
|
||||
|
||||
use CacheBundle\Feed\Fetcher\Factory\LazyFeedFetcherFactory;
|
||||
use CacheBundle\Feed\Fetcher\FeedFetcherInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
|
||||
/**
|
||||
* Class CollectFeedFetchersCompilerPass
|
||||
* @package CacheBundle\DependencyInjection\Compiler
|
||||
*/
|
||||
class CollectFeedFetchersCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
|
||||
const LAZY_FACTORY_ID = 'cache.feed_fetcher_factory.lazy';
|
||||
|
||||
/**
|
||||
* You can modify the container here before it is dumped to PHP code.
|
||||
*
|
||||
* @param ContainerBuilder $container A ContainerBuilder instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (! $container->has(self::LAZY_FACTORY_ID)) {
|
||||
$this->throwException('Lazy factory not registered.');
|
||||
}
|
||||
|
||||
$lazyFactory = $container->getDefinition(self::LAZY_FACTORY_ID);
|
||||
if ($lazyFactory->getClass() !== LazyFeedFetcherFactory::class) {
|
||||
$this->throwException(
|
||||
'Invalid factory, expected '. LazyFeedFetcherFactory::class
|
||||
.' but got '. $lazyFactory->getClass()
|
||||
);
|
||||
}
|
||||
|
||||
$fetchers = array_keys($container->findTaggedServiceIds('socialhose.feed_fetcher'));
|
||||
|
||||
$map = [];
|
||||
foreach ($fetchers as $id) {
|
||||
$class = $container->getDefinition($id)->getClass();
|
||||
$reflection = new \ReflectionClass($class);
|
||||
if (! $reflection->implementsInterface(FeedFetcherInterface::class)) {
|
||||
$this->throwException('');
|
||||
}
|
||||
|
||||
$map[$class::support()] = $id;
|
||||
}
|
||||
|
||||
$lazyFactory->replaceArgument(1, $map);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message A additional exception message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function throwException($message = '')
|
||||
{
|
||||
throw new \RuntimeException('Can\'t register feed fetchers in lazy factory. '. $message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Document\Extractor;
|
||||
|
||||
use AppBundle\Enum\UnhandledEnumException;
|
||||
use UserBundle\Enum\ThemeOptionExtractEnum;
|
||||
|
||||
/**
|
||||
* Class BasicDocumentContentExtractor
|
||||
*
|
||||
* @package CacheBundle\Document\Extractor
|
||||
*/
|
||||
class BasicDocumentContentExtractor implements DocumentContentExtractorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Words and symbols which should be ignored.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private static $unnecessaryWords = [
|
||||
'AND',
|
||||
'OR',
|
||||
'NOT',
|
||||
'(',
|
||||
')',
|
||||
'+',
|
||||
'*',
|
||||
'-',
|
||||
'\\',
|
||||
'//',
|
||||
];
|
||||
|
||||
/**
|
||||
* Regexp for unnecessary parts of query.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private static $unnecessaryRegexp = [
|
||||
'/\^\d+?/',
|
||||
'/~\d+?/',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $startExtractLen;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $contextExtractLen;
|
||||
|
||||
/**
|
||||
* @var \Closure[]
|
||||
*/
|
||||
private $converters = [];
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
private $keywordsCache = [];
|
||||
|
||||
/**
|
||||
* BasicDocumentContentExtractor constructor.
|
||||
*
|
||||
* @param integer $startExtractLen How many symbols extract for 'start'
|
||||
* extract type.
|
||||
* @param integer $contextExtractLen How many symbols extract before and after
|
||||
* keyword. Used for 'context' extract type.
|
||||
*/
|
||||
public function __construct($startExtractLen, $contextExtractLen)
|
||||
{
|
||||
$this->startExtractLen = $startExtractLen;
|
||||
$this->contextExtractLen = $contextExtractLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content The document contents.
|
||||
* @param string $query Search query.
|
||||
* @param ThemeOptionExtractEnum $extract Extract type.
|
||||
* @param boolean $highlight Should highlight matched keywords
|
||||
* or not.
|
||||
*
|
||||
* @return ExtractionResult
|
||||
*/
|
||||
public function extract(
|
||||
$content,
|
||||
$query,
|
||||
ThemeOptionExtractEnum $extract,
|
||||
$highlight = false
|
||||
) {
|
||||
switch ($extract->getValue()) {
|
||||
//
|
||||
// We should not extract document content.
|
||||
//
|
||||
case ThemeOptionExtractEnum::NO:
|
||||
$text = '';
|
||||
$offset = '';
|
||||
$extractedLength = '';
|
||||
break;
|
||||
|
||||
//
|
||||
// Extract specific numbers of characters from start of document.
|
||||
//
|
||||
case ThemeOptionExtractEnum::START:
|
||||
$text = mb_substr($content, 0, $this->startExtractLen);
|
||||
$offset = 0;
|
||||
$extractedLength = $this->startExtractLen;
|
||||
break;
|
||||
|
||||
//
|
||||
// Extract specified number of character before and after first
|
||||
// matched keyword.
|
||||
//
|
||||
case ThemeOptionExtractEnum::CONTEXT:
|
||||
$keywords = $this->splitQueryOnKeywords($query);
|
||||
list ($offset, $keywordLength) = $this->getNearestKeyword($keywords, $content);
|
||||
|
||||
if ($offset === -1) {
|
||||
//
|
||||
// We don't find any of search keywords. It maybe when matched
|
||||
// keyword is found in another document property, like 'title'.
|
||||
//
|
||||
// In this case we fallback to 'start' extractor.
|
||||
//
|
||||
$text = mb_substr($content, 0, $this->startExtractLen);
|
||||
$offset = 0;
|
||||
$extractedLength = $this->startExtractLen;
|
||||
} else {
|
||||
//
|
||||
// Convert current offset into proper UTF value and extract
|
||||
// text.
|
||||
//
|
||||
$converter = $this->createOffsetConverter($content);
|
||||
$offset = $converter($offset);
|
||||
|
||||
//
|
||||
// Compute start index and length of extract.
|
||||
//
|
||||
$extractStart = $offset - $this->contextExtractLen;
|
||||
|
||||
$overplus = 0;
|
||||
if ($extractStart < 0) {
|
||||
$overplus = abs($extractStart);
|
||||
$extractStart = 0;
|
||||
}
|
||||
|
||||
$extractedLength = $keywordLength + ($this->contextExtractLen * 2) - $overplus;
|
||||
|
||||
$text = mb_substr($content, $extractStart, $extractedLength, 'UTF-8');
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw UnhandledEnumException::fromInstance($extract);
|
||||
}
|
||||
|
||||
if ($highlight) {
|
||||
// termporarily disable highlighting because of how it appears in emails
|
||||
// ~me 20200425
|
||||
// $text = $this->highlight($text, $query);
|
||||
}
|
||||
|
||||
return new ExtractionResult($text, $offset, $extractedLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query Search query.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function splitQueryOnKeywords($query)
|
||||
{
|
||||
//
|
||||
// Cache all splitted keywords in order to speedup processing.
|
||||
//
|
||||
if (! isset($this->keywordsCache[$query])) {
|
||||
$query = str_replace(self::$unnecessaryWords, '', $query);
|
||||
$query = preg_replace(self::$unnecessaryRegexp, '', $query);
|
||||
|
||||
$this->keywordsCache[$query] = array_filter(\nspl\a\map('trim', mb_split(' ', $query)));
|
||||
}
|
||||
|
||||
return $this->keywordsCache[$query];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keywords Array of keywords.
|
||||
* @param string $content A ArticleDocument content.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getNearestKeyword(array $keywords, $content)
|
||||
{
|
||||
$offset = -1;
|
||||
$keywordLength = 0;
|
||||
foreach ($keywords as $keyword) {
|
||||
$matched = [];
|
||||
preg_match('/(' . $keyword . ')/i', $content, $matched, PREG_OFFSET_CAPTURE);
|
||||
if (isset($matched[0]) && (($offset === -1) || ($offset > $matched[0][1]))) {
|
||||
$offset = $matched[0][1];
|
||||
$keywordLength = mb_strlen($matched[0][0], 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
return [ $offset, $keywordLength ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content Document content.
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
private function createOffsetConverter($content)
|
||||
{
|
||||
//
|
||||
// Save all created converters in order to speedup processing.
|
||||
//
|
||||
$hash = sha1($content);
|
||||
if (! isset($this->converters[$hash])) {
|
||||
$contentLength = mb_strlen($content);
|
||||
$utfMap = [];
|
||||
|
||||
for ($offset = 0; $offset < $contentLength; $offset++) {
|
||||
//
|
||||
// Single unicode character in ANSI format may have one and more
|
||||
// 'characters' (character codes). So for proper offset computation
|
||||
// we should get current character, compute it length in ANSI format
|
||||
// and create proper map between ANSI offset and Unicode offset.
|
||||
//
|
||||
$char = mb_substr($content, $offset, 1);
|
||||
$nonUtfLength = strlen($char);
|
||||
|
||||
for ($charOffset = 0; $charOffset < $nonUtfLength; $charOffset++) {
|
||||
$utfMap[] = $offset;
|
||||
}
|
||||
}
|
||||
|
||||
$this->converters[$hash] = static function ($offset) use ($utfMap) {
|
||||
return $utfMap[$offset];
|
||||
};
|
||||
}
|
||||
|
||||
return $this->converters[$hash];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $text Highlighted text.
|
||||
* @param string $query Query.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function highlight($text, $query)
|
||||
{
|
||||
$keywords = $this->splitQueryOnKeywords($query);
|
||||
|
||||
foreach ($keywords as $keyword) {
|
||||
// this is a very dumb line, but somewhere there is an issue with highlighting
|
||||
// where when "in' is present in a string, it only highlights it. E.g. if a search
|
||||
// string is: "building in public" the result is that it only highlights the word
|
||||
// "in".
|
||||
// [Need a real fix]
|
||||
if (!preg_match("/\bin\b/i", $keyword)) {
|
||||
$text = preg_replace('/('. $keyword .')/i', '<span class=\'cw-keyword--highlight\'>$1</span>', $text);
|
||||
}
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Document\Extractor;
|
||||
|
||||
use UserBundle\Enum\ThemeOptionExtractEnum;
|
||||
|
||||
/**
|
||||
* Interface DocumentContentExtractorInterface
|
||||
*
|
||||
* @package CacheBundle\Document\Extractor
|
||||
*/
|
||||
interface DocumentContentExtractorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $content The document contents.
|
||||
* @param string $query Search query.
|
||||
* @param ThemeOptionExtractEnum $extract Extract type.
|
||||
* @param boolean $highlight Should highlight matched keywords
|
||||
* or not.
|
||||
*
|
||||
* @return ExtractionResult
|
||||
*/
|
||||
public function extract(
|
||||
$content,
|
||||
$query,
|
||||
ThemeOptionExtractEnum $extract,
|
||||
$highlight = false
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Document\Extractor;
|
||||
|
||||
/**
|
||||
* Class ExtractionResult
|
||||
*
|
||||
* @package CacheBundle\Document\Extractor
|
||||
*/
|
||||
class ExtractionResult
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $text;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $start;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $length;
|
||||
|
||||
/**
|
||||
* ExtractionResult constructor.
|
||||
*
|
||||
* @param string $text Extracted text.
|
||||
* @param integer $start The position with which the extraction began.
|
||||
* @param integer $length How much extracts.
|
||||
*/
|
||||
public function __construct($text, $start, $length)
|
||||
{
|
||||
$this->text = $text;
|
||||
$this->start = $start;
|
||||
$this->length = $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getText()
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getStart()
|
||||
{
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getLength()
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity\Analytic;
|
||||
|
||||
use ApiBundle\Entity\ManageableEntityInterface;
|
||||
use ApiBundle\Entity\NormalizableEntityInterface;
|
||||
use ApiBundle\Serializer\Metadata\Metadata;
|
||||
use ApiBundle\Serializer\Metadata\PropertyMetadata;
|
||||
use AppBundle\Entity\BaseEntityTrait;
|
||||
use CacheBundle\Form\AnalyticType;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Analytic
|
||||
*
|
||||
* Holds all data necessary for viewing and computing analytics.
|
||||
*
|
||||
* @ORM\Table(name="analytics")
|
||||
* @ORM\Entity(
|
||||
* repositoryClass="CacheBundle\Repository\AnalyticRepository"
|
||||
* )
|
||||
*
|
||||
* @see Analytic
|
||||
* @ORM\HasLifecycleCallbacks
|
||||
*/
|
||||
class Analytic implements
|
||||
ManageableEntityInterface,
|
||||
NormalizableEntityInterface
|
||||
{
|
||||
|
||||
use BaseEntityTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(nullable=true)
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="UserBundle\Entity\User")
|
||||
*/
|
||||
private $owner;
|
||||
|
||||
/**
|
||||
* @var AnalyticContext
|
||||
*
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="CacheBundle\Entity\Analytic\AnalyticContext",fetch="EAGER",
|
||||
* cascade={ "persist" },
|
||||
* inversedBy="analytics"
|
||||
* )
|
||||
* @ORM\JoinColumn(name="context_id",referencedColumnName="hash")
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
protected $createdAt;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(type="datetime", nullable=true)
|
||||
*/
|
||||
protected $updatedAt;
|
||||
|
||||
|
||||
/**
|
||||
* Analytic constructor.
|
||||
*
|
||||
* @param User $owner Owner of this analytic.
|
||||
* @param AnalyticContext $context Used context.
|
||||
*/
|
||||
public function __construct(User $owner, AnalyticContext $context)
|
||||
{
|
||||
$this->owner = $owner;
|
||||
$this->context = $context;
|
||||
$this->createdAt = new \DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name A saved analytic name.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getOwner()
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $owner Saved analytic owner.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOwner(User $owner = null)
|
||||
{
|
||||
$this->owner = $owner;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AnalyticContext
|
||||
*/
|
||||
public function getContext()
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AnalyticContext $context Used context.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setContext(AnalyticContext $context)
|
||||
{
|
||||
$this->context = $context;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return metadata for current entity.
|
||||
*
|
||||
* @return \ApiBundle\Serializer\Metadata\Metadata
|
||||
*/
|
||||
public function getMetadata()
|
||||
{
|
||||
return new Metadata(static::class, [
|
||||
PropertyMetadata::createInteger('id', [ 'id' ]),
|
||||
PropertyMetadata::createDate('createdAt', [ 'analytic' ]),
|
||||
PropertyMetadata::createDate('updatedAt', [ 'analytic' ]),
|
||||
PropertyMetadata::createEntity('context',AnalyticContext::class,['context']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default normalization groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaultGroups()
|
||||
{
|
||||
return [ 'id', 'analytic','context'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fqcn of form used for creating this entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCreateFormClass()
|
||||
{
|
||||
return AnalyticType::class;
|
||||
}
|
||||
/**
|
||||
* Return fqcn of form used for updating this entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUpdateFormClass()
|
||||
{
|
||||
return AnalyticType::class;
|
||||
}
|
||||
/**
|
||||
* Check whether specified user owner.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOwnedBy(User $user)
|
||||
{
|
||||
return $user->getId() === $this->owner->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get createdAt
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getCreatedAt()
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\PreUpdate()
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function onUpdate()
|
||||
{
|
||||
$this->setUpdatedAt(new \DateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set updatedAt
|
||||
*
|
||||
* @param \DateTime $updatedAt When this analytic is updated.
|
||||
*
|
||||
* @return $Analytic
|
||||
*/
|
||||
public function setUpdatedAt(\DateTime $updatedAt = null)
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updatedAt
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getUpdatedAt()
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity\Analytic;
|
||||
|
||||
use ApiBundle\Entity\NormalizableEntityInterface;
|
||||
use ApiBundle\Serializer\Metadata\Metadata;
|
||||
use ApiBundle\Serializer\Metadata\PropertyMetadata;
|
||||
use AppBundle\Entity\EntityInterface;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use IndexBundle\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* AnalyticContext
|
||||
*
|
||||
* Holds all necessary data for making analytic computation.
|
||||
*
|
||||
* Created 'cause we may got same analytic request from different users, so we
|
||||
* should'nt create two equals analytic.
|
||||
*
|
||||
* @ORM\Table(name="analytics_context")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class AnalyticContext implements
|
||||
EntityInterface,
|
||||
NormalizableEntityInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="NONE")
|
||||
*/
|
||||
private $hash;
|
||||
|
||||
/**
|
||||
* @var Collection
|
||||
*
|
||||
* @ORM\ManyToMany(targetEntity="CacheBundle\Entity\Feed\AbstractFeed")
|
||||
* @ORM\JoinTable(
|
||||
* name="cross_analytics_feeds",
|
||||
* joinColumns={@ORM\JoinColumn(referencedColumnName="hash",onDelete="CASCADE")},
|
||||
* inverseJoinColumns={@ORM\JoinColumn(name="feed_id")}
|
||||
* )
|
||||
*/
|
||||
private $feeds;
|
||||
|
||||
/**
|
||||
* Array of normalized and actually used filters.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="array")
|
||||
*/
|
||||
private $filters;
|
||||
|
||||
/**
|
||||
* Filters in the form in which they came to us from the client.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json_array")
|
||||
*/
|
||||
private $rawFilters;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="CacheBundle\Entity\Analytic\Analytic", mappedBy="context")
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
*/
|
||||
private $analytics;
|
||||
|
||||
/**
|
||||
* AnalyticContext constructor.
|
||||
*
|
||||
* @param string $hash Internal analytic hash used for determining
|
||||
* uniqueness of analytic request.
|
||||
* @param array $feeds Feeds used as source for analytic.
|
||||
* @param array $filters Additional filters applying on data from feeds.
|
||||
* @param array $rawFilters Filters as is it passed from frontend.
|
||||
*/
|
||||
public function __construct(
|
||||
$hash,
|
||||
array $feeds,
|
||||
array $filters = [],
|
||||
array $rawFilters = []
|
||||
) {
|
||||
if (! \app\a\allInstanceOf($feeds, AbstractFeed::class)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'\'$feeds\' should be an array of \'%s\'',
|
||||
AbstractFeed::class
|
||||
));
|
||||
}
|
||||
|
||||
if (! \app\a\allInstanceOf($filters, FilterInterface::class)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'\'$filters\' should be an array of \'%s\'',
|
||||
FilterInterface::class
|
||||
));
|
||||
}
|
||||
|
||||
$this->hash = $hash;
|
||||
$this->feeds = new ArrayCollection($feeds);
|
||||
$this->filters = $filters;
|
||||
$this->rawFilters = $rawFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHash()
|
||||
{
|
||||
return $this->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hash Analytic hash.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHash($hash)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntityType()
|
||||
{
|
||||
return 'analytic';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractFeed $feed A added feed.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addFeed(AbstractFeed $feed)
|
||||
{
|
||||
$this->feeds[] = $feed;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractFeed $feed A removed feed.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeFeed(AbstractFeed $feed)
|
||||
{
|
||||
$this->feeds->removeElement($feed);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getFeeds()
|
||||
{
|
||||
return $this->feeds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $filters Array of normalized filters.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFilters(array $filters)
|
||||
{
|
||||
$this->filters = $filters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRawFilters()
|
||||
{
|
||||
return $this->rawFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rawFilters Array of filters as is.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setRawFilters(array $rawFilters)
|
||||
{
|
||||
$this->rawFilters = $rawFilters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAnalytics()
|
||||
{
|
||||
return $this->analytics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $analytics
|
||||
*/
|
||||
public function setAnalytics($analytics): void
|
||||
{
|
||||
$this->analytics = $analytics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return metadata for current entity.
|
||||
*
|
||||
* @return Metadata
|
||||
*/
|
||||
public function getMetadata()
|
||||
{
|
||||
return new Metadata(static::class, [
|
||||
PropertyMetadata::createString('hash', [ 'context' ]),
|
||||
PropertyMetadata::createObject('filters', [ 'context' ]),
|
||||
PropertyMetadata::createArray('rawFilters', [ 'context' ]),
|
||||
PropertyMetadata::createArray('feeds', [ 'context' ])
|
||||
->setField(function () {
|
||||
$feeds = $this->feeds->map(function (AbstractFeed $feed) {
|
||||
return [
|
||||
'id' => $feed->getId(),
|
||||
'name' => $feed->getName(),
|
||||
];
|
||||
})->toArray();
|
||||
|
||||
return $feeds;
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default normalization groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaultGroups()
|
||||
{
|
||||
return [ 'context'];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity;
|
||||
|
||||
use ApiBundle\Entity\ManageableEntityInterface;
|
||||
use ApiBundle\Entity\NormalizableEntityInterface;
|
||||
use ApiBundle\Serializer\Metadata\Metadata;
|
||||
use ApiBundle\Serializer\Metadata\PropertyMetadata;
|
||||
use AppBundle\Entity\BaseEntityTrait;
|
||||
use CacheBundle\Form\CategoryType;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Validator\Constraints\CategoryParent;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Category
|
||||
*
|
||||
* @ORM\Table(name="categories")
|
||||
* @ORM\Entity(
|
||||
* repositoryClass="CacheBundle\Repository\CategoryRepository"
|
||||
* )
|
||||
*
|
||||
* @Assert\GroupSequence({ "Category", "parent" , "unique" })
|
||||
*/
|
||||
class Category implements
|
||||
ManageableEntityInterface,
|
||||
NormalizableEntityInterface
|
||||
{
|
||||
|
||||
use BaseEntityTrait;
|
||||
|
||||
/**
|
||||
* My category.
|
||||
*/
|
||||
const TYPE_MY_CONTENT = 'my_content';
|
||||
|
||||
/**
|
||||
* My category name.
|
||||
*/
|
||||
const NAME_MY_CONTENT = 'My Content';
|
||||
|
||||
/**
|
||||
* Category for deleted feeds.
|
||||
*/
|
||||
const TYPE_DELETED_CONTENT = 'deleted_content';
|
||||
|
||||
/**
|
||||
* Name of category for deleted feeds.
|
||||
*/
|
||||
const NAME_DELETED_CONTENT = 'Deleted Content';
|
||||
|
||||
/**
|
||||
* Category for shared feeds.
|
||||
*/
|
||||
const TYPE_SHARED_CONTENT = 'shared_content';
|
||||
|
||||
/**
|
||||
* Name of category for shared feeds.
|
||||
*/
|
||||
const NAME_SHARED_CONTENT = 'Shared Content';
|
||||
|
||||
/**
|
||||
* User custom directories.
|
||||
*/
|
||||
const TYPE_CUSTOM = 'custom';
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Collections\Collection
|
||||
*
|
||||
* @ORM\OneToMany(
|
||||
* targetEntity="CacheBundle\Entity\Feed\AbstractFeed",
|
||||
* mappedBy="category",
|
||||
* cascade={ "persist", "remove" }
|
||||
* )
|
||||
*/
|
||||
protected $feeds;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="UserBundle\Entity\User",
|
||||
* inversedBy="categories"
|
||||
* )
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column
|
||||
*
|
||||
* @Assert\NotBlank
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column
|
||||
*/
|
||||
protected $type = self::TYPE_CUSTOM;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Collections\Collection
|
||||
*
|
||||
* @ORM\OneToMany(
|
||||
* targetEntity="CacheBundle\Entity\Category",
|
||||
* mappedBy="parent",
|
||||
* cascade={ "persist", "remove" }
|
||||
* )
|
||||
*/
|
||||
protected $childes;
|
||||
|
||||
/**
|
||||
* @var Category
|
||||
*
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="CacheBundle\Entity\Category",
|
||||
* inversedBy="childes"
|
||||
* )
|
||||
* @CategoryParent(groups={ "parent" })
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $exported = false;
|
||||
|
||||
/**
|
||||
* @param User $user A User entity instance, who create this category.
|
||||
* @param string $name Category name.
|
||||
*/
|
||||
public function __construct(User $user, $name = '')
|
||||
{
|
||||
$user->addCategory($this);
|
||||
|
||||
$this->name = $name;
|
||||
$this->feeds = new ArrayCollection();
|
||||
$this->childes = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Category $parent A parent Category entity instance.
|
||||
* @param User $user A User entity instance, who create this category.
|
||||
* @param string $name Category name.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public static function createChild(Category $parent, User $user, $name)
|
||||
{
|
||||
$category = new Category($user, $name);
|
||||
$parent->addChild($category);
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create main category for specified user.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public static function createMainCategory(User $user)
|
||||
{
|
||||
$category = new Category($user, self::NAME_MY_CONTENT);
|
||||
|
||||
return $category->setType(self::TYPE_MY_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create main category for specified user.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public static function createSharedCategory(User $user)
|
||||
{
|
||||
$category = new Category($user, self::NAME_SHARED_CONTENT);
|
||||
|
||||
return $category->setType(self::TYPE_SHARED_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create trash category for specified user.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public static function createTrashCategory(User $user)
|
||||
{
|
||||
$category = new Category($user, self::NAME_DELETED_CONTENT);
|
||||
|
||||
return $category->setType(self::TYPE_DELETED_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add query
|
||||
*
|
||||
* @param AbstractFeed $feed A AbstractFeed entity instance.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public function addFeed(AbstractFeed $feed)
|
||||
{
|
||||
$this->feeds[] = $feed;
|
||||
$feed->setCategory($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove query
|
||||
*
|
||||
* @param AbstractFeed $feed A AbstractFeed entity instance.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public function removeFeed(AbstractFeed $feed)
|
||||
{
|
||||
$this->feeds->removeElement($feed);
|
||||
$feed->setCategory(null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queries
|
||||
*
|
||||
* @return \Doctrine\Common\Collections\Collection
|
||||
*/
|
||||
public function getFeeds()
|
||||
{
|
||||
return $this->feeds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setUser(User $user = null)
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether specified user owner.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOwnedBy(User $user)
|
||||
{
|
||||
return $user->getId() === $this->user->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name
|
||||
*
|
||||
* @param string $name Category name.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set type
|
||||
*
|
||||
* @param string $type Category type.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public function setType($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if current category is my content, deleted content or shared
|
||||
* content.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isInternal()
|
||||
{
|
||||
return $this->type !== self::TYPE_CUSTOM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fqcn of form used for creating this entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCreateFormClass()
|
||||
{
|
||||
return CategoryType::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fqcn of form used for updating this entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUpdateFormClass()
|
||||
{
|
||||
return CategoryType::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add child
|
||||
*
|
||||
* @param Category $child A child Category entity instance.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public function addChild(Category $child)
|
||||
{
|
||||
if ($child === $this) {
|
||||
$message = 'Try to put category inside itself.';
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
$this->childes[] = $child;
|
||||
$child->setParent($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove child
|
||||
*
|
||||
* @param Category $child A child Category entity instance.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public function removeChild(Category $child)
|
||||
{
|
||||
$this->childes->removeElement($child);
|
||||
$child->setParent(null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get childs
|
||||
*
|
||||
* @return \Doctrine\Common\Collections\Collection|array
|
||||
*/
|
||||
public function getChildes()
|
||||
{
|
||||
return $this->childes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parent
|
||||
*
|
||||
* @param Category $parent A parent Category entity instance.
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public function setParent(Category $parent = null)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parent
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isExported()
|
||||
{
|
||||
return $this->exported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $exported Is this feed exported or not.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setExported($exported)
|
||||
{
|
||||
$this->exported = $exported;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return metadata for current entity.
|
||||
*
|
||||
* @return Metadata
|
||||
*/
|
||||
public function getMetadata()
|
||||
{
|
||||
return new Metadata(static::class, [
|
||||
PropertyMetadata::createInteger('id', [ 'id' ]),
|
||||
PropertyMetadata::createString('name', [ 'category', 'category_tree' ]),
|
||||
PropertyMetadata::createString('subType', [ 'category', 'category_tree' ])
|
||||
->setField('type'),
|
||||
PropertyMetadata::createCollection('childes', Category::class, [
|
||||
'category',
|
||||
'category_tree',
|
||||
]),
|
||||
PropertyMetadata::createBoolean('exported', [ 'category', 'category_tree' ]),
|
||||
PropertyMetadata::createCollection('feeds', AbstractFeed::class, [
|
||||
'feed_tree',
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default normalization groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaultGroups()
|
||||
{
|
||||
return [ 'feed_tree', 'category', 'id' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntityType()
|
||||
{
|
||||
return 'directory';
|
||||
}
|
||||
|
||||
/**
|
||||
* @Assert\Callback(groups={ "unique" })
|
||||
*
|
||||
* @param ExecutionContextInterface $context A ExecutionContextInterface instance.
|
||||
*/
|
||||
public function validateUnique(ExecutionContextInterface $context)
|
||||
{
|
||||
$categoriesWithSameName = $this->getParent()->getChildes()->filter(function (Category $category) {
|
||||
return $category->getName() === $this->getName();
|
||||
});
|
||||
|
||||
if (count($categoriesWithSameName) > 0) {
|
||||
$context->buildViolation('Category with name \'{{ value }}\' is already exists')
|
||||
->setParameter('{{ value }}', $this->getName())
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity;
|
||||
|
||||
use ApiBundle\Entity\ManageableEntityInterface;
|
||||
use ApiBundle\Entity\NormalizableEntityInterface;
|
||||
use ApiBundle\Serializer\Metadata\Metadata;
|
||||
use ApiBundle\Serializer\Metadata\PropertyMetadata;
|
||||
use AppBundle\Entity\BaseEntityTrait;
|
||||
use AppBundle\Entity\EntityInterface;
|
||||
use CacheBundle\Form\CommentType;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Comment
|
||||
*
|
||||
* @ORM\Table(name="comments")
|
||||
* @ORM\Entity(repositoryClass="CacheBundle\Repository\CommentRepository")
|
||||
*/
|
||||
class Comment implements EntityInterface, NormalizableEntityInterface, ManageableEntityInterface
|
||||
{
|
||||
|
||||
use BaseEntityTrait;
|
||||
|
||||
/**
|
||||
* @var Document
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="CacheBundle\Entity\Document", inversedBy="comments")
|
||||
*/
|
||||
private $document;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="UserBundle\Entity\User")
|
||||
*/
|
||||
private $author;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column
|
||||
*/
|
||||
private $title = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="text")
|
||||
*
|
||||
* @Assert\NotBlank
|
||||
* @Assert\Length(max=5000)
|
||||
*/
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
private $createdAt;
|
||||
|
||||
/**
|
||||
* Only last of specified number of comments is marked as 'new'.
|
||||
* We use this marker for simplify fetching comments while we get list of
|
||||
* document.
|
||||
*
|
||||
* @var boolean
|
||||
*
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $new = true;
|
||||
|
||||
/**
|
||||
* Comment constructor.
|
||||
*
|
||||
* @param User $user A comment author.
|
||||
* @param string $content Comment content.
|
||||
* @param string $title Comment title.
|
||||
*/
|
||||
public function __construct(User $user, $content, $title = '')
|
||||
{
|
||||
$this
|
||||
->setAuthor($user)
|
||||
->setContent($content)
|
||||
->setTitle($title);
|
||||
|
||||
$this->createdAt = new \DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set title
|
||||
*
|
||||
* @param string $title Comment title.
|
||||
*
|
||||
* @return Comment
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = trim($title);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content
|
||||
*
|
||||
* @param string $content Comment content.
|
||||
*
|
||||
* @return Comment
|
||||
*/
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->content = trim($content);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set createdAt
|
||||
*
|
||||
* @param \DateTime $createdAt When comment was created.
|
||||
*
|
||||
* @return Comment
|
||||
*/
|
||||
public function setCreatedAt(\DateTime $createdAt = null)
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get createdAt
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getCreatedAt()
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set document
|
||||
*
|
||||
* @param Document $document A Document entity instance.
|
||||
*
|
||||
* @return Comment
|
||||
*/
|
||||
public function setDocument(Document $document = null)
|
||||
{
|
||||
$this->document = $document;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function getDocument()
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set author
|
||||
*
|
||||
* @param User $author A User entity instance.
|
||||
*
|
||||
* @return Comment
|
||||
*/
|
||||
public function setAuthor(User $author = null)
|
||||
{
|
||||
$this->author = $author;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get author
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getAuthor()
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new
|
||||
*
|
||||
* @param boolean $new Flag, true if this comment is new.
|
||||
*
|
||||
* @return Comment
|
||||
*/
|
||||
public function setNew($new = true)
|
||||
{
|
||||
$this->new = $new;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is new
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isNew()
|
||||
{
|
||||
return $this->new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return metadata for current entity.
|
||||
*
|
||||
* @return \ApiBundle\Serializer\Metadata\Metadata
|
||||
*/
|
||||
public function getMetadata()
|
||||
{
|
||||
return new Metadata(static::class, [
|
||||
PropertyMetadata::createInteger('id', [ 'id' ]),
|
||||
PropertyMetadata::createString('title', [ 'comment' ]),
|
||||
PropertyMetadata::createString('content', [ 'comment' ]),
|
||||
PropertyMetadata::createEntity('author', User::class, [ 'comment' ]),
|
||||
PropertyMetadata::createDate('createdAt', [ 'comment' ]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default normalization groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaultGroups()
|
||||
{
|
||||
return [ 'id', 'comment' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fqcn of form used for creating this entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCreateFormClass()
|
||||
{
|
||||
return CommentType::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fqcn of form used for updating this entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUpdateFormClass()
|
||||
{
|
||||
return CommentType::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity;
|
||||
|
||||
use ApiBundle\Entity\NormalizableEntityInterface;
|
||||
use ApiBundle\Serializer\Metadata\Metadata;
|
||||
use ApiBundle\Serializer\Metadata\PropertyMetadata;
|
||||
use AppBundle\Entity\EntityInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Class Document
|
||||
*
|
||||
* @ORM\Table(name="documents")
|
||||
* @ORM\Entity(repositoryClass="CacheBundle\Repository\DocumentRepository")
|
||||
*/
|
||||
class Document implements EntityInterface, NormalizableEntityInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string")
|
||||
* @ORM\Id()
|
||||
* @ORM\GeneratedValue(strategy="NONE")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $platform;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json_array")
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @var Collection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="CacheBundle\Entity\Page", mappedBy="document")
|
||||
*/
|
||||
protected $pages;
|
||||
|
||||
/**
|
||||
* @var Collection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="CacheBundle\Entity\Comment", mappedBy="document")
|
||||
*/
|
||||
protected $comments;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $commentsCount = 0;
|
||||
|
||||
/**
|
||||
* Map Document entity fields to external document fields.
|
||||
*
|
||||
* Contains only fields which has different's.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $externalMap = [
|
||||
'mainLength' => 'main_length',
|
||||
'dateFound' => 'date_found',
|
||||
'sourceHashcode' => 'source_hashcode',
|
||||
'sourceLink' => 'source_link',
|
||||
'sourcePublisherType' => 'source_publisher_type',
|
||||
'sourcePublisherSubtype' => 'source_publisher_subtype',
|
||||
'sourceDateFound' => 'source_date_found',
|
||||
'sourceTitle' => 'source_title',
|
||||
'sourceDescription' => 'source_description',
|
||||
'sourceLocation' => 'source_location',
|
||||
'summaryText' => 'summary_text',
|
||||
'htmlLength' => 'html_length',
|
||||
'authorName' => 'author_name',
|
||||
'authorLink' => 'author_link',
|
||||
'authorGender' => 'author_gender',
|
||||
'imageSrc' => 'image_src',
|
||||
'country' => 'geo_country',
|
||||
'state' => 'geo_state',
|
||||
'city' => 'geo_city',
|
||||
'point' => 'geo_point',
|
||||
'duplicatesCount' => 'duplicates_count',
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->pages = new ArrayCollection();
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function create()
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id New document id.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPlatform()
|
||||
{
|
||||
return $this->platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $platform Platform name from which we get document.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function setPlatform($platform)
|
||||
{
|
||||
$this->platform = $platform;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data A index document data.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function setData(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add page
|
||||
*
|
||||
* @param Page $page A Page entity instance.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function addPage(Page $page)
|
||||
{
|
||||
$this->pages[] = $page;
|
||||
$page->setDocument($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove page
|
||||
*
|
||||
* @param Page $page A Page entity instance.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function removePage(Page $page)
|
||||
{
|
||||
$this->pages->removeElement($page);
|
||||
$page->setDocument(null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pages
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getPages()
|
||||
{
|
||||
return $this->pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntityType()
|
||||
{
|
||||
return 'document';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add comment
|
||||
*
|
||||
* @param Comment $comment A Comment entity instance.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function addComment(Comment $comment)
|
||||
{
|
||||
$this->comments[] = $comment;
|
||||
$comment->setDocument($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove comment
|
||||
*
|
||||
* @param Comment $comment A Comment entity instance.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function removeComment(Comment $comment)
|
||||
{
|
||||
$this->comments->removeElement($comment);
|
||||
$comment->setDocument(null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set comments
|
||||
*
|
||||
* @param Comment[]|ArrayCollection $comments Array of Document entities.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function setComments($comments)
|
||||
{
|
||||
if (is_array($comments)) {
|
||||
$comments = new ArrayCollection($comments);
|
||||
}
|
||||
|
||||
$this->comments = $comments;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getComments()
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set commentsCount
|
||||
*
|
||||
* @param integer $count Comments count.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function setCommentsCount($count)
|
||||
{
|
||||
$this->commentsCount = $count;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get commentsCount
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getCommentsCount()
|
||||
{
|
||||
return $this->commentsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment comments counts for this document
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function incCommentsCount()
|
||||
{
|
||||
$this->commentsCount++;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement comments counts for this document
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function decCommentsCount()
|
||||
{
|
||||
$this->commentsCount--;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return metadata for current entity.
|
||||
*
|
||||
* @return \ApiBundle\Serializer\Metadata\Metadata
|
||||
*/
|
||||
public function getMetadata()
|
||||
{
|
||||
return new Metadata(static::class, [
|
||||
PropertyMetadata::createString('id', [ 'id' ]),
|
||||
PropertyMetadata::createString('title', [ 'document' ]),
|
||||
PropertyMetadata::createDate('dateFound', [ 'document' ]),
|
||||
PropertyMetadata::createDate('published', [ 'document' ]),
|
||||
PropertyMetadata::createString('permalink', [ 'document' ]),
|
||||
PropertyMetadata::createString('content', [ 'document' ]),
|
||||
PropertyMetadata::createString('language', [ 'document' ]),
|
||||
PropertyMetadata::createString('publisher', [ 'document' ]),
|
||||
PropertyMetadata::groupProperties('source', [
|
||||
PropertyMetadata::createString('title', [ 'document' ]),
|
||||
PropertyMetadata::createString('type', [ 'document' ]),
|
||||
PropertyMetadata::createString('link', [ 'document' ]),
|
||||
PropertyMetadata::createString('section', [ 'document' ]),
|
||||
PropertyMetadata::createString('country', [ 'document' ]),
|
||||
PropertyMetadata::createString('state', [ 'document' ]),
|
||||
PropertyMetadata::createString('city', [ 'document' ]),
|
||||
], [ 'document' ]),
|
||||
PropertyMetadata::groupProperties('author', [
|
||||
PropertyMetadata::createString('name', [ 'document' ]),
|
||||
PropertyMetadata::createString('link', [ 'document' ]),
|
||||
], [ 'document' ]),
|
||||
PropertyMetadata::createInteger('duplicates', [ 'document' ]),
|
||||
PropertyMetadata::createString('image', [ 'document' ])->setNullable(true),
|
||||
PropertyMetadata::createInteger('views', [ 'document' ]),
|
||||
PropertyMetadata::createString('sentiment', [ 'document' ]),
|
||||
PropertyMetadata::groupProperties('comments', [
|
||||
PropertyMetadata::createCollection('comments', Comment::class, [ 'document' ])
|
||||
->setName('data'),
|
||||
PropertyMetadata::createInteger('count', [ 'document' ]),
|
||||
PropertyMetadata::createInteger('limit', [ 'document' ]),
|
||||
], [ 'document' ]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default normalization groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaultGroups()
|
||||
{
|
||||
return [ 'id', 'document' ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity;
|
||||
|
||||
use Common\Enum\CollectionTypeEnum;
|
||||
|
||||
/**
|
||||
* Interface DocumentCollectionInterface
|
||||
*
|
||||
* Used for classes which can be used for holding document collection.
|
||||
*
|
||||
* @package CacheBundle\Entity
|
||||
*/
|
||||
interface DocumentCollectionInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Set totalCount
|
||||
*
|
||||
* @param integer $totalCount Count of all available data.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setTotalCount($totalCount);
|
||||
|
||||
/**
|
||||
* Get totalCount
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTotalCount();
|
||||
|
||||
/**
|
||||
* Create proper Page entity instance for binding document and current
|
||||
* collection.
|
||||
*
|
||||
* @param integer $number Page number.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function createPage($number);
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getCollectionId();
|
||||
|
||||
/**
|
||||
* @return CollectionTypeEnum
|
||||
*/
|
||||
public function getCollectionType();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity;
|
||||
|
||||
/**
|
||||
* Trait DocumentCollectionInterface
|
||||
* @package CacheBundle\Entity
|
||||
*/
|
||||
trait DocumentCollectionTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $totalCount = 0;
|
||||
|
||||
/**
|
||||
* Set totalCount
|
||||
*
|
||||
* @param integer $totalCount Count of all available data.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setTotalCount($totalCount)
|
||||
{
|
||||
$this->totalCount = $totalCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get totalCount
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTotalCount()
|
||||
{
|
||||
return $this->totalCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity\Feed;
|
||||
|
||||
use ApiBundle\Entity\ManageableEntityInterface;
|
||||
use ApiBundle\Entity\NormalizableEntityInterface;
|
||||
use AppBundle\Entity\BaseEntityTrait;
|
||||
use AppBundle\Entity\EntityInterface;
|
||||
use CacheBundle\Entity\Category;
|
||||
use CacheBundle\Entity\Document;
|
||||
use Common\Enum\CollectionTypeEnum;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* AbstractFeed
|
||||
*
|
||||
* @ORM\Table(name="feeds")
|
||||
* @ORM\Entity(
|
||||
* repositoryClass="CacheBundle\Repository\CommonFeedRepository"
|
||||
* )
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
* @ORM\DiscriminatorColumn(name="type", type="string")
|
||||
* @ORM\DiscriminatorMap({
|
||||
* "query"="QueryFeed",
|
||||
* "clip"="ClipFeed",
|
||||
* })
|
||||
*/
|
||||
abstract class AbstractFeed implements
|
||||
EntityInterface,
|
||||
NormalizableEntityInterface,
|
||||
ManageableEntityInterface
|
||||
{
|
||||
|
||||
use BaseEntityTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column
|
||||
*
|
||||
* @Assert\NotBlank(groups={ "Feed_Create" })
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="UserBundle\Entity\User"
|
||||
* )
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var Category
|
||||
*
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="CacheBundle\Entity\Category",
|
||||
* inversedBy="feeds"
|
||||
* )
|
||||
*
|
||||
* @Assert\NotBlank(groups={ "Feed_Create" })
|
||||
*/
|
||||
protected $category;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $exported = false;
|
||||
|
||||
/**
|
||||
* @var Collection
|
||||
*
|
||||
* @ORM\ManyToMany(targetEntity="CacheBundle\Entity\Document", cascade={ "persist", "remove" })
|
||||
* @ORM\JoinTable(name="deleted_documents")
|
||||
*/
|
||||
protected $excludedDocuments;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->excludedDocuments = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name
|
||||
*
|
||||
* @param string $name Feed name.
|
||||
*
|
||||
* @return AbstractFeed
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setUser(User $user = null)
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether specified user owner.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOwnedBy(User $user)
|
||||
{
|
||||
return $user->getId() === $this->user->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set category
|
||||
*
|
||||
* @param Category $category A Category entity instance.
|
||||
*
|
||||
* @return AbstractFeed
|
||||
*/
|
||||
public function setCategory(Category $category = null)
|
||||
{
|
||||
$this->category = $category;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category
|
||||
*
|
||||
* @return Category
|
||||
*/
|
||||
public function getCategory()
|
||||
{
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isExported()
|
||||
{
|
||||
return $this->exported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*
|
||||
* @deprecated
|
||||
* @see AbstractFeed::isExported()
|
||||
*/
|
||||
public function getExported()
|
||||
{
|
||||
return $this->exported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $exported Is this feed exported or not.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setExported($exported)
|
||||
{
|
||||
$this->exported = $exported;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fqcn of form used for creating this entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCreateFormClass()
|
||||
{
|
||||
// All derived feed's will be created in different ways.
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fqcn of form used for updating this entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUpdateFormClass()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntityType()
|
||||
{
|
||||
// For all feeds we shouldn't return specific types, only 'feed'.
|
||||
return 'feed';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $subType A feed subtype.
|
||||
*
|
||||
* @return AbstractFeed
|
||||
*/
|
||||
public static function createBySubType($subType)
|
||||
{
|
||||
switch ($subType) {
|
||||
case ClipFeed::getSubType():
|
||||
return new ClipFeed();
|
||||
|
||||
case QueryFeed::getSubType():
|
||||
return new QueryFeed();
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unknown sub type.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get concrete feed type.
|
||||
*
|
||||
* @param AbstractFeed $feed A AbstractFeed entity instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSubType(AbstractFeed $feed = null)
|
||||
{
|
||||
return \app\op\camelCaseToUnderscore(\app\c\getShortName($feed ?: static::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific feed type.
|
||||
*
|
||||
* Used by frontend.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getSpecificType();
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
abstract public function getCollectionId();
|
||||
|
||||
/**
|
||||
* @return CollectionTypeEnum
|
||||
*/
|
||||
abstract public function getCollectionType();
|
||||
|
||||
/**
|
||||
* Add excludedDocument
|
||||
*
|
||||
* @param Document $excludedDocument Excluded Document entity instance.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function addExcludedDocument(Document $excludedDocument)
|
||||
{
|
||||
$this->excludedDocuments[] = $excludedDocument;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove excludedDocument
|
||||
*
|
||||
* @param Document $excludedDocument Removed Document entity instance.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function removeExcludedDocument(Document $excludedDocument)
|
||||
{
|
||||
$this->excludedDocuments->removeElement($excludedDocument);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get excludedDocuments
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getExcludedDocuments()
|
||||
{
|
||||
return $this->excludedDocuments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity\Feed;
|
||||
|
||||
use ApiBundle\Serializer\Metadata\Metadata;
|
||||
use ApiBundle\Serializer\Metadata\PropertyMetadata;
|
||||
use CacheBundle\Entity\Category;
|
||||
use CacheBundle\Entity\DocumentCollectionInterface;
|
||||
use CacheBundle\Entity\DocumentCollectionTrait;
|
||||
use CacheBundle\Entity\Page;
|
||||
use Common\Enum\CollectionTypeEnum;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* ClipFeed
|
||||
* Contains document clipped from another feeds.
|
||||
*
|
||||
* @ORM\Entity(repositoryClass="CacheBundle\Repository\ClipFeedRepository")
|
||||
*/
|
||||
class ClipFeed extends AbstractFeed implements DocumentCollectionInterface
|
||||
{
|
||||
|
||||
const READ_LATER = 'Read Later';
|
||||
|
||||
use DocumentCollectionTrait;
|
||||
|
||||
/**
|
||||
* @var Collection
|
||||
*
|
||||
* @ORM\OneToMany(
|
||||
* targetEntity="CacheBundle\Entity\Page",
|
||||
* mappedBy="clipFeed",
|
||||
* cascade={ "remove" }
|
||||
* )
|
||||
*/
|
||||
private $pages;
|
||||
|
||||
/**
|
||||
* Array of normalized actual used filters.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="array")
|
||||
*/
|
||||
protected $filters = [];
|
||||
|
||||
/**
|
||||
* Advanced filters in the form in which they came to us from the client.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json_array")
|
||||
*/
|
||||
protected $rawFilters = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->pages = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filters
|
||||
*
|
||||
* @param array $filters Array of filters.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setFilters(array $filters)
|
||||
{
|
||||
$this->filters = $filters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set rawFilters
|
||||
*
|
||||
* @param array $rawFilters Raw filters.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setRawFilters(array $rawFilters)
|
||||
{
|
||||
$this->rawFilters = $rawFilters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rawFilters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRawFilters()
|
||||
{
|
||||
return $this->rawFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific feed type.
|
||||
*
|
||||
* Used by frontend.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSpecificType()
|
||||
{
|
||||
return 'feed-type-clippings';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return metadata for current entity.
|
||||
*
|
||||
* @return Metadata
|
||||
*/
|
||||
public function getMetadata()
|
||||
{
|
||||
return new Metadata(static::class, [
|
||||
PropertyMetadata::createInteger('id', [ 'id' ]),
|
||||
PropertyMetadata::createString('name', [ 'feed', 'feed_tree' ]),
|
||||
PropertyMetadata::createString('subType', [ 'feed', 'feed_tree' ])
|
||||
->setField(function () {
|
||||
return static::getSubType();
|
||||
}),
|
||||
PropertyMetadata::createString('class', [ 'feed', 'feed_tree' ])
|
||||
->setField(function () {
|
||||
return $this->getSpecificType();
|
||||
}),
|
||||
PropertyMetadata::createBoolean('exported', [ 'feed', 'feed_tree' ])
|
||||
->setField(function () {
|
||||
return $this->getExported();
|
||||
}),
|
||||
PropertyMetadata::createEntity('category', Category::class, [ 'feed' ]),
|
||||
PropertyMetadata::createEntity('user', User::class, [ 'feed' ]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default normalization groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaultGroups()
|
||||
{
|
||||
return [ 'feed', 'id' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add page
|
||||
*
|
||||
* @param Page $page A page entity instance.
|
||||
*
|
||||
* @return ClipFeed
|
||||
*/
|
||||
public function addPage(Page $page)
|
||||
{
|
||||
$this->pages[] = $page;
|
||||
$page->setClipFeed($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove page
|
||||
*
|
||||
* @param Page $page A Page entity instance.
|
||||
*
|
||||
* @return ClipFeed
|
||||
*/
|
||||
public function removePage(Page $page)
|
||||
{
|
||||
$this->pages->removeElement($page);
|
||||
$page->setClipFeed(null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pages
|
||||
*
|
||||
* @return \Doctrine\Common\Collections\Collection
|
||||
*/
|
||||
public function getPages()
|
||||
{
|
||||
return $this->pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create proper Page entity instance for binding document and current
|
||||
* collection.
|
||||
*
|
||||
* @param integer $number Page number.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function createPage($number)
|
||||
{
|
||||
return Page::create()
|
||||
->setClipFeed($this)
|
||||
->setNumber($number);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getCollectionId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CollectionTypeEnum
|
||||
*/
|
||||
public function getCollectionType()
|
||||
{
|
||||
return CollectionTypeEnum::feed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity\Feed;
|
||||
|
||||
use ApiBundle\Serializer\Metadata\Metadata;
|
||||
use ApiBundle\Serializer\Metadata\PropertyMetadata;
|
||||
use CacheBundle\Entity\Category;
|
||||
use CacheBundle\Entity\Query\StoredQuery;
|
||||
use Common\Enum\CollectionTypeEnum;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* QueryFeed
|
||||
* Feed which create from stored query.
|
||||
*
|
||||
* @ORM\Entity(repositoryClass="CacheBundle\Repository\QueryFeedRepository")
|
||||
*/
|
||||
class QueryFeed extends AbstractFeed
|
||||
{
|
||||
|
||||
/**
|
||||
* @var StoredQuery
|
||||
*
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="CacheBundle\Entity\Query\StoredQuery",
|
||||
* inversedBy="feeds"
|
||||
* )
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="array")
|
||||
*/
|
||||
protected $publisherTypes;
|
||||
|
||||
/**
|
||||
* Set query
|
||||
*
|
||||
* @param StoredQuery $query A StoredQuery entity instance.
|
||||
*
|
||||
* @return QueryFeed
|
||||
*/
|
||||
public function setQuery(StoredQuery $query = null)
|
||||
{
|
||||
$this->query = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query
|
||||
*
|
||||
* @return StoredQuery
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set publisherTypes
|
||||
*
|
||||
* @param array|string $publisherTypes Query publisher type.
|
||||
*
|
||||
* @return QueryFeed
|
||||
*/
|
||||
public function setPublisherTypes($publisherTypes)
|
||||
{
|
||||
$this->publisherTypes = (array) $publisherTypes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get publisherTypes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPublisherTypes()
|
||||
{
|
||||
return $this->publisherTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific feed type.
|
||||
*
|
||||
* Used by frontend.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSpecificType()
|
||||
{
|
||||
if (is_array($this->publisherTypes) && count($this->publisherTypes) === 1) {
|
||||
return 'feed-type-'
|
||||
. strtolower(current($this->publisherTypes));
|
||||
}
|
||||
|
||||
return 'feed-type-mixed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return metadata for current entity.
|
||||
*
|
||||
* @return Metadata
|
||||
*/
|
||||
public function getMetadata()
|
||||
{
|
||||
return new Metadata(static::class, [
|
||||
PropertyMetadata::createInteger('id', [ 'id' ]),
|
||||
PropertyMetadata::createInteger('query', [ 'feed', 'feed_tree' ])
|
||||
->setField(function () {
|
||||
return $this->query->getId();
|
||||
}),
|
||||
PropertyMetadata::createString('name', [ 'feed', 'feed_tree' ]),
|
||||
PropertyMetadata::createString('subType', [ 'feed', 'feed_tree' ])
|
||||
->setField(function () {
|
||||
return static::getSubType();
|
||||
}),
|
||||
PropertyMetadata::createString('class', [ 'feed', 'feed_tree' ])
|
||||
->setField(function () {
|
||||
return $this->getSpecificType();
|
||||
}),
|
||||
PropertyMetadata::createBoolean('exported', [ 'feed', 'feed_tree' ]),
|
||||
// ->setField(function () {
|
||||
// return $this->isExported();
|
||||
// }),
|
||||
PropertyMetadata::createEntity('category', Category::class, [ 'feed' ]),
|
||||
PropertyMetadata::createEntity('user', User::class, [ 'feed' ]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default normalization groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaultGroups()
|
||||
{
|
||||
return [ 'feed', 'id' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getCollectionId()
|
||||
{
|
||||
return $this->query->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CollectionTypeEnum
|
||||
*/
|
||||
public function getCollectionType()
|
||||
{
|
||||
return CollectionTypeEnum::query();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity;
|
||||
|
||||
use AppBundle\Entity\BaseEntityTrait;
|
||||
use AppBundle\Entity\EntityInterface;
|
||||
use CacheBundle\Entity\Feed\ClipFeed;
|
||||
use CacheBundle\Entity\Query\AbstractQuery;
|
||||
use Common\Enum\CollectionTypeEnum;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Page
|
||||
*
|
||||
* @ORM\Table(name="pages")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Page implements EntityInterface
|
||||
{
|
||||
|
||||
use BaseEntityTrait;
|
||||
|
||||
/**
|
||||
* @var AbstractQuery
|
||||
*
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="CacheBundle\Entity\Query\AbstractQuery",
|
||||
* inversedBy="pages"
|
||||
* )
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* @var ClipFeed
|
||||
*
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="CacheBundle\Entity\Feed\ClipFeed",
|
||||
* inversedBy="pages"
|
||||
* )
|
||||
*/
|
||||
protected $clipFeed;
|
||||
|
||||
/**
|
||||
* Page number, starts from 1.
|
||||
*
|
||||
* @var integer
|
||||
*
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $number;
|
||||
|
||||
/**
|
||||
* @var Document
|
||||
*
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="CacheBundle\Entity\Document",
|
||||
* inversedBy="pages"
|
||||
* )
|
||||
*/
|
||||
protected $document;
|
||||
|
||||
/**
|
||||
* Set number
|
||||
*
|
||||
* @param integer $number A page number.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function setNumber($number)
|
||||
{
|
||||
$this->number = $number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getNumber()
|
||||
{
|
||||
return $this->number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set query
|
||||
*
|
||||
* @param AbstractQuery $query A AbstractQuery entity instance.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function setQuery(AbstractQuery $query = null)
|
||||
{
|
||||
$this->query = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query
|
||||
*
|
||||
* @return AbstractQuery
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set document
|
||||
*
|
||||
* @param Document $document A Document entity instance.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function setDocument(Document $document = null)
|
||||
{
|
||||
$this->document = $document;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function getDocument()
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set clipFeed
|
||||
*
|
||||
* @param ClipFeed $clipFeed A ClipFeed entity instance.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function setClipFeed(ClipFeed $clipFeed = null)
|
||||
{
|
||||
$this->clipFeed = $clipFeed;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get clipFeed
|
||||
*
|
||||
* @return ClipFeed
|
||||
*/
|
||||
public function getClipFeed()
|
||||
{
|
||||
return $this->clipFeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get associated document collection type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCollectionType()
|
||||
{
|
||||
return $this->query ? CollectionTypeEnum::QUERY : CollectionTypeEnum::FEED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get associated document collection entity id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCollectionId()
|
||||
{
|
||||
return \app\op\invokeIf($this->query, 'getId') ?: $this->clipFeed->getId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity\Query;
|
||||
|
||||
use AppBundle\Entity\BaseEntityTrait;
|
||||
use AppBundle\Entity\EntityInterface;
|
||||
use CacheBundle\Entity\DocumentCollectionInterface;
|
||||
use CacheBundle\Entity\DocumentCollectionTrait;
|
||||
use CacheBundle\Entity\Page;
|
||||
use Common\Enum\CollectionTypeEnum;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use IndexBundle\SearchRequest\SearchRequestInterface;
|
||||
|
||||
/**
|
||||
* AbstractQuery
|
||||
*
|
||||
* @ORM\Table(
|
||||
* name="queries",
|
||||
* indexes={
|
||||
* @ORM\Index(name="hash_idx", columns={ "hash" })
|
||||
* }
|
||||
* )
|
||||
* @ORM\Entity
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
* @ORM\DiscriminatorColumn(name="type", type="string")
|
||||
* @ORM\DiscriminatorMap({
|
||||
* "simple"="SimpleQuery",
|
||||
* "stored"="StoredQuery"
|
||||
* })
|
||||
*/
|
||||
abstract class AbstractQuery implements EntityInterface, DocumentCollectionInterface
|
||||
{
|
||||
|
||||
use
|
||||
BaseEntityTrait,
|
||||
DocumentCollectionTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="text")
|
||||
*/
|
||||
protected $raw;
|
||||
|
||||
/**
|
||||
* Filters in the form in which they came to us from the client.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json_array")
|
||||
*/
|
||||
protected $rawFilters = [];
|
||||
|
||||
/**
|
||||
* Advanced filters in the form in which they came to us from the client.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json_array")
|
||||
*/
|
||||
protected $rawAdvancedFilters = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*
|
||||
* @ORM\Column(type="array")
|
||||
*/
|
||||
protected $fields = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="text")
|
||||
*/
|
||||
protected $normalized;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $hash;
|
||||
|
||||
/**
|
||||
* Array of normalized actual used filters.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="array")
|
||||
*/
|
||||
protected $filters = [];
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Collections\Collection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="CacheBundle\Entity\Page", mappedBy="query")
|
||||
*/
|
||||
protected $pages;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
protected $date;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->pages = new ArrayCollection();
|
||||
$this->date = new \DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set raw
|
||||
*
|
||||
* @param string $raw Raw query string typed by user.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setRaw($raw)
|
||||
{
|
||||
$this->raw = $raw;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRaw()
|
||||
{
|
||||
return $this->raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set rawFilters
|
||||
*
|
||||
* @param array $rawFilters Raw filters.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setRawFilters(array $rawFilters)
|
||||
{
|
||||
$this->rawFilters = $rawFilters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rawFilters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRawFilters()
|
||||
{
|
||||
return $this->rawFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set rawAdvancedFilters
|
||||
*
|
||||
* @param array $rawAdvancedFilters Raw filters.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setRawAdvancedFilters(array $rawAdvancedFilters)
|
||||
{
|
||||
$this->rawAdvancedFilters = $rawAdvancedFilters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rawAdvancedFilters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRawAdvancedFilters()
|
||||
{
|
||||
return $this->rawAdvancedFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set fields
|
||||
*
|
||||
* @param array $fields Array of field involved in search.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setFields(array $fields = [])
|
||||
{
|
||||
$this->fields = $fields;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fields
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFields()
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set normalized
|
||||
*
|
||||
* @param string $normalized Normalized query string.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setNormalized($normalized)
|
||||
{
|
||||
$this->normalized = $normalized;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get normalized
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNormalized()
|
||||
{
|
||||
return $this->normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set hash
|
||||
*
|
||||
* @param string $hash Query hash.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setHash($hash)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHash()
|
||||
{
|
||||
return $this->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set date
|
||||
*
|
||||
* @param \DateTime $date When query was requested.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setDate(\DateTime $date)
|
||||
{
|
||||
$this->date = $date;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getDate()
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add page
|
||||
*
|
||||
* @param Page $page A Page entity instance.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function addPage(Page $page)
|
||||
{
|
||||
$this->pages[] = $page;
|
||||
$page->setQuery($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove page
|
||||
*
|
||||
* @param Page $page A Page entity instance.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function removePage(Page $page)
|
||||
{
|
||||
$this->pages->removeElement($page);
|
||||
$page->setQuery(null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pages
|
||||
*
|
||||
* @return \Doctrine\Common\Collections\Collection
|
||||
*/
|
||||
public function getPages()
|
||||
{
|
||||
return $this->pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filters
|
||||
*
|
||||
* @param array $filters Array of filters.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setFilters(array $filters)
|
||||
{
|
||||
$this->filters = $filters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create query entity instance from search request instance.
|
||||
*
|
||||
* @param SearchRequestInterface $searchRequest A SearchRequestInterface
|
||||
* instance.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function fromSearchRequest(SearchRequestInterface $searchRequest)
|
||||
{
|
||||
$instance = new static();
|
||||
|
||||
return $instance
|
||||
->setFilters($searchRequest->getFilters())
|
||||
->setFields($searchRequest->getFields())
|
||||
->setNormalized($searchRequest->getNormalizedQuery())
|
||||
->setRaw($searchRequest->getQuery())
|
||||
->setHash($searchRequest->getHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create proper Page entity instance for binding document and current
|
||||
* collection.
|
||||
*
|
||||
* @param integer $number Page number.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function createPage($number)
|
||||
{
|
||||
return Page::create()
|
||||
->setQuery($this)
|
||||
->setNumber($number);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getCollectionId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CollectionTypeEnum
|
||||
*/
|
||||
public function getCollectionType()
|
||||
{
|
||||
return CollectionTypeEnum::query();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity\Query;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* SimpleQuery
|
||||
*
|
||||
* @ORM\Entity(
|
||||
* repositoryClass="CacheBundle\Repository\SimpleQueryRepository"
|
||||
* )
|
||||
*/
|
||||
class SimpleQuery extends AbstractQuery
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
protected $expirationDate;
|
||||
|
||||
/**
|
||||
* Set expirationDate
|
||||
*
|
||||
* @param \DateTime|string $expirationDate May be a \DateTime instance or
|
||||
* string for computing relative to
|
||||
* query 'date' property.
|
||||
*
|
||||
* @return SimpleQuery
|
||||
*/
|
||||
public function setExpirationDate($expirationDate)
|
||||
{
|
||||
if (is_string($expirationDate)) {
|
||||
$date = clone $this->date;
|
||||
$expirationDate = $date->modify($expirationDate);
|
||||
}
|
||||
|
||||
$this->expirationDate = $expirationDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get expirationDate
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getExpirationDate()
|
||||
{
|
||||
return $this->expirationDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that this simple query is still fresh.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isFresh()
|
||||
{
|
||||
return $this->expirationDate >= date_create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity\Query;
|
||||
|
||||
use CacheBundle\Entity\Feed\QueryFeed;
|
||||
use Common\Enum\StoredQueryStatusEnum;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* StoredQuery
|
||||
*
|
||||
* @ORM\Entity(
|
||||
* repositoryClass="CacheBundle\Repository\StoredQueryRepository"
|
||||
* )
|
||||
*/
|
||||
class StoredQuery extends AbstractQuery
|
||||
{
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $limitExceed = false;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
protected $lastUpdateAt;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column
|
||||
*/
|
||||
protected $status = StoredQueryStatusEnum::INITIALIZE;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Collections\Collection
|
||||
*
|
||||
* @ORM\OneToMany(
|
||||
* targetEntity="CacheBundle\Entity\Feed\QueryFeed",
|
||||
* mappedBy="query"
|
||||
* )
|
||||
*/
|
||||
protected $feeds;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->lastUpdateAt = new \DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set limitExceed
|
||||
*
|
||||
* @param boolean $limitExceed Flag, if true current stored query exceed
|
||||
* limit.
|
||||
*
|
||||
* @return StoredQuery
|
||||
*/
|
||||
public function setLimitExceed($limitExceed)
|
||||
{
|
||||
$this->limitExceed = $limitExceed;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get limitExceed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isLimitExceed()
|
||||
{
|
||||
return $this->limitExceed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lastUpdateAt
|
||||
*
|
||||
* @param \DateTime $lastUpdateAt Date of last updated of this query.
|
||||
*
|
||||
* @return StoredQuery
|
||||
*/
|
||||
public function setLastUpdateAt(\DateTime $lastUpdateAt)
|
||||
{
|
||||
$this->lastUpdateAt = $lastUpdateAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get lastUpdateAt
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getLastUpdateAt()
|
||||
{
|
||||
return $this->lastUpdateAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set status
|
||||
*
|
||||
* @param string $status Stored query status.
|
||||
*
|
||||
* @return StoredQuery
|
||||
*/
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that this stored query is in specified status.
|
||||
*
|
||||
* @param string|string[] $status Stored query status.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isInStatus($status)
|
||||
{
|
||||
if (is_string($status)) {
|
||||
$status = [ $status ];
|
||||
}
|
||||
|
||||
return \nspl\a\any($status, \nspl\f\partial('\nspl\op\idnt', $this->status));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get limitExceed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function getLimitExceed()
|
||||
{
|
||||
return $this->limitExceed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add feed
|
||||
*
|
||||
* @param QueryFeed $feed A QueryFeed instance.
|
||||
*
|
||||
* @return StoredQuery
|
||||
*/
|
||||
public function addFeed(QueryFeed $feed)
|
||||
{
|
||||
$this->feeds[] = $feed;
|
||||
$feed->setQuery($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove feed
|
||||
*
|
||||
* @param QueryFeed $feed A QueryFeed instance.
|
||||
*
|
||||
* @return StoredQuery
|
||||
*/
|
||||
public function removeFeed(QueryFeed $feed)
|
||||
{
|
||||
$this->feeds->removeElement($feed);
|
||||
$feed->setQuery(null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queries
|
||||
*
|
||||
* @return \Doctrine\Common\Collections\Collection
|
||||
*/
|
||||
public function getFeeds()
|
||||
{
|
||||
return $this->feeds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity;
|
||||
|
||||
use ApiBundle\Entity\NormalizableEntityInterface;
|
||||
use ApiBundle\Serializer\Metadata\Metadata;
|
||||
use ApiBundle\Serializer\Metadata\PropertyMetadata;
|
||||
use AppBundle\Entity\BaseEntityTrait;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Source
|
||||
*
|
||||
* @ORM\Table(name="source_list")
|
||||
* @ORM\Entity(
|
||||
* repositoryClass="CacheBundle\Repository\SourceListRepository"
|
||||
* )
|
||||
* @UniqueEntity({"name", "user"})
|
||||
* @ORM\HasLifecycleCallbacks
|
||||
*/
|
||||
class SourceList implements NormalizableEntityInterface
|
||||
{
|
||||
|
||||
use BaseEntityTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Assert\NotBlank()
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $isGlobal = false;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
protected $createdAt;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(type="datetime", nullable=true)
|
||||
*/
|
||||
protected $updatedAt;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="UserBundle\Entity\User")
|
||||
*/
|
||||
protected $updatedBy;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="UserBundle\Entity\User", inversedBy="sourcesLists")
|
||||
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Collections\Collection
|
||||
*
|
||||
* @ORM\OneToMany(
|
||||
* targetEntity="CacheBundle\Entity\SourceToSourceList",
|
||||
* mappedBy="list",
|
||||
* cascade={ "persist", "remove" }
|
||||
* )
|
||||
*/
|
||||
protected $sources;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $sourceNumber = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->sources = new ArrayCollection();
|
||||
$this->createdAt = new \DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SourceList
|
||||
*/
|
||||
public function cloneList()
|
||||
{
|
||||
$clone = clone $this;
|
||||
|
||||
$clone
|
||||
->setSourceNumber(0)
|
||||
->setUpdatedAt(null)
|
||||
->setUpdatedBy(null);
|
||||
$clone->sources = new ArrayCollection();
|
||||
|
||||
return $clone;
|
||||
}
|
||||
/**
|
||||
* Return metadata for current entity.
|
||||
*
|
||||
* @return \ApiBundle\Serializer\Metadata\Metadata
|
||||
*/
|
||||
public function getMetadata()
|
||||
{
|
||||
return new Metadata(static::class, [
|
||||
PropertyMetadata::createInteger('id', [ 'id' ]),
|
||||
PropertyMetadata::createInteger('sourceNumber', [ 'source_list' ]),
|
||||
PropertyMetadata::createBoolean('shared', [ 'source_list' ])
|
||||
->setField('isGlobal'),
|
||||
PropertyMetadata::createString('name', [ 'source_list' ]),
|
||||
PropertyMetadata::createEntity('user', User::class, [ 'source_list' ]),
|
||||
PropertyMetadata::createDate('createdAt', [ 'source_list' ]),
|
||||
PropertyMetadata::createDate('updatedAt', [ 'source_list' ]),
|
||||
PropertyMetadata::createEntity('updatedBy', User::class, [ 'source_list' ]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default normalization groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaultGroups()
|
||||
{
|
||||
return [ 'source_list', 'id' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\PreUpdate()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onUpdate()
|
||||
{
|
||||
$this->setUpdatedAt(new \DateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set title
|
||||
*
|
||||
* @param string $name Source list name.
|
||||
*
|
||||
* @return SourceList
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set createdAt
|
||||
*
|
||||
* @param \DateTime $createdAt When this list is created.
|
||||
*
|
||||
* @return SourceList
|
||||
*/
|
||||
public function setCreatedAt(\DateTime $createdAt = null)
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get createdAt
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getCreatedAt()
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set updatedAt
|
||||
*
|
||||
* @param \DateTime $updatedAt When this list is updated.
|
||||
*
|
||||
* @return SourceList
|
||||
*/
|
||||
public function setUpdatedAt(\DateTime $updatedAt = null)
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updatedAt
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getUpdatedAt()
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set updatedBy
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return SourceList
|
||||
*/
|
||||
public function setUpdatedBy(User $user = null)
|
||||
{
|
||||
$this->updatedBy = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updatedBy
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getUpdatedBy()
|
||||
{
|
||||
return $this->updatedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user
|
||||
*
|
||||
* @param User $user A owner User entity instance.
|
||||
*
|
||||
* @return SourceList
|
||||
*/
|
||||
public function setUser(User $user = null)
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set isGlobal
|
||||
*
|
||||
* @param boolean $isGlobal Is global source list or not.
|
||||
*
|
||||
* @return SourceList
|
||||
*/
|
||||
public function setIsGlobal($isGlobal)
|
||||
{
|
||||
$this->isGlobal = $isGlobal;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get isGlobal
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIsGlobal()
|
||||
{
|
||||
return $this->isGlobal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether specified user owner.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOwnedBy(User $user)
|
||||
{
|
||||
return $user->getId() === $this->user->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sourceNumber
|
||||
*
|
||||
* @param integer $sourceNumber Sources count.
|
||||
*
|
||||
* @return SourceList
|
||||
*/
|
||||
public function setSourceNumber($sourceNumber)
|
||||
{
|
||||
$this->sourceNumber = $sourceNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sourceNumber
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getSourceNumber()
|
||||
{
|
||||
return $this->sourceNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add source
|
||||
*
|
||||
* @param SourceToSourceList $source A SourceToSourceList entity instance.
|
||||
*
|
||||
* @return SourceList
|
||||
*/
|
||||
public function addSource(SourceToSourceList $source)
|
||||
{
|
||||
$this->sources[] = $source;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove source
|
||||
*
|
||||
* @param SourceToSourceList $source A SourceToSourceList entity instance.
|
||||
*
|
||||
* @return SourceList
|
||||
*/
|
||||
public function removeSource(SourceToSourceList $source)
|
||||
{
|
||||
$this->sources->removeElement($source);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sources
|
||||
*
|
||||
* @return \Doctrine\Common\Collections\Collection
|
||||
*/
|
||||
public function getSources()
|
||||
{
|
||||
return $this->sources;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* SourceToSourceList
|
||||
* Many to many relation between source list in database and sources in index.
|
||||
*
|
||||
* @ORM\Table(name="cross_sources_source_lists")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class SourceToSourceList
|
||||
{
|
||||
|
||||
/**
|
||||
* Source id from cache index.
|
||||
*
|
||||
* @var string|resource
|
||||
*
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="binary")
|
||||
*/
|
||||
private $source;
|
||||
|
||||
/**
|
||||
* @var SourceList
|
||||
*
|
||||
* @ORM\Id
|
||||
* @ORM\ManyToOne(targetEntity="CacheBundle\Entity\SourceList", inversedBy="sources")
|
||||
*/
|
||||
private $list;
|
||||
|
||||
/**
|
||||
* @param string $source Source id from cache index.
|
||||
* @param SourceList $list A SourceList entity instance.
|
||||
*
|
||||
* @return SourceToSourceList
|
||||
*/
|
||||
public static function create($source, SourceList $list)
|
||||
{
|
||||
$instance = new self();
|
||||
|
||||
return $instance
|
||||
->setSource($source)
|
||||
->setList($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set source
|
||||
*
|
||||
* @param string $source Source id from cache index.
|
||||
*
|
||||
* @return SourceToSourceList
|
||||
*/
|
||||
public function setSource($source)
|
||||
{
|
||||
$this->source = $source;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get source
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSource()
|
||||
{
|
||||
if (! is_string($this->source)) {
|
||||
//
|
||||
// Because of Doctrine.
|
||||
//
|
||||
$this->source = stream_get_contents($this->source);
|
||||
}
|
||||
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set list
|
||||
*
|
||||
* @param SourceList $list A SourceList entity instance.
|
||||
*
|
||||
* @return SourceToSourceList
|
||||
*/
|
||||
public function setList(SourceList $list)
|
||||
{
|
||||
$this->list = $list;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list
|
||||
*
|
||||
* @return SourceList
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
return $this->list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Fetcher;
|
||||
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Entity\Feed\ClipFeed;
|
||||
use CacheBundle\Feed\Response\FeedResponse;
|
||||
use CacheBundle\Feed\Response\FeedResponseInterface;
|
||||
use Common\Enum\CollectionTypeEnum;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use IndexBundle\Index\Internal\InternalIndexInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
|
||||
/**
|
||||
* Class ClipFeedFetcher
|
||||
*
|
||||
* Fetch document and meta information for query feed.
|
||||
*
|
||||
* @package CacheBundle\Feed\Fetcher
|
||||
*/
|
||||
class ClipFeedFetcher implements FeedFetcherInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var InternalIndexInterface
|
||||
*/
|
||||
private $index;
|
||||
|
||||
/**
|
||||
* QueryFeedFetcher constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface instance.
|
||||
* @param InternalIndexInterface $index A InternalIndexInterface instance.
|
||||
*/
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
InternalIndexInterface $index
|
||||
) {
|
||||
$this->em = $em;
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch information for specified feed
|
||||
*
|
||||
* @param AbstractFeed $feed A AbstractFeed entity
|
||||
* instance.
|
||||
* @param SearchRequestBuilderInterface $builder A SearchRequestBuilderInterface
|
||||
* instance.
|
||||
*
|
||||
* @return FeedResponseInterface
|
||||
*/
|
||||
public function fetch(AbstractFeed $feed, SearchRequestBuilderInterface $builder)
|
||||
{
|
||||
if (! $feed instanceof ClipFeed) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Expect '. ClipFeed::class . ' but got '. get_class($feed)
|
||||
);
|
||||
}
|
||||
|
||||
$factory = $this->index->getFilterFactory();
|
||||
$request = $this->index->createRequestBuilder()
|
||||
->setFilters($builder->getFilters())
|
||||
->addFilter($factory->eq(FieldNameEnum::COLLECTION_ID, $feed->getId()))
|
||||
->addFilter($factory->eq(FieldNameEnum::COLLECTION_TYPE, CollectionTypeEnum::FEED))
|
||||
->build();
|
||||
|
||||
return new FeedResponse(
|
||||
$request->execute(),
|
||||
$request->getAvailableAdvancedFilters(), // AFSourceEnum::FEED
|
||||
[
|
||||
'type' => 'clip_feed',
|
||||
'status' => 'synced',
|
||||
'search' => [
|
||||
'advancedFilters' => count($feed->getRawFilters()) > 0 ? $feed->getRawFilters() : (object) [],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create search builder for specified feed.
|
||||
*
|
||||
* @param AbstractFeed $feed A AbstractFeed entity instance.
|
||||
*
|
||||
* @return SearchRequestBuilderInterface|null
|
||||
*/
|
||||
public function createRequestBuilder(AbstractFeed $feed)
|
||||
{
|
||||
if (! $feed instanceof ClipFeed) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Expect '. ClipFeed::class . ' but got '. get_class($feed)
|
||||
);
|
||||
}
|
||||
|
||||
$factory = $this->index->getFilterFactory();
|
||||
$filters = $feed->getFilters();
|
||||
$filters[] = $factory->eq(FieldNameEnum::COLLECTION_ID, $feed->getId());
|
||||
$filters[] = $factory->eq(FieldNameEnum::COLLECTION_TYPE, CollectionTypeEnum::FEED);
|
||||
|
||||
return $this->index->createRequestBuilder()
|
||||
->setFilters($filters)
|
||||
->setFields([
|
||||
FieldNameEnum::TITLE,
|
||||
FieldNameEnum::MAIN,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return supported feed fqcn.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function support()
|
||||
{
|
||||
return ClipFeed::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Fetcher\Factory;
|
||||
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Feed\Fetcher\FeedFetcherInterface;
|
||||
|
||||
/**
|
||||
* Interface FeedFetcherFactoryInterface
|
||||
*
|
||||
* Return feed fetcher factory.
|
||||
*
|
||||
* @package CacheBundle\Feed\Fetcher\Factory
|
||||
*/
|
||||
interface FeedFetcherFactoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get feed fetcher for specified feed.
|
||||
*
|
||||
* @param string|AbstractFeed $feedClass Feed fqcn or instance.
|
||||
*
|
||||
* @return FeedFetcherInterface
|
||||
*/
|
||||
public function get($feedClass);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Fetcher\Factory;
|
||||
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Feed\Fetcher\FeedFetcherInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class LazyFeedFetcherFactory
|
||||
*
|
||||
* Return feed fetcher factory.
|
||||
*
|
||||
* @package CacheBundle\Feed\Fetcher\Factory
|
||||
*/
|
||||
class LazyFeedFetcherFactory implements FeedFetcherFactoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $map;
|
||||
|
||||
/**
|
||||
* LazyFeedFetcherFactory constructor.
|
||||
*
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
* @param array $map Map between feed fqcn and proper
|
||||
* fetcher service id.
|
||||
*/
|
||||
public function __construct(ContainerInterface $container, array $map)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->map = $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get feed fetcher for specified feed.
|
||||
*
|
||||
* @param string|AbstractFeed $feedClass Feed fqcn.
|
||||
*
|
||||
* @return FeedFetcherInterface
|
||||
*/
|
||||
public function get($feedClass)
|
||||
{
|
||||
if (is_object($feedClass)) {
|
||||
$feedClass = get_class($feedClass);
|
||||
}
|
||||
|
||||
if (! is_string($feedClass)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid parameter feedClass. Should be string or instance of AbstractFeed.'
|
||||
);
|
||||
}
|
||||
|
||||
$fetcher = $this->container->get($this->map[$feedClass]);
|
||||
if (! $fetcher instanceof FeedFetcherInterface) {
|
||||
throw new \RuntimeException('Got invalid fetcher.');
|
||||
}
|
||||
|
||||
return $fetcher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Fetcher;
|
||||
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Feed\Response\FeedResponseInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
|
||||
/**
|
||||
* Interface FeedFetcherInterface
|
||||
*
|
||||
* Fetch document and meta information for feeds.
|
||||
*
|
||||
* @package CacheBundle\Feed\Fetcher
|
||||
*/
|
||||
interface FeedFetcherInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Fetch information for specified feed
|
||||
*
|
||||
* @param AbstractFeed $feed A AbstractFeed entity
|
||||
* instance.
|
||||
* @param SearchRequestBuilderInterface $builder A SearchRequestBuilderInterface
|
||||
* instance.
|
||||
*
|
||||
* @return FeedResponseInterface
|
||||
*/
|
||||
public function fetch(AbstractFeed $feed, SearchRequestBuilderInterface $builder);
|
||||
|
||||
/**
|
||||
* Create search builder for specified feed.
|
||||
*
|
||||
* @param AbstractFeed $feed A AbstractFeed entity instance.
|
||||
*
|
||||
* @return SearchRequestBuilderInterface|null
|
||||
*/
|
||||
public function createRequestBuilder(AbstractFeed $feed);
|
||||
|
||||
/**
|
||||
* Return supported feed fqcn.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function support();
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Fetcher;
|
||||
|
||||
use AppBundle\AdvancedFilters\AdvancedFiltersConfig;
|
||||
use AppBundle\Manager\Source\SourceManagerInterface;
|
||||
use AppBundle\Manager\StoredQuery\StoredQueryManagerInterface;
|
||||
use AppBundle\Response\SearchResponse;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Entity\Feed\QueryFeed;
|
||||
use CacheBundle\Feed\Response\FeedResponse;
|
||||
use CacheBundle\Feed\Response\FeedResponseInterface;
|
||||
use CacheBundle\Repository\StoredQueryRepository;
|
||||
use Common\Enum\AFSourceEnum;
|
||||
use Common\Enum\CollectionTypeEnum;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use Common\Enum\StoredQueryStatusEnum;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
|
||||
/**
|
||||
* Class QueryFeedFetcher
|
||||
*
|
||||
* Fetch document and meta information for query feed.
|
||||
*
|
||||
* @package CacheBundle\Feed\Fetcher
|
||||
*/
|
||||
class QueryFeedFetcher implements FeedFetcherInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var StoredQueryManagerInterface
|
||||
*/
|
||||
private $manager;
|
||||
|
||||
/**
|
||||
* @var SourceManagerInterface
|
||||
*/
|
||||
private $sourceManager;
|
||||
|
||||
|
||||
/**
|
||||
* QueryFeedFetcher constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface
|
||||
* instance.
|
||||
* @param StoredQueryManagerInterface $manager A StoredQueryManagerInterface
|
||||
* instance.
|
||||
* @param SourceManagerInterface $sourceManager A SourceManagerInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
StoredQueryManagerInterface $manager,
|
||||
SourceManagerInterface $sourceManager
|
||||
) {
|
||||
$this->em = $em;
|
||||
$this->manager = $manager;
|
||||
$this->sourceManager = $sourceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch information for specified feed
|
||||
*
|
||||
* @param AbstractFeed $feed A AbstractFeed entity
|
||||
* instance.
|
||||
* @param SearchRequestBuilderInterface $builder A SearchRequestBuilderInterface
|
||||
* instance.
|
||||
*
|
||||
* @return FeedResponseInterface
|
||||
*/
|
||||
public function fetch(AbstractFeed $feed, SearchRequestBuilderInterface $builder)
|
||||
{
|
||||
if (! $feed instanceof QueryFeed) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Expect '. QueryFeed::class . ' but got '. get_class($feed)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Get proper stored query for fetched feed.
|
||||
//
|
||||
/** @var StoredQueryRepository $repository */
|
||||
$repository = $this->em->getRepository('CacheBundle:Query\StoredQuery');
|
||||
$query = $repository->getByFeed($feed->getId());
|
||||
|
||||
//
|
||||
// Collect information.
|
||||
//
|
||||
$queryStatus = $query->isInStatus([
|
||||
StoredQueryStatusEnum::INITIALIZE,
|
||||
StoredQueryStatusEnum::DELETED,
|
||||
]);
|
||||
$response = new SearchResponse();
|
||||
|
||||
$sources = $this->sourceManager->getSourcesForQuery($query, [ 'id', 'title', 'type' ]);
|
||||
$sourceLists = $this->sourceManager->getSourceListsForQuery($query, [ 'id', 'name' ]);
|
||||
|
||||
$meta = [
|
||||
'type' => 'query_feed',
|
||||
'status' => $queryStatus ? 'not_synced' : 'synced',
|
||||
'search' => [
|
||||
'query' => $query->getRaw(),
|
||||
'filters' => $query->getRawFilters(),
|
||||
'advancedFilters' => count($query->getRawAdvancedFilters()) > 0 ? $query->getRawAdvancedFilters() : (object) [],
|
||||
],
|
||||
'sources' => $sources,
|
||||
'sourceLists' => $sourceLists,
|
||||
];
|
||||
|
||||
$advancedFilters = AdvancedFiltersConfig::getDefault(AFSourceEnum::FEED);
|
||||
if (! $query->isInStatus([
|
||||
StoredQueryStatusEnum::INITIALIZE,
|
||||
StoredQueryStatusEnum::DELETED,
|
||||
])) {
|
||||
$factory = $builder->getIndex()->getFilterFactory();
|
||||
$builder
|
||||
->addFilter($factory->andX([
|
||||
$factory->eq(FieldNameEnum::COLLECTION_ID, $query->getId()),
|
||||
$factory->eq(FieldNameEnum::COLLECTION_TYPE, CollectionTypeEnum::QUERY),
|
||||
]))
|
||||
->addFilter($factory->not($factory->eq(FieldNameEnum::DELETE_FROM, $feed->getId())));
|
||||
|
||||
$response = $this->manager->get($feed->getUser(), $query, $builder);
|
||||
$advancedFilters = $this->manager->getAdvancedFilters($query, $builder);
|
||||
}
|
||||
|
||||
return new FeedResponse($response, $advancedFilters, $meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create search builder for specified feed.
|
||||
*
|
||||
* @param AbstractFeed $feed A AbstractFeed entity instance.
|
||||
*
|
||||
* @return SearchRequestBuilderInterface|null
|
||||
*/
|
||||
public function createRequestBuilder(AbstractFeed $feed)
|
||||
{
|
||||
if (! $feed instanceof QueryFeed) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Expect '. QueryFeed::class . ' but got '. get_class($feed)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Get proper stored query for fetched feed.
|
||||
//
|
||||
/** @var StoredQueryRepository $repository */
|
||||
$repository = $this->em->getRepository('CacheBundle:Query\StoredQuery');
|
||||
$query = $repository->getByFeed($feed->getId());
|
||||
|
||||
if (! $query->isInStatus([
|
||||
StoredQueryStatusEnum::INITIALIZE,
|
||||
StoredQueryStatusEnum::DELETED,
|
||||
])) {
|
||||
return $this->manager->createRequestBuilder(
|
||||
$feed->getUser(),
|
||||
$query
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return supported feed fqcn.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function support()
|
||||
{
|
||||
return QueryFeed::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Formatter;
|
||||
|
||||
use AppBundle\Manager\Feed\FeedManagerInterface;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Feed\Formatter\Strategy\FeedFormatterStrategyInterface;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use Common\Enum\FormatNameEnum;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class BasicFeedFormatter
|
||||
*
|
||||
* @package CacheBundle\Feed\Formatter
|
||||
*/
|
||||
class BasicFeedFormatter implements FeedFormatterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var FeedManagerInterface
|
||||
*/
|
||||
private $feedManager;
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* BasicFeedFormatter constructor.
|
||||
*
|
||||
* @param FeedManagerInterface $feedManager A FeedManagerInterface instance.
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
*/
|
||||
public function __construct(
|
||||
FeedManagerInterface $feedManager,
|
||||
ContainerInterface $container
|
||||
) {
|
||||
$this->feedManager = $feedManager;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format feed documents.
|
||||
*
|
||||
* @param AbstractFeed $feed A formatted feed entity instance.
|
||||
* @param FormatterOptions $options Used format options.
|
||||
*
|
||||
* @return FormattedData
|
||||
*/
|
||||
public function formatFeed(AbstractFeed $feed, FormatterOptions $options)
|
||||
{
|
||||
$strategy = $this->createStrategy($options->getFormat());
|
||||
|
||||
$filterFactory = $this->feedManager->getIndex()->getFilterFactory();
|
||||
|
||||
$sourceFields = $strategy->requiredFields($options);
|
||||
$sourceFields[] = FieldNameEnum::SEQUENCE;
|
||||
$sourceFields[] = FieldNameEnum::COLLECTION_ID;
|
||||
$sourceFields[] = FieldNameEnum::COLLECTION_TYPE;
|
||||
|
||||
$documents = $this->feedManager->getIndex()->createRequestBuilder()
|
||||
->setFilters($filterFactory->andX([
|
||||
$filterFactory->eq(FieldNameEnum::COLLECTION_ID, $feed->getCollectionId()),
|
||||
$filterFactory->eq(FieldNameEnum::COLLECTION_TYPE, $feed->getCollectionType()),
|
||||
]))
|
||||
->setSources($sourceFields)
|
||||
->setLimit($options->getNumberOfDocuments())
|
||||
->setSorts([ FieldNameEnum::PUBLISHED => 'desc' ])
|
||||
->build()
|
||||
->execute()
|
||||
->getDocuments();
|
||||
|
||||
return new FormattedData(
|
||||
$strategy->serialize($feed, $documents, $options),
|
||||
$strategy->getMime()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create strategy for specified format.
|
||||
*
|
||||
* @param FormatNameEnum $format Used format name.
|
||||
*
|
||||
* @return FeedFormatterStrategyInterface
|
||||
*/
|
||||
private function createStrategy(FormatNameEnum $format)
|
||||
{
|
||||
$name = 'cache.feed_formatter_strategy.'. strtolower($format->getValue());
|
||||
|
||||
if (! $this->container->has($name)) {
|
||||
throw new \InvalidArgumentException('Unknown format '. $format->getValue());
|
||||
}
|
||||
|
||||
$strategy = $this->container->get($name);
|
||||
|
||||
if (! $strategy instanceof FeedFormatterStrategyInterface) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Feed formatter strategy should implements %s interface.',
|
||||
FeedFormatterStrategyInterface::class
|
||||
));
|
||||
}
|
||||
|
||||
return $strategy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Formatter;
|
||||
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
|
||||
/**
|
||||
* Interface FeedFormatterInterface
|
||||
*
|
||||
* @package CacheBundle\Feed\Formatter
|
||||
*/
|
||||
interface FeedFormatterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Format feed documents.
|
||||
*
|
||||
* @param AbstractFeed $feed A formatted feed entity instance.
|
||||
* @param FormatterOptions $options Used format options.
|
||||
*
|
||||
* @return FormattedData
|
||||
*/
|
||||
public function formatFeed(AbstractFeed $feed, FormatterOptions $options);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Formatter;
|
||||
|
||||
/**
|
||||
* Class FormattedData
|
||||
*
|
||||
* @package CacheBundle\Feed\Formatter
|
||||
*/
|
||||
class FormattedData
|
||||
{
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $mime;
|
||||
|
||||
/**
|
||||
* FormattedData constructor.
|
||||
*
|
||||
* @param mixed $data Formatted data.
|
||||
* @param string $mime Data mime type.
|
||||
*/
|
||||
public function __construct($data, $mime)
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->mime = $mime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMime()
|
||||
{
|
||||
return $this->mime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Formatter;
|
||||
|
||||
use Common\Enum\FormatNameEnum;
|
||||
use UserBundle\Enum\ThemeOptionExtractEnum;
|
||||
|
||||
/**
|
||||
* Class FormatterOptions
|
||||
*
|
||||
* @package CacheBundle\Feed\Formatter
|
||||
*/
|
||||
class FormatterOptions
|
||||
{
|
||||
|
||||
/**
|
||||
* @var FormatNameEnum
|
||||
*/
|
||||
private $format;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $numberOfDocuments;
|
||||
|
||||
/**
|
||||
* @var ThemeOptionExtractEnum
|
||||
*/
|
||||
private $extract;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $showImages;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $asPlain;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $highlight;
|
||||
|
||||
/**
|
||||
* FormatterOptions constructor.
|
||||
*
|
||||
* @param FormatNameEnum $format A FormatNameEnum instance.
|
||||
* @param integer $numberOfDocuments A required number of
|
||||
* documents.
|
||||
* @param ThemeOptionExtractEnum|null $extract A ThemeOptionExtractEnum
|
||||
* instance. No extract
|
||||
* if null.
|
||||
* @param boolean $showImages Should fetch image
|
||||
* url or not.
|
||||
* @param boolean $asPlain Show content as plain
|
||||
* text.
|
||||
* @param boolean $highlight Should highlight
|
||||
* matched keywords or
|
||||
* not.
|
||||
*/
|
||||
public function __construct(
|
||||
FormatNameEnum $format,
|
||||
$numberOfDocuments = 1,
|
||||
ThemeOptionExtractEnum $extract = null,
|
||||
$showImages = false,
|
||||
$asPlain = false,
|
||||
$highlight = false
|
||||
) {
|
||||
$this->format = $format;
|
||||
$this->numberOfDocuments = $numberOfDocuments;
|
||||
$this->extract = $extract ?: ThemeOptionExtractEnum::no();
|
||||
$this->showImages = $showImages;
|
||||
$this->asPlain = $asPlain;
|
||||
$this->highlight = $highlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormatNameEnum
|
||||
*/
|
||||
public function getFormat()
|
||||
{
|
||||
return $this->format;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getNumberOfDocuments()
|
||||
{
|
||||
return $this->numberOfDocuments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ThemeOptionExtractEnum
|
||||
*/
|
||||
public function getExtract()
|
||||
{
|
||||
return $this->extract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isShowImages()
|
||||
{
|
||||
return $this->showImages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAsPlain()
|
||||
{
|
||||
return $this->asPlain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isHighlight()
|
||||
{
|
||||
return $this->highlight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Formatter\Strategy;
|
||||
|
||||
use CacheBundle\Document\Extractor\DocumentContentExtractorInterface;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Entity\Feed\QueryFeed;
|
||||
use CacheBundle\Feed\Formatter\FormatterOptions;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use UserBundle\Enum\ThemeOptionExtractEnum;
|
||||
|
||||
/**
|
||||
* Class AbstractFeedFormatStrategy
|
||||
*
|
||||
* @package CacheBundle\Feed\Formatter\Strategy
|
||||
*/
|
||||
abstract class AbstractFeedFormatStrategy implements FeedFormatterStrategyInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var DocumentContentExtractorInterface
|
||||
*/
|
||||
private $extractor;
|
||||
|
||||
/**
|
||||
* AbstractFeedFormatStrategy constructor.
|
||||
*
|
||||
* @param DocumentContentExtractorInterface $extractor A DocumentContentExtractorInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(DocumentContentExtractorInterface $extractor)
|
||||
{
|
||||
$this->extractor = $extractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of required document fields.
|
||||
*
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function requiredFields(FormatterOptions $options)
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
if (! $options->getExtract()->is(ThemeOptionExtractEnum::NO)) {
|
||||
$fields[] = FieldNameEnum::MAIN;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content Document content.
|
||||
* @param FormatterOptions $options FormatterOptions.
|
||||
* @param AbstractFeed $feed A serialized feed entity instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function extract($content, FormatterOptions $options, AbstractFeed $feed)
|
||||
{
|
||||
$extract = $options->getExtract();
|
||||
|
||||
//
|
||||
// We should get normalized search query only if it requested.
|
||||
//
|
||||
$query = '';
|
||||
if (! $extract->is(ThemeOptionExtractEnum::no())) {
|
||||
if ($feed instanceof QueryFeed) {
|
||||
$query = $feed->getQuery()->getNormalized();
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->extractor->extract($content, $query, $extract);
|
||||
|
||||
return $result->getText() . ($result->getLength() > 0 ? '...' : '');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Formatter\Strategy;
|
||||
|
||||
use CacheBundle\Document\Extractor\DocumentContentExtractorInterface;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Feed\Formatter\FormatterOptions;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use IndexBundle\Model\ArticleDocumentInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
/**
|
||||
* Class AtomFeedFormatterStrategy
|
||||
*
|
||||
* @package CacheBundle\Feed\Formatter\Strategy
|
||||
*
|
||||
* @link https://validator.w3.org/feed/docs/atom.html
|
||||
*/
|
||||
class AtomFeedFormatterStrategy extends AbstractFeedFormatStrategy
|
||||
{
|
||||
|
||||
/**
|
||||
* @var UrlGeneratorInterface
|
||||
*/
|
||||
private $generator;
|
||||
|
||||
/**
|
||||
* AtomFeedFormatterStrategy constructor.
|
||||
*
|
||||
* @param DocumentContentExtractorInterface $extractor A
|
||||
* DocumentContentExtractorInterface
|
||||
* instance.
|
||||
* @param UrlGeneratorInterface $generator A UrlGeneratorInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(
|
||||
DocumentContentExtractorInterface $extractor,
|
||||
UrlGeneratorInterface $generator
|
||||
) {
|
||||
parent::__construct($extractor);
|
||||
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of required document fields.
|
||||
*
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function requiredFields(FormatterOptions $options)
|
||||
{
|
||||
$fields = parent::requiredFields($options);
|
||||
$fields[] = FieldNameEnum::TITLE;
|
||||
$fields[] = FieldNameEnum::PERMALINK;
|
||||
$fields[] = FieldNameEnum::SOURCE_TITLE;
|
||||
$fields[] = FieldNameEnum::SOURCE_LINK;
|
||||
$fields[] = FieldNameEnum::PUBLISHED;
|
||||
$fields[] = FieldNameEnum::SECTION;
|
||||
|
||||
if ($options->isShowImages()) {
|
||||
$fields[] = FieldNameEnum::IMAGE_SRC;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize feed.
|
||||
*
|
||||
* @param AbstractFeed $feed A serialized feed entity
|
||||
* instance.
|
||||
* @param ArticleDocumentInterface[] $documents Array of fetched documents
|
||||
* which should by serialized.
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function serialize(
|
||||
AbstractFeed $feed,
|
||||
array $documents,
|
||||
FormatterOptions $options
|
||||
) {
|
||||
$node = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"></feed>');
|
||||
|
||||
// Attach feed info.
|
||||
$node->addChild('id', $this->generator->generate('app_index_index', [], UrlGeneratorInterface::ABSOLUTE_URL));
|
||||
$node->addChild('title', $feed->getName());
|
||||
$node->addChild('updated', date_create()->format('c'));
|
||||
|
||||
$link = $node->addChild('link');
|
||||
$link->addAttribute('rel', 'self');
|
||||
$link->addAttribute('href', $this->generator->generate('app_index_exportfeed', [
|
||||
'format' => $options->getFormat()->getValue(),
|
||||
'id' => $feed->getId(),
|
||||
]));
|
||||
|
||||
$textType = $options->isAsPlain() ? 'text' : 'html';
|
||||
|
||||
foreach ($documents as $document) {
|
||||
$data = $document->getNormalizedData();
|
||||
|
||||
$item = $node->addChild('entry');
|
||||
$item
|
||||
->addChild('title', $data['title'])
|
||||
->addAttribute('type', $textType);
|
||||
|
||||
$link = $item->addChild('link');
|
||||
$link->addAttribute('rel', 'alternate');
|
||||
$link->addAttribute('href', $data['permalink']);
|
||||
|
||||
if ($options->isShowImages()) {
|
||||
$item->addChild('image', $data['image']);
|
||||
}
|
||||
|
||||
$author = $item->addChild('author');
|
||||
$author->addChild('name', $data['source']['title']);
|
||||
$author->addChild('uri', $data['source']['link']);
|
||||
|
||||
$item->addChild('pubDate', $data['published']->format('d M Y H:i:s e'));
|
||||
$item
|
||||
->addChild('summary', $this->extract($data['content'], $options, $feed))
|
||||
->addAttribute('type', $textType);
|
||||
}
|
||||
|
||||
return $node->asXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get format mime type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMime()
|
||||
{
|
||||
return 'text/xml';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Formatter\Strategy;
|
||||
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Feed\Formatter\FormatterOptions;
|
||||
use IndexBundle\Model\ArticleDocumentInterface;
|
||||
|
||||
/**
|
||||
* Interface FeedFormatterStrategyInterface
|
||||
*
|
||||
* @package CacheBundle\Feed\Formatter\Strategy
|
||||
*/
|
||||
interface FeedFormatterStrategyInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Return list of required document fields.
|
||||
*
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function requiredFields(FormatterOptions $options);
|
||||
|
||||
/**
|
||||
* Serialize feed.
|
||||
*
|
||||
* @param AbstractFeed $feed A serialized feed entity
|
||||
* instance.
|
||||
* @param ArticleDocumentInterface[] $documents Array of fetched documents
|
||||
* which should by serialized.
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function serialize(
|
||||
AbstractFeed $feed,
|
||||
array $documents,
|
||||
FormatterOptions $options
|
||||
);
|
||||
|
||||
/**
|
||||
* Get format mime type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMime();
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Formatter\Strategy;
|
||||
|
||||
use CacheBundle\Document\Extractor\DocumentContentExtractorInterface;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Feed\Formatter\FormatterOptions;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use IndexBundle\Model\ArticleDocumentInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
|
||||
|
||||
/**
|
||||
* Class HtmlFeedFormatterStrategy
|
||||
*
|
||||
* @package CacheBundle\Feed\Formatter\Strategy
|
||||
*/
|
||||
class HtmlFeedFormatterStrategy extends AbstractFeedFormatStrategy
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EngineInterface
|
||||
*/
|
||||
private $templating;
|
||||
|
||||
/**
|
||||
* HtmlFeedFormatterStrategy constructor.
|
||||
*
|
||||
* @param DocumentContentExtractorInterface $extractor A DocumentContentExtractorInterface
|
||||
* instance.
|
||||
* @param EngineInterface $templating A templating EngineInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(
|
||||
DocumentContentExtractorInterface $extractor,
|
||||
EngineInterface $templating
|
||||
) {
|
||||
parent::__construct($extractor);
|
||||
|
||||
$this->templating = $templating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of required document fields.
|
||||
*
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function requiredFields(FormatterOptions $options)
|
||||
{
|
||||
$fields = parent::requiredFields($options);
|
||||
$fields[] = FieldNameEnum::PERMALINK;
|
||||
$fields[] = FieldNameEnum::SOURCE_TITLE;
|
||||
$fields[] = FieldNameEnum::SOURCE_LINK;
|
||||
$fields[] = FieldNameEnum::PUBLISHED;
|
||||
$fields[] = FieldNameEnum::AUTHOR_NAME;
|
||||
$fields[] = FieldNameEnum::TITLE;
|
||||
|
||||
if ($options->isShowImages()) {
|
||||
$fields[] = FieldNameEnum::IMAGE_SRC;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize feed.
|
||||
*
|
||||
* @param AbstractFeed $feed A serialized feed entity
|
||||
* instance.
|
||||
* @param ArticleDocumentInterface[] $documents Array of fetched documents
|
||||
* which should by serialized.
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function serialize(
|
||||
AbstractFeed $feed,
|
||||
array $documents,
|
||||
FormatterOptions $options
|
||||
) {
|
||||
$data = \nspl\a\map(function (ArticleDocumentInterface $document) use ($options, $feed) {
|
||||
$data = $document->getNormalizedData();
|
||||
|
||||
$data['content'] = $this->extract($data['content'], $options, $feed);
|
||||
|
||||
return $data;
|
||||
}, $documents);
|
||||
|
||||
return $this->templating->render('CacheBundle::feed.html.twig', [
|
||||
'feed' => $feed,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get format mime type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMime()
|
||||
{
|
||||
return 'text/html';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Formatter\Strategy;
|
||||
|
||||
use CacheBundle\Document\Extractor\DocumentContentExtractorInterface;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Feed\Formatter\FormatterOptions;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use IndexBundle\Model\ArticleDocumentInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
/**
|
||||
* Class RssFeedFormatterStrategy
|
||||
*
|
||||
* @package CacheBundle\Feed\Formatter\Strategy
|
||||
*
|
||||
* @link https://validator.w3.org/feed/docs/rss2.html
|
||||
*/
|
||||
class RssFeedFormatterStrategy extends AbstractFeedFormatStrategy
|
||||
{
|
||||
|
||||
/**
|
||||
* @var UrlGeneratorInterface
|
||||
*/
|
||||
private $generator;
|
||||
|
||||
/**
|
||||
* AtomFeedFormatterStrategy constructor.
|
||||
*
|
||||
* @param DocumentContentExtractorInterface $extractor A
|
||||
* DocumentContentExtractorInterface
|
||||
* instance.
|
||||
* @param UrlGeneratorInterface $generator A UrlGeneratorInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(
|
||||
DocumentContentExtractorInterface $extractor,
|
||||
UrlGeneratorInterface $generator
|
||||
) {
|
||||
parent::__construct($extractor);
|
||||
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of required document fields.
|
||||
*
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function requiredFields(FormatterOptions $options)
|
||||
{
|
||||
$fields = parent::requiredFields($options);
|
||||
$fields[] = FieldNameEnum::TITLE;
|
||||
$fields[] = FieldNameEnum::PERMALINK;
|
||||
$fields[] = FieldNameEnum::SOURCE_TITLE;
|
||||
$fields[] = FieldNameEnum::SOURCE_LINK;
|
||||
$fields[] = FieldNameEnum::PUBLISHED;
|
||||
|
||||
if ($options->isShowImages()) {
|
||||
$fields[] = FieldNameEnum::IMAGE_SRC;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize feed.
|
||||
*
|
||||
* @param AbstractFeed $feed A serialized feed entity
|
||||
* instance.
|
||||
* @param ArticleDocumentInterface[] $documents Array of fetched documents
|
||||
* which should by serialized.
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function serialize(
|
||||
AbstractFeed $feed,
|
||||
array $documents,
|
||||
FormatterOptions $options
|
||||
) {
|
||||
$node = new \SimpleXMLElement('<rss version="2.0"></rss>');
|
||||
|
||||
// Attach channel info.
|
||||
$channel = $node->addChild('channel');
|
||||
$channel->addChild('title', $feed->getName());
|
||||
$channel->addChild('link', $this->generator->generate('app_index_index', [], UrlGeneratorInterface::ABSOLUTE_URL));
|
||||
|
||||
foreach ($documents as $document) {
|
||||
$data = $document->getNormalizedData();
|
||||
|
||||
$item = $channel->addChild('item');
|
||||
$item
|
||||
->addChild('title', $data['title'])
|
||||
->addAttribute('url', $data['permalink']);
|
||||
if ($options->isShowImages()) {
|
||||
$item->addChild('image', $data['image']);
|
||||
}
|
||||
$item->addChild('description', $data['content']);
|
||||
$item
|
||||
->addChild('source', $data['source']['title'])
|
||||
->addAttribute('url', $data['source']['link']);
|
||||
$item->addChild('pubDate', $data['published']->format('d M Y H:i:s e'));
|
||||
}
|
||||
|
||||
return $node->asXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get format mime type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMime()
|
||||
{
|
||||
return 'text/xml';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Formatter\Strategy;
|
||||
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Feed\Formatter\FormatterOptions;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use IndexBundle\Model\ArticleDocumentInterface;
|
||||
|
||||
/**
|
||||
* Class TsvFeedFormatterStrategy
|
||||
*
|
||||
* @package CacheBundle\Feed\Formatter\Strategy
|
||||
*
|
||||
* @link http://www.iana.org/assignments/media-types/text/tab-separated-values
|
||||
*/
|
||||
class TsvFeedFormatterStrategy extends AbstractFeedFormatStrategy
|
||||
{
|
||||
|
||||
/**
|
||||
* TSV column titles.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private static $columns = [
|
||||
'Link',
|
||||
'Headline Text',
|
||||
'Source Name',
|
||||
'Source URL',
|
||||
'Harvest Time',
|
||||
'Extractor Author',
|
||||
'Content',
|
||||
];
|
||||
|
||||
/**
|
||||
* Return list of required document fields.
|
||||
*
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function requiredFields(FormatterOptions $options)
|
||||
{
|
||||
$fields = parent::requiredFields($options);
|
||||
$fields[] = FieldNameEnum::PERMALINK;
|
||||
$fields[] = FieldNameEnum::SOURCE_TITLE;
|
||||
$fields[] = FieldNameEnum::SOURCE_LINK;
|
||||
$fields[] = FieldNameEnum::PUBLISHED;
|
||||
$fields[] = FieldNameEnum::AUTHOR_NAME;
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize feed.
|
||||
*
|
||||
* @param AbstractFeed $feed A serialized feed entity
|
||||
* instance.
|
||||
* @param ArticleDocumentInterface[]|array $documents Array of fetched documents
|
||||
* which should by serialized.
|
||||
* @param FormatterOptions $options Formatter options.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function serialize(
|
||||
AbstractFeed $feed,
|
||||
array $documents,
|
||||
FormatterOptions $options
|
||||
) {
|
||||
$body = implode("\t", self::$columns) . PHP_EOL;
|
||||
|
||||
$processor = \nspl\f\compose(
|
||||
\nspl\f\partial('implode', "\t"),
|
||||
function (ArticleDocumentInterface $document) use ($options, $feed) {
|
||||
$data = $document->getNormalizedData();
|
||||
|
||||
$date = $data['published'];
|
||||
if (! $date instanceof \DateTimeInterface) {
|
||||
$date = date_create();
|
||||
}
|
||||
|
||||
return [
|
||||
$data['permalink'],
|
||||
$data['title'],
|
||||
$data['source']['title'],
|
||||
$data['source']['link'],
|
||||
$date->format('Y-m-d H:i:s'),
|
||||
$data['author']['name'],
|
||||
$this->extract($data['content'], $options, $feed),
|
||||
];
|
||||
}
|
||||
);
|
||||
$lines = \nspl\a\map($processor, $documents);
|
||||
|
||||
return $body . implode(PHP_EOL, $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get format mime type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMime()
|
||||
{
|
||||
//
|
||||
// TSV has own mime type: 'text/tab-separated-values' but response with
|
||||
// this mime got strange encoding so we use 'text/plain' instead.
|
||||
//
|
||||
return 'text/plain';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Response;
|
||||
|
||||
use AppBundle\Response\SearchResponseInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class FeedResponse
|
||||
* @package CacheBundle\Feed\Response
|
||||
*/
|
||||
class FeedResponse implements FeedResponseInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var SearchResponseInterface
|
||||
*/
|
||||
private $response;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $advancedFilters;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $meta;
|
||||
|
||||
/**
|
||||
* FeedResponse constructor.
|
||||
*
|
||||
* @param SearchResponseInterface $response A SearchResponseInterface
|
||||
* instance.
|
||||
* @param array $advancedFilters Advanced filters.
|
||||
* @param array $meta Response meta information.
|
||||
*/
|
||||
public function __construct(
|
||||
SearchResponseInterface $response,
|
||||
array $advancedFilters = [],
|
||||
array $meta = []
|
||||
) {
|
||||
$this->response = $response;
|
||||
$this->advancedFilters = $advancedFilters;
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response.
|
||||
*
|
||||
* @return SearchResponseInterface
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available advanced filters values.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdvancedFilters()
|
||||
{
|
||||
return $this->advancedFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response meta information.
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMeta(Request $request)
|
||||
{
|
||||
$currentAdvancedFilters = $request->request->get('advancedFilters', []);
|
||||
|
||||
//
|
||||
// Add currently selected advanced filters if they are provided.
|
||||
//
|
||||
if (count($currentAdvancedFilters)) {
|
||||
$this->meta['search']['advancedFilters'] = $currentAdvancedFilters;
|
||||
}
|
||||
|
||||
return $this->meta;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Feed\Response;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Interface FeedResponseInterface
|
||||
* @package CacheBundle\Feed\Response
|
||||
*/
|
||||
interface FeedResponseInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get response.
|
||||
*
|
||||
* @return \AppBundle\Response\SearchResponseInterface
|
||||
*/
|
||||
public function getResponse();
|
||||
|
||||
/**
|
||||
* Get response meta information.
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMeta(Request $request);
|
||||
|
||||
/**
|
||||
* Get available advanced filters values.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdvancedFilters();
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Form;
|
||||
|
||||
use AppBundle\Form\SearchRequest\AbstractSearchRequestType;
|
||||
use AppBundle\Form\Type\FiltersType;
|
||||
use CacheBundle\DTO\AnalyticDTO;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Form\Type\CurrentUserOwnedEntityType;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
/**
|
||||
* Class AnalyticType
|
||||
*
|
||||
* Transform http request into AnalyticDTO object.
|
||||
*
|
||||
* @package CacheBundle\Form
|
||||
*/
|
||||
class AnalyticType extends AbstractType implements DataMapperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var IndexInterface
|
||||
*/
|
||||
private $index;
|
||||
|
||||
/**
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
private $tokenStorage;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $rawFilters;
|
||||
|
||||
/**
|
||||
* AnalyticType constructor.
|
||||
*
|
||||
* @param IndexInterface $index A IndexInterface instance.
|
||||
* @param TokenStorageInterface $tokenStorage A TokenStorageInterface instance.
|
||||
*/
|
||||
public function __construct(
|
||||
IndexInterface $index,
|
||||
TokenStorageInterface $tokenStorage
|
||||
) {
|
||||
$this->index = $index;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('feeds', CurrentUserOwnedEntityType::class, [
|
||||
'class' => AbstractFeed::class,
|
||||
'multiple' => true,
|
||||
'description' => 'Array of current user feeds ids.',
|
||||
'constraints' => new NotBlank(),
|
||||
])
|
||||
->add('filters', FiltersType::class, [
|
||||
'filter_factory' => $this->index->getFilterFactory(),
|
||||
'description' => 'Search filters.',
|
||||
'empty_data' => [],
|
||||
'filters' => AbstractSearchRequestType::$filters,
|
||||
'required' => false,
|
||||
])
|
||||
->setDataMapper($this)
|
||||
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
|
||||
$data = $event->getData();
|
||||
|
||||
$this->rawFilters = [];
|
||||
if (isset($data['filters'])) {
|
||||
$this->rawFilters = $data['filters'];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AnalyticDTO::class,
|
||||
'empty_data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prefix of the template block name for this type.
|
||||
*
|
||||
* The block prefix defaults to the underscored short class name with
|
||||
* the "Type" suffix removed (e.g. "UserProfileType" => "user_profile").
|
||||
*
|
||||
* @return string The prefix of the template block name.
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps properties of some data to a list of forms.
|
||||
*
|
||||
* @param AnalyticDTO|null $data Structured data.
|
||||
* @param FormInterface[]|\RecursiveIteratorIterator $forms A list of
|
||||
* {@link FormInterface}
|
||||
* instances.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function mapDataToForms($data, $forms)
|
||||
{
|
||||
// Do nothing because it's not necessary method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the data of a list of forms into the properties of some data.
|
||||
*
|
||||
* @param FormInterface[]|\RecursiveIteratorIterator $forms A list of
|
||||
* {@link FormInterface}
|
||||
* instances.
|
||||
* @param AnalyticDTO|null $data Structured data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mapFormsToData($forms, &$data)
|
||||
{
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
$feeds = $forms['feeds']->getData();
|
||||
if ($feeds instanceof Collection) {
|
||||
$feeds = $feeds->toArray();
|
||||
}
|
||||
|
||||
$data = new AnalyticDTO(
|
||||
$feeds,
|
||||
\app\op\invokeIf($this->tokenStorage->getToken(), 'getUser'),
|
||||
$forms['filters']->getData(),
|
||||
$this->rawFilters
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Form;
|
||||
|
||||
use CacheBundle\Entity\Category;
|
||||
use CacheBundle\Form\Type\CurrentUserOwnedEntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class CategoryType
|
||||
* @package CacheBundle\Form
|
||||
*/
|
||||
class CategoryType extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('name')
|
||||
->add('parent', CurrentUserOwnedEntityType::class, [
|
||||
'class' => Category::class,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('data_class', Category::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Form;
|
||||
|
||||
use CacheBundle\Entity\Comment;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class CommentType
|
||||
* @package CacheBundle\Form
|
||||
*/
|
||||
class CommentType extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
|
||||
$builder
|
||||
->add('title', null, [ 'empty_data' => '' ])
|
||||
->add('content');
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('data_class', Comment::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Form;
|
||||
|
||||
use CacheBundle\Entity\Category;
|
||||
use CacheBundle\Entity\Document;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Entity\Feed\ClipFeed;
|
||||
use CacheBundle\Entity\Feed\QueryFeed;
|
||||
use CacheBundle\Form\Type\CurrentUserOwnedEntityType;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
/**
|
||||
* Class FeedInfoType
|
||||
*
|
||||
* @package CacheBundle\Form
|
||||
*/
|
||||
class FeedInfoType extends AbstractType implements DataMapperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$availableSubtypes = [
|
||||
ClipFeed::getSubType(),
|
||||
QueryFeed::getSubType(),
|
||||
];
|
||||
|
||||
$builder
|
||||
->add('subType', ChoiceType::class, [
|
||||
'choices' => $availableSubtypes,
|
||||
'invalid_message' => sprintf(
|
||||
'Unknown feed sub type. Available: %s',
|
||||
implode(', ', $availableSubtypes)
|
||||
),
|
||||
])
|
||||
->add('excludedDocuments', EntityType::class, [
|
||||
'class' => Document::class,
|
||||
'multiple' => true,
|
||||
])
|
||||
->add('name', null, [
|
||||
'constraints' => new NotBlank(),
|
||||
])
|
||||
->add('category', CurrentUserOwnedEntityType::class, [
|
||||
'class' => Category::class,
|
||||
])
|
||||
->setDataMapper($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AbstractFeed::class,
|
||||
'empty_data' => null,
|
||||
'validation_groups' => [ 'Feed_Create' ],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps properties of some data to a list of forms.
|
||||
*
|
||||
* @param AbstractFeed|null $data Structured data.
|
||||
* @param FormInterface[]|\RecursiveIteratorIterator $forms A list of
|
||||
* {@link FormInterface}
|
||||
* instances.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mapDataToForms($data, $forms)
|
||||
{
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
if ($data instanceof AbstractFeed) {
|
||||
$forms['name']->setData($data->getName());
|
||||
$forms['category']->setData($data->getCategory());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the data of a list of forms into the properties of some data.
|
||||
*
|
||||
* @param FormInterface[]|\RecursiveIteratorIterator $forms A list of
|
||||
* {@link FormInterface}
|
||||
* instances.
|
||||
* @param AbstractFeed|null $data Structured data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mapFormsToData($forms, &$data)
|
||||
{
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
//
|
||||
// Create new proper feed instance if it's not provided to us.
|
||||
//
|
||||
if (! $data instanceof AbstractFeed) {
|
||||
try {
|
||||
$data = AbstractFeed::createBySubType($forms['subType']->getData());
|
||||
} catch (\Exception $exception) {
|
||||
// This should be handled by constraints.
|
||||
}
|
||||
}
|
||||
|
||||
$data
|
||||
->setName($forms['name']->getData())
|
||||
->setCategory($forms['category']->getData());
|
||||
|
||||
$excludedDocuments = $forms['excludedDocuments']->getData();
|
||||
foreach ($excludedDocuments as $document) {
|
||||
$data->addExcludedDocument($document);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Form\Sources;
|
||||
|
||||
use AppBundle\Form\Transformer\OnlyReverseTransformerTrait;
|
||||
use CacheBundle\Form\Sources\Type\SortType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class SourceListSearchType
|
||||
* @package CacheBundle\Form\Sources
|
||||
*/
|
||||
class SourceListSearchType extends AbstractType implements DataTransformerInterface
|
||||
{
|
||||
|
||||
use OnlyReverseTransformerTrait;
|
||||
|
||||
public static $fields = [
|
||||
'name' => 'name',
|
||||
'sources' => 'sourceNumber',
|
||||
'createdBy' => 'user',
|
||||
'lastUpdated' => 'updatedAt',
|
||||
'lastUpdatedBy' => 'updatedBy',
|
||||
];
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('page', null, [
|
||||
'description' => 'Requested page number, should start from 1. Default value 1.',
|
||||
'empty_data' => 1,
|
||||
])
|
||||
->add('limit', null, [
|
||||
'description' => 'Max sources per page. Default 20.',
|
||||
'empty_data' => 20,
|
||||
])
|
||||
->add('sort', SortType::class, [
|
||||
'fields' => self::$fields,
|
||||
'default_field' => 'name',
|
||||
'default_direction' => 'asc',
|
||||
])
|
||||
->add('onlyShared', CheckboxType::class, [
|
||||
'description' => 'Show only shared source lists.',
|
||||
'required' => false,
|
||||
])
|
||||
->addModelTransformer($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('key', 'searchSourceList');
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a value from the transformed representation to its original
|
||||
* representation.
|
||||
*
|
||||
* This method is called when {@link Form::submit()} is called to transform
|
||||
* the requests tainted data into an acceptable format for your data
|
||||
* processing/model layer.
|
||||
*
|
||||
* This method must be able to deal with empty values. Usually this will
|
||||
* be an empty string, but depending on your implementation other empty
|
||||
* values are possible as well (such as NULL). The reasoning behind
|
||||
* this is that value transformers must be chainable. If the
|
||||
* reverseTransform() method of the first value transformer outputs an
|
||||
* empty string, the second value transformer must be able to process that
|
||||
* value.
|
||||
*
|
||||
* By convention, reverseTransform() should return NULL if an empty string
|
||||
* is passed.
|
||||
*
|
||||
* @param mixed $data The value in the transformed representation.
|
||||
*
|
||||
* @return mixed The value in the original representation
|
||||
*
|
||||
* @throws TransformationFailedException When the transformation fails.
|
||||
*/
|
||||
public function reverseTransform($data)
|
||||
{
|
||||
if (count($data['sort']) === 0) {
|
||||
$data['sort'] = [ 'name' => 'asc' ];
|
||||
}
|
||||
|
||||
if (! isset($data['onlyShared'])) {
|
||||
$data['onlyShared'] = false;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Form\Sources;
|
||||
|
||||
use CacheBundle\Entity\SourceList;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class SourceListType
|
||||
* @package CacheBundle\Form\Sources
|
||||
*/
|
||||
class SourceListType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('data_class', SourceList::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Form\Sources;
|
||||
|
||||
use AppBundle\AdvancedFilters\AdvancedFiltersConfig;
|
||||
use AppBundle\Form\AbstractConnectionAwareType;
|
||||
use AppBundle\Form\Transformer\OnlyReverseTransformer;
|
||||
use AppBundle\Form\Type\AdvancedFiltersType;
|
||||
use AppBundle\Form\Type\Filter as QueryFilter;
|
||||
use AppBundle\Form\Type\FiltersType;
|
||||
use CacheBundle\Form\Sources\Type\SortType;
|
||||
use Common\Enum\AFSourceEnum;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
|
||||
/**
|
||||
* Class SourceSearchType
|
||||
* @package CacheBundle\Form\Sources
|
||||
*/
|
||||
class SourceSearchType extends AbstractConnectionAwareType implements DataMapperInterface
|
||||
{
|
||||
|
||||
public static $fields = [
|
||||
'name' => FieldNameEnum::SOURCE_TITLE,
|
||||
'mediaType' => FieldNameEnum::SOURCE_PUBLISHER_TYPE,
|
||||
'country' => FieldNameEnum::COUNTRY,
|
||||
];
|
||||
|
||||
private static $filters = [
|
||||
'publisher' => [
|
||||
'type' => QueryFilter\PublisherFilterType::class,
|
||||
'description' => 'Filter by publisher type.',
|
||||
],
|
||||
'language' => [
|
||||
'type' => QueryFilter\LanguageFilterType::class,
|
||||
'description' => 'Filter by language, use ISO 639-1 two-letters codes.',
|
||||
],
|
||||
'country' => [
|
||||
'type' => QueryFilter\CountryFilterType::class,
|
||||
'description' => 'Filter by countries, ISO 3166-1 Alpha-2 two-letters codes.',
|
||||
],
|
||||
'state' => [
|
||||
'type' => QueryFilter\StateFilterType::class,
|
||||
'description' => 'Filter by US states, ANSI standard INCITS 38:2009 two-letters codes.',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
|
||||
/**
|
||||
* Custom data mapping
|
||||
*
|
||||
* @param FormEvent $event A FormEvent instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
$postSubmit = function (FormEvent $event) {
|
||||
/** @var SearchRequestBuilderInterface $builder */
|
||||
$builder = $event->getData();
|
||||
$builder->setSorts($event->getForm()->get('sort')->getData());
|
||||
$event->setData($builder);
|
||||
};
|
||||
|
||||
$builder
|
||||
->add('query', null, [
|
||||
'description' => 'Search query, maybe empty. Search by source title and url.',
|
||||
'empty_data' => '',
|
||||
'constraints' => new Length([
|
||||
'max' => 40,
|
||||
'maxMessage' => 'Search query is too long. Should be 40 characters long or less.',
|
||||
]),
|
||||
])
|
||||
->add('page', null, [
|
||||
'description' => 'Requested page number, should start from 1. Default value 1.',
|
||||
'empty_data' => 1,
|
||||
])
|
||||
->add('limit', null, [
|
||||
'description' => 'Max sources per page. Default 20.',
|
||||
'empty_data' => 20,
|
||||
])
|
||||
->add('filters', FiltersType::class, [
|
||||
'filter_factory' => $this->index->getFilterFactory(),
|
||||
'description' => 'Search filters.',
|
||||
'empty_data' => [],
|
||||
'filters' => self::$filters,
|
||||
])
|
||||
->add('sort', SortType::class, [
|
||||
'fields' => self::$fields,
|
||||
'default_field' => 'name',
|
||||
'default_direction' => 'asc',
|
||||
'mapped' => false,
|
||||
])
|
||||
->add('advancedFilters', AdvancedFiltersType::class, [
|
||||
'description' => 'Advanced filters.',
|
||||
'config' => AdvancedFiltersConfig::getConfig(AFSourceEnum::SOURCE),
|
||||
'empty_data' => [],
|
||||
'connection' => $this->index,
|
||||
'required' => false,
|
||||
])
|
||||
->addEventListener(FormEvents::POST_SUBMIT, $postSubmit)
|
||||
->setEmptyData($this->index->createRequestBuilder())
|
||||
->setDataMapper($this);
|
||||
|
||||
$builder
|
||||
->get('sort')
|
||||
->addModelTransformer(new OnlyReverseTransformer(function (array $data) {
|
||||
if (count($data) === 0) {
|
||||
// Default sort order.
|
||||
$data = [ FieldNameEnum::SOURCE_TITLE => 'asc' ];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
$resolver->setDefault('key', 'searchSource');
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps properties of some data to a list of forms.
|
||||
*
|
||||
* @param mixed $data Structured data.
|
||||
* @param FormInterface[]|\RecursiveIteratorIterator $forms A list of
|
||||
* {@link FormInterface}
|
||||
* instances.
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function mapDataToForms($data, $forms)
|
||||
{
|
||||
// Do nothing because it's senseless.
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the data of a list of forms into the properties of some data.
|
||||
*
|
||||
* @param FormInterface[]|\RecursiveIteratorIterator $forms A list of
|
||||
* {@link FormInterface}
|
||||
* instances.
|
||||
* @param mixed|SearchRequestBuilderInterface $data Structured data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mapFormsToData($forms, &$data)
|
||||
{
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
$data
|
||||
->setQuery($forms['query']->getData())
|
||||
->setPage($forms['page']->getData())
|
||||
->setLimit($forms['limit']->getData())
|
||||
->setFilters(array_merge(
|
||||
$forms['filters']->getData(),
|
||||
$forms['advancedFilters']->getData()
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Form\Sources\Type;
|
||||
|
||||
use AppBundle\Form\Transformer\OnlyReverseTransformer;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class SortType
|
||||
* @package CacheBundle\Form\Sources\Type
|
||||
*/
|
||||
class SortType extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$fields = $options['fields'];
|
||||
|
||||
$builder
|
||||
->add('field', ChoiceType::class, [
|
||||
'description' => 'Field name on which we should sort.',
|
||||
'choices' => array_keys($options['fields']),
|
||||
'empty_data' => $options['default_field'],
|
||||
])
|
||||
->add('direction', ChoiceType::class, [
|
||||
'description' => 'Sorting direction.',
|
||||
'choices' => [ 'asc', 'desc' ],
|
||||
'empty_data' => $options['default_direction'],
|
||||
]);
|
||||
|
||||
$transformer = new OnlyReverseTransformer(function (array $sortObject) use ($fields) {
|
||||
if (! is_array($sortObject)) {
|
||||
throw new TransformationFailedException('Expect array got '. gettype($sortObject));
|
||||
}
|
||||
|
||||
if (! isset($sortObject['field'], $sortObject['direction'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [ $fields[$sortObject['field']] => $sortObject['direction'] ];
|
||||
});
|
||||
|
||||
$builder->addModelTransformer($transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setRequired('fields')
|
||||
->setDefined('default_field')
|
||||
->setDefined('default_direction')
|
||||
->setAllowedTypes('fields', 'array');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Form\Type;
|
||||
|
||||
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class CurrentUserCategoryType
|
||||
*
|
||||
* Extend default EntityType with proper query builder which is returns categories
|
||||
* which is owned by current user.
|
||||
*
|
||||
* @package CacheBundle\Form\Type
|
||||
*/
|
||||
class CurrentUserOwnedEntityType extends EntityType
|
||||
{
|
||||
|
||||
/**
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
private $storage;
|
||||
|
||||
/**
|
||||
* CurrentUserOwnedEntityType constructor.
|
||||
*
|
||||
* @param ManagerRegistry $managerRegistry A ManagerRegistry instance.
|
||||
* @param TokenStorageInterface $storage A TokenStorageInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(
|
||||
ManagerRegistry $managerRegistry,
|
||||
TokenStorageInterface $storage
|
||||
) {
|
||||
parent::__construct($managerRegistry);
|
||||
$this->storage = $storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setRequired('user_property')
|
||||
->setAllowedTypes('user_property', 'string')
|
||||
->setDefaults([
|
||||
'user_property' => 'user',
|
||||
'query_builder' => function (Options $options) {
|
||||
$userProperty = $options['user_property'];
|
||||
|
||||
return function (EntityRepository $repository) use ($userProperty) {
|
||||
// Get current user.
|
||||
$user = \app\op\invokeIf($this->storage->getToken(), 'getUser');
|
||||
|
||||
if ($user instanceof User) {
|
||||
$user = $user->getId();
|
||||
}
|
||||
|
||||
$qb = $repository->createQueryBuilder('Entity');
|
||||
|
||||
if ($user instanceof User) {
|
||||
$qb
|
||||
->where("Entity.{$userProperty} = :user")
|
||||
->setParameter('user', $user);
|
||||
}
|
||||
|
||||
return $qb;
|
||||
};
|
||||
},
|
||||
]);
|
||||
parent::configureOptions($resolver);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
use CacheBundle\Entity\Analytic\Analytic;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* Class AnalyticRepository
|
||||
* @package CacheBundle\Repository
|
||||
*/
|
||||
class AnalyticRepository extends EntityRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* Get array of specified user analytics.
|
||||
*
|
||||
* @param integer $user A User entity id.
|
||||
*
|
||||
* @return Analytic[]
|
||||
*/
|
||||
public function getList($user)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
return $this->createQueryBuilder('Analytic')
|
||||
->select(
|
||||
'partial Analytic.{id,createdAt,updatedAt}',
|
||||
'partial context.{hash,filters,rawFilters}'
|
||||
)
|
||||
->leftJoin('Analytic.context', 'context')
|
||||
->where($expr->eq('Analytic.owner', ':user'))
|
||||
->setParameter('user', $user)
|
||||
->addOrderBy('Analytic.id', 'desc')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
use CacheBundle\Entity\Category;
|
||||
use Doctrine\DBAL\Driver\Connection;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* Class CategoryRepository
|
||||
* @package CacheBundle\Repository
|
||||
*/
|
||||
class CategoryRepository extends EntityRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* Get single entity from repository.
|
||||
*
|
||||
* @param integer $id A Entity instance id.
|
||||
* @param string $method A CRUD method name.
|
||||
*
|
||||
* @return Category|null
|
||||
*/
|
||||
public function getOne($id, $method)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
$condition = $expr->andX($expr->eq('Category.id', ':id'));
|
||||
$parameters = [ 'id' => $id ];
|
||||
|
||||
if ($method !== 'get') {
|
||||
$condition->add($expr->eq('Category.internal', false));
|
||||
}
|
||||
|
||||
return $this->createQueryBuilder('Category')
|
||||
->addSelect('partial Query.{id, raw, status}')
|
||||
->where($condition)
|
||||
->setParameters($parameters)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute feed count in specified category and all childes categories.
|
||||
*
|
||||
* @param integer $id A Category entity id.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function computeFeedCounts($id)
|
||||
{
|
||||
return (int) $this->_em->getConnection()->fetchColumn("
|
||||
SELECT COUNT(feeds.id)
|
||||
FROM feeds
|
||||
WHERE
|
||||
category_id in (
|
||||
SELECT id
|
||||
FROM (
|
||||
SELECT *
|
||||
FROM categories
|
||||
ORDER BY parent_id, id
|
||||
) categories_sorted,
|
||||
(SELECT @pv := '{$id}') initialisation
|
||||
WHERE
|
||||
(
|
||||
FIND_IN_SET(parent_id, @pv) > 0
|
||||
OR id = {$id}
|
||||
)
|
||||
AND @pv := CONCAT(@pv, ',', id)
|
||||
)
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Export all feeds inside this category.
|
||||
*
|
||||
* @param integer $category A Category entity id.
|
||||
* @param boolean $export Export all feeds if true and unexport otherwise.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function exportFeedsIn($category, $export = true)
|
||||
{
|
||||
$this->_em->getConnection()->transactional(function (Connection $conn) use ($category, $export) {
|
||||
$conn->exec(sprintf("
|
||||
UPDATE feeds
|
||||
SET exported = %d
|
||||
WHERE
|
||||
category_id in (
|
||||
SELECT id
|
||||
FROM (
|
||||
SELECT *
|
||||
FROM categories
|
||||
ORDER BY parent_id, id
|
||||
) categories_sorted,
|
||||
(SELECT @pv := '%s') initialisation
|
||||
WHERE
|
||||
(
|
||||
FIND_IN_SET(parent_id, @pv) > 0
|
||||
OR id = %s
|
||||
)
|
||||
AND @pv := CONCAT(@pv, ',', id)
|
||||
)
|
||||
", $export, $category, $category));
|
||||
$conn->exec(sprintf("
|
||||
UPDATE categories
|
||||
SET exported = %d
|
||||
WHERE
|
||||
id in (
|
||||
SELECT id
|
||||
FROM (
|
||||
SELECT *
|
||||
FROM categories
|
||||
ORDER BY parent_id, id
|
||||
) categories_sorted,
|
||||
(SELECT @pv := '%s') initialisation
|
||||
WHERE
|
||||
(
|
||||
FIND_IN_SET(parent_id, @pv) > 0
|
||||
OR id = %s
|
||||
)
|
||||
AND @pv := CONCAT(@pv, ',', id)
|
||||
)
|
||||
", $export, $category, $category));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active category.
|
||||
*
|
||||
* @param integer $id A Category entity id.
|
||||
* @param integer $user Filter categories by specified owner if set.
|
||||
* @param string|array $type Filter by category types.
|
||||
*
|
||||
* @return Category|null
|
||||
*/
|
||||
public function get($id, $user = null, $type = null)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
$condition = $expr->andX($expr->eq('Category.id', ':id'));
|
||||
$parameters = [ 'id' => $id ];
|
||||
|
||||
if ($type) {
|
||||
$type = (array) $type;
|
||||
$condition->add($expr->in('Category.type', (array) $type));
|
||||
}
|
||||
|
||||
if ($user !== null) {
|
||||
$condition->add($expr->eq('Category.user', ':user'));
|
||||
$parameters['user'] = $user;
|
||||
}
|
||||
|
||||
return $this->createQueryBuilder('Category')
|
||||
->where($condition)
|
||||
->setParameters($parameters)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of specified user categories.
|
||||
*
|
||||
* @param integer $user A User entity id.
|
||||
*
|
||||
* @return Category[]
|
||||
*/
|
||||
public function getList($user)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
return $this->createQueryBuilder('Category')
|
||||
->select(
|
||||
'partial Category.{id, name, type}',
|
||||
'partial Child.{id, name, type}',
|
||||
'Feed'
|
||||
)
|
||||
->leftJoin('Category.parent', 'Parent')
|
||||
->leftJoin('Category.childes', 'Child')
|
||||
->leftJoin('Category.feeds', 'Feed')
|
||||
->where($expr->andX(
|
||||
$expr->eq('Category.user', ':user'),
|
||||
$expr->isNull('Category.parent')
|
||||
))
|
||||
->setParameter('user', $user)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that specified 'child' category is child of 'parent'.
|
||||
*
|
||||
* @param integer $child A Category entity id which may by child.
|
||||
* @param integer $parent A Category entity id which must by parent of
|
||||
* specified child.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isChildOf($child, $parent)
|
||||
{
|
||||
//
|
||||
// TODO: May be exists more efficient way to do it.
|
||||
//
|
||||
$position = (int) $this->_em
|
||||
->getConnection()
|
||||
->fetchColumn("
|
||||
SELECT
|
||||
FIND_IN_SET({$child}, lvl) AS result
|
||||
FROM (
|
||||
SELECT
|
||||
GROUP_CONCAT(lvl SEPARATOR ',') AS lvl
|
||||
FROM (
|
||||
SELECT @parent := (SELECT GROUP_CONCAT(id SEPARATOR ',')
|
||||
FROM categories
|
||||
WHERE FIND_IN_SET(parent_id, @parent)
|
||||
) AS lvl FROM categories JOIN (SELECT @parent := {$parent}) tmp ) a ) b;
|
||||
");
|
||||
|
||||
return $position !== 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
use CacheBundle\Entity\Category;
|
||||
use CacheBundle\Entity\Feed\ClipFeed;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* ClipFeedRepository
|
||||
*
|
||||
* This class was generated by the Doctrine ORM. Add your own custom
|
||||
* repository methods below.
|
||||
*/
|
||||
class ClipFeedRepository extends EntityRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* Get read later clip feed.
|
||||
*
|
||||
* @param integer $user Owner of read later feed.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Feed\AbstractFeed|null
|
||||
*/
|
||||
public function getReadLater($user)
|
||||
{
|
||||
return $this->createQueryBuilder('Feed')
|
||||
->where('Feed.user = :user AND Feed.name = :name')
|
||||
->setParameters([
|
||||
'user' => $user,
|
||||
'name' => ClipFeed::READ_LATER,
|
||||
])
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create read later feed.
|
||||
*
|
||||
* @param integer $user Owner of read later feed.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Feed\AbstractFeed
|
||||
*/
|
||||
public function createReadLater($user)
|
||||
{
|
||||
$mainCategory = $this->_em->createQueryBuilder()
|
||||
->select('partial Category.{id}')
|
||||
->from(Category::class, 'Category')
|
||||
->where('Category.user = :user AND Category.type = :type')
|
||||
->setParameters([
|
||||
'user' => $user,
|
||||
'type' => Category::TYPE_MY_CONTENT,
|
||||
])
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
|
||||
$feed = ClipFeed::create()
|
||||
->setName(ClipFeed::READ_LATER)
|
||||
->setUser($this->_em->getReference(User::class, $user))
|
||||
->setCategory($mainCategory);
|
||||
|
||||
$this->_em->persist($feed);
|
||||
$this->_em->flush($feed);
|
||||
|
||||
return $feed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* Class CommentRepository
|
||||
* @package CacheBundle\Repository
|
||||
*/
|
||||
class CommentRepository extends EntityRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* Get list of comments for documents.
|
||||
*
|
||||
* @param integer $document A Document entity id.
|
||||
* @param array $fields Array of comment fields. Fetch only specified
|
||||
* fields if set.
|
||||
* @param integer $count Number of comments.
|
||||
*
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
public function getListForDocument($document, array $fields = [], $count = null)
|
||||
{
|
||||
$qb = $this->createQueryBuilder('Comment')
|
||||
->where('Comment.document = :document')
|
||||
->addOrderBy('Comment.createdAt', 'desc')
|
||||
->setParameter('document', $document);
|
||||
|
||||
if (count($fields) > 0) {
|
||||
$authorFields = [];
|
||||
if (isset($fields['author'])) {
|
||||
$authorFields = $fields['author'];
|
||||
unset($fields['author']);
|
||||
}
|
||||
|
||||
$qb->select('partial Comment.{id, '. implode(',', $fields) .'}');
|
||||
if (count($authorFields) > 0) {
|
||||
$qb
|
||||
->join('Comment.author', 'Author')
|
||||
->addSelect('partial Author.{id, '. implode(',', $authorFields) .'}');
|
||||
}
|
||||
} else {
|
||||
$qb
|
||||
->join('Comment.author', 'Author')
|
||||
->addSelect('Author');
|
||||
}
|
||||
|
||||
if ($count !== null) {
|
||||
$qb->setMaxResults($count);
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $documentId A Document entity id.
|
||||
* @param integer $poolSize Max new comments for specified document.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateCommentMarks($documentId, $poolSize)
|
||||
{
|
||||
$this->_em->getConnection()->executeUpdate(sprintf('
|
||||
UPDATE comments
|
||||
SET new = 0
|
||||
WHERE id IN (
|
||||
SELECT id
|
||||
FROM (
|
||||
SELECT id
|
||||
FROM comments
|
||||
WHERE document_id = :document AND new = 1
|
||||
ORDER BY created_at DESC LIMIT %d, %d
|
||||
) AS
|
||||
u)
|
||||
', $poolSize, 1000), [ 'document' => $documentId ]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* Class CommonFeedRepository
|
||||
* @package CacheBundle\Repository
|
||||
*/
|
||||
class CommonFeedRepository extends EntityRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* Get single feed from repository.
|
||||
*
|
||||
* @param integer $id A Feed entity instance id.
|
||||
* @param integer $user Filter feeds by specified owner if set.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Feed\AbstractFeed|null
|
||||
*/
|
||||
public function getOne($id, $user)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
$condition = $expr->andX($expr->eq('Feed.id', ':id'));
|
||||
$parameters = [ 'id' => $id ];
|
||||
|
||||
if ($user !== null) {
|
||||
$condition->add($expr->eq('Feed.user', ':user'));
|
||||
$parameters['user'] = $user;
|
||||
}
|
||||
|
||||
return $this->createQueryBuilder('Feed')
|
||||
->where($condition)
|
||||
->setParameters($parameters)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
use CacheBundle\Entity\Document;
|
||||
use Common\Enum\CollectionTypeEnum;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
|
||||
/**
|
||||
* Class DocumentRepository
|
||||
* @package CacheBundle\Repository
|
||||
*/
|
||||
class DocumentRepository extends EntityRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* Get document for specified query.
|
||||
*
|
||||
* @param integer $query A Query entity id.
|
||||
* @param integer $page Request page number.
|
||||
*
|
||||
* @return Document[]
|
||||
*/
|
||||
public function getForQuery($query, $page)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
return $this->createQueryBuilder('Document')
|
||||
->addSelect('Comment, CommentAuthor')
|
||||
->join('Document.pages', 'Page')
|
||||
->leftJoin('Document.comments', 'Comment', Join::WITH, $expr->andX(
|
||||
$expr->eq('Comment.document', 'Document.id'),
|
||||
$expr->eq('Comment.new', 1)
|
||||
))
|
||||
->leftJoin('Comment.author', 'CommentAuthor')
|
||||
->where($expr->andX(
|
||||
$expr->eq('Page.number', ':number'),
|
||||
$expr->eq('Page.query', ':query')
|
||||
))
|
||||
->setParameters([
|
||||
'number' => $page,
|
||||
'query' => $query,
|
||||
])
|
||||
->addOrderBy('Comment.createdAt', 'desc')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by given ids for specified query without related pages.
|
||||
* Result will be ordered depends on specified ids order.
|
||||
*
|
||||
* @param integer $collectionId A document collection entity id.
|
||||
* @param string $collectionType A document collection type.
|
||||
* @param array $ids Array of document ids.
|
||||
* @param string[]|array $fields Array of required document fields. Fetch
|
||||
* all if empty.
|
||||
*
|
||||
* @return Document[]
|
||||
*/
|
||||
public function getFromCollectionByIds($collectionId, $collectionType, array $ids, array $fields = [])
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
$condition = $expr->andX($expr->in('Document.id', $ids));
|
||||
switch ($collectionType) {
|
||||
case CollectionTypeEnum::FEED:
|
||||
$condition->add($expr->eq('Page.clipFeed', ':collectionId'));
|
||||
break;
|
||||
|
||||
case CollectionTypeEnum::QUERY:
|
||||
$condition->add($expr->eq('Page.query', ':collectionId'));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid collection type.');
|
||||
}
|
||||
|
||||
$select = 'Document';
|
||||
if (count($fields) > 0) {
|
||||
$select = 'partial Document.{'. implode(',', $fields). '}';
|
||||
}
|
||||
|
||||
$results = $this->createQueryBuilder('Document')
|
||||
->select($select)
|
||||
->addSelect('Comment, CommentAuthor, Page')
|
||||
->join('Document.pages', 'Page')
|
||||
->leftJoin('Document.comments', 'Comment', Join::WITH, $expr->andX(
|
||||
$expr->eq('Comment.document', 'Document.id'),
|
||||
$expr->eq('Comment.new', 1)
|
||||
))
|
||||
->leftJoin('Comment.author', 'CommentAuthor')
|
||||
->where($condition)
|
||||
->setParameter('collectionId', $collectionId)
|
||||
//
|
||||
// We should clearly say how we want to order because mysql does not
|
||||
// guarantee what result will be ordered by primary key.
|
||||
//
|
||||
->addOrderBy('Document.id', 'asc')
|
||||
->addOrderBy('Comment.createdAt', 'desc')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
//
|
||||
// Now we use specified ids as index for fetched documents. Since we got
|
||||
// ordered result we may use binary search for fetching proper document
|
||||
// by id.
|
||||
//
|
||||
|
||||
$orderedResult = [];
|
||||
foreach ($ids as $id) {
|
||||
$idx = \app\a\binarySearch($results, $id, \nspl\op\methodCaller('getId'));
|
||||
if ($idx === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$orderedResult[] = $results[$idx];
|
||||
}
|
||||
|
||||
return $orderedResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by given ids for specified query.
|
||||
*
|
||||
* @param array $ids Array of document ids.
|
||||
*
|
||||
* @return Document[]
|
||||
*/
|
||||
public function getByIds(array $ids)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
return $this->createQueryBuilder('Document')
|
||||
->addSelect('Comment, CommentAuthor')
|
||||
->where($expr->in('Document.id', $ids))
|
||||
->leftJoin('Document.comments', 'Comment', Join::WITH, $expr->andX(
|
||||
$expr->eq('Comment.document', 'Document.id'),
|
||||
$expr->eq('Comment.new', 1)
|
||||
))
|
||||
->leftJoin('Comment.author', 'CommentAuthor')
|
||||
->addOrderBy('Comment.createdAt', 'desc')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields Array of required fields names, `id` return
|
||||
* always.
|
||||
* @param array $ids Array of document ids.
|
||||
*
|
||||
* @return Document[]
|
||||
*/
|
||||
public function getWithFieldsByIds(array $fields, array $ids)
|
||||
{
|
||||
return $this->createQueryBuilder('Document')
|
||||
->select('partial Document.{id, '.implode(',', $fields).'}')
|
||||
->where('Document.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the documents exists.
|
||||
*
|
||||
* @param string[] $checkedIds Array of document ids.
|
||||
*
|
||||
* @return string[] Array of not exists ids from passed.
|
||||
*/
|
||||
public function checkIds(array $checkedIds)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
$existsIds = $this->createQueryBuilder('Document')
|
||||
->select('Document.id')
|
||||
->where($expr->in('Document.id', array_map(\nspl\op\str, $checkedIds)))
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
return array_diff($checkedIds, \nspl\a\flatten($existsIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove from provided ids whose document id which already exists in specified document collection.
|
||||
*
|
||||
* @param string $collectionId A DocumentCollectionInterface entity id.
|
||||
* @param string $collectionType A DocumentCollectionInterface type.
|
||||
* @param array $ids Array of document ids.
|
||||
*
|
||||
* @return string[] Array of documents which is not exists in specified document collection.
|
||||
*/
|
||||
public function sanitizeIds($collectionId, $collectionType, array $ids)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
$condition = $expr->andX($expr->in('Document.id', array_map(\nspl\op\str, $ids)));
|
||||
switch ($collectionType) {
|
||||
case CollectionTypeEnum::FEED:
|
||||
$condition->add($expr->eq('Page.clipFeed', ':collectionId'));
|
||||
break;
|
||||
|
||||
case CollectionTypeEnum::QUERY:
|
||||
$condition->add($expr->eq('Page.query', ':collectionId'));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid collection type.');
|
||||
}
|
||||
|
||||
$existsIds = $this->createQueryBuilder('Document')
|
||||
->select('Document.id')
|
||||
->join('Document.pages', 'Page')
|
||||
->where($condition)
|
||||
->setParameter('collectionId', $collectionId)
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
return array_diff($ids, \nspl\a\flatten($existsIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $queryId
|
||||
* @return array
|
||||
*/
|
||||
public function getByQuery(array $queryId)
|
||||
{
|
||||
return $this->createQueryBuilder('Document')
|
||||
->select('Document.data','Query.id')
|
||||
->join('Document.pages', 'Page')
|
||||
->join('Page.query', 'Query')
|
||||
->where('Query.id IN (:queryId)')
|
||||
->setParameter('queryId', $queryId)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $clipFeedId
|
||||
* @return array
|
||||
*/
|
||||
public function getByClip(array $clipFeedId)
|
||||
{
|
||||
return $this->createQueryBuilder('Document')
|
||||
->select('Document.data','IDENTITY(Page.clipFeed) as clipFeedId ')
|
||||
->join('Document.pages', 'Page')
|
||||
->where('Page.clipFeed IN (:clipFeedId)')
|
||||
->setParameter('clipFeedId', $clipFeedId)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
use CacheBundle\Entity\Query\StoredQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* QueryFeedRepository
|
||||
*
|
||||
* This class was generated by the Doctrine ORM. Add your own custom
|
||||
* repository methods below.
|
||||
*/
|
||||
class QueryFeedRepository extends EntityRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* Get single query feed from repository.
|
||||
*
|
||||
* @param integer $id A Feed entity instance id.
|
||||
* @param integer $user Filter feeds by specified owner if set.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Feed\AbstractFeed|null
|
||||
*/
|
||||
public function getOne($id, $user)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
$condition = $expr->andX($expr->eq('Feed.id', ':id'));
|
||||
$parameters = [ 'id' => $id ];
|
||||
|
||||
if ($user !== null) {
|
||||
$condition->add($expr->eq('Feed.user', ':user'));
|
||||
$parameters['user'] = $user;
|
||||
}
|
||||
|
||||
return $this->createQueryBuilder('Feed')
|
||||
->where($condition)
|
||||
->setParameters($parameters)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $user A User entity id.
|
||||
*
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
public function getFeedsForFormQb($user)
|
||||
{
|
||||
return $this->createQueryBuilder('ch')
|
||||
->where('ch.userId = :userId')
|
||||
->setParameter(':userId', $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $query A stored query instance.
|
||||
*
|
||||
* @return StoredQuery[]
|
||||
*/
|
||||
public function getWithExcludedDocumentsForQuery($query)
|
||||
{
|
||||
return $this->createQueryBuilder('Feed')
|
||||
->addSelect('partial Document.{id}')
|
||||
->innerJoin('Feed.excludedDocuments', 'Document')
|
||||
->where('Feed.query = :query')
|
||||
->setParameter('query', $query)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
/**
|
||||
* Interface QueryRepositoryInterface
|
||||
* @package CacheBundle\Repository
|
||||
*/
|
||||
interface QueryRepositoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get query entity by internal representation of search query.
|
||||
*
|
||||
* @param string $hash A search query hash.
|
||||
* @param integer $user A User entity id, who made search request.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Query\AbstractQuery|null
|
||||
*/
|
||||
public function get($hash, $user = null);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* Class SimpleQueryRepository
|
||||
* @package CacheBundle\Repository
|
||||
*/
|
||||
class SimpleQueryRepository extends EntityRepository implements
|
||||
QueryRepositoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get query entity by internal representation of search query.
|
||||
*
|
||||
* @param string $hash A search query hash.
|
||||
* @param integer $user A User entity id, who made search request.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Query\SimpleQuery|null
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function get($hash, $user = null)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
return $this->createQueryBuilder('Query')
|
||||
->select('partial Query.{id, totalCount, expirationDate, raw, rawFilters, rawAdvancedFilters}')
|
||||
->where($expr->andX(
|
||||
$expr->eq('Query.hash', ':hash'),
|
||||
$expr->gt('Query.expirationDate', ':date')
|
||||
))
|
||||
->setParameters([
|
||||
'hash' => $hash,
|
||||
'date' => date_create(),
|
||||
])
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all old queries.
|
||||
*
|
||||
* @return integer[] Old queries ids.
|
||||
*/
|
||||
public function getOld()
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
// Get all old queries ids.
|
||||
$ids = $this->createQueryBuilder('Query')
|
||||
->select('Query.id')
|
||||
->where($expr->lte('Query.expirationDate', ':date'))
|
||||
->setParameter('date', date_create())
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
return array_map(function (array $row) {
|
||||
return $row['id'];
|
||||
}, $ids);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
use CacheBundle\Entity\SourceList;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* SourceListRepository
|
||||
*
|
||||
* This class was generated by the Doctrine ORM. Add your own custom
|
||||
* repository methods below.
|
||||
*/
|
||||
class SourceListRepository extends EntityRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* Get QueryBuilder for list of sourceLists for the user
|
||||
*
|
||||
* @param integer $user A User entity id.
|
||||
* @param array $order Array of order config where key - is field name
|
||||
* and value is order direction.
|
||||
* @param boolean $onlyShared Show only shared source lists if set.
|
||||
*
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
public function getSourcesListsQB($user, array $order = [], $onlyShared = false)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
$qb = $this->createQueryBuilder('SourceList')
|
||||
->addSelect('partial User.{id, firstName, lastName}')
|
||||
->join('SourceList.user', 'User')
|
||||
->where($expr->eq('SourceList.user', ':user'))
|
||||
->setParameter(':user', $user);
|
||||
|
||||
if ($onlyShared) {
|
||||
$qb->andWhere($expr->eq('SourceList.isGlobal', 1));
|
||||
} else {
|
||||
$qb->orWhere($expr->eq('SourceList.isGlobal', 1));
|
||||
}
|
||||
|
||||
foreach ($order as $field => $direction) {
|
||||
$qb->addOrderBy("SourceList.{$field}", $direction);
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get concrete SourceLists for the user.
|
||||
*
|
||||
* @param integer $id A SourceList entity id.
|
||||
* @param integer $user A User entity id.
|
||||
*
|
||||
* @return SourceList|null
|
||||
*/
|
||||
public function getSourcesLists($id, $user)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
return $this->getSourcesListsQB($user)
|
||||
->andWhere($expr->eq('SourceList.id', ':id'))
|
||||
->setParameter('id', $id)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all source list ids which not exists, not global or not owned by
|
||||
* specified user.
|
||||
*
|
||||
* @param array $ids List of requested source list ids.
|
||||
*
|
||||
* @param integer $user A User entity id.
|
||||
*
|
||||
* @return integer[]
|
||||
*/
|
||||
public function sanitizeIds(array $ids, $user)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
return array_map(function (array $result) {
|
||||
return $result['id'];
|
||||
}, $this->getSourcesListsQB($user)
|
||||
->select('SourceList.id')
|
||||
->andWhere($expr->in('SourceList', ':ids'))
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->getArrayResult());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $user A User entity id.
|
||||
*
|
||||
* @return integer[]
|
||||
*/
|
||||
public function getAvailableIdsForUser($user)
|
||||
{
|
||||
return \nspl\a\map(
|
||||
\nspl\op\itemGetter('id'),
|
||||
$this->getSourcesListsQB($user)
|
||||
->select('SourceList.id')
|
||||
->getQuery()
|
||||
->getArrayResult()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
use CacheBundle\Entity\Query\StoredQuery;
|
||||
use Common\Enum\StoredQueryStatusEnum;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* Class SimpleQueryRepository
|
||||
* @package CacheBundle\Repository
|
||||
*/
|
||||
class StoredQueryRepository extends EntityRepository implements
|
||||
QueryRepositoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get query entity by internal representation of search query.
|
||||
*
|
||||
* @param string $hash A search query hash.
|
||||
* @param integer $user A User entity id, who made search request.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Query\StoredQuery|null
|
||||
*/
|
||||
public function get($hash, $user = null)
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
$condition = $expr->andX($expr->eq('Query.hash', ':hash'));
|
||||
$parameters = [ 'hash' => $hash ];
|
||||
|
||||
if ($user !== null) {
|
||||
$condition->add($expr->eq('Query.user', ':user'));
|
||||
$parameters['user'] = $user;
|
||||
}
|
||||
|
||||
return $this->createQueryBuilder('Query')
|
||||
->select('partial Query.{id, totalCount}')
|
||||
->where($condition)
|
||||
->setParameters($parameters)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* List stored queries ready for updating.
|
||||
*
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
public function getForUpdating()
|
||||
{
|
||||
$expr = $this->_em->getExpressionBuilder();
|
||||
|
||||
return $this->createQueryBuilder('Query')
|
||||
->distinct('Query.id')
|
||||
->join('Query.feeds', 'feeds')
|
||||
->where($expr->andX(
|
||||
$expr->neq('Query.status', ':status'),
|
||||
$expr->eq('Query.limitExceed', 0)
|
||||
))
|
||||
->setParameter('status', StoredQueryStatusEnum::INITIALIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Query by feed
|
||||
*
|
||||
* @param integer $feed A Feed entity id.
|
||||
*
|
||||
* @return StoredQuery
|
||||
*/
|
||||
public function getByFeed($feed)
|
||||
{
|
||||
return $this->createQueryBuilder('Query')
|
||||
->leftJoin('Query.feeds', 'feeds')
|
||||
->where('feeds.id = :feedId')
|
||||
->setParameter(':feedId', $feed)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Repository;
|
||||
|
||||
/**
|
||||
* UserStoredQueryRepository
|
||||
*
|
||||
* This class was generated by the Doctrine ORM. Add your own custom
|
||||
* repository methods below.
|
||||
*/
|
||||
class UserStoredQueryRepository extends \Doctrine\ORM\EntityRepository
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
services:
|
||||
#
|
||||
# Fetcher factories.
|
||||
#
|
||||
cache.feed_fetcher_factory.lazy:
|
||||
class: 'CacheBundle\Feed\Fetcher\Factory\LazyFeedFetcherFactory'
|
||||
arguments:
|
||||
- '@service_container'
|
||||
- # Injected by compiler phase
|
||||
|
||||
cache.feed_fetcher_factory: '@cache.feed_fetcher_factory.lazy'
|
||||
|
||||
#
|
||||
# Fetchers.
|
||||
#
|
||||
cache.feed_fetcher.query:
|
||||
class: 'CacheBundle\Feed\Fetcher\QueryFeedFetcher'
|
||||
arguments:
|
||||
- '@doctrine.orm.default_entity_manager'
|
||||
- '@app.stored_query_manager'
|
||||
- '@app.source_manager'
|
||||
tags:
|
||||
- { name: socialhose.feed_fetcher }
|
||||
|
||||
cache.feed_fetcher.clip:
|
||||
class: 'CacheBundle\Feed\Fetcher\ClipFeedFetcher'
|
||||
arguments:
|
||||
- '@doctrine.orm.default_entity_manager'
|
||||
- '@index.articles'
|
||||
tags:
|
||||
- { name: socialhose.feed_fetcher }
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
services:
|
||||
#
|
||||
# Fetchers.
|
||||
#
|
||||
cache.feed_formatter.basic:
|
||||
class: 'CacheBundle\Feed\Formatter\BasicFeedFormatter'
|
||||
arguments:
|
||||
- '@app.feed_manager'
|
||||
- '@service_container'
|
||||
|
||||
cache.feed_formatter: '@cache.feed_formatter.basic'
|
||||
|
||||
#
|
||||
# Strategies.
|
||||
#
|
||||
cache.feed_formatter_strategy.abstract:
|
||||
class: 'CacheBundle\Feed\Formatter\Strategy\AbstractFeedFormatStrategy'
|
||||
arguments:
|
||||
- '@cache.document_content_extractor'
|
||||
abstract: true
|
||||
|
||||
cache.feed_formatter_strategy.tsv:
|
||||
class: 'CacheBundle\Feed\Formatter\Strategy\TsvFeedFormatterStrategy'
|
||||
parent: 'cache.feed_formatter_strategy.abstract'
|
||||
|
||||
cache.feed_formatter_strategy.rss:
|
||||
class: 'CacheBundle\Feed\Formatter\Strategy\RssFeedFormatterStrategy'
|
||||
parent: 'cache.feed_formatter_strategy.abstract'
|
||||
arguments:
|
||||
- '@router'
|
||||
|
||||
cache.feed_formatter_strategy.html:
|
||||
class: 'CacheBundle\Feed\Formatter\Strategy\HtmlFeedFormatterStrategy'
|
||||
parent: 'cache.feed_formatter_strategy.abstract'
|
||||
arguments:
|
||||
- '@templating'
|
||||
|
||||
cache.feed_formatter_strategy.atom:
|
||||
class: 'CacheBundle\Feed\Formatter\Strategy\AtomFeedFormatterStrategy'
|
||||
parent: 'cache.feed_formatter_strategy.abstract'
|
||||
arguments:
|
||||
- '@router'
|
||||
@@ -0,0 +1,35 @@
|
||||
services:
|
||||
cache.form_type.current_user_category:
|
||||
class: 'CacheBundle\Form\Type\CurrentUserOwnedEntityType'
|
||||
arguments:
|
||||
- '@doctrine'
|
||||
- '@security.token_storage'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
cache.form.source_list_creation:
|
||||
class: 'CacheBundle\Form\Sources\SourceListType'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
app.form_factory.filter_factory_aware.source:
|
||||
class: 'AppBundle\Form\Factory\FilterFactoryAwareTypeFactory'
|
||||
arguments:
|
||||
- '@index.sources'
|
||||
- '%search.page_size%'
|
||||
public: false
|
||||
|
||||
cache.form.soure_index.search:
|
||||
class: 'CacheBundle\Form\Sources\SourceSearchType'
|
||||
factory: [ '@app.form_factory.filter_factory_aware.source', 'create' ]
|
||||
arguments: [ 'CacheBundle\Form\Sources\SourceSearchType' ]
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
app.form.analytic:
|
||||
class: 'CacheBundle\Form\AnalyticType'
|
||||
arguments:
|
||||
- '@index.articles'
|
||||
- '@security.token_storage'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
@@ -0,0 +1,39 @@
|
||||
services:
|
||||
#
|
||||
# Category inspector.
|
||||
#
|
||||
cache.inspector.category:
|
||||
class: 'CacheBundle\Security\Inspector\CategoryInspector'
|
||||
tags:
|
||||
- { name: socialhose.inspector }
|
||||
#
|
||||
# Comment inspector.
|
||||
#
|
||||
cache.inspector.comment:
|
||||
class: 'CacheBundle\Security\Inspector\CommentInspector'
|
||||
tags:
|
||||
- { name: socialhose.inspector }
|
||||
|
||||
#
|
||||
# Query feed inspector.
|
||||
#
|
||||
cache.inspector.query_feed:
|
||||
class: 'CacheBundle\Security\Inspector\FeedInspector'
|
||||
tags:
|
||||
- { name: socialhose.inspector }
|
||||
|
||||
#
|
||||
# Source list inspector
|
||||
#
|
||||
cache.inspector.source_list:
|
||||
class: 'CacheBundle\Security\Inspector\SourceListInspector'
|
||||
tags:
|
||||
- { name: socialhose.inspector }
|
||||
|
||||
#
|
||||
# Analytic inspector
|
||||
#
|
||||
cache.inspector.analytic:
|
||||
class: 'CacheBundle\Security\Inspector\AnalyticInspector'
|
||||
tags:
|
||||
- { name: socialhose.inspector }
|
||||
@@ -0,0 +1,42 @@
|
||||
imports:
|
||||
- { resource: inspectors.yml }
|
||||
- { resource: forms.yml }
|
||||
- { resource: feed_fetchers.yml }
|
||||
- { resource: feed_formatters.yml }
|
||||
|
||||
services:
|
||||
cache.repository.category:
|
||||
class: 'CacheBundle\Repository\CategoryRepository'
|
||||
factory: [ '@doctrine.orm.default_entity_manager', 'getRepository' ]
|
||||
arguments: [ 'CacheBundle\Entity\Category' ]
|
||||
public: false
|
||||
|
||||
cache.repository.analytic_context:
|
||||
class: 'AppBundle\Doctrine\ORM\BaseEntityRepository'
|
||||
factory: [ '@doctrine.orm.default_entity_manager', 'getRepository' ]
|
||||
arguments: [ 'CacheBundle\Entity\Analytic\AnalyticContext' ]
|
||||
public: false
|
||||
|
||||
cache.validator.category_parent:
|
||||
class: 'CacheBundle\Validator\Constraints\CategoryParentValidator'
|
||||
arguments: [ '@cache.repository.category' ]
|
||||
tags:
|
||||
- { name: validator.constraint_validator }
|
||||
|
||||
cache.comment_manager:
|
||||
class: 'CacheBundle\Comment\Manager\CommentManager'
|
||||
arguments:
|
||||
- '@doctrine.orm.default_entity_manager'
|
||||
|
||||
cache.document_content_extractor.basic:
|
||||
class: 'CacheBundle\Document\Extractor\BasicDocumentContentExtractor'
|
||||
arguments:
|
||||
- "@=service('app.configuration').getParameter('notification.start_extract_length')"
|
||||
- "@=service('app.configuration').getParameter('notification.context_extract_length')"
|
||||
|
||||
cache.document_content_extractor: '@cache.document_content_extractor.basic'
|
||||
|
||||
cache.analytic_factory:
|
||||
class: 'CacheBundle\Service\Factory\Analytic\AnalyticFactory'
|
||||
arguments:
|
||||
- '@cache.repository.analytic_context'
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Security\Inspector;
|
||||
|
||||
use ApiBundle\Security\Inspector\AbstractInspector;
|
||||
use CacheBundle\Entity\Analytic\Analytic;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class AnalyticInspector
|
||||
* @package CacheBundle\Security\Inspector
|
||||
*/
|
||||
class AnalyticInspector extends AbstractInspector
|
||||
{
|
||||
|
||||
/**
|
||||
* Return supported entity fqcn.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function supportedClass()
|
||||
{
|
||||
return Analytic::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can create specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Analytic|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canCreate(User $user, $entity)
|
||||
{
|
||||
$this->addReasonIf(
|
||||
"Can't create analytics 'cause you don't have permissions for it.",
|
||||
! $user->getBillingSubscription()->getPlan()->isAnalytics()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can read specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Analytic|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canRead(User $user, $entity)
|
||||
{
|
||||
// todo implement check
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can update specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Analytic|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function canUpdate(User $user, $entity)
|
||||
{
|
||||
// todo implement check
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can delete specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Analytic|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canDelete(User $user, $entity)
|
||||
{
|
||||
$this
|
||||
->addReasonIf(
|
||||
"Can't delete analytic owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Security\Inspector;
|
||||
|
||||
use ApiBundle\Security\Inspector\AbstractInspector;
|
||||
use CacheBundle\Entity\Category;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class CategoryInspector
|
||||
* @package CacheBundle\Security\Inspector
|
||||
*/
|
||||
class CategoryInspector extends AbstractInspector
|
||||
{
|
||||
|
||||
/**
|
||||
* Return supported entity fqcn.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function supportedClass()
|
||||
{
|
||||
return Category::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can create specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Category|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canCreate(User $user, $entity)
|
||||
{
|
||||
$this->addReasonIf(
|
||||
"Can't create category for other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can read specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Category|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canRead(User $user, $entity)
|
||||
{
|
||||
$this->addReasonIf(
|
||||
"Can't read category owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can update specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Category|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function canUpdate(User $user, $entity)
|
||||
{
|
||||
$this
|
||||
->addReasonIf(
|
||||
"Can't update category owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
)
|
||||
->addReasonIf(
|
||||
"Can't update internal category.",
|
||||
$entity->isInternal()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can delete specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Category|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canDelete(User $user, $entity)
|
||||
{
|
||||
$this
|
||||
->addReasonIf(
|
||||
"Can't delete category owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
)
|
||||
->addReasonIf(
|
||||
"Can't delete internal category.",
|
||||
$entity->isInternal()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Security\Inspector;
|
||||
|
||||
use ApiBundle\Security\Inspector\AbstractInspector;
|
||||
use CacheBundle\Entity\Comment;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class CommentInspector
|
||||
* @package CacheBundle\Security\Inspector
|
||||
*/
|
||||
class CommentInspector extends AbstractInspector
|
||||
{
|
||||
|
||||
/**
|
||||
* Return supported entity fqcn.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function supportedClass()
|
||||
{
|
||||
return Comment::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can create specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Comment|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function canCreate(User $user, $entity)
|
||||
{
|
||||
$this->addReasonIf(
|
||||
"Can't create comment for other user.",
|
||||
$entity->getAuthor()->getId() !== $user->getId()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can read specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Comment|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function canRead(User $user, $entity)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can update specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Comment|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canUpdate(User $user, $entity)
|
||||
{
|
||||
$this
|
||||
->addReasonIf(
|
||||
"Can't update comment owned by other user.",
|
||||
$entity->getAuthor()->getId() !== $user->getId()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can delete specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param Comment|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canDelete(User $user, $entity)
|
||||
{
|
||||
$this
|
||||
->addReasonIf(
|
||||
"Can't delete comment owned by other user.",
|
||||
$entity->getAuthor()->getId() !== $user->getId()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Security\Inspector;
|
||||
|
||||
use ApiBundle\Security\Inspector\AbstractInspector;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Entity\Feed\ClipFeed;
|
||||
use CacheBundle\Entity\Feed\QueryFeed;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class FeedInspector
|
||||
* @package CacheBundle\Security\Inspector
|
||||
*/
|
||||
class FeedInspector extends AbstractInspector
|
||||
{
|
||||
|
||||
/**
|
||||
* Return supported entity fqcn.
|
||||
*
|
||||
* @return string|string[]
|
||||
*/
|
||||
public static function supportedClass()
|
||||
{
|
||||
return [ QueryFeed::class, ClipFeed::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can create specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param AbstractFeed|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canCreate(User $user, $entity)
|
||||
{
|
||||
$this->addReasonIf(
|
||||
"Can't create feed for other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can read specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param AbstractFeed|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canRead(User $user, $entity)
|
||||
{
|
||||
$this->addReasonIf(
|
||||
"Can't read feed owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can update specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param AbstractFeed|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function canUpdate(User $user, $entity)
|
||||
{
|
||||
$this
|
||||
->addReasonIf(
|
||||
"Can't update feed owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can delete specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param AbstractFeed|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canDelete(User $user, $entity)
|
||||
{
|
||||
$this
|
||||
->addReasonIf(
|
||||
"Can't delete feed owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Security\Inspector;
|
||||
|
||||
use ApiBundle\Security\Inspector\AbstractInspector;
|
||||
use CacheBundle\Entity\SourceList;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class SourceListInspector
|
||||
* @package CacheBundle\Security\Inspector
|
||||
*/
|
||||
class SourceListInspector extends AbstractInspector
|
||||
{
|
||||
|
||||
const SHARE = 'share';
|
||||
const UNSHARE = 'unshare';
|
||||
|
||||
/**
|
||||
* Return supported entity fqcn.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function supportedClass()
|
||||
{
|
||||
return [ SourceList::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that given user can make given action with specified entity.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
* @param object|SourceList $entity A Entity instance or array of instances.
|
||||
* @param string $action Action name.
|
||||
*
|
||||
* @return string[] Array of restriction reasons.
|
||||
*/
|
||||
public function inspect(User $user, $entity, $action)
|
||||
{
|
||||
parent::inspect($user, $entity, $action);
|
||||
|
||||
if ($action === self::SHARE) {
|
||||
$this->addReasonIf(
|
||||
"Can't share source list owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
} elseif ($action === self::UNSHARE) {
|
||||
$this->addReasonIf(
|
||||
"Can't unshare source list owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->reasons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can create specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param SourceList|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canCreate(User $user, $entity)
|
||||
{
|
||||
$this->addReasonIf(
|
||||
"Can't create feed for other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can read specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param SourceList|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canRead(User $user, $entity)
|
||||
{
|
||||
$this->addReasonIf(
|
||||
"Can't read feed owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can update specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param SourceList|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function canUpdate(User $user, $entity)
|
||||
{
|
||||
$this
|
||||
->addReasonIf(
|
||||
"Can't update feed owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can delete specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param SourceList|object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function canDelete(User $user, $entity)
|
||||
{
|
||||
$this
|
||||
->addReasonIf(
|
||||
"Can't delete feed owned by other user.",
|
||||
! $entity->isOwnedBy($user)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Service\Factory\Analytic;
|
||||
|
||||
use AppBundle\Doctrine\ORM\BaseEntityRepository;
|
||||
use AppBundle\Exception\NotAllowedException;
|
||||
use CacheBundle\DTO\AnalyticDTO;
|
||||
use CacheBundle\Entity\Analytic\Analytic;
|
||||
use CacheBundle\Entity\Analytic\AnalyticContext;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use IndexBundle\Filter\FilterInterface;
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\AppPermissionEnum;
|
||||
|
||||
/**
|
||||
* Class AnalyticFactory
|
||||
*
|
||||
* @package CacheBundle\Service\Factory\Analytic
|
||||
*/
|
||||
class AnalyticFactory implements AnalyticFactoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var BaseEntityRepository
|
||||
*/
|
||||
private $analyticRepository;
|
||||
|
||||
/**
|
||||
* AnalyticFactory constructor.
|
||||
*
|
||||
* @param BaseEntityRepository $analyticRepository A internal analytic repository.
|
||||
*/
|
||||
public function __construct(BaseEntityRepository $analyticRepository)
|
||||
{
|
||||
$this->analyticRepository = $analyticRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AnalyticDTO $dto A analytic dto instance.
|
||||
* @param User $user User for which we create analytic.
|
||||
*
|
||||
* @return Analytic
|
||||
*
|
||||
* @throws \InvalidArgumentException If got invalid dto.
|
||||
* @throws NotAllowedException If specified user can't create save.
|
||||
*/
|
||||
public function createAnalytic(AnalyticDTO $dto, User $user)
|
||||
{
|
||||
if (!$user->isAllowedTo(AppPermissionEnum::analytics())) {
|
||||
throw new NotAllowedException($user, AppPermissionEnum::analytics());
|
||||
}
|
||||
|
||||
$context = $this->createContext($dto);
|
||||
|
||||
return new Analytic($user, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AnalyticDTO $dto A data required for creating analytic.
|
||||
*
|
||||
* @return AnalyticContext
|
||||
*/
|
||||
private function createContext(AnalyticDTO $dto)
|
||||
{
|
||||
if (!\app\a\allInstanceOf($dto->feeds, AbstractFeed::class)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'\'$dto->feeds\' should be an array of \'%s\' instances',
|
||||
AbstractFeed::class
|
||||
));
|
||||
}
|
||||
|
||||
if (!\app\a\allInstanceOf($dto->filters, FilterInterface::class)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'\'$dto->filters\' should be an array of \'%s\' instances',
|
||||
FilterInterface::class
|
||||
));
|
||||
}
|
||||
|
||||
if (!is_array($dto->rawFilters)) {
|
||||
throw new \InvalidArgumentException('\'$dto->rawFilters\' should be an array');
|
||||
}
|
||||
|
||||
$hash = $this->computeHash($dto->feeds, $dto->filters);
|
||||
|
||||
$analytic = $this->analyticRepository->find($hash);
|
||||
if ($analytic === null) {
|
||||
$analytic = new AnalyticContext(
|
||||
$hash,
|
||||
$dto->feeds,
|
||||
$dto->filters,
|
||||
$dto->rawFilters
|
||||
);
|
||||
}
|
||||
|
||||
return $analytic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute hash for analytic.
|
||||
*
|
||||
* @param AbstractFeed[] $feeds Array of used feeds.
|
||||
* @param FilterInterface[] $filters Array of used filters.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function computeHash(array $feeds, array $filters)
|
||||
{
|
||||
$collectionIds = $this->getUniqueCollectionIds($feeds);
|
||||
sort($collectionIds);
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
$filter->sort();
|
||||
}
|
||||
|
||||
return md5(serialize($collectionIds) . serialize($filters));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractFeed[] $feeds Collection of used feeds.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function getUniqueCollectionIds(array $feeds)
|
||||
{
|
||||
$collectionIds = [];
|
||||
|
||||
foreach ($feeds as $feed) {
|
||||
$collectionIds[$feed->getCollectionId()] = true;
|
||||
}
|
||||
|
||||
return array_keys($collectionIds);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param AnalyticDTO $dto
|
||||
* @param User $user
|
||||
* @param Analytic $oldAnalytic
|
||||
* @return Analytic|AnalyticContext|object|null
|
||||
*/
|
||||
public function updateAnalytic(AnalyticDTO $dto, User $user, Analytic $oldAnalytic)
|
||||
{
|
||||
if (!$user->isAllowedTo(AppPermissionEnum::analytics())) {
|
||||
throw new NotAllowedException($user, AppPermissionEnum::analytics());
|
||||
}
|
||||
|
||||
if (!\app\a\allInstanceOf($dto->feeds, AbstractFeed::class)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'\'$dto->feeds\' should be an array of \'%s\' instances',
|
||||
AbstractFeed::class
|
||||
));
|
||||
}
|
||||
|
||||
if (!\app\a\allInstanceOf($dto->filters, FilterInterface::class)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'\'$dto->filters\' should be an array of \'%s\' instances',
|
||||
FilterInterface::class
|
||||
));
|
||||
}
|
||||
|
||||
if (!is_array($dto->rawFilters)) {
|
||||
throw new \InvalidArgumentException('\'$dto->rawFilters\' should be an array');
|
||||
}
|
||||
|
||||
$hash = $this->computeHash($dto->feeds, $dto->filters);
|
||||
$analytic = $this->analyticRepository->find($hash);
|
||||
if ($analytic === null) {
|
||||
$analytic = new AnalyticContext(
|
||||
$hash,
|
||||
$dto->feeds,
|
||||
$dto->filters,
|
||||
$dto->rawFilters
|
||||
);
|
||||
|
||||
/** @var $oldAnalytic $analytic */
|
||||
$oldAnalytic->setContext($analytic);
|
||||
}
|
||||
|
||||
return $oldAnalytic;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Service\Factory\Analytic;
|
||||
|
||||
use AppBundle\Exception\NotAllowedException;
|
||||
use CacheBundle\DTO\AnalyticDTO;
|
||||
use CacheBundle\Entity\Analytic\Analytic;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Interface AnalyticFactoryInterface
|
||||
*
|
||||
* Factory for creating saved analytics for specified users.
|
||||
*
|
||||
* @package CacheBundle\Service\Factory\Analytic
|
||||
*/
|
||||
interface AnalyticFactoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param AnalyticDTO $dto A analytic dto instance.
|
||||
* @param User $user User for which we create analytic.
|
||||
*
|
||||
* @return Analytic
|
||||
*
|
||||
* @throws \InvalidArgumentException If got invalid dto.
|
||||
* @throws NotAllowedException If specified user can't create save.
|
||||
*/
|
||||
public function createAnalytic(AnalyticDTO $dto, User $user);
|
||||
|
||||
/**
|
||||
* @param AnalyticDTO $dto
|
||||
* @param User $user
|
||||
* @param Analytic $analytic
|
||||
* @return mixed
|
||||
*/
|
||||
public function updateAnalytic(AnalyticDTO $dto, User $user, Analytic $analytic);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Class CategoryParent
|
||||
*
|
||||
* @Annotation
|
||||
* @Target({"PROPERTY", "ANNOTATION"})
|
||||
*
|
||||
* @package CacheBundle\Validator\Constraints
|
||||
*/
|
||||
class CategoryParent extends Constraint
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns whether the constraint can be put onto classes, properties or
|
||||
* both.
|
||||
*
|
||||
* This method should return one or more of the constants
|
||||
* Constraint::CLASS_CONSTRAINT and Constraint::PROPERTY_CONSTRAINT.
|
||||
*
|
||||
* @return string|array One or more constant values.
|
||||
*/
|
||||
public function getTargets()
|
||||
{
|
||||
return [ self::PROPERTY_CONSTRAINT ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace CacheBundle\Validator\Constraints;
|
||||
|
||||
use CacheBundle\Entity\Category;
|
||||
use CacheBundle\Repository\CategoryRepository;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\RuntimeException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* Class CategoryParentValidator
|
||||
* @package CacheBundle\Validator\Constraints
|
||||
*/
|
||||
class CategoryParentValidator extends ConstraintValidator
|
||||
{
|
||||
|
||||
/**
|
||||
* @var CategoryRepository
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* CategoryParentValidator constructor.
|
||||
*
|
||||
* @param CategoryRepository $repository A CategoryRepository instance.
|
||||
*/
|
||||
public function __construct(CategoryRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed value is valid.
|
||||
*
|
||||
* @param mixed $value The value that should be validated.
|
||||
* @param Constraint $constraint The constraint for the validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (! $constraint instanceof CategoryParent) {
|
||||
throw new UnexpectedTypeException($constraint, CategoryParent::CLASS_CONSTRAINT);
|
||||
}
|
||||
|
||||
// Check current object.
|
||||
$current = $this->context->getObject();
|
||||
if (! $current instanceof Category) {
|
||||
throw new RuntimeException('This validator works only with categories.');
|
||||
}
|
||||
|
||||
// We don't make any checks if current category is new.
|
||||
if ($current->getId() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we try to put category inside it self.
|
||||
if ($value instanceof Category) {
|
||||
$currentId = $current->getId();
|
||||
$valueId = $value->getId();
|
||||
|
||||
if ($valueId === $currentId) {
|
||||
$this->context->addViolation('Try to place category inside itself.');
|
||||
}
|
||||
|
||||
// Check that we try to put category inside one of it childes.
|
||||
if ($this->repository->isChildOf($valueId, $currentId)) {
|
||||
$this->context->addViolation('Try to place category inside it child.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user