at the end of the day, it was inevitable
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\AdvancedFilters;
|
||||
|
||||
use AppBundle\AdvancedFilters\Aggregator\AFAggregatorInterface;
|
||||
use AppBundle\Form\Type\AdvancedFilter\AdvancedFilterParameters;
|
||||
use Common\Enum\AFTypeEnum;
|
||||
use IndexBundle\Filter\Factory\FilterFactoryInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestInterface;
|
||||
|
||||
/**
|
||||
* Class AFResolver
|
||||
*
|
||||
* @package AppBundle\AdvancedFilters\Elasticsearch
|
||||
*/
|
||||
class AFResolver implements AFResolverInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var AFAggregatorInterface
|
||||
*/
|
||||
private $aggregator;
|
||||
|
||||
/**
|
||||
* @var FilterFactoryInterface
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* AFResolver constructor.
|
||||
*
|
||||
* @param AFAggregatorInterface $aggregator A AFAggregatorInterface
|
||||
* instance.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(
|
||||
AFAggregatorInterface $aggregator,
|
||||
FilterFactoryInterface $factory
|
||||
) {
|
||||
$this->aggregator = $aggregator;
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Available values for specified filter or for all.
|
||||
*
|
||||
* @param SearchRequestInterface $request A SearchRequestInterface instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAvailables(SearchRequestInterface $request)
|
||||
{
|
||||
//
|
||||
// Return assoc array for all available filters with its values.
|
||||
//
|
||||
return array_map(function ($values) {
|
||||
return [ 'data' => $values ];
|
||||
}, $this->aggregator->getValues($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate proper FilterInterface instance for specified filter name.
|
||||
*
|
||||
* @param array $AFConfig Advanced filters configuration.
|
||||
* @param string $name Filter name.
|
||||
* @param AdvancedFilterParameters $params Filter value or value label.
|
||||
*
|
||||
* @return \IndexBundle\Filter\FilterInterface
|
||||
*/
|
||||
public function generateFilter(array $AFConfig, $name, AdvancedFilterParameters $params)
|
||||
{
|
||||
if (! isset($AFConfig[$name])) {
|
||||
throw new \InvalidArgumentException("Unknown filter '{$name}'.");
|
||||
}
|
||||
$config = $AFConfig[$name];
|
||||
$fieldName = $config['field_name'];
|
||||
|
||||
switch ($config['type']) {
|
||||
//
|
||||
// Additional query.
|
||||
//
|
||||
case AFTypeEnum::QUERY:
|
||||
$filter = $params->createQueryFilter($config['names'], $this->factory);
|
||||
break;
|
||||
|
||||
//
|
||||
// Filter by range.
|
||||
//
|
||||
case AFTypeEnum::RANGE:
|
||||
$ranges = $config['ranges'];
|
||||
$filter = $params->createRangeFilter($fieldName, $ranges, $this->factory);
|
||||
break;
|
||||
|
||||
//
|
||||
// Filter by single value.
|
||||
//
|
||||
case AFTypeEnum::SIMPLE:
|
||||
//
|
||||
// NOTICE:
|
||||
//
|
||||
// We not validate given value so client may provide valid value
|
||||
// for current filtered field but not existing in available filter
|
||||
// values for current search request.
|
||||
//
|
||||
// In this case client will receive zero documents, so maybe
|
||||
// validating is not necessary.
|
||||
//
|
||||
$filter = $params->createSimpleFilter($fieldName, $this->factory);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \RuntimeException("Unsupported type {$config['type']}");
|
||||
}
|
||||
|
||||
return $filter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\AdvancedFilters;
|
||||
|
||||
use AppBundle\Form\Type\AdvancedFilter\AdvancedFilterParameters;
|
||||
use Common\Enum\AFSourceEnum;
|
||||
use IndexBundle\Filter\FilterInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestInterface;
|
||||
|
||||
/**
|
||||
* Interface AFResolverInterface
|
||||
* Resolve concrete advanced filter.
|
||||
*
|
||||
* @package AppBundle\AdvancedFilters
|
||||
*/
|
||||
interface AFResolverInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Max advanced filters values per page for each filter.
|
||||
*/
|
||||
const MAX_VALUES = 10;
|
||||
|
||||
/**
|
||||
* Get Available values for specified filter or for all.
|
||||
*
|
||||
* @param SearchRequestInterface $request A SearchRequestInterface instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAvailables(SearchRequestInterface $request);
|
||||
|
||||
/**
|
||||
* Generate proper FilterInterface instance for specified filter name.
|
||||
*
|
||||
* @param array $AFConfig Advanced filters configuration.
|
||||
* @param string $name Filter name.
|
||||
* @param AdvancedFilterParameters $params Filter value or value label.
|
||||
*
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function generateFilter(array $AFConfig, $name, AdvancedFilterParameters $params);
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\AdvancedFilters;
|
||||
|
||||
use Common\Enum\AFSourceEnum;
|
||||
use Common\Enum\DocumentsAFNameEnum;
|
||||
use Common\Enum\AFTypeEnum;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use Common\Enum\SourcesAFNameEnum;
|
||||
|
||||
/**
|
||||
* Class AdvancedFiltersConfig
|
||||
* @package AppBundle\AdvancedFilters
|
||||
*/
|
||||
class AdvancedFiltersConfig
|
||||
{
|
||||
|
||||
/**
|
||||
* Configuration of advanced filter for sources.
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
private static $configs = [
|
||||
//
|
||||
// Configuration of advanced filter for documents.
|
||||
//
|
||||
AFSourceEnum::FEED => [
|
||||
DocumentsAFNameEnum::ADDITIONAL_QUERY => [
|
||||
'type' => AFTypeEnum::QUERY,
|
||||
'description' => 'Additional specifying query.',
|
||||
'field_name' => '',
|
||||
'names' => [
|
||||
FieldNameEnum::MAIN,
|
||||
FieldNameEnum::TITLE,
|
||||
],
|
||||
],
|
||||
DocumentsAFNameEnum::SOURCE => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::SOURCE_TITLE,
|
||||
'description' => 'Filter documents by source title.',
|
||||
],
|
||||
DocumentsAFNameEnum::ARTICLE_DATE => [
|
||||
'type' => AFTypeEnum::RANGE,
|
||||
'field_name' => FieldNameEnum::PUBLISHED,
|
||||
'ranges' => [
|
||||
'15 Minutes' => [ 'from' => 'now-15m', 'key' => '15 Minutes' ],
|
||||
'30 Minutes' => [ 'from' => 'now-30m', 'key' => '30 Minutes' ],
|
||||
'1 Hour' => [ 'from' => 'now-1H', 'key' => '1 Hour' ],
|
||||
'24 Hour' => [ 'from' => 'now-1d', 'key' => '24 Hour' ],
|
||||
'7 Days' => [ 'from' => 'now-7d', 'key' => '7 Days' ],
|
||||
],
|
||||
'description' => 'Filter documents by found date.',
|
||||
],
|
||||
DocumentsAFNameEnum::SOURCE_COUNTRY => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::COUNTRY,
|
||||
'description' => 'Filter documents by source country.',
|
||||
],
|
||||
DocumentsAFNameEnum::SOURCE_STATE => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::STATE,
|
||||
'description' => 'Filter documents by source state.',
|
||||
],
|
||||
DocumentsAFNameEnum::SOURCE_CITY => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::CITY,
|
||||
'description' => 'Filter documents by source city.',
|
||||
],
|
||||
// DocumentsAFNameEnum::SOURCE_SECTION => [
|
||||
// 'type' => AFTypeEnum::SIMPLE,
|
||||
// 'field_name' => FieldNameEnum::SECTION,
|
||||
// 'description' => 'Filter documents by source section.',
|
||||
// ],
|
||||
DocumentsAFNameEnum::ARTICLE_LANGUAGE => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::LANG,
|
||||
'description' => 'Filter documents by language.',
|
||||
],
|
||||
DocumentsAFNameEnum::AUTHOR => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::AUTHOR_NAME,
|
||||
'description' => 'Filter documents by author name.',
|
||||
],
|
||||
DocumentsAFNameEnum::PUBLISHER => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::PUBLISHER,
|
||||
'description' => 'Filter documents by publisher.',
|
||||
],
|
||||
DocumentsAFNameEnum::REACH => [
|
||||
'type' => AFTypeEnum::RANGE,
|
||||
'field_name' => FieldNameEnum::VIEWS,
|
||||
'ranges' => [
|
||||
'0+' => [ 'from' => 0, 'to' => 1000 ],
|
||||
'1000+' => [ 'from' => 1000, 'to' => 5000 ],
|
||||
'5000+' => [ 'from' => 5000, 'to' => 10000 ],
|
||||
'10000+' => [ 'from' => 10000, 'to' => 25000 ],
|
||||
'25000+' => [ 'from' => 25000, 'to' => 50000 ],
|
||||
'50000+' => [ 'from' => 50000, 'to' => 100000 ],
|
||||
'100000+' => [ 'from' => 100000, 'to' => 250000 ],
|
||||
'250000+' => [ 'from' => 250000, 'to' => 500000 ],
|
||||
'500000+' => [ 'from' => 500000, 'to' => 1000000 ],
|
||||
'1000000+' => [ 'from' => 1000000, 'to' => 2500000 ],
|
||||
'2500000+' => [ 'from' => 2500000, 'to' => 5000000 ],
|
||||
'5000000+' => [ 'from' => 5000000, 'to' => 10000000 ],
|
||||
'10000000+' => [ 'from' => 10000000 ],
|
||||
],
|
||||
'description' => 'Filter documents by views count.',
|
||||
],
|
||||
DocumentsAFNameEnum::SENTIMENT => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::SENTIMENT,
|
||||
'description' => 'Filter documents by sentiment.',
|
||||
],
|
||||
],
|
||||
//
|
||||
// Configuration of advanced filter for sources.
|
||||
//
|
||||
AFSourceEnum::SOURCE => [
|
||||
SourcesAFNameEnum::LANG => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::LANG,
|
||||
'description' => 'Filter sources by language.',
|
||||
],
|
||||
SourcesAFNameEnum::COUNTRY => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::COUNTRY,
|
||||
'description' => 'Filter sources by country.',
|
||||
],
|
||||
SourcesAFNameEnum::STATE => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::STATE,
|
||||
'description' => 'Filter sources by state.',
|
||||
],
|
||||
SourcesAFNameEnum::CITY => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::CITY,
|
||||
'description' => 'Filter sources by city.',
|
||||
],
|
||||
SourcesAFNameEnum::SECTION => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::SECTION,
|
||||
'description' => 'Filter sources by section.',
|
||||
],
|
||||
SourcesAFNameEnum::MEDIA_TYPE => [
|
||||
'type' => AFTypeEnum::SIMPLE,
|
||||
'field_name' => FieldNameEnum::SOURCE_PUBLISHER_TYPE,
|
||||
'description' => 'Filter sources by media type.',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Get configuration for specified document's source.
|
||||
*
|
||||
* @param string $name One of available constants from AFSourceEnum.
|
||||
*
|
||||
* @return array[]
|
||||
*
|
||||
* @see AFSourceEnum
|
||||
*/
|
||||
public static function getConfig($name)
|
||||
{
|
||||
if (! isset(self::$configs[$name])) {
|
||||
throw new \InvalidArgumentException("Unknown config '{$name}'");
|
||||
}
|
||||
|
||||
return self::$configs[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default value for specified source.
|
||||
*
|
||||
* @param string $name One of available constants from AFSourceEnum.
|
||||
*
|
||||
* @return array[]
|
||||
*
|
||||
* @see AFSourceEnum
|
||||
*/
|
||||
public static function getDefault($name)
|
||||
{
|
||||
switch ($name) {
|
||||
case AFSourceEnum::FEED:
|
||||
return [
|
||||
DocumentsAFNameEnum::SOURCE => [ 'data' => [] ],
|
||||
DocumentsAFNameEnum::ARTICLE_DATE => [ 'data' => [] ],
|
||||
DocumentsAFNameEnum::SOURCE_COUNTRY => [ 'data' => [] ],
|
||||
DocumentsAFNameEnum::SOURCE_STATE => [ 'data' => [] ],
|
||||
DocumentsAFNameEnum::SOURCE_CITY => [ 'data' => [] ],
|
||||
// DocumentsAFNameEnum::SOURCE_SECTION => [ 'data' => [] ],
|
||||
DocumentsAFNameEnum::ARTICLE_LANGUAGE => [ 'data' => [] ],
|
||||
DocumentsAFNameEnum::AUTHOR => [ 'data' => [] ],
|
||||
DocumentsAFNameEnum::PUBLISHER => [ 'data' => [] ],
|
||||
DocumentsAFNameEnum::REACH => [ 'data' => [] ],
|
||||
DocumentsAFNameEnum::SENTIMENT => [ 'data' => [] ],
|
||||
];
|
||||
|
||||
case AFSourceEnum::SOURCE:
|
||||
return [
|
||||
SourcesAFNameEnum::LANG => [ 'data' => [] ],
|
||||
SourcesAFNameEnum::COUNTRY => [ 'data' => [] ],
|
||||
SourcesAFNameEnum::STATE => [ 'data' => [] ],
|
||||
SourcesAFNameEnum::CITY => [ 'data' => [] ],
|
||||
SourcesAFNameEnum::SECTION => [ 'data' => [] ],
|
||||
SourcesAFNameEnum::MEDIA_TYPE => [ 'data' => [] ],
|
||||
];
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unknown source '. $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\AdvancedFilters\Aggregator;
|
||||
|
||||
use IndexBundle\SearchRequest\SearchRequestInterface;
|
||||
|
||||
/**
|
||||
* Interface AFAggregatorInterface
|
||||
*
|
||||
* Advanced filters aggregator interface.
|
||||
*
|
||||
* @package AppBundle\AdvancedFilters\Aggregator
|
||||
*/
|
||||
interface AFAggregatorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Return available filters values for specified request.
|
||||
*
|
||||
* @param SearchRequestInterface $request A SearchRequestInterface instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues(SearchRequestInterface $request);
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\AdvancedFilters\Aggregator;
|
||||
|
||||
use Common\Enum\DocumentsAFNameEnum;
|
||||
use Common\Enum\AFTypeEnum;
|
||||
use IndexBundle\Aggregation\AggregationInterface;
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractElasticSearchAFAggregator
|
||||
*
|
||||
* Realization of AFAggregatorInterface for Elasticsearch.
|
||||
*
|
||||
* @package AppBundle\AdvancedFilters\Aggregator
|
||||
*/
|
||||
abstract class AbstractElasticSearchAFAggregator implements AFAggregatorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var IndexInterface
|
||||
*/
|
||||
private $index;
|
||||
|
||||
/**
|
||||
* ElasticsearchAFAggregator constructor.
|
||||
*
|
||||
* @param IndexInterface $index A IndexInterface instance.
|
||||
*/
|
||||
public function __construct(IndexInterface $index)
|
||||
{
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return available filters values for specified request.
|
||||
*
|
||||
* @param SearchRequestInterface $request A SearchRequestInterface instance.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @see AFSourceEnum
|
||||
*/
|
||||
public function getValues(SearchRequestInterface $request)
|
||||
{
|
||||
$aggregations = [];
|
||||
$AFConfig = $this->getAggregationConfig();
|
||||
|
||||
foreach ($AFConfig as $aggregationName => $config) {
|
||||
if ($config['type'] !== AFTypeEnum::QUERY) {
|
||||
$aggregations[] = $this
|
||||
->createAggregation($aggregationName, $config);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new builder for aggregation only.
|
||||
$builder = $this->index->createRequestBuilder();
|
||||
$response = $builder
|
||||
->fromSearchRequest($request)
|
||||
->setAggregation($aggregations)
|
||||
->setLimit(0) // We don't need any founded documents, only aggregations
|
||||
// results.
|
||||
->build()
|
||||
->execute();
|
||||
|
||||
// Normalize aggregation results.
|
||||
$results = $response->getAggregationResults();
|
||||
$values = [];
|
||||
|
||||
foreach ($results as $name => $body) {
|
||||
//
|
||||
// Normalize concrete filter aggregation.
|
||||
//
|
||||
// For some reasons ElasticSearch invert aggregation data in buckets
|
||||
// when we try to aggregate filed with 'date' type and our custom
|
||||
// names from config are assigned to invalid values. So for 'articleDate'
|
||||
// filter we invert all values in bucket.
|
||||
//
|
||||
if ($name === DocumentsAFNameEnum::ARTICLE_DATE) {
|
||||
$body = array_reverse($body);
|
||||
}
|
||||
|
||||
$values[$name] = [];
|
||||
foreach ($body as $value) {
|
||||
//
|
||||
// Normalize concrete filter aggregation result.
|
||||
//
|
||||
$valueName = $value['value'];
|
||||
|
||||
$values[$name][] = [
|
||||
'value' => $valueName,
|
||||
'count' => $value['count'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Get not founded advanced filters values and force it into response.
|
||||
//
|
||||
$notFounded = array_diff(array_keys($this->getDefaultValue()), array_keys($results));
|
||||
foreach ($notFounded as $filter) {
|
||||
$values[$filter] = [];
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new Aggregation instance from specified config.
|
||||
*
|
||||
* @param string $name Aggregation name.
|
||||
* @param array $config Aggregation config.
|
||||
*
|
||||
* @return AggregationInterface
|
||||
*/
|
||||
private function createAggregation($name, array $config)
|
||||
{
|
||||
$factory = $this->index->getAggregationFactory();
|
||||
$aggregation = $this->index->getAggregation();
|
||||
|
||||
// Get aggregation type and convert to proper ElasticSearch aggregation type.
|
||||
$type = ($config['type'] === AFTypeEnum::SIMPLE) ? 'terms' : 'range';
|
||||
|
||||
$params = [
|
||||
'type' => $type,
|
||||
'field_name' => $config['field_name'],
|
||||
];
|
||||
|
||||
if ($type === AFTypeEnum::RANGE) {
|
||||
// We should get only ranges without names.
|
||||
$params['ranges'] = array_values($config['ranges']);
|
||||
}
|
||||
|
||||
// Create new aggregation.
|
||||
return $aggregation->getAggregation($name, $factory->{$type}($params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregation config.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function getAggregationConfig();
|
||||
|
||||
/**
|
||||
* Get default value for this aggregation results.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function getDefaultValue();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\AdvancedFilters\Aggregator;
|
||||
|
||||
use AppBundle\AdvancedFilters\AdvancedFiltersConfig;
|
||||
use Common\Enum\AFSourceEnum;
|
||||
|
||||
/**
|
||||
* Class ArticleAFAggregator
|
||||
*
|
||||
* @package AppBundle\AdvancedFilters\Aggregator
|
||||
*/
|
||||
class ArticleAFAggregator extends AbstractElasticSearchAFAggregator
|
||||
{
|
||||
|
||||
/**
|
||||
* Aggregation config.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAggregationConfig()
|
||||
{
|
||||
return AdvancedFiltersConfig::getConfig(AFSourceEnum::FEED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default value for this aggregation results.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultValue()
|
||||
{
|
||||
return AdvancedFiltersConfig::getDefault(AFSourceEnum::FEED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\AdvancedFilters\Aggregator;
|
||||
|
||||
use AppBundle\Entity\CacheItem;
|
||||
use IndexBundle\SearchRequest\SearchRequestInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
/**
|
||||
* Class CachedAFAggregator
|
||||
*
|
||||
* @package AppBundle\AdvancedFilters\Aggregator
|
||||
*/
|
||||
class CachedAFAggregator implements AFAggregatorInterface
|
||||
{
|
||||
|
||||
const LIFETIME = 1800;
|
||||
|
||||
/**
|
||||
* @var CacheItemPoolInterface
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @var AFAggregatorInterface
|
||||
*/
|
||||
private $internal;
|
||||
|
||||
/**
|
||||
* CachedAFAggregator constructor.
|
||||
*
|
||||
* @param CacheItemPoolInterface $cache A CacheItemPoolInterface instance.
|
||||
* @param AFAggregatorInterface $internal A AFAggregatorInterface instance.
|
||||
*/
|
||||
public function __construct(
|
||||
CacheItemPoolInterface $cache,
|
||||
AFAggregatorInterface $internal
|
||||
) {
|
||||
$this->cache = $cache;
|
||||
$this->internal = $internal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return available filters values for specified request.
|
||||
*
|
||||
* @param SearchRequestInterface $request A SearchRequestInterface instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues(SearchRequestInterface $request)
|
||||
{
|
||||
$cachedValues = $this->cache->getItem($request->getHash());
|
||||
if (! $cachedValues->isHit()) {
|
||||
$cachedValues = new CacheItem(
|
||||
$request->getHash(),
|
||||
$this->internal->getValues($request),
|
||||
self::LIFETIME
|
||||
);
|
||||
|
||||
$this->cache->save($cachedValues);
|
||||
}
|
||||
|
||||
return $cachedValues->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\AdvancedFilters\Aggregator;
|
||||
|
||||
use AppBundle\AdvancedFilters\AdvancedFiltersConfig;
|
||||
use Common\Enum\AFSourceEnum;
|
||||
use Common\Enum\DocumentsAFNameEnum;
|
||||
use Common\Enum\AFTypeEnum;
|
||||
use IndexBundle\Aggregation\AggregationInterface;
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestInterface;
|
||||
|
||||
/**
|
||||
* Class SourceAFAggregator
|
||||
*
|
||||
* @package AppBundle\AdvancedFilters\Aggregator
|
||||
*/
|
||||
class SourceAFAggregator extends AbstractElasticSearchAFAggregator
|
||||
{
|
||||
|
||||
/**
|
||||
* Aggregation config.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAggregationConfig()
|
||||
{
|
||||
return AdvancedFiltersConfig::getConfig(AFSourceEnum::SOURCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default value for this aggregation results.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultValue()
|
||||
{
|
||||
return AdvancedFiltersConfig::getDefault(AFSourceEnum::SOURCE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* Class AppBundle
|
||||
* @package AppBundle
|
||||
*/
|
||||
class AppBundle extends Bundle
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle;
|
||||
|
||||
/**
|
||||
* Class AppBundleServices
|
||||
* @package AppBundle
|
||||
*/
|
||||
class AppBundleServices
|
||||
{
|
||||
|
||||
/**
|
||||
* Application configuration.
|
||||
*
|
||||
* Implements {@see \AppBundle\Configuration\ConfigurationInterface}
|
||||
* interface.
|
||||
*/
|
||||
const CONFIGURATION = 'app.configuration';
|
||||
|
||||
/**
|
||||
* Manager for sources.
|
||||
*/
|
||||
const SOURCE_MANAGER = 'app.source_manager';
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Cache;
|
||||
|
||||
use AppBundle\Entity\CacheItem;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
/**
|
||||
* Class DoctrineCacheItemPool
|
||||
*
|
||||
* @package AppBundle\Cache
|
||||
*/
|
||||
class DoctrineCacheItemPool implements CacheItemPoolInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var object[]
|
||||
*/
|
||||
private $deferred = [];
|
||||
|
||||
/**
|
||||
* DoctrineCacheItemPool constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface instance.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Cache Item representing the specified key.
|
||||
*
|
||||
* This method must always return a CacheItemInterface object, even in case
|
||||
* of a cache miss. It MUST NOT return null.
|
||||
*
|
||||
* @param string $key The key for which to return the corresponding Cache Item.
|
||||
*
|
||||
* @return CacheItemInterface
|
||||
*/
|
||||
public function getItem($key)
|
||||
{
|
||||
$item = $this->em->find(CacheItem::class, $key);
|
||||
if (! $item instanceof CacheItem) {
|
||||
$item = new CacheItem($key, null, null, false);
|
||||
} elseif (time() > $item->getExpiresAt()) {
|
||||
$this->em->remove($item);
|
||||
$this->em->flush($item);
|
||||
|
||||
$item = new CacheItem($key, null, null, false);
|
||||
}
|
||||
|
||||
$this->garbageCollector();
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a traversable set of cache items.
|
||||
*
|
||||
* @param string[] $keys An indexed array of keys of items to retrieve.
|
||||
*
|
||||
* @return array|\Traversable
|
||||
*/
|
||||
public function getItems(array $keys = [])
|
||||
{
|
||||
$items = $this->em->getRepository(CacheItem::class)
|
||||
->findBy([ 'key' => $keys ]);
|
||||
|
||||
$this->garbageCollector();
|
||||
|
||||
//
|
||||
// todo add proper code here!
|
||||
//
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms if the cache contains specified cache item.
|
||||
*
|
||||
* Note: This method MAY avoid retrieving the cached value for performance
|
||||
* reasons. This could result in a race condition with
|
||||
* CacheItemInterface::get(). To avoid such situation use
|
||||
* CacheItemInterface::isHit() instead.
|
||||
*
|
||||
* @param string $key The key for which to check existence.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
return $this->getItem($key)->isHit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all items in the pool.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
/** @var EntityRepository $repository */
|
||||
$repository = $this->em->getRepository(CacheItem::class);
|
||||
|
||||
$repository->createQueryBuilder('Item')
|
||||
->delete()
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the item from the pool.
|
||||
*
|
||||
* @param string $key The key to delete.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
$this->garbageCollector();
|
||||
|
||||
return $this->deleteItems([ $key ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes multiple items from the pool.
|
||||
*
|
||||
* @param string[] $keys An array of keys that should be removed from the pool.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
/** @var EntityRepository $repository */
|
||||
$repository = $this->em->getRepository(CacheItem::class);
|
||||
|
||||
$repository->createQueryBuilder('Item')
|
||||
->delete()
|
||||
->where('Item.key IN ('. implode(', ', \nspl\a\map(function ($key) {
|
||||
return "'{$key}'";
|
||||
}, $keys)) .')')
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$this->garbageCollector();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists a cache item immediately.
|
||||
*
|
||||
* @param CacheItemInterface $item The cache item to save.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function save(CacheItemInterface $item)
|
||||
{
|
||||
if (! $item instanceof CacheItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->em->persist($item);
|
||||
$this->em->flush($item);
|
||||
|
||||
$this->garbageCollector();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a cache item to be persisted later.
|
||||
*
|
||||
* @param CacheItemInterface $item The cache item to save.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item)
|
||||
{
|
||||
$this->deferred[] = $item;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists any deferred cache items.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
foreach ($this->deferred as $item) {
|
||||
$this->em->persist($item);
|
||||
}
|
||||
$this->em->flush($this->deferred);
|
||||
$this->deferred = [];
|
||||
|
||||
$this->garbageCollector();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function garbageCollector()
|
||||
{
|
||||
if (mt_rand(0, 10) <= 1) {
|
||||
$this->em->createQueryBuilder()
|
||||
->delete()
|
||||
->from(CacheItem::class, 'Item')
|
||||
->where('Item.expiresAt < CURRENT_TIMESTAMP()')
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Command;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Filesystem\LockHandler;
|
||||
|
||||
/**
|
||||
* Class AbstractSingleCopyCommand
|
||||
*
|
||||
* Base class for commands which should be run in single instance at time.
|
||||
*
|
||||
* @package AppBundle\Command
|
||||
*/
|
||||
abstract class AbstractSingleCopyCommand extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* AbstractSingleCopyCommand constructor.
|
||||
*
|
||||
* @param string $name Command name.
|
||||
* @param LoggerInterface $logger A LoggerInterface instabce.
|
||||
*/
|
||||
public function __construct($name, LoggerInterface $logger)
|
||||
{
|
||||
parent::__construct($name);
|
||||
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @see setCode()
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$lock = new LockHandler($this->getName());
|
||||
if (!$lock->lock()) {
|
||||
$output->writeln(sprintf(
|
||||
'Command \'%s\' is already executing.',
|
||||
$this->getName()
|
||||
));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->doExecute($input, $output);
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->critical(sprintf(
|
||||
'Command \'%s\' got exception \'%s\' while executing. %s',
|
||||
$this->getName(),
|
||||
get_class($exception),
|
||||
$exception->getMessage()
|
||||
));
|
||||
$result = 127;
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input An InputInterface instance.
|
||||
* @param OutputInterface $output An OutputInterface instance.
|
||||
*
|
||||
* @return null|integer
|
||||
*/
|
||||
abstract protected function doExecute(InputInterface $input, OutputInterface $output);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Command;
|
||||
|
||||
use AppBundle\Manager\Source\SourceManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class FetchSourcesCommand
|
||||
* @package AppBundle\Command
|
||||
*/
|
||||
class FetchSourcesCommand extends AbstractSingleCopyCommand
|
||||
{
|
||||
|
||||
/**
|
||||
* Command name.
|
||||
*/
|
||||
const NAME = 'socialhose:sources:fetch';
|
||||
|
||||
/**
|
||||
* @var SourceManagerInterface
|
||||
*/
|
||||
private $manager;
|
||||
|
||||
/**
|
||||
* FetchSourcesCommand constructor.
|
||||
*
|
||||
* @param SourceManagerInterface $manager A SourceManagerInterface instance.
|
||||
* @param LoggerInterface $logger A LoggerInterface instance.
|
||||
*/
|
||||
public function __construct(
|
||||
SourceManagerInterface $manager,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
parent::__construct(self::NAME, $logger);
|
||||
|
||||
$this->manager = $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the current command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setDescription('Fetch all sources from external index.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input An InputInterface instance.
|
||||
* @param OutputInterface $output An OutputInterface instance.
|
||||
*
|
||||
* @return null|integer
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->manager->pullFromExternal();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Command;
|
||||
|
||||
use IndexBundle\Index\External\InternalHoseIndex;
|
||||
use IndexBundle\Model\Generator\ExternalDocumentGenerator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class GenerateCommand
|
||||
*
|
||||
* This command generate random documents and put it into demo elasticsearch
|
||||
* server.
|
||||
*
|
||||
* @package AppBundle\Command
|
||||
*/
|
||||
class GenerateCommand extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* Command name.
|
||||
*/
|
||||
const NAME = 'socialhose:generate';
|
||||
|
||||
/**
|
||||
* @var InternalHoseIndex
|
||||
*/
|
||||
private $index;
|
||||
|
||||
/**
|
||||
* GenerateCommand constructor.
|
||||
*
|
||||
* @param InternalHoseIndex $index A InternalHoseIndex instance.
|
||||
*/
|
||||
public function __construct(InternalHoseIndex $index)
|
||||
{
|
||||
parent::__construct(self::NAME);
|
||||
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the current command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDescription('Generate random documents. Use only for development.')
|
||||
->addOption(
|
||||
'count',
|
||||
'c',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'How much document create',
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$generator = new ExternalDocumentGenerator();
|
||||
|
||||
$count = $input->getOption('count');
|
||||
|
||||
$documents = [];
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$documents[] = $generator->generate();
|
||||
}
|
||||
|
||||
$this->index->index($documents);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Command;
|
||||
|
||||
use AppBundle\Utils\Purger\TruncateORMPurger;
|
||||
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
|
||||
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use IndexBundle\Fixture\Executor\Factory\IndexFixtureExecutorFactory;
|
||||
use IndexBundle\Fixture\Loader\IndexFixtureLoader;
|
||||
use IndexBundle\Index\External\InternalHoseIndex;
|
||||
use IndexBundle\Index\Internal\InternalIndexInterface;
|
||||
use IndexBundle\Index\Source\SourceIndexInterface;
|
||||
use IndexBundle\Util\Initializer\ExternalIndexInitializer;
|
||||
use IndexBundle\Util\Initializer\InternalIndexInitializer;
|
||||
use IndexBundle\Util\Initializer\SourceIndexInitializer;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader as DataFixturesLoader;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
/**
|
||||
* Class LoadDataFixturesCommand
|
||||
* @package AppBundle\Command
|
||||
*/
|
||||
class LoadDataFixturesCommand extends AbstractSingleCopyCommand
|
||||
{
|
||||
|
||||
/**
|
||||
* Command name.
|
||||
*/
|
||||
const NAME = 'socialhose:fixtures:load';
|
||||
|
||||
/**
|
||||
* @var KernelInterface
|
||||
*/
|
||||
private $kernel;
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var InternalHoseIndex
|
||||
*/
|
||||
private $externalIndex;
|
||||
|
||||
/**
|
||||
* @var InternalIndexInterface
|
||||
*/
|
||||
private $internalIndex;
|
||||
|
||||
/**
|
||||
* @var SourceIndexInterface
|
||||
*/
|
||||
private $sourceIndex;
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* LoadDataFixturesCommand constructor.
|
||||
* @param KernelInterface $kernel A KernelInterface instance.
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface
|
||||
* instance.
|
||||
* @param InternalHoseIndex $externalIndex A HoseIndex instance.
|
||||
* @param InternalIndexInterface $internalIndex A InternalIndexInterface
|
||||
* instance.
|
||||
* @param SourceIndexInterface $sourceIndex A SourceIndexInterface
|
||||
* instance.
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
* @param LoggerInterface $logger A LoggerInterface instance.
|
||||
*/
|
||||
public function __construct(
|
||||
KernelInterface $kernel,
|
||||
EntityManagerInterface $em,
|
||||
InternalHoseIndex $externalIndex,
|
||||
InternalIndexInterface $internalIndex,
|
||||
SourceIndexInterface $sourceIndex,
|
||||
ContainerInterface $container,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
parent::__construct(self::NAME, $logger);
|
||||
|
||||
$this->kernel = $kernel;
|
||||
$this->em = $em;
|
||||
$this->externalIndex = $externalIndex;
|
||||
$this->internalIndex = $internalIndex;
|
||||
$this->sourceIndex = $sourceIndex;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the current command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName(self::NAME)
|
||||
->setDescription('Load database and index fixtures.')
|
||||
->addOption('force', null, InputOption::VALUE_NONE)
|
||||
->addOption(
|
||||
'without-index',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Do not load index fixtures.'
|
||||
)
|
||||
->addOption(
|
||||
'without-database',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Do not load database fixtures.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input An InputInterface instance.
|
||||
* @param OutputInterface $output An OutputInterface instance.
|
||||
*
|
||||
* @return null|integer
|
||||
*/
|
||||
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$withoutDatabase = $input->getOption('without-database');
|
||||
$withoutIndex = $input->getOption('without-index');
|
||||
|
||||
if ($withoutDatabase && $withoutIndex) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (! $input->getOption('force')) {
|
||||
// Because this command can rebuild all index we use this option as
|
||||
// as flag in order to prevent accidental run.
|
||||
$message = 'Provide --force option if you really want to initialize index.';
|
||||
$output->writeln($message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (! $input->getOption('no-interaction') && ! $this->confirm($input, $output)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get list of available data fixtures paths.
|
||||
$paths = $this->getFixturesPaths();
|
||||
|
||||
// Load database fixtures.
|
||||
if (! $withoutDatabase) {
|
||||
$this->loadDatabase($output, $paths);
|
||||
}
|
||||
|
||||
// Load index fixtures.
|
||||
if (! $withoutIndex) {
|
||||
$this->loadIndex($output, $paths);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getFixturesPaths()
|
||||
{
|
||||
$paths = [];
|
||||
foreach ($this->kernel->getBundles() as $bundle) {
|
||||
$path = $bundle->getPath() . '/DataFixtures/';
|
||||
if (is_dir($path)) {
|
||||
$paths[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input A InputInterface instance.
|
||||
* @param OutputInterface $output A OutputInterface instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function confirm(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
/** @var \Symfony\Component\Console\Helper\SymfonyQuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
|
||||
$output->writeln('');
|
||||
$output->writeln('<comment>All data will be purged.</comment>');
|
||||
$question = new ConfirmationQuestion('Are you sure (y/N)? ', false);
|
||||
|
||||
return (boolean) $helper->ask($input, $output, $question);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output A OutputInterface instance.
|
||||
* @param array $paths Array of fixtures directories path.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadDatabase(OutputInterface $output, array $paths)
|
||||
{
|
||||
//
|
||||
// Purge internal tables.
|
||||
//
|
||||
$this->em->getConnection()->executeQuery('TRUNCATE internal_notification_scheduling');
|
||||
|
||||
$output->writeln('<comment>Load database fixtures:</comment>');
|
||||
|
||||
$loader = new DataFixturesLoader($this->container);
|
||||
foreach ($paths as $path) {
|
||||
$loader->loadFromDirectory($path);
|
||||
}
|
||||
|
||||
$fixtures = $loader->getFixtures();
|
||||
if (!$fixtures) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Could not find any fixtures to load in: %s', "\n\n- " . implode("\n- ", $paths))
|
||||
);
|
||||
}
|
||||
|
||||
$purger = new TruncateORMPurger(new ORMPurger($this->em));
|
||||
$purger->purge();
|
||||
|
||||
$executor = new ORMExecutor($this->em);
|
||||
$executor->setLogger(function ($message) use ($output) {
|
||||
$output->writeln(sprintf(' <comment>></comment> <info>%s</info>', $message));
|
||||
});
|
||||
$executor->execute($fixtures, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output A OutputInterface instance.
|
||||
* @param array $paths Array of fixtures directories path.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadIndex(OutputInterface $output, array $paths)
|
||||
{
|
||||
$output->writeln('<comment>Load index fixtures:</comment>');
|
||||
|
||||
$loader = new IndexFixtureLoader($this->container);
|
||||
foreach ($paths as $path) {
|
||||
$loader->loadFromDirectory($path);
|
||||
}
|
||||
|
||||
$fixtures = $loader->getFixtures();
|
||||
|
||||
// Purge indexes.
|
||||
ExternalIndexInitializer::initialize($this->externalIndex);
|
||||
InternalIndexInitializer::initialize($this->internalIndex);
|
||||
SourceIndexInitializer::initialize($this->sourceIndex);
|
||||
|
||||
$executorFactory = new IndexFixtureExecutorFactory();
|
||||
$executorFactory->external($this->externalIndex)
|
||||
->setLogger(function ($message) use ($output) {
|
||||
$output->writeln(sprintf(' <comment>></comment> <info>%s</info>', $message));
|
||||
})
|
||||
->execute($fixtures);
|
||||
$executorFactory->internal($this->internalIndex)
|
||||
->setLogger(function ($message) use ($output) {
|
||||
$output->writeln(sprintf(' <comment>></comment> <info>%s</info>', $message));
|
||||
})
|
||||
->execute($fixtures);
|
||||
$executorFactory->source($this->sourceIndex)
|
||||
->setLogger(function ($message) use ($output) {
|
||||
$output->writeln(sprintf(' <comment>></comment> <info>%s</info>', $message));
|
||||
})
|
||||
->execute($fixtures);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Command;
|
||||
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use Elasticsearch\Client;
|
||||
use Elasticsearch\ClientBuilder;
|
||||
use IndexBundle\Index\Strategy\HoseIndexStrategy;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class ReindexDocumentsCommand
|
||||
* @package AppBundle\Command
|
||||
*/
|
||||
class ReindexDocumentsCommand extends AbstractSingleCopyCommand
|
||||
{
|
||||
|
||||
/**
|
||||
* Command name.
|
||||
*/
|
||||
const NAME = 'socialhose:reindex:documents';
|
||||
|
||||
/**
|
||||
* Size of document which is fetched for reindex.
|
||||
*/
|
||||
const BUCKET_SIZE = 10;
|
||||
|
||||
/**
|
||||
* Name of the file where command should store idx of failed bucket.
|
||||
*/
|
||||
const FAILED_IDX_FILE = 'document_reindex_fail_idx';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $host;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $port;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $varPath;
|
||||
|
||||
/**
|
||||
* @var HoseIndexStrategy
|
||||
*/
|
||||
private $strategy;
|
||||
|
||||
/**
|
||||
* FetchSourcesCommand constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger A LoggerInterface instance.
|
||||
* @param string $host A elasticsearch host name.
|
||||
* @param string $port A elasticsearch port.
|
||||
* @param string $varPath Path to directory where failed idx file
|
||||
* should stored.
|
||||
*/
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
$host,
|
||||
$port,
|
||||
$varPath
|
||||
) {
|
||||
parent::__construct(self::NAME, $logger);
|
||||
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
$this->varPath = realpath($varPath);
|
||||
|
||||
if ($this->varPath === false) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'$varPath value \'%s\' is invalid path.',
|
||||
$varPath
|
||||
));
|
||||
}
|
||||
|
||||
if (! is_dir($this->varPath)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'$varPath value \'%s\' is not a directory.',
|
||||
$varPath
|
||||
));
|
||||
}
|
||||
|
||||
if (! is_writable($this->varPath)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'$varPath value \'%s\' is not available for writing.',
|
||||
$varPath
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the current command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDescription('Migrate documents from one document index to another')
|
||||
->addArgument('src', InputArgument::REQUIRED, 'Source index name')
|
||||
->addArgument('dest', InputArgument::REQUIRED, 'Destination index name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input An InputInterface instance.
|
||||
* @param OutputInterface $output An OutputInterface instance.
|
||||
*
|
||||
* @return null|integer
|
||||
*
|
||||
* @throws \Exception If can't reindex documents.
|
||||
*/
|
||||
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$currentBucketIdx = null;
|
||||
|
||||
try {
|
||||
$destIndex = $input->getArgument('dest');
|
||||
|
||||
$client = ClientBuilder::create()
|
||||
->setHosts([
|
||||
[
|
||||
'host' => $this->host,
|
||||
'port' => $this->port,
|
||||
],
|
||||
])
|
||||
->build();
|
||||
|
||||
$response = $client->search([
|
||||
'body' => ['query' => ['match_all' => (object) []]],
|
||||
'index' => $input->getArgument('src'),
|
||||
'type' => 'document',
|
||||
'scroll' => '1m',
|
||||
'size' => self::BUCKET_SIZE,
|
||||
]);
|
||||
|
||||
$totalBucketCount = $response['hits']['total'] / self::BUCKET_SIZE;
|
||||
|
||||
$scrollId = $response['_scroll_id'];
|
||||
$currentBucketIdx = $this->getCurrentBucketIdx();
|
||||
|
||||
//
|
||||
// Scroll required number of bucket.
|
||||
//
|
||||
// We can't use offset 'cause ElasticSearch are limiting max allowed
|
||||
// offset value. Also because of it we use scroll api instead of just
|
||||
// make search with offset.
|
||||
//
|
||||
for ($i = 0; $i < $currentBucketIdx; ++$i) {
|
||||
$response = $client->scroll([ 'scroll_id' => $scrollId ]);
|
||||
}
|
||||
|
||||
//
|
||||
// Reindex all documents.
|
||||
//
|
||||
while ($this->indexDocuments($response['hits']['hits'], $client, $destIndex)) {
|
||||
$output->writeln(sprintf(
|
||||
'Process %d from %d buckets',
|
||||
$currentBucketIdx,
|
||||
$totalBucketCount
|
||||
));
|
||||
|
||||
$response = $client->scroll([
|
||||
'scroll_id' => $scrollId,
|
||||
'scroll' => '1m',
|
||||
]);
|
||||
$currentBucketIdx++;
|
||||
}
|
||||
|
||||
if (file_exists($this->varPath . DIRECTORY_SEPARATOR . self::FAILED_IDX_FILE)) {
|
||||
unlink($this->varPath . DIRECTORY_SEPARATOR . self::FAILED_IDX_FILE);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
if ($currentBucketIdx !== null) {
|
||||
file_put_contents(
|
||||
$this->varPath . DIRECTORY_SEPARATOR . self::FAILED_IDX_FILE,
|
||||
$currentBucketIdx
|
||||
);
|
||||
}
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $documents Array of raw documents.
|
||||
* @param Client $client A ElasticSearch Client instance.
|
||||
* @param string $index A index name.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function indexDocuments(array $documents, Client $client, $index)
|
||||
{
|
||||
if (count($documents) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We should split documents into several buckets 'cause we may exceed allowed
|
||||
// request size for ElasticSearch (10mb for AWS instance).
|
||||
$buckets = [];
|
||||
|
||||
$idx = 0;
|
||||
$count = 0;
|
||||
foreach ($documents as $document) {
|
||||
if (++$count > self::BUCKET_SIZE / 2) {
|
||||
$idx++;
|
||||
$count = 0;
|
||||
}
|
||||
|
||||
$data = $document['_source'];
|
||||
if (isset($data['collection_id'])) {
|
||||
$data[FieldNameEnum::COLLECTION_ID] = $data['collection_id'];
|
||||
$data[FieldNameEnum::COLLECTION_TYPE] = $data['collection_type'];
|
||||
}
|
||||
|
||||
if (isset($data['deleted_from'])) {
|
||||
$data[FieldNameEnum::DELETE_FROM] = $data['deleted_from'];
|
||||
}
|
||||
|
||||
if (! isset($data[FieldNameEnum::DELETE_FROM])) {
|
||||
$data[FieldNameEnum::DELETE_FROM] = [];
|
||||
}
|
||||
|
||||
$buckets[$idx][] = [
|
||||
'index' => [
|
||||
'_index' => $index,
|
||||
'_type' => 'document',
|
||||
],
|
||||
];
|
||||
$buckets[$idx][] = $this->getStrategy()->getIndexableData($data);
|
||||
}
|
||||
|
||||
foreach ($buckets as $bucket) {
|
||||
$client->bulk(['body' => $bucket]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HoseIndexStrategy
|
||||
*/
|
||||
private function getStrategy()
|
||||
{
|
||||
if ($this->strategy === null) {
|
||||
$this->strategy = new HoseIndexStrategy();
|
||||
}
|
||||
|
||||
return $this->strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
private function getCurrentBucketIdx()
|
||||
{
|
||||
$filePath = $this->varPath . DIRECTORY_SEPARATOR . self::FAILED_IDX_FILE;
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
return (int) file_get_contents($filePath);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Command;
|
||||
|
||||
use AppBundle\Configuration\ConfigurationInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class SyncSiteConfigCommand
|
||||
* @package AppBundle\Command
|
||||
*/
|
||||
class SyncSiteConfigCommand extends Command
|
||||
{
|
||||
|
||||
const NAME = 'socialhose:site-settings:sync';
|
||||
|
||||
/**
|
||||
* @var ConfigurationInterface
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* SyncSiteConfigCommand constructor.
|
||||
*
|
||||
* @param ConfigurationInterface $configuration A ConfigurationInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(ConfigurationInterface $configuration)
|
||||
{
|
||||
parent::__construct(self::NAME);
|
||||
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the current command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setDescription('Sync site settings with base');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$this->configuration->syncWithDefinitions();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Command;
|
||||
|
||||
use CacheBundle\Entity\Query\StoredQuery;
|
||||
use CacheBundle\Repository\StoredQueryRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class UpdateStoredQueriesCommand
|
||||
* @package AppBundle\Command
|
||||
*/
|
||||
class UpdateStoredQueriesCommand extends Command
|
||||
{
|
||||
|
||||
const NAME = 'socialhose:stored-query:update';
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var ProducerInterface
|
||||
*/
|
||||
private $producer;
|
||||
|
||||
/**
|
||||
* UpdateStoredQueriesCommand constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface instance.
|
||||
* @param ProducerInterface $producer A ProducerInterface instance.
|
||||
*/
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
ProducerInterface $producer
|
||||
) {
|
||||
parent::__construct(self::NAME);
|
||||
|
||||
$this->em = $em;
|
||||
$this->producer = $producer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configures the current command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setDescription('Update stored queries.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$queries = $this->getQueries();
|
||||
|
||||
/** @var StoredQuery $query */
|
||||
foreach ($queries as $query) {
|
||||
$this->producer->publish($query->getId());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator
|
||||
*/
|
||||
private function getQueries()
|
||||
{
|
||||
/** @var StoredQueryRepository $repository */
|
||||
$repository = $this->em->getRepository(StoredQuery::class);
|
||||
|
||||
$iterate = $repository->getForUpdating()->getQuery()->iterate();
|
||||
|
||||
foreach ($iterate as $query) {
|
||||
$query = $query[0];
|
||||
yield $query;
|
||||
$this->em->detach($query);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Configuration;
|
||||
|
||||
/**
|
||||
* Class AbstractConfiguration
|
||||
* @package AppBundle\Configuration
|
||||
*/
|
||||
abstract class AbstractConfiguration implements ConfigurationInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ConfigurationParameterInterface[]
|
||||
*/
|
||||
private $map = [];
|
||||
|
||||
/**
|
||||
* Array of changed parameters name.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $changed = [];
|
||||
|
||||
/**
|
||||
* Array of removed parameters name.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $removed = [];
|
||||
|
||||
/**
|
||||
* @var ConfigurationDefinitionMap
|
||||
*/
|
||||
protected $definitions;
|
||||
|
||||
/**
|
||||
* AbstractConfiguration constructor.
|
||||
*
|
||||
* @param ConfigurationDefinitionMap $definitions A ConfigurationDefinitionMap
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(ConfigurationDefinitionMap $definitions)
|
||||
{
|
||||
$this->syncParameters();
|
||||
$this->definitions = $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parameter value by name.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
* @param mixed $default Default value if parameter not found.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParameter($name, $default = null)
|
||||
{
|
||||
if (! isset($this->map[$name]) || isset($this->removed[$name])) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$param = $this->map[$name];
|
||||
if ($param === null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$value = $param->getValue();
|
||||
settype($value, $this->definitions->getDefinition($name)['type']);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync current parameters with database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function syncParameters()
|
||||
{
|
||||
$params = $this->loadData();
|
||||
|
||||
foreach ($params as $param) {
|
||||
$this->map[$param->getName()] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available parameters.
|
||||
*
|
||||
* @return ConfigurationParameterInterface[]
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parameter value by name.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
* @param mixed $value New parameter value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setParameter($name, $value)
|
||||
{
|
||||
$this->map[$name]->setValue($this->definitions->normalize($name, $value));
|
||||
$this->changed[$name] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters.
|
||||
*
|
||||
* @param array $params Array where key is parameter name and value is new
|
||||
* value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setParameters(array $params)
|
||||
{
|
||||
foreach ($params as $name => $newValue) {
|
||||
$this->setParameter($name, $newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync configuration with storage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sync()
|
||||
{
|
||||
$changed = \nspl\a\filter(function (ConfigurationParameterInterface $parameter) {
|
||||
return isset($this->changed[$parameter->getName()]);
|
||||
}, $this->map);
|
||||
|
||||
$removed = \nspl\a\filter(function (ConfigurationParameterInterface $parameter) {
|
||||
return isset($this->removed[$parameter->getName()]);
|
||||
}, $this->map);
|
||||
|
||||
if ((count($changed) === 0) && (count($removed) === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->doSync($changed, $removed);
|
||||
|
||||
$this->map = \nspl\a\filter(function (ConfigurationParameterInterface $parameter) {
|
||||
return ! isset($this->removed[$parameter->getName()]);
|
||||
}, $this->map);
|
||||
|
||||
$this->changed = [];
|
||||
$this->removed = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync parameters with list of available.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function syncWithDefinitions()
|
||||
{
|
||||
$notExists = array_flip(ParametersName::getAvailables());
|
||||
|
||||
/** @var ConfigurationParameterMutableInterface $parameter */
|
||||
foreach ($this->map as $name => $parameter) {
|
||||
if (! ParametersName::isExists($name)) {
|
||||
$this->removed[$name] = $parameter;
|
||||
} else {
|
||||
$definition = $this->definitions->getDefinition($name);
|
||||
$parameter
|
||||
->setTitle($definition['title'])
|
||||
->setSection($definition['section']);
|
||||
$this->changed[$name] = $parameter;
|
||||
unset($notExists[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
$notExists = array_keys($notExists);
|
||||
foreach ($notExists as $name) {
|
||||
$this->map[$name] = $this->createParameter($name);
|
||||
$this->changed[$name] = true;
|
||||
}
|
||||
|
||||
$this->sync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default parameter from config.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
*
|
||||
* @return ConfigurationParameterInterface
|
||||
*/
|
||||
abstract protected function createParameter($name);
|
||||
|
||||
/**
|
||||
* Load configuration from storage.
|
||||
*
|
||||
* @return ConfigurationParameterInterface[]
|
||||
*/
|
||||
abstract protected function loadData();
|
||||
|
||||
/**
|
||||
* @param ConfigurationParameterInterface[]|array $changed Array of changed
|
||||
* instances.
|
||||
* @param ConfigurationParameterInterface[]|array $removed Array of removed
|
||||
* parameter names.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function doSync(array $changed, array $removed);
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Configuration;
|
||||
|
||||
use Ivory\CKEditorBundle\Form\Type\CKEditorType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Validator\Constraints\Callback;
|
||||
use Symfony\Component\Validator\Constraints\Type;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Class ConfigurationDefinitionMap
|
||||
* @package AppBundle\Configuration
|
||||
*/
|
||||
class ConfigurationDefinitionMap implements \IteratorAggregate
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $availablePeriods = [
|
||||
'day',
|
||||
'days',
|
||||
'week',
|
||||
'weeks',
|
||||
'month',
|
||||
'months',
|
||||
'year',
|
||||
'years',
|
||||
'hour',
|
||||
'hours',
|
||||
'minute',
|
||||
'minutes',
|
||||
'second',
|
||||
'seconds',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
private $definitions;
|
||||
|
||||
/**
|
||||
* ConfigurationDefinitionMap constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->definitions = [
|
||||
ParametersName::MAILER_ADDRESS => [
|
||||
'section' => 'Mailer',
|
||||
'title' => 'support@socialhose.io',
|
||||
'type' => 'string',
|
||||
'formType' => null,
|
||||
'default' => 'support@socialhose.io',
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'string' ]),
|
||||
],
|
||||
ParametersName::MAILER_SENDER_NAME => [
|
||||
'section' => 'Mailer',
|
||||
'title' => 'Socialhose',
|
||||
'type' => 'string',
|
||||
'formType' => null,
|
||||
'default' => 'Socialhose',
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'string' ]),
|
||||
],
|
||||
ParametersName::NOTIFICATION_COMMENTS_PER_DOCUMENT => [
|
||||
'section' => 'Notification',
|
||||
'title' => 'Max comments per document',
|
||||
'type' => 'integer',
|
||||
'formType' => null,
|
||||
'default' => 5,
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'numeric' ]),
|
||||
],
|
||||
ParametersName::NOTIFICATION_DOCUMENT_PER_FEED => [
|
||||
'section' => 'Notification',
|
||||
'title' => 'Max documents per feed in notification',
|
||||
'type' => 'integer',
|
||||
'formType' => null,
|
||||
'default' => 10,
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'numeric' ]),
|
||||
],
|
||||
ParametersName::NOTIFICATION_START_EXTRACT_LENGTH => [
|
||||
'section' => 'Search',
|
||||
'title' => 'Number of character for \'Start of text extract\'',
|
||||
'type' => 'integer',
|
||||
'formType' => null,
|
||||
'default' => 400,
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'numeric' ]),
|
||||
],
|
||||
ParametersName::NOTIFICATION_CONTEXT_EXTRACT_LENGTH => [
|
||||
'section' => 'Search',
|
||||
'title' => 'Numbers of character before and after first search keyword',
|
||||
'type' => 'integer',
|
||||
'formType' => null,
|
||||
'default' => 150,
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'numeric' ]),
|
||||
],
|
||||
ParametersName::SEARCH_DOCUMENTS_FROM_FUTURE => [
|
||||
'section' => 'Search',
|
||||
'title' => 'What we should do if documents published date in future',
|
||||
'type' => 'string',
|
||||
'formType' => ChoiceType::class,
|
||||
'choices' => [
|
||||
'Exclude' => 'exclude',
|
||||
'Fix date' => 'fix_date',
|
||||
],
|
||||
'default' => 'exclude',
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'numeric' ]),
|
||||
],
|
||||
ParametersName::NOTIFICATION_EMPTY_MESSAGE => [
|
||||
'section' => 'Notification',
|
||||
'title' => 'Empty notification message',
|
||||
'type' => 'string',
|
||||
'formType' => CKEditorType::class,
|
||||
'default' => '<p>We have not found any mentions for your search criteria today.</p>',
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'string' ]),
|
||||
],
|
||||
ParametersName::NOTIFICATION_SEND_HISTORY_MODIFY => [
|
||||
'section' => 'Notification',
|
||||
'title' => 'How long we story notification history',
|
||||
'type' => 'string',
|
||||
'formType' => null,
|
||||
'default' => '-3 months',
|
||||
'normalizer' => function ($value) {
|
||||
return preg_replace('/(\d+)/', '-$1', str_replace('-', '', $value));
|
||||
},
|
||||
'denormalizer' => function ($value) {
|
||||
return str_replace('-', '', $value);
|
||||
},
|
||||
'constrains' => [
|
||||
new Type([ 'type' => 'string' ]),
|
||||
new Callback([ $this, 'validateHistoryLifetime' ]),
|
||||
],
|
||||
],
|
||||
|
||||
ParametersName::REGISTRATION_PAYMENT_AWAITING => [
|
||||
'section' => 'Registration',
|
||||
'title' => 'Message after user provide billing information',
|
||||
'type' => 'string',
|
||||
'formType' => CKEditorType::class,
|
||||
'default' => '<p>Thanks for submitting the form. Your payment is processing. When it done, you will receive email with passwird.</p>',
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'string' ]),
|
||||
],
|
||||
|
||||
ParametersName::MAIL_PASSWORD => [
|
||||
'section' => 'Email',
|
||||
'title' => 'Password email content',
|
||||
'type' => 'string',
|
||||
'formType' => CKEditorType::class,
|
||||
'default' => '<h4>Hello {{ user.firstName }} {{ user.lastName }}!</h4><p>You new password is {{ password }}</p><p>Regards, the Team.</p>',
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'string' ]),
|
||||
],
|
||||
ParametersName::MAIL_VERIFICATION_SUCCESS => [
|
||||
'section' => 'Email',
|
||||
'title' => 'Verification success email content',
|
||||
'type' => 'string',
|
||||
'formType' => CKEditorType::class,
|
||||
'default' => '<h4>Hello {{ user.firstName }} {{ user.lastName }}!</h4><p>You registration is verified and you may proceed login with you credentials </p><p> Email: {{ user.email }} Password: {{ password }}</p><p>Regards, the Team.</p>',
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'string' ]),
|
||||
],
|
||||
ParametersName::MAIL_VERIFICATION_REJECT => [
|
||||
'section' => 'Email',
|
||||
'title' => 'Verification success email content',
|
||||
'type' => 'string',
|
||||
'formType' => CKEditorType::class,
|
||||
'default' => '<h4>Hello {{ user.firstName }} {{ user.lastName }}!</h4><p>Unfortunately you registration is rejected. Payments will be refund. </p><p>Regards, the Team.</p>',
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'string' ]),
|
||||
],
|
||||
ParametersName::MAIL_RESETTING_CONFIRMATION => [
|
||||
'section' => 'Email',
|
||||
'title' => 'Password resetting email content',
|
||||
'type' => 'string',
|
||||
'formType' => CKEditorType::class,
|
||||
'default' => '<h4>Hello {{ user.firstName }} {{ user.lastName }}!</h4> <p> To reset your password - please visit {{ confirmationUrl }} </p> <p> Regards, the Team. </p>',
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'string' ]),
|
||||
],
|
||||
ParametersName::MAIL_UNSUBSCRIBE => [
|
||||
'section' => 'Email',
|
||||
'title' => 'Unsubscribe email content',
|
||||
'type' => 'string',
|
||||
'formType' => CKEditorType::class,
|
||||
'default' => '<p>{{ user.firstName}} {{ user.lastName }} has unsubscribed from your notification</p>',
|
||||
'normalizer' => null,
|
||||
'denormalizer' => null,
|
||||
'constrains' => new Type([ 'type' => 'string' ]),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get definition for specified parameter.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDefinition($name)
|
||||
{
|
||||
if (! isset($this->definitions[$name])) {
|
||||
throw new \InvalidArgumentException('Unknown '. $name);
|
||||
}
|
||||
|
||||
return $this->definitions[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize value.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
* @param mixed $value Raw value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function normalize($name, $value)
|
||||
{
|
||||
$definition = $this->getDefinition($name);
|
||||
|
||||
if (isset($definition['normalizer'])) {
|
||||
$value = $definition['normalizer']($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Denormalize value.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
* @param mixed $value Normalized value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function denormalize($name, $value)
|
||||
{
|
||||
$definition = $this->getDefinition($name);
|
||||
|
||||
if (isset($definition['denormalizer'])) {
|
||||
$value = $definition['denormalizer']($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an external iterator.
|
||||
*
|
||||
* @return Traversable An instance of an object implementing \Iterator or
|
||||
* \Traversable
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value Raw value from user.
|
||||
* @param ExecutionContextInterface $context A ExecutionContextInterface instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function validateHistoryLifetime($value, ExecutionContextInterface $context)
|
||||
{
|
||||
//
|
||||
// Split expiration time on groups
|
||||
//
|
||||
$valid = true;
|
||||
$matches = [];
|
||||
$result = preg_match_all('/(\d+\s?[A-Za-z]+)/', $value, $matches);
|
||||
|
||||
if (($result === 0) || ($result === false)) {
|
||||
$valid = false;
|
||||
} else {
|
||||
$matches = $matches[0];
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$match = trim($match);
|
||||
$parts = explode(' ', $match);
|
||||
|
||||
$count = 1;
|
||||
$period = $parts[0];
|
||||
if (count($parts) === 2) {
|
||||
list($count, $period) = $parts;
|
||||
}
|
||||
|
||||
if (! is_numeric($count) || !in_array($period, self::$availablePeriods, true)) {
|
||||
$valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! $valid) {
|
||||
$context->buildViolation('Invalid expiration time, should be space separated string where each element id match to patter "number year(s)|month(s)|week(s)|day(s)|hour(s)|minute(s)|second(s)"')
|
||||
->atPath('expirationTime')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Configuration;
|
||||
|
||||
/**
|
||||
* Interface ConfigurationImmutableInterface
|
||||
* @package AppBundle\Configuration
|
||||
*/
|
||||
interface ConfigurationImmutableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get parameter value by name.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
* @param mixed $default Default value if parameter not found.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParameter($name, $default = null);
|
||||
|
||||
/**
|
||||
* Sync current parameters with database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function syncParameters();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Configuration;
|
||||
|
||||
/**
|
||||
* Interface ConfigurationMutableInterface
|
||||
* @package AppBundle\Configuration
|
||||
*/
|
||||
interface ConfigurationInterface extends
|
||||
ConfigurationMutableInterface,
|
||||
ConfigurationImmutableInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Configuration;
|
||||
|
||||
/**
|
||||
* Interface ConfigurationMutableInterface
|
||||
* @package AppBundle\Configuration
|
||||
*/
|
||||
interface ConfigurationMutableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get all available parameters.
|
||||
*
|
||||
* @return ConfigurationParameterInterface[]
|
||||
*/
|
||||
public function getParameters();
|
||||
|
||||
/**
|
||||
* Sync parameters with list of available.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function syncWithDefinitions();
|
||||
|
||||
/**
|
||||
* Set parameter value by name.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
* @param mixed $value New parameter value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setParameter($name, $value);
|
||||
|
||||
/**
|
||||
* Set parameters.
|
||||
*
|
||||
* @param array $params Array where key is parameter name and value is new
|
||||
* value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setParameters(array $params);
|
||||
|
||||
/**
|
||||
* Sync configuration with storage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sync();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Configuration;
|
||||
|
||||
/**
|
||||
* Interface ConfigurationParameterInterface
|
||||
* @package AppBundle\Configuration
|
||||
*/
|
||||
interface ConfigurationParameterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get parameter section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSection();
|
||||
|
||||
/**
|
||||
* Get parameter name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Get parameter title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle();
|
||||
|
||||
/**
|
||||
* Get parameter value.
|
||||
*
|
||||
* @param mixed $value Parameter value.
|
||||
*
|
||||
* @return ConfigurationParameterInterface
|
||||
*/
|
||||
public function setValue($value);
|
||||
|
||||
/**
|
||||
* Get parameter value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Configuration;
|
||||
|
||||
/**
|
||||
* Interface ConfigurationParameterMutableInterface
|
||||
* @package AppBundle\Configuration
|
||||
*/
|
||||
interface ConfigurationParameterMutableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Set section
|
||||
*
|
||||
* @param string $section Section name.
|
||||
*
|
||||
* @return ConfigurationParameterMutableInterface
|
||||
*/
|
||||
public function setSection($section);
|
||||
|
||||
/**
|
||||
* Set value
|
||||
*
|
||||
* @param mixed $value Parameter value.
|
||||
*
|
||||
* @return ConfigurationParameterMutableInterface
|
||||
*/
|
||||
public function setValue($value);
|
||||
|
||||
/**
|
||||
* Set title
|
||||
*
|
||||
* @param string $title Human readable parameter title.
|
||||
*
|
||||
* @return ConfigurationParameterMutableInterface
|
||||
*/
|
||||
public function setTitle($title);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Configuration;
|
||||
|
||||
use AdminBundle\Entity\SiteSettings;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* Class ORMConfiguration
|
||||
* @package AppBundle\Configuration
|
||||
*/
|
||||
class ORMConfiguration extends AbstractConfiguration
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* Configuration constructor.
|
||||
*
|
||||
* @param ConfigurationDefinitionMap $definitions A ConfigurationDefinitionMap
|
||||
* instance.
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(
|
||||
ConfigurationDefinitionMap $definitions,
|
||||
EntityManagerInterface $em
|
||||
) {
|
||||
$this->em = $em;
|
||||
|
||||
parent::__construct($definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default parameter from config.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
*
|
||||
* @return ConfigurationParameterInterface
|
||||
*/
|
||||
protected function createParameter($name)
|
||||
{
|
||||
$config = $this->definitions->getDefinition($name);
|
||||
|
||||
return SiteSettings::create()
|
||||
->setSection($config['section'])
|
||||
->setName($name)
|
||||
->setTitle($config['title'])
|
||||
->setValue($config['default']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from storage.
|
||||
*
|
||||
* @return ConfigurationParameterInterface[]
|
||||
*/
|
||||
protected function loadData()
|
||||
{
|
||||
return $this->em->getRepository(SiteSettings::class)->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConfigurationParameterInterface[]|array $changed Array of changed
|
||||
* instances.
|
||||
* @param ConfigurationParameterInterface[]|array $removed Array of removed
|
||||
* parameter names.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function doSync(array $changed, array $removed)
|
||||
{
|
||||
foreach ($changed as $parameter) {
|
||||
$this->em->persist($parameter);
|
||||
}
|
||||
|
||||
foreach ($removed as $parameter) {
|
||||
$this->em->remove($parameter);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Configuration;
|
||||
|
||||
/**
|
||||
* Class ParametersName
|
||||
* @package AppBundle\Configuration
|
||||
*/
|
||||
final class ParametersName
|
||||
{
|
||||
|
||||
const MAILER_ADDRESS = 'mailer.address';
|
||||
const MAILER_SENDER_NAME = 'mailer.sender_name';
|
||||
|
||||
const NOTIFICATION_COMMENTS_PER_DOCUMENT = 'notification.comments_per_document_limit';
|
||||
const NOTIFICATION_DOCUMENT_PER_FEED = 'notification.documents_per_feed_limit';
|
||||
const NOTIFICATION_START_EXTRACT_LENGTH = 'notification.start_extract_length';
|
||||
const NOTIFICATION_CONTEXT_EXTRACT_LENGTH = 'notification.context_extract_length';
|
||||
const NOTIFICATION_SEND_HISTORY_MODIFY = 'notification.notification_send_history_modify';
|
||||
const NOTIFICATION_EMPTY_MESSAGE = 'notification.empty_massage';
|
||||
|
||||
const REGISTRATION_PAYMENT_AWAITING = 'registration.payment.awaiting';
|
||||
|
||||
const SEARCH_DOCUMENTS_FROM_FUTURE = 'search.documents_from_future';
|
||||
|
||||
const MAIL_PASSWORD = 'mail.password';
|
||||
const MAIL_VERIFICATION_SUCCESS = 'mail.verification.success';
|
||||
const MAIL_VERIFICATION_REJECT = 'mail.verification.reject';
|
||||
const MAIL_RESETTING_CONFIRMATION = 'mail.resetting_confirmation';
|
||||
const MAIL_UNSUBSCRIBE = 'mail.unsubscribe';
|
||||
|
||||
/**
|
||||
* Get available parameters name.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getAvailables()
|
||||
{
|
||||
return [
|
||||
self::MAILER_ADDRESS,
|
||||
self::MAILER_SENDER_NAME,
|
||||
|
||||
self::NOTIFICATION_COMMENTS_PER_DOCUMENT,
|
||||
self::NOTIFICATION_DOCUMENT_PER_FEED,
|
||||
self::NOTIFICATION_START_EXTRACT_LENGTH,
|
||||
self::NOTIFICATION_CONTEXT_EXTRACT_LENGTH,
|
||||
self::NOTIFICATION_SEND_HISTORY_MODIFY,
|
||||
self::NOTIFICATION_EMPTY_MESSAGE,
|
||||
|
||||
self::REGISTRATION_PAYMENT_AWAITING,
|
||||
|
||||
self::SEARCH_DOCUMENTS_FROM_FUTURE,
|
||||
|
||||
self::MAIL_PASSWORD,
|
||||
self::MAIL_VERIFICATION_SUCCESS,
|
||||
self::MAIL_VERIFICATION_REJECT,
|
||||
self::MAIL_RESETTING_CONFIRMATION,
|
||||
self::MAIL_UNSUBSCRIBE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Checks that specified parameter is exists.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isExists($name)
|
||||
{
|
||||
return in_array($name, self::getAvailables(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters constructor.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function __clone()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller;
|
||||
|
||||
use AppBundle\Controller\V1\AbstractV1Controller;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Feed\Formatter\FeedFormatterInterface;
|
||||
use CacheBundle\Feed\Formatter\FormatterOptions;
|
||||
use CacheBundle\Repository\CommonFeedRepository;
|
||||
use Common\Enum\FormatNameEnum;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use UserBundle\Enum\ThemeOptionExtractEnum;
|
||||
|
||||
/**
|
||||
* Class IndexController
|
||||
* @package AppBundle\Controller
|
||||
*
|
||||
* @Route("/", service="app.controller.index")
|
||||
*/
|
||||
class IndexController extends AbstractV1Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var FeedFormatterInterface
|
||||
*/
|
||||
private $feedFormatter;
|
||||
|
||||
/**
|
||||
* IndexController constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface
|
||||
* instance.
|
||||
* @param FeedFormatterInterface $feedFormatter A FeedFormatterInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, FeedFormatterInterface $feedFormatter)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->feedFormatter = $feedFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/feed/{id}.{format}")
|
||||
*
|
||||
* @param Request $request A HTTP Request instance.
|
||||
* @param integer $id A Feed entity id.
|
||||
* @param string $format A format name.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function exportFeedAction(Request $request, $id, $format)
|
||||
{
|
||||
/** @var CommonFeedRepository $repository */
|
||||
$repository = $this->em->getRepository(AbstractFeed::class);
|
||||
|
||||
$feed = $repository->find($id);
|
||||
if ((! $feed instanceof AbstractFeed) || ! $feed->getExported()) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$format = strtolower(trim($format));
|
||||
if (! FormatNameEnum::isValid($format)) {
|
||||
throw new BadRequestHttpException('Unknown format '. $format);
|
||||
}
|
||||
|
||||
$data = $this->feedFormatter->formatFeed($feed, new FormatterOptions(
|
||||
new FormatNameEnum($format),
|
||||
$this->getNumber($request),
|
||||
$this->getExtract($request),
|
||||
$this->getShowImage($request),
|
||||
$this->getAsPlain($request)
|
||||
));
|
||||
|
||||
return Response::create($data->getData(), 200, [
|
||||
'Content-Type' => $data->getMime(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/{part}",
|
||||
* methods={ "GET" },
|
||||
* requirements={ "part"=".*" },
|
||||
* defaults={ "part"="" }
|
||||
* )
|
||||
* @Template("AppBundle::index.html.twig")
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request A HTTP Request instance.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
private function getNumber(Request $request)
|
||||
{
|
||||
$number = $request->query->getInt('n', 30);
|
||||
|
||||
if (($number < 1) || ($number > 200)) {
|
||||
throw new BadRequestHttpException("'n' should be integer between 1 and 200.");
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request A HTTP Request instance.
|
||||
*
|
||||
* @return ThemeOptionExtractEnum
|
||||
*/
|
||||
private function getExtract(Request $request)
|
||||
{
|
||||
$extract = strtolower(trim($request->query->get('ext', 'n')));
|
||||
|
||||
switch ($extract) {
|
||||
case 's':
|
||||
$extract = ThemeOptionExtractEnum::start();
|
||||
break;
|
||||
|
||||
case 'sc':
|
||||
$extract = ThemeOptionExtractEnum::context();
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
$extract = ThemeOptionExtractEnum::no();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BadRequestHttpException("'ext' should be one of: s, sc, n.");
|
||||
}
|
||||
|
||||
return $extract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function getShowImage(Request $request)
|
||||
{
|
||||
$showImage = $request->query->get('img', '0');
|
||||
|
||||
if (($showImage !== '0') && ($showImage !== '1')) {
|
||||
throw new BadRequestHttpException("'img' should be 0 or 1.");
|
||||
}
|
||||
|
||||
return $showImage === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function getAsPlain(Request $request)
|
||||
{
|
||||
$textFormat = strtolower(trim($request->query->get('text_format')));
|
||||
|
||||
if (($textFormat !== '') && ($textFormat !== 'text')) {
|
||||
throw new BadRequestHttpException("'text_format' should not be defined or contains 'text' value.");
|
||||
}
|
||||
|
||||
return $textFormat === 'text';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace AppBundle\Controller\Traits;
|
||||
|
||||
use ApiBundle\Security\AccessChecker\AccessCheckerInterface;
|
||||
|
||||
/**
|
||||
* Trait AccessCheckerTrait
|
||||
*
|
||||
* @package AppBundle\Controller\Traits
|
||||
*/
|
||||
trait AccessCheckerTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* @var AccessCheckerInterface
|
||||
*/
|
||||
protected $accessChecker;
|
||||
|
||||
/**
|
||||
* @param string $action Action name.
|
||||
* @param object|object[] $entity A Entity instance or array of entity instances.
|
||||
*
|
||||
* @return string[] Array of restriction reasons.
|
||||
*/
|
||||
protected function checkAccess($action, $entity)
|
||||
{
|
||||
if ($entity instanceof \Traversable) {
|
||||
$entity = iterator_to_array($entity);
|
||||
} elseif (is_object($entity)) {
|
||||
$entity = [ $entity ];
|
||||
}
|
||||
|
||||
if (! is_array($entity)) {
|
||||
throw new \InvalidArgumentException('Expects single object or array of objects.');
|
||||
}
|
||||
|
||||
$grantChecker = \nspl\f\partial([ $this->accessChecker, 'isGranted' ], $action);
|
||||
|
||||
return \nspl\a\flatten(\nspl\a\map($grantChecker, $entity));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace AppBundle\Controller\Traits;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
|
||||
/**
|
||||
* Trait FormFactoryAwareTrait
|
||||
*
|
||||
* @package AppBundle\Controller\Traits
|
||||
*/
|
||||
trait FormFactoryAwareTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* @var FormFactoryInterface
|
||||
*/
|
||||
protected $formFactory;
|
||||
|
||||
/**
|
||||
* Creates and returns a Form instance from the type of the form.
|
||||
*
|
||||
* @param string $type The fully qualified class name of the form type.
|
||||
* @param mixed $data The initial data for the form.
|
||||
* @param array $options Options for the form.
|
||||
*
|
||||
* @return FormInterface
|
||||
*/
|
||||
protected function createForm($type, $data = null, array $options = array())
|
||||
{
|
||||
return $this->formFactory->create($type, $data, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a form builder instance.
|
||||
*
|
||||
* @param mixed $data The initial data for the form.
|
||||
* @param array $options Options for the form.
|
||||
*
|
||||
* @return FormBuilderInterface
|
||||
*/
|
||||
protected function createFormBuilder($data = null, array $options = array())
|
||||
{
|
||||
return $this->formFactory->createBuilder(FormType::class, $data, $options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace AppBundle\Controller\Traits;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Trait TokenStorageAwareTrait
|
||||
*
|
||||
* @package AppBundle\Controller\Traits
|
||||
* @deprecated
|
||||
*/
|
||||
trait TokenStorageAwareTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
protected $tokenStorage;
|
||||
|
||||
/**
|
||||
* Return current use if it exists.
|
||||
*
|
||||
* @return User|null
|
||||
*/
|
||||
public function getCurrentUser()
|
||||
{
|
||||
$user = null;
|
||||
$token = $this->tokenStorage->getToken();
|
||||
|
||||
if ($token !== null) {
|
||||
$user = $token->getUser();
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller\V1;
|
||||
|
||||
use ApiBundle\Controller\AbstractApiController;
|
||||
use ApiBundle\Response\View;
|
||||
use AppBundle\Response\SearchResponseInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class AbstractV1Controller
|
||||
*
|
||||
* @package AppBundle\Controller\V1
|
||||
*
|
||||
* @deprecated
|
||||
* @see AbstractApiController
|
||||
*/
|
||||
abstract class AbstractV1Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Generate proper server response.
|
||||
*
|
||||
* @param mixed $data A data sent to client.
|
||||
* @param integer $code Response http code.
|
||||
* @param array $groups Serialization groups.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function generateResponse(
|
||||
$data = null,
|
||||
$code = null,
|
||||
array $groups = []
|
||||
) {
|
||||
return new View($data, $groups, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data A data for pagination.
|
||||
* @param integer $page A requested page, starts from 1.
|
||||
* @param integer $limit Required numbers of data per page.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function paginate($data, $page, $limit)
|
||||
{
|
||||
if ($data instanceof SearchResponseInterface) {
|
||||
//
|
||||
// Response from index or cache already paginated so we just return
|
||||
// values.
|
||||
//
|
||||
return [
|
||||
'data' => $data->getDocuments(),
|
||||
'count' => count($data),
|
||||
'totalCount' => $data->getTotalCount(),
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
];
|
||||
} elseif ($data instanceof QueryBuilder) {
|
||||
$data
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult(($page - 1) * $limit);
|
||||
|
||||
$paginator = new Paginator($data);
|
||||
$data = iterator_to_array($paginator);
|
||||
|
||||
return [
|
||||
'data' => $data,
|
||||
'count' => count($data),
|
||||
'totalCount' => $paginator->count(),
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
];
|
||||
}
|
||||
|
||||
return [];// TODO add code for over paginated data.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a NotFoundHttpException.
|
||||
*
|
||||
* This will result in a 404 response code. Usage example:
|
||||
*
|
||||
* throw $this->createNotFoundException('Page not found!');
|
||||
*
|
||||
* @param string $message A message.
|
||||
* @param \Exception|null $previous The previous exception.
|
||||
*
|
||||
* @return NotFoundHttpException
|
||||
*/
|
||||
protected function createNotFoundException($message = 'Not Found', \Exception $previous = null)
|
||||
{
|
||||
return new NotFoundHttpException($message, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller\V1;
|
||||
|
||||
use ApiBundle\Controller\AbstractApiController;
|
||||
use ApiBundle\Entity\ManageableEntityInterface;
|
||||
use ApiBundle\Form\EntitiesBatchType;
|
||||
use ApiBundle\Security\AccessChecker\AccessCheckerInterface;
|
||||
use ApiBundle\Security\Inspector\InspectorInterface;
|
||||
use AppBundle\Controller\Traits\AccessCheckerTrait;
|
||||
use AppBundle\Controller\Traits\FormFactoryAwareTrait;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class AbstractV1CrudController
|
||||
*
|
||||
* @package AppBundle\Controller\V1
|
||||
*
|
||||
* @deprecated
|
||||
* @see AbstractApiController
|
||||
*/
|
||||
abstract class AbstractV1CrudController extends AbstractV1Controller
|
||||
{
|
||||
|
||||
use
|
||||
FormFactoryAwareTrait,
|
||||
AccessCheckerTrait;
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
/**
|
||||
* Entity fqcn.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* AbstractV1CrudController constructor.
|
||||
*
|
||||
* @param FormFactoryInterface $formFactory A FormFactoryInterface instance.
|
||||
* @param AccessCheckerInterface $accessChecker A AccessCheckerInterface instance.
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface instance.
|
||||
* @param string $entity A used entity name.
|
||||
*/
|
||||
public function __construct(
|
||||
FormFactoryInterface $formFactory,
|
||||
AccessCheckerInterface $accessChecker,
|
||||
EntityManagerInterface $em,
|
||||
$entity
|
||||
) {
|
||||
$this->formFactory = $formFactory;
|
||||
$this->accessChecker = $accessChecker;
|
||||
$this->em = $em;
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new entity.
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param ManageableEntityInterface $entity A ManageableEntityInterface
|
||||
* instance.
|
||||
*
|
||||
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function createEntity(Request $request, ManageableEntityInterface $entity)
|
||||
{
|
||||
$form = $this->createForm($entity->getCreateFormClass(), $entity);
|
||||
|
||||
// Submit data into form.
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isValid()) {
|
||||
// Check that current user can create this entity.
|
||||
// If user don't have rights to create this entity we should send all
|
||||
// founded restrictions to client.
|
||||
$reasons = $this->checkAccess(InspectorInterface::CREATE, $entity);
|
||||
if (count($reasons) > 0) {
|
||||
// User don't have rights to create this entity so send all
|
||||
// founded restriction reasons to client.
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$this->em->persist($entity);
|
||||
$this->em->flush();
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
// Client send invalid data.
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about single entity.
|
||||
*
|
||||
* @param integer|ManageableEntityInterface|null $id A entity id.
|
||||
*
|
||||
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function getEntity($id)
|
||||
{
|
||||
$foundedEntity = $id;
|
||||
if (is_numeric($id)) {
|
||||
$repository = $this->em->getRepository($this->entity);
|
||||
|
||||
$foundedEntity = $repository->find($id);
|
||||
}
|
||||
|
||||
if ($foundedEntity === null) {
|
||||
$name = \app\c\getShortName($this->entity);
|
||||
// Remove 'Abstract' prefix if it exists.
|
||||
if (strpos($name, 'Abstract') !== false) {
|
||||
$name = substr($name, 8);
|
||||
}
|
||||
|
||||
return $this->generateResponse("Can't find {$name} with id {$id}.", 404);
|
||||
}
|
||||
|
||||
// Check that current user can read this entity.
|
||||
// If user don't have rights to read this entity we should send all
|
||||
// founded restrictions to client.
|
||||
$reasons = $this->checkAccess(InspectorInterface::READ, $foundedEntity);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
return $foundedEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update entity.
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param integer|ManageableEntityInterface|null $entity A entity id.
|
||||
*
|
||||
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function putEntity(Request $request, $entity)
|
||||
{
|
||||
$foundedEntity = $entity;
|
||||
if (is_numeric($entity)) {
|
||||
$repository = $this->em->getRepository($this->entity);
|
||||
/** @var \ApiBundle\Entity\ManageableEntityInterface $entity */
|
||||
$foundedEntity = $repository->find($entity);
|
||||
}
|
||||
|
||||
if ($foundedEntity === null) {
|
||||
$name = \app\c\getShortName($this->entity);
|
||||
// Remove 'Abstract' prefix if it exists.
|
||||
if (strpos($name, 'Abstract') !== false) {
|
||||
$name = substr($name, 8);
|
||||
}
|
||||
|
||||
return $this->generateResponse("Can't find {$name} with id {$entity}.", 404);
|
||||
}
|
||||
|
||||
$form = $this->createForm($foundedEntity->getUpdateFormClass(), $foundedEntity, [
|
||||
'method' => 'PUT',
|
||||
]);
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isValid()) {
|
||||
// Check that current user can update this entity.
|
||||
// If user don't have rights to update this entity we should send all
|
||||
// founded restrictions to client.
|
||||
$reasons = $this->checkAccess(InspectorInterface::UPDATE, $foundedEntity);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$this->em->persist($foundedEntity);
|
||||
$this->em->flush();
|
||||
|
||||
return $foundedEntity;
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete entity.
|
||||
*
|
||||
* @param integer|ManageableEntityInterface|null $entity A entity id.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function deleteEntity($entity)
|
||||
{
|
||||
$foundedEntity = $entity;
|
||||
if (is_numeric($entity)) {
|
||||
$repository = $this->em->getRepository($this->entity);
|
||||
/** @var \ApiBundle\Entity\ManageableEntityInterface $entity */
|
||||
$foundedEntity = $repository->find($entity);
|
||||
}
|
||||
|
||||
if ($foundedEntity === null) {
|
||||
$name = \app\c\getShortName($this->entity);
|
||||
// Remove 'Abstract' prefix if it exists.
|
||||
if (strpos($name, 'Abstract') !== false) {
|
||||
$name = substr($name, 8);
|
||||
}
|
||||
|
||||
return $this->generateResponse("Can't find {$name} with id {$entity}.", 404);
|
||||
}
|
||||
// Check that current user can delete this entity.
|
||||
// If user don't have rights to delete this entity we should send all
|
||||
// founded restrictions to client.
|
||||
$reasons = $this->checkAccess(InspectorInterface::DELETE, $foundedEntity);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$this->em->remove($foundedEntity);
|
||||
$this->em->flush();
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request A Request instance.
|
||||
* @param string|callable $permission A requested permission.
|
||||
* @param string $formClass Form class fqcn.
|
||||
* @param callable $processor Function which process founded entities.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function batchProcessing(
|
||||
Request $request,
|
||||
$permission,
|
||||
$formClass,
|
||||
callable $processor
|
||||
) {
|
||||
$this->checkFormClass($formClass);
|
||||
|
||||
$form = $this->createForm($formClass, null, [ 'class' => $this->entity ]);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$data = $form->getData();
|
||||
|
||||
if (is_callable($permission)) {
|
||||
$permission = call_user_func_array($permission, $data);
|
||||
}
|
||||
|
||||
if (! is_string($permission)) {
|
||||
throw new \InvalidArgumentException('$permission should be string or callable');
|
||||
}
|
||||
|
||||
$reasons = $this->checkAccess($permission, $data['entities']);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$response = call_user_func_array($processor, $data);
|
||||
if ($response === null) {
|
||||
$response = $this->generateResponse();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $formClass Form class fqcn.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function checkFormClass($formClass)
|
||||
{
|
||||
if (! is_string($formClass) || ! class_exists($formClass)) {
|
||||
throw new \InvalidArgumentException('$formClass should be fqcn');
|
||||
}
|
||||
|
||||
if (($formClass !== EntitiesBatchType::class)
|
||||
&& ! in_array(EntitiesBatchType::class, class_parents($formClass), true)) {
|
||||
throw new \InvalidArgumentException('Invalid form class '. $formClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller\V1;
|
||||
|
||||
use ApiBundle\Controller\AbstractCRUDController;
|
||||
use ApiBundle\Controller\Annotation\Roles;
|
||||
use ApiDocBundle\Controller\Annotation\AppApiDoc;
|
||||
use AppBundle\Exception\NotAllowedException;
|
||||
use CacheBundle\DTO\AnalyticDTO;
|
||||
use CacheBundle\Entity\Analytic\Analytic;
|
||||
use CacheBundle\Form\AnalyticType;
|
||||
use CacheBundle\Service\Factory\Analytic\AnalyticFactoryInterface;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use ApiBundle\Security\Inspector\InspectorInterface;
|
||||
use CacheBundle\Repository\AnalyticRepository;
|
||||
|
||||
/**
|
||||
* Class AnalyticController
|
||||
* @package AppBundle\Controller\V1
|
||||
*
|
||||
* @Route("/analysis", service="app.controller.analytic")
|
||||
*/
|
||||
class AnalyticController extends AbstractCRUDController
|
||||
{
|
||||
|
||||
/**
|
||||
* @var AnalyticFactoryInterface
|
||||
*/
|
||||
private $analyticFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Create new analytic entity.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* methods={ "POST" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* section="Analytic",
|
||||
* resource=false,
|
||||
* input={
|
||||
* "class"="CacheBundle\Form\AnalyticType"
|
||||
* },
|
||||
* output={
|
||||
* "class"="CacheBundle\Entity\Analytic\Analytic",
|
||||
* "groups"={ "analytic", "id" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="Analytics successfully created.",
|
||||
* 400="Invalid data provided.",
|
||||
* 403="You don't have permissions to create analytics."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Http Request instance.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function postAction(Request $request)
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
$this->analyticFactory = $this->get('cache.analytic_factory');
|
||||
|
||||
$form = $this->createForm(AnalyticType::class)
|
||||
->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var AnalyticDTO $dto */
|
||||
$dto = $form->getData();
|
||||
|
||||
try {
|
||||
$analytic = $this->analyticFactory->createAnalytic($dto, $user);
|
||||
} catch (NotAllowedException $exception) {
|
||||
return $this->generateResponse('You not allowed to make analytics');
|
||||
}
|
||||
|
||||
$this->getManager()->persist($analytic);
|
||||
$this->getManager()->flush();
|
||||
|
||||
return $this->generateResponse($analytic, 200, ['id', 'analytic']);
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specified analytic by id.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/{id}",
|
||||
* requirements={ "id"="\d+" },
|
||||
* methods={ "GET" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* resource=true,
|
||||
* section="Analytic",
|
||||
* output={
|
||||
* "class"="CacheBundle\Entity\Analytic\Analytic",
|
||||
* "groups"={"id"}
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="Analytics successfully returned.",
|
||||
* 403="You don't have permissions to view this analytics.",
|
||||
* 404="Can't find analytic by specified id."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param integer $id Analytic entity id.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Analytic\Analytic|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function getAction($id)
|
||||
{
|
||||
return parent::getEntity($id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete specified analytic.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/{id}",
|
||||
* requirements={ "id"="\d+" },
|
||||
* methods={ "DELETE" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* resource=true,
|
||||
* section="Analytic",
|
||||
* statusCodes={
|
||||
* 204="Analytic successfully deleted.",
|
||||
* 403="You don't have permissions to delete this analytic.",
|
||||
* 404="Can't find analytic by specified id."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param integer $id A Analytic entity id.
|
||||
*
|
||||
* @return array|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$repository = $this->getManager()->getRepository($this->entity);
|
||||
/** @var Analytic $analytic */
|
||||
$analytic = $repository->find($id);
|
||||
|
||||
if (!$analytic instanceof Analytic) {
|
||||
return $this->generateResponse("Can't find analytic with id {$id}.", 404);
|
||||
}
|
||||
$reasons = $this->checkAccess(InspectorInterface::DELETE, $analytic);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
$analyticContext = $analytic->getContext();
|
||||
|
||||
if (isset($analyticContext)) {
|
||||
($analyticContext->getAnalytics()->count() == 1) ? $this->getManager()->remove($analyticContext) : "";
|
||||
}
|
||||
|
||||
$this->getManager()->remove($analytic);
|
||||
$this->getManager()->flush();
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update analytic.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route("/{id}", methods={ "PUT" }, requirements={ "id"="\d+" })
|
||||
* @AppApiDoc(
|
||||
* section="Analytic",
|
||||
* resource=true,
|
||||
* input={
|
||||
* "class"="CacheBundle\Form\AnalyticType",
|
||||
* "name"=false
|
||||
* },
|
||||
* output={
|
||||
* "class"="CacheBundle\Entity\Analytic\Analytic",
|
||||
* "groups"={ "analytic", "id" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="Analytics successfully updated.",
|
||||
* 400="Invalid data provided."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param integer $id Analytic entity id.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function putAction(Request $request, $id)
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
$this->analyticFactory = $this->get('cache.analytic_factory');
|
||||
|
||||
/** @var AnalyticRepository $analyticRepository */
|
||||
$repository = $this->getManager()->getRepository($this->entity);
|
||||
/** @var Analytic $analytic */
|
||||
$analytic = $repository->find($id);
|
||||
|
||||
if (!$analytic instanceof Analytic) {
|
||||
return $this->generateResponse("Can't find analytic with id {$id}.", 404);
|
||||
}
|
||||
$feeds = $analytic->getContext()->getFeeds();
|
||||
$feedsId = [];
|
||||
foreach ($feeds as $feedsVal) {
|
||||
$feedsId[] = $feedsVal->getId();
|
||||
}
|
||||
|
||||
$analyticDto = new AnalyticDTO($feedsId, null, $analytic->getContext()->getFilters(), $analytic->getContext()->getRawFilters());
|
||||
$form = $this->createForm(AnalyticType::class, $analyticDto);
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isValid()) {
|
||||
/** @var AnalyticDTO $dto */
|
||||
$dto = $form->getData();
|
||||
|
||||
try {
|
||||
$analyticContext = $analytic->getContext();
|
||||
if (isset($analyticContext)) {
|
||||
($analyticContext->getAnalytics()->count() == 1) ? $this->getManager()->remove($analyticContext) : "";
|
||||
}
|
||||
$analytic = $this->analyticFactory->updateAnalytic($dto, $user, $analytic);
|
||||
|
||||
} catch (NotAllowedException $exception) {
|
||||
return $this->generateResponse('You not allowed to update analytics');
|
||||
}
|
||||
|
||||
$this->getManager()->persist($analytic);
|
||||
$this->getManager()->flush();
|
||||
|
||||
return $this->generateResponse($analytic, 200, ['id', 'analytic']);
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get list of categories for current user.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(methods={ "GET" })
|
||||
* @AppApiDoc(
|
||||
* section="Analytic",
|
||||
* output={
|
||||
* "class"="Pagination<CacheBundle\Entity\Analytic\Analytic>",
|
||||
* "groups"={ "analytic", "id","context" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="List of analytic successfully returned."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function listAction(Request $request)
|
||||
{
|
||||
/** @var AnalyticRepository $repository */
|
||||
$repository = $this->getManager()->getRepository(Analytic::class);
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
|
||||
$pagination = $this->paginate(
|
||||
$request,
|
||||
$repository->getList($user->getId())
|
||||
);
|
||||
|
||||
// Simulate pagination serialization.
|
||||
return $this->generateResponse([
|
||||
$pagination
|
||||
], 200, [
|
||||
'analytic',
|
||||
'id',
|
||||
'context'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller\V1;
|
||||
|
||||
use ApiBundle\Controller\AbstractCRUDController;
|
||||
use ApiBundle\Controller\Annotation\Roles;
|
||||
use CacheBundle\Entity\Analytic\Analytic;
|
||||
use DateInterval;
|
||||
use DatePeriod;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use CacheBundle\Repository\AnalyticRepository;
|
||||
use CacheBundle\Entity\Document;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class AnalyticGraphController
|
||||
* @package AppBundle\Controller\V1
|
||||
*
|
||||
* @Route(service="app.controller.analytic-graph")
|
||||
*/
|
||||
class AnalyticGraphController extends AbstractCRUDController
|
||||
{
|
||||
|
||||
/**
|
||||
* Get data for influence list
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/influencer/{id}",
|
||||
* requirements={
|
||||
* "id": "\d+",
|
||||
* },
|
||||
* methods={ "POST" }
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param $id
|
||||
*
|
||||
* @return array|\ApiBundle\Response\ViewInterface
|
||||
*
|
||||
*/
|
||||
public function getInfluenceAction(Request $request, $id)
|
||||
{
|
||||
$isAuthorType = $request->request->get('isAuthorType', false);
|
||||
$groupByField = 'source_hashcode';
|
||||
if ($isAuthorType === true) {
|
||||
$groupByField = 'author_name';
|
||||
}
|
||||
|
||||
/** @var AnalyticRepository $analyticRepository */
|
||||
$repository = $this->getManager()->getRepository($this->entity);
|
||||
/** @var Analytic $analytic */
|
||||
$analytic = $repository->find($id);
|
||||
|
||||
if (!$analytic instanceof Analytic) {
|
||||
return $this->generateResponse("Can't find analytic with id {$id}.", 404);
|
||||
}
|
||||
$analyticContext = $analytic->getContext();
|
||||
$feeds = $analyticContext->getFeeds();
|
||||
$queryId = [];
|
||||
$clipFeedId = [];
|
||||
foreach ($feeds as $feedsVal) {
|
||||
if ($feedsVal->getSubType() == 'query_feed') {
|
||||
$queryId[] = $feedsVal->getQuery()->getId();
|
||||
} else {
|
||||
$clipFeedId[] = $feedsVal->getId();
|
||||
}
|
||||
}
|
||||
$filters = $analyticContext->getFilters();
|
||||
$influenceData = [];
|
||||
if (count($filters) > 0) {
|
||||
if (array_key_exists('date', $filters)) {
|
||||
|
||||
$startDt = $filters['date']->getFilters()[0]->getValue()->format('Y-m-d');
|
||||
$endDt = $filters['date']->getFilters()[1]->getValue()->format('Y-m-d');
|
||||
$repository = $this->getManager()->getRepository(Document::class);
|
||||
$documents = $repository->getByQuery($queryId);
|
||||
$clipDocuments = $repository->getByClip($clipFeedId);
|
||||
|
||||
foreach ($feeds as $key => $feedsVal) {
|
||||
$influenceData[$key]['name'] = $feedsVal->getName();
|
||||
$influenceData[$key]['data'] = [];
|
||||
foreach ($documents as $document) {
|
||||
if ($feedsVal->getSubType() == 'query_feed') {
|
||||
if ($feedsVal->getQuery()->getId() == $document['id']) {
|
||||
$publishDate = substr($document['data']['published'], 0, 10);
|
||||
$publishDate = date('Y-m-d', strtotime($publishDate));
|
||||
if (($publishDate >= $startDt) && ($publishDate <= $endDt)) {
|
||||
if (array_key_exists($groupByField, $document['data'])) {
|
||||
$engagementCount = 0;
|
||||
if (array_key_exists("likes", $document['data'])) {
|
||||
$engagementCount = $document['data']['likes'];
|
||||
}
|
||||
if (array_key_exists("dislikes", $document['data'])) {
|
||||
$engagementCount += $document['data']['dislikes'];
|
||||
}
|
||||
if (array_key_exists("comments", $document['data'])) {
|
||||
$engagementCount += $document['data']['comments'];
|
||||
}
|
||||
if (array_key_exists("shares", $document['data'])) {
|
||||
$engagementCount += $document['data']['shares'];
|
||||
}
|
||||
$tempInfluenceData = $influenceData[$key]['data'];
|
||||
if (count($tempInfluenceData) > 0) {
|
||||
$sourceHashCodeKey = array_search($document['data'][$groupByField], array_column($tempInfluenceData, $groupByField));
|
||||
if ($sourceHashCodeKey === false) {
|
||||
$tempData = [$groupByField => $document['data'][$groupByField], 'influence' => $document['data']['source_link'],
|
||||
'source_type' => $document['data']['source_publisher_type'], 'engagement' => $engagementCount, 'totalSentiment' => 0];
|
||||
if (array_key_exists("sentiment", $document['data'])) {
|
||||
$sentiment = 1;
|
||||
$tempData['totalSentiment'] = $sentiment;
|
||||
$tempData[$document['data']['sentiment']] = $sentiment;
|
||||
}
|
||||
array_push($tempInfluenceData, $tempData);
|
||||
$influenceData[$key]['data'] = $tempInfluenceData;
|
||||
} else {
|
||||
if (array_key_exists("sentiment", $document['data'])) {
|
||||
$influenceData[$key]['data'][$sourceHashCodeKey]['totalSentiment'] += 1;
|
||||
if (array_key_exists($document['data']['sentiment'], $influenceData[$key]['data'][$sourceHashCodeKey])) {
|
||||
$influenceData[$key]['data'][$sourceHashCodeKey][$document['data']['sentiment']] += 1;
|
||||
} else {
|
||||
$influenceData[$key]['data'][$sourceHashCodeKey][$document['data']['sentiment']] = 1;
|
||||
}
|
||||
|
||||
}
|
||||
$influenceData[$key]['data'][$sourceHashCodeKey]['engagement'] += $engagementCount;
|
||||
}
|
||||
} else {
|
||||
$tempData = [$groupByField => $document['data'][$groupByField], 'influence' => $document['data']['source_link'],
|
||||
'source_type' => $document['data']['source_publisher_type'], 'engagement' => $engagementCount, 'totalSentiment' => 0];
|
||||
if (array_key_exists("sentiment", $document['data'])) {
|
||||
$sentiment = 1;
|
||||
$tempData['totalSentiment'] = $sentiment;
|
||||
$tempData[$document['data']['sentiment']] = $sentiment;
|
||||
}
|
||||
$influenceData[$key]['data'][0] = $tempData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($clipDocuments as $clipDocument) {
|
||||
if ($feedsVal->getSubType() == 'clip_feed') {
|
||||
if ($feedsVal->getId() == $clipDocument['clipFeedId']) {
|
||||
$publishDate = substr($clipDocument['data']['published'], 0, 10);
|
||||
$publishDate = date('Y-m-d', strtotime($publishDate));
|
||||
if (($publishDate >= $startDt) && ($publishDate <= $endDt)) {
|
||||
if (array_key_exists($groupByField, $clipDocument['data'])) {
|
||||
$engagementCount = 0;
|
||||
if (array_key_exists("likes", $clipDocument['data'])) {
|
||||
$engagementCount = $clipDocument['data']['likes'];
|
||||
}
|
||||
if (array_key_exists("dislikes", $clipDocument['data'])) {
|
||||
$engagementCount += $clipDocument['data']['dislikes'];
|
||||
}
|
||||
if (array_key_exists("comments", $clipDocument['data'])) {
|
||||
$engagementCount += $clipDocument['data']['comments'];
|
||||
}
|
||||
if (array_key_exists("shares", $clipDocument['data'])) {
|
||||
$engagementCount += $clipDocument['data']['shares'];
|
||||
}
|
||||
$tempInfluenceData = $influenceData[$key]['data'];
|
||||
if (count($tempInfluenceData) > 0) {
|
||||
$sourceHashCodeKey = array_search($clipDocument['data'][$groupByField], array_column($tempInfluenceData, $groupByField));
|
||||
if ($sourceHashCodeKey === false) {
|
||||
$tempData = [$groupByField => $clipDocument['data'][$groupByField], 'influence' => $clipDocument['data']['source_link'],
|
||||
'source_type' => $clipDocument['data']['source_publisher_type'], 'engagement' => $engagementCount, 'totalSentiment' => 0];
|
||||
if (array_key_exists("sentiment", $clipDocument['data'])) {
|
||||
$sentiment = 1;
|
||||
$tempData['totalSentiment'] = $sentiment;
|
||||
$tempData[$clipDocument['data']['sentiment']] = $sentiment;
|
||||
}
|
||||
array_push($tempInfluenceData, $tempData);
|
||||
$influenceData[$key]['data'] = $tempInfluenceData;
|
||||
} else {
|
||||
if (array_key_exists("sentiment", $clipDocument['data'])) {
|
||||
$influenceData[$key]['data'][$sourceHashCodeKey]['totalSentiment'] += 1;
|
||||
if (array_key_exists($clipDocument['data']['sentiment'], $influenceData[$key]['data'][$sourceHashCodeKey])) {
|
||||
$influenceData[$key]['data'][$sourceHashCodeKey][$clipDocument['data']['sentiment']] += 1;
|
||||
} else {
|
||||
$influenceData[$key]['data'][$sourceHashCodeKey][$clipDocument['data']['sentiment']] = 1;
|
||||
}
|
||||
}
|
||||
$influenceData[$key]['data'][$sourceHashCodeKey]['engagement'] += $engagementCount;
|
||||
}
|
||||
} else {
|
||||
|
||||
$tempData = [$groupByField => $clipDocument['data'][$groupByField], 'influence' => $clipDocument['data']['source_link'],
|
||||
'source_type' => $clipDocument['data']['source_publisher_type'], 'engagement' => $engagementCount, 'totalSentiment' => 0];
|
||||
if (array_key_exists("sentiment", $clipDocument['data'])) {
|
||||
$sentiment = 1;
|
||||
$tempData['totalSentiment'] = $sentiment;
|
||||
$tempData[$clipDocument['data']['sentiment']] = $sentiment;
|
||||
}
|
||||
$influenceData[$key]['data'][0] = $tempData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($influenceData as $key => $influenceDataVal) {
|
||||
usort($influenceData[$key]['data'], function ($a, $b) {
|
||||
return $b['totalSentiment'] <=> $a['totalSentiment'];
|
||||
});
|
||||
}
|
||||
|
||||
foreach ($influenceData as $key => $dataVal) {
|
||||
$influenceData[$key]['data'] = array_slice($dataVal['data'], 0, 10);
|
||||
}
|
||||
|
||||
return $this->generateResponse([
|
||||
'data' => $influenceData
|
||||
], 200, []);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $filters
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getDuration($filters)
|
||||
{
|
||||
$duration = [];
|
||||
if (count($filters) > 0) {
|
||||
if (array_key_exists('date', $filters)) {
|
||||
$period = new DatePeriod(
|
||||
$filters['date']->getFilters()[0]->getValue(),
|
||||
new DateInterval('P1D'),
|
||||
$filters['date']->getFilters()[1]->getValue()
|
||||
);
|
||||
foreach ($period as $key => $value) {
|
||||
$duration[$value->format('Y-m-d')] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $duration;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller\V1;
|
||||
|
||||
use ApiBundle\Controller\AbstractCRUDController;
|
||||
use ApiBundle\Controller\Annotation\Roles;
|
||||
use ApiBundle\Response\ViewInterface;
|
||||
use ApiBundle\Security\Inspector\InspectorInterface;
|
||||
use ApiDocBundle\Controller\Annotation\AppApiDoc;
|
||||
use CacheBundle\Entity\Category;
|
||||
use CacheBundle\Repository\CategoryRepository;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
use UserBundle\Enum\AppLimitEnum;
|
||||
|
||||
/**
|
||||
* Class CategoryController
|
||||
* @package AppBundle\Controller\V1
|
||||
*
|
||||
* @Route("/categories", service="app.controller.category")
|
||||
*/
|
||||
class CategoryController extends AbstractCRUDController
|
||||
{
|
||||
|
||||
/**
|
||||
* Move specified feed to another category.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/{movedId}/move_to/{destinationId}",
|
||||
* requirements={
|
||||
* "movedId": "\d+",
|
||||
* "destinationId": "\d+"
|
||||
* },
|
||||
* methods={ "POST" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* resource=true,
|
||||
* section="Category",
|
||||
* output={
|
||||
* "class"="Pagination<CacheBundle\Entity\Category>",
|
||||
* "groups"={ "id", "category_tree", "feed_tree" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="List of updated categories successfully returned.",
|
||||
* 400="Invalid data provided.",
|
||||
* 403="You don't have permissions to move this category.",
|
||||
* 404="Can't find moved or destination category."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param integer $movedId A moved Category entity id.
|
||||
* @param integer $destinationId A Category entity id where the category is
|
||||
* moved.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function moveAction($movedId, $destinationId)
|
||||
{
|
||||
$movedId = (integer) $movedId;
|
||||
$destinationId = (integer) $destinationId;
|
||||
$userId = \app\op\invokeIf($this->getCurrentUser(), 'getId');
|
||||
|
||||
/** @var CategoryRepository $repository */
|
||||
$repository = $this->getManager()->getRepository(Category::class);
|
||||
|
||||
$moved = $repository->get($movedId, $userId);
|
||||
if (! $moved instanceof Category) {
|
||||
return $this->generateResponse("Can't find category with id {$movedId}.", 404);
|
||||
}
|
||||
|
||||
//
|
||||
// Check that user don't try to move internal category.
|
||||
//
|
||||
if ($moved->isInternal()) {
|
||||
return $this->generateResponse('Can\'t move internal category.', 403);
|
||||
}
|
||||
|
||||
//
|
||||
// We should don't make any changes if client try to move category into
|
||||
// the same category.
|
||||
//
|
||||
|
||||
if ($moved->getParent()->getId() !== $destinationId) {
|
||||
$destination = $repository->get($destinationId, $userId, [
|
||||
Category::TYPE_CUSTOM,
|
||||
Category::TYPE_MY_CONTENT,
|
||||
]);
|
||||
if (! $destination instanceof Category) {
|
||||
return $this->generateResponse("Can't find category with id {$destinationId}.", 404);
|
||||
}
|
||||
|
||||
//
|
||||
// All ok, now we need to validate destination category id.
|
||||
//
|
||||
$moved->setParent($destination);
|
||||
|
||||
/** @var ValidatorInterface $validator */
|
||||
$validator = $this->get('validator');
|
||||
$errors = $validator->validate($moved);
|
||||
|
||||
if (count($errors) > 0) {
|
||||
//
|
||||
// Get all violation errors and send it to client.
|
||||
//
|
||||
$errors = array_map(function (ConstraintViolationInterface $violation) {
|
||||
return $violation->getMessage();
|
||||
}, iterator_to_array($errors));
|
||||
|
||||
return $this->generateResponse($errors, 400);
|
||||
}
|
||||
|
||||
//
|
||||
// Validation passed, update entity.
|
||||
//
|
||||
$this->getManager()->persist($moved);
|
||||
$this->getManager()->flush();
|
||||
}
|
||||
|
||||
return $this->forward('app.controller.category:listAction');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new category for current user.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(methods={ "POST" })
|
||||
* @AppApiDoc(
|
||||
* resource=true,
|
||||
* section="Category",
|
||||
* input={
|
||||
* "class"="CacheBundle\Form\CategoryType",
|
||||
* "name"=false
|
||||
* },
|
||||
* output={
|
||||
* "class"="CacheBundle\Entity\Category",
|
||||
* "groups"={ "id", "category" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="Category successfully created.",
|
||||
* 400="Invalid data provided.",
|
||||
* 403="You don't have permissions to create category."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Category|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function createAction(Request $request)
|
||||
{
|
||||
return parent::createEntity($request, new Category($this->getCurrentUser()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of categories for current user.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(methods={ "GET" })
|
||||
* @AppApiDoc(
|
||||
* section="Category",
|
||||
* output={
|
||||
* "class"="Pagination<CacheBundle\Entity\Category>",
|
||||
* "groups"={ "id", "category_tree", "feed_tree" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="List of categories successfully returned."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return ViewInterface
|
||||
*/
|
||||
public function listAction()
|
||||
{
|
||||
/** @var CategoryRepository $repository */
|
||||
$repository = $this->getManager()->getRepository(Category::class);
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
$categories = $repository->getList($user->getId());
|
||||
$count = count($categories);
|
||||
|
||||
// Simulate pagination serialization.
|
||||
return $this->generateResponse([
|
||||
'data' => $categories,
|
||||
'count' => $count,
|
||||
'totalCount' => $count,
|
||||
'page' => 1,
|
||||
'limit' => $count,
|
||||
], 200, [
|
||||
'id',
|
||||
'category_tree',
|
||||
'feed_tree',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specified category by id.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/{id}",
|
||||
* requirements={ "id"="\d+" },
|
||||
* methods={ "GET" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* resource=true,
|
||||
* section="Category",
|
||||
* output={
|
||||
* "class"="CacheBundle\Entity\Category",
|
||||
* "groups"={ "id", "category", "feed_tree" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="Category successfully returned.",
|
||||
* 403="You don't have permissions to view this category.",
|
||||
* 404="Can't find category by specified id."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param integer $id A Category entity id.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Category|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function getAction($id)
|
||||
{
|
||||
return parent::getEntity($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update specified category.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/{id}",
|
||||
* requirements={ "id"="\d+" },
|
||||
* methods={ "PUT" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* resource=true,
|
||||
* section="Category",
|
||||
* input={
|
||||
* "class"="CacheBundle\Form\CategoryType",
|
||||
* "name"=false
|
||||
* },
|
||||
* output={
|
||||
* "class"="CacheBundle\Entity\Category",
|
||||
* "groups"={ "id", "category" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="Category successfully updated.",
|
||||
* 400="Invalid data provided.",
|
||||
* 403="You don't have permissions to update this category.",
|
||||
* 404="Can't find category by specified id."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param integer $id A Category entity id.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Category|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function putAction(Request $request, $id)
|
||||
{
|
||||
return parent::putEntity($request, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete specified category.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/{id}",
|
||||
* requirements={ "id"="\d+" },
|
||||
* methods={ "DELETE" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* resource=true,
|
||||
* section="Category",
|
||||
* statusCodes={
|
||||
* 204="Category successfully deleted.",
|
||||
* 403="You don't have permissions to delete this category.",
|
||||
* 404="Can't find category by specified id."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param integer $id A Category entity id.
|
||||
*
|
||||
* @return array|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
/** @var CategoryRepository $repository */
|
||||
$repository = $this->getManager()->getRepository($this->entity);
|
||||
$category = $repository->find($id);
|
||||
|
||||
if ($category === null) {
|
||||
return $this->generateResponse("Can't find category with id {$id}.", 404);
|
||||
}
|
||||
|
||||
$reasons = $this->checkAccess(InspectorInterface::DELETE, $category);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
//
|
||||
// Update restriction limit for current user only if deleted category has
|
||||
// some feeds.
|
||||
//
|
||||
$feedCount = $repository->computeFeedCounts($id);
|
||||
if ($feedCount > 0) {
|
||||
$user = $this->getCurrentUser();
|
||||
$user->releaseLimit(AppLimitEnum::feeds(), $feedCount);
|
||||
|
||||
$this->getManager()->persist($user);
|
||||
}
|
||||
|
||||
$this->getManager()->remove($category);
|
||||
$this->getManager()->flush();
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller\V1;
|
||||
|
||||
use ApiBundle\Controller\Annotation\Roles;
|
||||
use ApiBundle\Security\Inspector\InspectorInterface;
|
||||
use ApiDocBundle\Controller\Annotation\AppApiDoc;
|
||||
use CacheBundle\Entity\Comment;
|
||||
use CacheBundle\Entity\Document;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class CommentController
|
||||
* @package AppBundle\Controller\V1
|
||||
*
|
||||
* @Route("/comments", service="app.controller.comment")
|
||||
*/
|
||||
class CommentController extends AbstractV1CrudController
|
||||
{
|
||||
|
||||
/**
|
||||
* Update specified comment.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/{commentId}",
|
||||
* requirements={ "commentId"="\d+" },
|
||||
* methods={ "PUT" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* section="Comment",
|
||||
* resource=false,input={
|
||||
* "class"="CacheBundle\Form\CommentType",
|
||||
* "name"=false
|
||||
* },
|
||||
* output={
|
||||
* "class"="CacheBundle\Entity\Comment",
|
||||
* "groups"={ "comment", "id" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="Comment successfully updated.",
|
||||
* 400="Invalid data provided.",
|
||||
* 403="You don't have permissions to update this comment.",
|
||||
* 404="Can't find comment by specified id."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param integer $commentId A one of comment entity id.
|
||||
*
|
||||
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function putAction(Request $request, $commentId)
|
||||
{
|
||||
return parent::putEntity($request, $commentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete specified comment.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/{commentId}",
|
||||
* requirements={ "commentId"="\d+" },
|
||||
* methods={ "DELETE" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* section="Comment",
|
||||
* resource=false,
|
||||
* statusCodes={
|
||||
* 204="Comment successfully deleted.",
|
||||
* 403="You don't have permissions to delete this comment.",
|
||||
* 404="Can't find comment by specified id."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param integer $commentId A Comment entity id.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function deleteAction($commentId)
|
||||
{
|
||||
$entity = $this->em->getRepository(Comment::class)->find($commentId);
|
||||
|
||||
if ($entity === null) {
|
||||
return $this->generateResponse("Can't find comment with id {$commentId}.", 404);
|
||||
}
|
||||
|
||||
$reasons = $this->checkAccess(InspectorInterface::DELETE, $entity);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$this->em->getRepository(Document::class)
|
||||
->createQueryBuilder('Document')
|
||||
->update()
|
||||
->set('Document.commentsCount', 'Document.commentsCount - 1')
|
||||
->where('Document.id = :id')
|
||||
->setParameter('id', \app\op\invokeIf($entity->getDocument(), 'getId'))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$this->em->remove($entity);
|
||||
$this->em->flush();
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller\V1;
|
||||
|
||||
use ApiBundle\Controller\Annotation\Roles;
|
||||
use ApiBundle\Security\AccessChecker\AccessCheckerInterface;
|
||||
use ApiBundle\Security\Inspector\InspectorInterface;
|
||||
use ApiDocBundle\Controller\Annotation\AppApiDoc;
|
||||
use AppBundle\Controller\Traits\AccessCheckerTrait;
|
||||
use AppBundle\Controller\Traits\FormFactoryAwareTrait;
|
||||
use AppBundle\Controller\Traits\TokenStorageAwareTrait;
|
||||
use AppBundle\Entity\EmailedDocument;
|
||||
use AppBundle\Form\EmailedDocumentType;
|
||||
use CacheBundle\Comment\Manager\CommentManagerInterface;
|
||||
use CacheBundle\Entity\Comment;
|
||||
use CacheBundle\Entity\Document;
|
||||
use CacheBundle\Repository\CommentRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
|
||||
/**
|
||||
* Class DocumentController
|
||||
* @package AppBundle\Controller\V1
|
||||
*
|
||||
* @Route("/documents", service="app.controller.document")
|
||||
*/
|
||||
class DocumentController extends AbstractV1Controller
|
||||
{
|
||||
|
||||
use
|
||||
TokenStorageAwareTrait,
|
||||
FormFactoryAwareTrait,
|
||||
AccessCheckerTrait;
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var CommentManagerInterface
|
||||
*/
|
||||
private $commentManager;
|
||||
|
||||
/**
|
||||
* @var ProducerInterface
|
||||
*/
|
||||
private $emailProducer;
|
||||
|
||||
/**
|
||||
* DocumentController constructor.
|
||||
*
|
||||
* @param TokenStorageInterface $tokenStorage A TokenStorageInterface
|
||||
* instance.
|
||||
* @param FormFactoryInterface $formFactory A FormFactoryInterface
|
||||
* instance.
|
||||
* @param AccessCheckerInterface $accessChecker A AccessCheckerInterface
|
||||
* instance.
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface
|
||||
* instance.
|
||||
* @param CommentManagerInterface $commentManager A CommentManagerInterface
|
||||
* instance.
|
||||
* @param ProducerInterface $emailProducer A producer interface for
|
||||
* emailing documents.
|
||||
*/
|
||||
public function __construct(
|
||||
TokenStorageInterface $tokenStorage,
|
||||
FormFactoryInterface $formFactory,
|
||||
AccessCheckerInterface $accessChecker,
|
||||
EntityManagerInterface $em,
|
||||
CommentManagerInterface $commentManager,
|
||||
ProducerInterface $emailProducer
|
||||
) {
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->formFactory = $formFactory;
|
||||
$this->accessChecker = $accessChecker;
|
||||
$this->em = $em;
|
||||
$this->commentManager = $commentManager;
|
||||
$this->emailProducer = $emailProducer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new comment for specified document.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/{documentId}/comments",
|
||||
* requirements={ "documentId"="\d+" },
|
||||
* methods={ "POST" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* section="Document",
|
||||
* resource=true,
|
||||
* input={
|
||||
* "class"="CacheBundle\Form\CommentType",
|
||||
* "name"=false
|
||||
* },
|
||||
* output={
|
||||
* "class"="CacheBundle\Entity\Comment",
|
||||
* "groups"={ "comment", "id" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="Comment successfully saved.",
|
||||
* 400="Invalid data provided."
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param integer $documentId Commented Document entity id.
|
||||
*
|
||||
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function createCommentAction(Request $request, $documentId)
|
||||
{
|
||||
$document = $this->em->getRepository(Document::class)->find($documentId);
|
||||
|
||||
if (! $document instanceof Document) {
|
||||
return $this->generateResponse([[
|
||||
'message' => 'Document not found',
|
||||
'transKey' => 'commentDocumentInvalidDocument',
|
||||
'type' => 'error',
|
||||
'parameters' => [ 'current' => $documentId ],
|
||||
], ], 404);
|
||||
}
|
||||
|
||||
$comment = new Comment($this->getCurrentUser(), '');
|
||||
|
||||
$form = $this->createForm($comment->getCreateFormClass(), $comment);
|
||||
|
||||
// Submit data into form.
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isValid()) {
|
||||
//
|
||||
// Check that current user can create this entity.
|
||||
// If user don't have rights to create this entity we should send all
|
||||
// founded restrictions to client.
|
||||
//
|
||||
$reasons = $this->checkAccess(InspectorInterface::CREATE, $comment);
|
||||
if (count($reasons) > 0) {
|
||||
//
|
||||
// User don't have rights to create this entity so send all
|
||||
// founded restriction reasons to client.
|
||||
//
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$this->commentManager->addComment($comment, $document);
|
||||
|
||||
$this->em->persist($comment);
|
||||
$this->em->flush();
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
// Client send invalid data.
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of comments for specified document.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/{documentId}/comments",
|
||||
* requirements={ "documentId"="\d+" },
|
||||
* methods={ "GET" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* section="Document",
|
||||
* resource=true,
|
||||
* filters={
|
||||
* {
|
||||
* "name"="offset",
|
||||
* "dataType"="integer",
|
||||
* "description"="Offset from beginning of collection, start from 1",
|
||||
* "requirements"="\d+",
|
||||
* "default"="1"
|
||||
* },
|
||||
* {
|
||||
* "name"="limit",
|
||||
* "dataType"="integer",
|
||||
* "description"="Max entities per page, default 10",
|
||||
* "requirements"="\d+",
|
||||
* "default"="10"
|
||||
* },
|
||||
* },
|
||||
* output={
|
||||
* "class"="Paginated<CacheBundle\Entity\Comment>",
|
||||
* "groups"={ "comment", "id" }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="List of comments returned.",
|
||||
* 404="Invalid document id."
|
||||
* }
|
||||
* )
|
||||
* @param Request $request A Request instance.
|
||||
* @param integer $documentId A Document entity id.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function getCommentsAction(Request $request, $documentId)
|
||||
{
|
||||
$document = $this->em->getRepository(Document::class)->find($documentId);
|
||||
|
||||
if (! $document instanceof Document) {
|
||||
return $this->generateResponse([[
|
||||
'message' => 'Document not found',
|
||||
'transKey' => 'getDocumentCommentsInvalidDocument',
|
||||
'type' => 'error',
|
||||
'parameters' => [ 'current' => $documentId ],
|
||||
], ], 404);
|
||||
}
|
||||
|
||||
/** @var CommentRepository $repository */
|
||||
$repository = $this->em->getRepository(Comment::class);
|
||||
$qb = $repository->getListForDocument($documentId);
|
||||
|
||||
$offset = $request->query->getInt('offset', CommentManagerInterface::NEW_COMMENT_POOL_SIZE);
|
||||
$limit = $request->query->getInt('limit', 10);
|
||||
|
||||
$qb
|
||||
->setFirstResult($offset)
|
||||
->setMaxResults($limit);
|
||||
|
||||
return $this->generateResponse(new Paginator($qb), 200, [ 'id', 'comment' ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send specified documents content to recipients.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route(
|
||||
* "/email",
|
||||
* methods={ "POST" }
|
||||
* )
|
||||
* @AppApiDoc(
|
||||
* section="Document",
|
||||
* resource=true,
|
||||
* input={
|
||||
* "class"="AppBundle\Form\EmailedDocumentType",
|
||||
* "name"=false
|
||||
* },
|
||||
* statusCodes={
|
||||
* 204="Email's sent.",
|
||||
* 400="Invalid data."
|
||||
* }
|
||||
* )
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function emailAction(Request $request)
|
||||
{
|
||||
$emailedDocument = new EmailedDocument();
|
||||
$form = $this->createForm(EmailedDocumentType::class, $emailedDocument);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->em->persist($emailedDocument);
|
||||
$this->em->flush();
|
||||
|
||||
$this->emailProducer->publish($emailedDocument->getId());
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller\V1;
|
||||
|
||||
use ApiBundle\Controller\Annotation\Roles;
|
||||
use AppBundle\Controller\Traits\FormFactoryAwareTrait;
|
||||
use AppBundle\Controller\Traits\TokenStorageAwareTrait;
|
||||
use AppBundle\Exception\LimitExceedException;
|
||||
use AppBundle\Form\SearchRequest\SimpleQuerySearchRequestType;
|
||||
use AppBundle\Manager\SimpleQuery\SimpleQueryManagerInterface;
|
||||
use AppBundle\Manager\Source\SourceManagerInterface;
|
||||
use CacheBundle\Document\Extractor\DocumentContentExtractorInterface;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use IndexBundle\Model\ArticleDocumentInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use UserBundle\Enum\AppLimitEnum;
|
||||
use UserBundle\Enum\ThemeOptionExtractEnum;
|
||||
|
||||
/**
|
||||
* Class QueryController
|
||||
* @package AppBundle\Controller\V1
|
||||
*
|
||||
* @Route("/query", service="app.controller.query")
|
||||
*/
|
||||
class QueryController extends AbstractV1Controller
|
||||
{
|
||||
|
||||
use
|
||||
FormFactoryAwareTrait,
|
||||
TokenStorageAwareTrait;
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var SourceManagerInterface
|
||||
*/
|
||||
private $sourceManager;
|
||||
|
||||
/**
|
||||
* @var SimpleQueryManagerInterface
|
||||
*/
|
||||
private $queryManager;
|
||||
|
||||
/**
|
||||
* @var DocumentContentExtractorInterface
|
||||
*/
|
||||
private $extractor;
|
||||
|
||||
/**
|
||||
* QueryController constructor.
|
||||
*
|
||||
* @param FormFactoryInterface $formFactory A
|
||||
* FormFactoryInterface
|
||||
* instance.
|
||||
* @param EntityManagerInterface $em A
|
||||
* RestrictionsRepositoryInterface
|
||||
* instance.
|
||||
* @param TokenStorageInterface $tokenStorage A
|
||||
* TokenStorageInterface
|
||||
* instance.
|
||||
* @param SourceManagerInterface $sourceManager A
|
||||
* SourceManagerInterface
|
||||
* instance.
|
||||
* @param SimpleQueryManagerInterface $queryManager A
|
||||
* SimpleQueryManagerInterface
|
||||
* instance.
|
||||
* @param DocumentContentExtractorInterface $extractor A
|
||||
* DocumentContentExtractorInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(
|
||||
FormFactoryInterface $formFactory,
|
||||
EntityManagerInterface $em,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
SourceManagerInterface $sourceManager,
|
||||
SimpleQueryManagerInterface $queryManager,
|
||||
DocumentContentExtractorInterface $extractor
|
||||
) {
|
||||
$this->formFactory = $formFactory;
|
||||
$this->em = $em;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->sourceManager = $sourceManager;
|
||||
$this->queryManager = $queryManager;
|
||||
$this->extractor = $extractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make simple search without saving query in database.
|
||||
* Fetched documents are cached but not indexed.
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @Route("/search", methods={ "POST" })
|
||||
* @ApiDoc(
|
||||
* resource="Search",
|
||||
* section="Query",
|
||||
* input={
|
||||
* "class"="AppBundle\Form\SearchRequest\SimpleQuerySearchRequestType",
|
||||
* "name"=false
|
||||
* },
|
||||
* output={
|
||||
* "class"="",
|
||||
* "data"={
|
||||
* "documents"={
|
||||
* "class"="Pagination<CacheBundle\Entity\Document>",
|
||||
* "groups"={ "document" }
|
||||
* },
|
||||
* "advancedFilters"={
|
||||
* "dataType"="array",
|
||||
* "required"=true,
|
||||
* "readonly"=true,
|
||||
* "description"="Array of advanced filters values for this search request."
|
||||
* },
|
||||
* "stats"={
|
||||
* "dataType"="object",
|
||||
* "required"=true,
|
||||
* "readonly"=true,
|
||||
* "description"="Internal statistics, showed only in staging and local developers machine.",
|
||||
* "children"={
|
||||
* "totalOnPage"={
|
||||
* "dataType"="integer",
|
||||
* "required"=true,
|
||||
* "readonly"=true,
|
||||
* "description"="Total founded document on current page."
|
||||
* },
|
||||
* "newDocuments"={
|
||||
* "dataType"="integer",
|
||||
* "required"=true,
|
||||
* "readonly"=true,
|
||||
* "description"="Number of documents that were not in our database."
|
||||
* },
|
||||
* "alreadyExistsDocuments"={
|
||||
* "dataType"="integer",
|
||||
* "required"=true,
|
||||
* "readonly"=true,
|
||||
* "description"="Number of documents that already in our database."
|
||||
* },
|
||||
* "fromCache"={
|
||||
* "dataType"="boolean",
|
||||
* "required"=true,
|
||||
* "readonly"=true,
|
||||
* "description"="Flag, all documents fetched from our internal cache if set."
|
||||
* },
|
||||
* "expiresAt"={
|
||||
* "dataType"="datetime",
|
||||
* "required"=true,
|
||||
* "readonly"=true,
|
||||
* "description"="When this query is expired."
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* },
|
||||
* statusCodes={
|
||||
* 200="Search completed.",
|
||||
* 400="Invalid data provided"
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return array|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function searchAction(Request $request)
|
||||
{
|
||||
$form = $this->createForm(SimpleQuerySearchRequestType::class);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isValid()) {
|
||||
$user = $this->getCurrentUser();
|
||||
|
||||
try {
|
||||
$user->useLimit(AppLimitEnum::searches());
|
||||
} catch (LimitExceedException $exception) {
|
||||
return $this->generateResponse([
|
||||
'failedRestriction' => AppLimitEnum::SEARCHES,
|
||||
'restrictions' => $user->getRestrictions(),
|
||||
], 402);
|
||||
}
|
||||
|
||||
$this->em->persist($user);
|
||||
$this->em->flush();
|
||||
|
||||
/** @var SearchRequestBuilderInterface $builder */
|
||||
$builder = $form->getData();
|
||||
|
||||
$searchRequest = $builder
|
||||
->setFields([
|
||||
FieldNameEnum::TITLE,
|
||||
FieldNameEnum::MAIN,
|
||||
])
|
||||
->addSort(FieldNameEnum::PUBLISHED, 'desc')
|
||||
->build();
|
||||
|
||||
$response = $this->queryManager->searchAndCache(
|
||||
$searchRequest,
|
||||
$request->request->get('filters', []),
|
||||
$request->request->get('advancedFilters', [])
|
||||
);
|
||||
|
||||
$query = $response->getQuery();
|
||||
|
||||
$response->mapDocuments(function (ArticleDocumentInterface $document) use ($query) {
|
||||
return $document->mapNormalizedData(function (array $data) use ($query) {
|
||||
$result = $this->extractor->extract(
|
||||
$data['content'],
|
||||
$query->getRaw(),
|
||||
ThemeOptionExtractEnum::start(),
|
||||
true
|
||||
);
|
||||
|
||||
$data['content'] = $result->getText() . (
|
||||
mb_strlen($data['content']) > $result->getLength()
|
||||
? '...'
|
||||
: ''
|
||||
);
|
||||
|
||||
return $data;
|
||||
});
|
||||
});
|
||||
|
||||
$result = [
|
||||
'documents' => $this->paginate($response, $builder->getPage(), $builder->getLimit()),
|
||||
'advancedFilters' => $searchRequest->getAvailableAdvancedFilters() ?: (object) [],
|
||||
];
|
||||
|
||||
//
|
||||
// Return internal statistic.
|
||||
//
|
||||
$result['stats'] = [
|
||||
'newDocuments' => $response->getUniqueCount(),
|
||||
'alreadyExistsDocuments' => $response->count() - $response->getUniqueCount(),
|
||||
'fromCache' => $response->isFromCache(),
|
||||
'expiresAt' => $query->getExpirationDate()->format('c'),
|
||||
];
|
||||
|
||||
//
|
||||
// Return meta information about query.
|
||||
//
|
||||
$sources = $this->sourceManager->getSourcesForQuery($query, [ 'id', 'title', 'type' ]);
|
||||
$sourceLists = $this->sourceManager->getSourceListsForQuery($query, [ 'id', 'name' ]);
|
||||
|
||||
$result['meta'] = [
|
||||
'type' => 'query',
|
||||
'status' => 'synced',
|
||||
'search' => [
|
||||
'query' => $query->getRaw(),
|
||||
'filters' => $query->getRawFilters() ?: (object) [],
|
||||
'advancedFilters' => $query->getRawAdvancedFilters() ?: (object) [],
|
||||
],
|
||||
'sources' => $sources,
|
||||
'sourceLists' => $sourceLists,
|
||||
];
|
||||
|
||||
return $this->generateResponse($result);
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller\V1;
|
||||
|
||||
use AppBundle\Controller\Traits\FormFactoryAwareTrait;
|
||||
use AppBundle\Controller\Traits\TokenStorageAwareTrait;
|
||||
use AppBundle\Manager\Source\SourceManagerInterface;
|
||||
use CacheBundle\Entity\SourceList;
|
||||
use CacheBundle\Form\Sources\SourceSearchType;
|
||||
use CacheBundle\Repository\SourceListRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use IndexBundle\Model\SourceDocument;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilder;
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use ApiBundle\Controller\Annotation\Roles;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
|
||||
/**
|
||||
* Class SourceIndexController
|
||||
* @package AppBundle\Controller\V1
|
||||
*
|
||||
* @Route("/source-index", service="app.controller.source-index")
|
||||
*/
|
||||
class SourceIndexController extends AbstractV1Controller
|
||||
{
|
||||
|
||||
use
|
||||
TokenStorageAwareTrait,
|
||||
FormFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* @var SourceManagerInterface
|
||||
*/
|
||||
private $sourceManager;
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* SourceIndexController constructor.
|
||||
*
|
||||
* @param TokenStorageInterface $tokenStorage A TokenStorageInterface
|
||||
* instance.
|
||||
* @param FormFactoryInterface $formFactory A FormFactoryInterface
|
||||
* instance.
|
||||
* @param SourceManagerInterface $sourceManager A SourceManagerInterface
|
||||
* instance.
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(
|
||||
TokenStorageInterface $tokenStorage,
|
||||
FormFactoryInterface $formFactory,
|
||||
SourceManagerInterface $sourceManager,
|
||||
EntityManagerInterface $em
|
||||
) {
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->formFactory = $formFactory;
|
||||
$this->sourceManager = $sourceManager;
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch all sources from our cache.
|
||||
*
|
||||
* @Route("/", methods={ "POST" })
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* section="Source Index",
|
||||
* input={
|
||||
* "class"="CacheBundle\Form\Sources\SourceSearchType",
|
||||
* "name"=false
|
||||
* },
|
||||
* output={
|
||||
* "class"="Pagination<IndexBundle\Model\SourceDocument>",
|
||||
* "groups"={ "id", "source" }
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return \Knp\Component\Pager\Pagination\PaginationInterface|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function listAction(Request $request)
|
||||
{
|
||||
$form = $this->createForm(SourceSearchType::class);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
|
||||
if ($form->isValid()) {
|
||||
/** @var SearchRequestBuilder $searchRequestBuilder */
|
||||
$searchRequestBuilder = $form->getData();
|
||||
$searchRequestBuilder->setUser($this->getCurrentUser());
|
||||
|
||||
$response = $this->sourceManager->find($searchRequestBuilder);
|
||||
|
||||
$advancedFilters = $this->sourceManager->getAvailableFilters($searchRequestBuilder);
|
||||
|
||||
$sort = $searchRequestBuilder->getSorts();
|
||||
$sort = [
|
||||
'field' => array_search(key($sort), SourceSearchType::$fields),
|
||||
'direction' => current($sort),
|
||||
];
|
||||
|
||||
return $this->generateResponse([
|
||||
'sources' => $this->paginate($response, $searchRequestBuilder->getPage(), $searchRequestBuilder->getLimit()),
|
||||
'advancedFilters' => $advancedFilters ?: (object) [],
|
||||
'meta' => [
|
||||
'query' => $request->request->get('query'),
|
||||
'advancedFilters' => $request->request->get('advancedFilters', [])?: (object) [],
|
||||
'sort' => $sort,
|
||||
],
|
||||
], 200, [ 'id', 'source' ]);
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace source lists for specified source.
|
||||
*
|
||||
* @Route("/{id}/list", methods={ "POST" })
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* section="Source Index",
|
||||
* parameters={
|
||||
* "sourceList"={
|
||||
* "name"="sourceLists",
|
||||
* "dataType"="array",
|
||||
* "description"="Array of source lists ids."
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param integer $id A Source entity id.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function replaceListAction(Request $request, $id)
|
||||
{
|
||||
if (count($this->sourceManager->getIndex()->has($id)) > 0) {
|
||||
return $this->generateResponse([ [
|
||||
'message' => "Can't find source with id {$id}",
|
||||
'transKey' => 'replaceSourceUnknown',
|
||||
'type' => 'error',
|
||||
'parameters' => [ 'current' => $id ],
|
||||
], ], 404);
|
||||
}
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
$sourceLists = $request->request->get('sourceLists');
|
||||
|
||||
if (! is_array($sourceLists)) {
|
||||
return $this->generateResponse([[
|
||||
'message' => 'sourceLists: This value should not be empty.',
|
||||
'transKey' => 'replaceSourceListsEmpty',
|
||||
'type' => 'error',
|
||||
'parameters' => [ 'current' => null ],
|
||||
], ], 400);
|
||||
}
|
||||
|
||||
/** @var SourceListRepository $repository */
|
||||
$repository = $this->em->getRepository(SourceList::class);
|
||||
$foundedIds = $repository->sanitizeIds($sourceLists, $user->getId());
|
||||
|
||||
if (count($foundedIds) !== count($sourceLists)) {
|
||||
//
|
||||
// Some of provided id is not found or not owned by current user.
|
||||
//
|
||||
return $this->generateResponse([ [
|
||||
'message' => 'sourceLists: This value is invalid.',
|
||||
'transKey' => 'replaceSourceListInvalid',
|
||||
'type' => 'error',
|
||||
'parameters' => [
|
||||
'current' => $sourceLists,
|
||||
'invalid' => array_diff($sourceLists, $foundedIds),
|
||||
],
|
||||
], ], 400);
|
||||
}
|
||||
|
||||
$this->sourceManager->replaceRelation($id, $foundedIds);
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Sources to Sources lists
|
||||
*
|
||||
* @Route("/add-to-sources-list", methods={ "POST" })
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* section="Source Index",
|
||||
* parameters={
|
||||
* "sources"={
|
||||
* "name"="sources",
|
||||
* "dataType"="integer",
|
||||
* "actualType"="collection",
|
||||
* "required"=true,
|
||||
* "description"="Array of Source id."
|
||||
* },
|
||||
* "sourceLists"={
|
||||
* "name"="sourceLists",
|
||||
* "dataType"="integer",
|
||||
* "actualType"="collection",
|
||||
* "required"=true,
|
||||
* "description"="Array of Sources Lists id."
|
||||
* },
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function addToSourceListAction(Request $request)
|
||||
{
|
||||
$sources = (array) $request->request->get('sources', []);
|
||||
$sourceLists = (array) $request->request->get('sourceLists', []);
|
||||
|
||||
//
|
||||
// Check that all fields are provided.
|
||||
//
|
||||
if (count($sources) === 0) {
|
||||
return $this->generateResponse(
|
||||
[
|
||||
[
|
||||
'message' => 'Sources should be selected.',
|
||||
'transKey' => 'sourceToListsSourcesEmpty',
|
||||
'type' => 'error',
|
||||
'parameters' => [
|
||||
'current' => $sources,
|
||||
],
|
||||
],
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
if (count($sourceLists) === 0) {
|
||||
return $this->generateResponse(
|
||||
[
|
||||
[
|
||||
'message' => 'Source lists should be selected.',
|
||||
'transKey' => 'sourceToListsSourceListsEmpty',
|
||||
'type' => 'error',
|
||||
'parameters' => [
|
||||
'current' => $sourceLists,
|
||||
],
|
||||
],
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
|
||||
//
|
||||
// Validate specified sources and source lists ids.
|
||||
//
|
||||
/** @var SourceListRepository $repository */
|
||||
$repository = $this->em->getRepository('CacheBundle:SourceList');
|
||||
|
||||
$existsSources = $this->sourceManager->getIndex()->get($sources, 'id');
|
||||
$existsSources = array_map(function (SourceDocument $document) {
|
||||
return $document['id'];
|
||||
}, $existsSources);
|
||||
if (count($sources) !== count($existsSources)) {
|
||||
return $this->generateResponse(
|
||||
[
|
||||
[
|
||||
'message' => 'sources: This value is invalid.',
|
||||
'transKey' => 'sourceToListsSourcesInvalid',
|
||||
'type' => 'error',
|
||||
'parameters' => $sources,
|
||||
],
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
$existsSourceLists = $repository->sanitizeIds($sourceLists, $user->getId());
|
||||
if (count($sourceLists) !== count($existsSourceLists)) {
|
||||
return $this->generateResponse(
|
||||
[
|
||||
[
|
||||
'message' => 'sourceLists: This value is invalid.',
|
||||
'transKey' => 'sourceToListsSourceListsInvalid',
|
||||
'type' => 'error',
|
||||
'parameters' => $sourceLists,
|
||||
],
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
$this->sourceManager->bindSourcesToLists($user, $sources, $sourceLists);
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Controller\V1;
|
||||
|
||||
use ApiBundle\Controller\AbstractApiController;
|
||||
use ApiBundle\Security\Inspector\InspectorInterface;
|
||||
use AppBundle\AppBundleServices;
|
||||
use AppBundle\Manager\Source\SourceManagerInterface;
|
||||
use CacheBundle\CacheBundleServices;
|
||||
use CacheBundle\Entity\SourceList;
|
||||
use CacheBundle\Entity\SourceToSourceList;
|
||||
use CacheBundle\Form\Sources\SourceListSearchType;
|
||||
use CacheBundle\Form\Sources\SourceListType;
|
||||
use CacheBundle\Form\Sources\SourceSearchType;
|
||||
use CacheBundle\Repository\SourceListRepository;
|
||||
use CacheBundle\Security\Inspector\SourceListInspector;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilder;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use ApiBundle\Controller\Annotation\Roles;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* Class SourceIndexController
|
||||
* @package AppBundle\Controller\V1
|
||||
*
|
||||
* @Route("/source-list", service="app.controller.source-list")
|
||||
*/
|
||||
class SourceListController extends AbstractApiController
|
||||
{
|
||||
|
||||
/**
|
||||
* Get list of sources for the user
|
||||
*
|
||||
* @Route("/list", methods={ "POST" })
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* section="Source List",
|
||||
* input={
|
||||
* "class"="CacheBundle\Form\Sources\SourceListSearchType",
|
||||
* "name"=false
|
||||
* },
|
||||
* output={
|
||||
* "class"="Pagination<CacheBundle\Entity\SourceList>",
|
||||
* "groups"={ "id", "source_list" }
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function indexAction(Request $request)
|
||||
{
|
||||
$form = $this->createForm(SourceListSearchType::class);
|
||||
$form->submit($request->request->all());
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$data = $form->getData();
|
||||
|
||||
$page = (int) $data['page'];
|
||||
$limit = (int) $data['limit'];
|
||||
$onlyShared = (boolean) $data['onlyShared'];
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
$em = $this->getManager();
|
||||
|
||||
/** @var SourceListRepository $sourceListRepository */
|
||||
$sourceListRepository = $em->getRepository(SourceList::class);
|
||||
/** @var PaginatorInterface $paginator */
|
||||
$paginator = $this->get('knp_paginator');
|
||||
|
||||
$qb = $sourceListRepository->getSourcesListsQB($user->getId(), $data['sort'], $onlyShared);
|
||||
$pagination = $paginator->paginate(
|
||||
$qb,
|
||||
$page,
|
||||
$limit
|
||||
);
|
||||
|
||||
$sort = $data['sort'] ;
|
||||
$sort = [
|
||||
'field' => array_search(key($sort), SourceListSearchType::$fields),
|
||||
'direction' => current($sort),
|
||||
];
|
||||
|
||||
/** @var NormalizerInterface $normalizer */
|
||||
$normalizer = $this->get('serializer');
|
||||
$result = $normalizer->normalize($pagination, null, ['id', 'source_list']);
|
||||
$result['sort'] = $sort;
|
||||
|
||||
return $this->generateResponse($result, 200);
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a source list
|
||||
*
|
||||
* @Route("/", methods={ "POST" })
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* section="Source List",
|
||||
* input={
|
||||
* "class"="CacheBundle\Form\Sources\SourceListType",
|
||||
* "name"=false
|
||||
* },
|
||||
* output={
|
||||
* "class"="CacheBundle\Entity\SourceList",
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request entity instance.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function createAction(Request $request)
|
||||
{
|
||||
$sourceList = new SourceList();
|
||||
$sourceList->setUser($this->getCurrentUser());
|
||||
|
||||
$form = $this->createForm(SourceListType::class, $sourceList);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isValid()) {
|
||||
$em = $this->getManager();
|
||||
$em->persist($sourceList);
|
||||
$em->flush();
|
||||
|
||||
return $this->generateResponse($sourceList, 200, [
|
||||
'source_list',
|
||||
'id',
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a source list
|
||||
*
|
||||
* @Route(
|
||||
* "/{id}",
|
||||
* requirements={ "id": "\d+" },
|
||||
* methods={ "PUT" }
|
||||
* )
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* section="Source List",
|
||||
* parameters={
|
||||
* "name"={
|
||||
* "name"="name",
|
||||
* "dataType"="string",
|
||||
* "required"="true",
|
||||
* "description"="A new name of the source list"
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A HTTP Request instance.
|
||||
* @param SourceList $sourceList A updated SourceList instance.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function updateAction(Request $request, SourceList $sourceList)
|
||||
{
|
||||
$form = $this->createForm(SourceListType::class, $sourceList);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isValid()) {
|
||||
$em = $this->getManager();
|
||||
|
||||
$reasons = $this->checkAccess(InspectorInterface::UPDATE, $sourceList);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$sourceList->setUpdatedBy($this->getCurrentUser());
|
||||
|
||||
$em->persist($sourceList);
|
||||
$em->flush();
|
||||
|
||||
return $this->generateResponse($sourceList, 200, [
|
||||
'source_list',
|
||||
'id',
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a source list
|
||||
*
|
||||
* @Route("/{id}",
|
||||
* requirements={ "id": "\d+" },
|
||||
* methods={ "DELETE" }
|
||||
* )
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* section="Source List",
|
||||
* parameters={
|
||||
* "id"={
|
||||
* "name"="id",
|
||||
* "dataType"="integer",
|
||||
* "required"="true",
|
||||
* "description"="Id of the source list which changing"
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param SourceList $sourceList A deleted SourceList instance.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function deleteAction(SourceList $sourceList)
|
||||
{
|
||||
$reasons = $this->checkAccess(InspectorInterface::DELETE, $sourceList);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
//
|
||||
// Remove this source list from all sources which is contains in it.
|
||||
//
|
||||
/** @var SourceManagerInterface $sourceManager */
|
||||
$sourceManager = $this->container->get(CacheBundleServices::SOURCE_CACHE);
|
||||
$sourceManager->unbindSourcesFromLists($sourceList->getId());
|
||||
|
||||
$em = $this->getManager();
|
||||
$em->remove($sourceList);
|
||||
$em->flush();
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of sources for specified source list.
|
||||
*
|
||||
* @Route("/{id}/sources/search",
|
||||
* requirements={ "id": "\d+" },
|
||||
* methods={ "POST" }
|
||||
* )
|
||||
*
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource="Sources of specified source list",
|
||||
* section="Source List",
|
||||
* input={
|
||||
* "class"="CacheBundle\Form\Sources\SourceSearchType",
|
||||
* "name"=false
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param integer $id A SourceList entity id.
|
||||
*
|
||||
* @return \Knp\Component\Pager\Pagination\PaginationInterface|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function sourcesAction(Request $request, $id)
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
/** @var SourceListRepository $repository */
|
||||
$repository = $this->getManager()->getRepository('CacheBundle:SourceList');
|
||||
$sourceList = $repository->getSourcesLists($id, $user->getId());
|
||||
|
||||
if ($sourceList === null) {
|
||||
return $this->generateResponse("Can't find source list with id $id", 404);
|
||||
}
|
||||
|
||||
$form = $this->createForm(SourceSearchType::class);
|
||||
$form->submit($request->request->all());
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var SearchRequestBuilder $searchRequestBuilder */
|
||||
$searchRequestBuilder = $form->getData();
|
||||
/** @var SourceManagerInterface $manager */
|
||||
$manager = $this->get(AppBundleServices::SOURCE_MANAGER);
|
||||
$searchRequestBuilder->setUser($user);
|
||||
|
||||
$response = $manager->find($searchRequestBuilder, $sourceList);
|
||||
|
||||
/** @var PaginatorInterface $paginator */
|
||||
$paginator = $this->get('knp_paginator');
|
||||
$pagination = $paginator->paginate(
|
||||
$response,
|
||||
$searchRequestBuilder->getPage(),
|
||||
$searchRequestBuilder->getLimit()
|
||||
);
|
||||
|
||||
$sort = $searchRequestBuilder->getSorts();
|
||||
$sort = [
|
||||
'field' => array_search(key($sort), SourceSearchType::$fields),
|
||||
'direction' => current($sort),
|
||||
];
|
||||
|
||||
return $this->generateResponse([
|
||||
'sources' => $pagination,
|
||||
'filters' => $request->request->get('filters', (object) []),
|
||||
'sort' => $sort,
|
||||
], 200, [ 'id', 'source' ]);
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone current list.
|
||||
*
|
||||
* @Route("/{id}/clone",
|
||||
* requirements={ "id": "\d+" },
|
||||
* methods={ "POST" }
|
||||
* )
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource="Clone specified source list",
|
||||
* section="Source List",
|
||||
* parameters={
|
||||
* {
|
||||
* "name"="name",
|
||||
* "dataType"="string",
|
||||
* "required"="true"
|
||||
* }
|
||||
* },
|
||||
* output={
|
||||
* "class"="CacheBundle\Entity\SourceList",
|
||||
* "groups"={ "id", "source_list" }
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param integer $id A SourceList entity id.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function cloneAction(Request $request, $id)
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
/** @var SourceListRepository $repository */
|
||||
$repository = $this->getManager()->getRepository('CacheBundle:SourceList');
|
||||
$sourceList = $repository->getSourcesLists($id, $user->getId());
|
||||
$em = $this->getManager();
|
||||
|
||||
$name = $request->request->get('name');
|
||||
|
||||
if ($name === null) {
|
||||
return $this->generateResponse('Required field \'name\' is not provided or empty.');
|
||||
}
|
||||
|
||||
if ($sourceList === null) {
|
||||
return $this->generateResponse("Can't find source list with id $id", 404);
|
||||
}
|
||||
|
||||
$clone = $sourceList->cloneList();
|
||||
$clone->setName($name);
|
||||
/** @var SourceManagerInterface $sourceManager */
|
||||
$sourceManager = $this->get(CacheBundleServices::SOURCE_CACHE);
|
||||
|
||||
$sources = $sourceList->getSources()->map(function (SourceToSourceList $source) {
|
||||
return $source->getSource();
|
||||
})->toArray();
|
||||
|
||||
$em->persist($clone);
|
||||
$em->flush();
|
||||
|
||||
//
|
||||
// We should add and original id 'cause otherwise he lost his binding.
|
||||
//
|
||||
$sourceManager->bindSourcesToLists($user, $sources, [ $sourceList->getId(), $clone->getId() ]);
|
||||
|
||||
return $this->generateResponse($clone, 200, [
|
||||
'source_list',
|
||||
'id',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Share specified source list.
|
||||
*
|
||||
* @Route("/{id}/share",
|
||||
* requirements={ "id": "\d+" },
|
||||
* methods={ "POST" }
|
||||
* )
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource="Sharing",
|
||||
* section="Source List"
|
||||
* )
|
||||
*
|
||||
* @param string $id A SourceList entity id.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function shareAction($id)
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
$em = $this->getManager();
|
||||
|
||||
/** @var SourceListRepository $repository */
|
||||
$repository = $em->getRepository('CacheBundle:SourceList');
|
||||
$sourceList = $repository->getSourcesLists($id, $user->getId());
|
||||
|
||||
if ($sourceList === null) {
|
||||
return $this->generateResponse("Can't find source list with id $id", 404);
|
||||
}
|
||||
|
||||
$reasons = $this->checkAccess(SourceListInspector::SHARE, $sourceList);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
if (! $sourceList->getIsGlobal()) {
|
||||
$sourceList
|
||||
->setUpdatedBy($this->getCurrentUser())
|
||||
->setIsGlobal(true);
|
||||
$em->persist($sourceList);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshare specified source list.
|
||||
*
|
||||
* @Route("/{id}/unshare",
|
||||
* requirements={ "id": "\d+" },
|
||||
* methods={ "POST" }
|
||||
* )
|
||||
* @Roles("ROLE_SUBSCRIBER")
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource="Sharing",
|
||||
* section="Source List"
|
||||
* )
|
||||
*
|
||||
* @param string $id A SourceList entity id.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
public function unshareAction($id)
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
$em = $this->getManager();
|
||||
|
||||
/** @var SourceListRepository $repository */
|
||||
$repository = $em->getRepository('CacheBundle:SourceList');
|
||||
$sourceList = $repository->getSourcesLists($id, $user->getId());
|
||||
|
||||
if ($sourceList === null) {
|
||||
return $this->generateResponse("Can't find source list with id $id", 404);
|
||||
}
|
||||
|
||||
$reasons = $this->checkAccess(SourceListInspector::UNSHARE, $sourceList);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
if ($sourceList->getIsGlobal()) {
|
||||
$sourceList
|
||||
->setUpdatedBy($this->getCurrentUser())
|
||||
->setIsGlobal(false);
|
||||
$em->persist($sourceList);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures;
|
||||
|
||||
use IndexBundle\Fixture\IndexFixtureInterface;
|
||||
use IndexBundle\Model\Generator\ExternalDocumentGenerator;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractExternalFixture
|
||||
* Base class for external index fixtures.
|
||||
*
|
||||
* @package AppBundle\DataFixtures\Index
|
||||
*/
|
||||
abstract class AbstractExternalFixture implements
|
||||
IndexFixtureInterface,
|
||||
ContainerAwareInterface
|
||||
{
|
||||
|
||||
use BaseFixtureTrait;
|
||||
|
||||
/**
|
||||
* @var ExternalDocumentGenerator
|
||||
*/
|
||||
protected $generator;
|
||||
|
||||
/**
|
||||
* AbstractExternalFixture constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->generator = new ExternalDocumentGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return index type for this fixture.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return self::INDEX_EXTERNAL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures;
|
||||
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture as BaseClass;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Faker\Factory;
|
||||
use Faker\Generator;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractFixture
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
abstract class AbstractFixture extends BaseClass implements
|
||||
ContainerAwareInterface,
|
||||
OrderedFixtureInterface
|
||||
{
|
||||
|
||||
use BaseFixtureTrait;
|
||||
|
||||
/**
|
||||
* Get the order of this fixture.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures;
|
||||
|
||||
use IndexBundle\Fixture\IndexFixtureInterface;
|
||||
use IndexBundle\Model\Generator\ExternalDocumentGenerator;
|
||||
use IndexBundle\Model\Generator\InternalDocumentGenerator;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractInternalFixture
|
||||
* Base class for internal index fixtures.
|
||||
*
|
||||
* @package AppBundle\DataFixtures\Index
|
||||
*/
|
||||
abstract class AbstractInternalIndexFixture implements
|
||||
IndexFixtureInterface,
|
||||
ContainerAwareInterface
|
||||
{
|
||||
|
||||
use BaseFixtureTrait;
|
||||
|
||||
/**
|
||||
* @var ExternalDocumentGenerator
|
||||
*/
|
||||
protected $generator;
|
||||
|
||||
/**
|
||||
* AbstractExternalFixture constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->generator = new InternalDocumentGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return index type for this fixture.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return self::INDEX_INTERNAL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures;
|
||||
|
||||
use IndexBundle\Fixture\IndexFixtureInterface;
|
||||
use IndexBundle\Model\Generator\SourceDocumentGenerator;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractSourceIndexFixture
|
||||
* Base class for source index fixtures.
|
||||
*
|
||||
* @package AppBundle\DataFixtures\Index
|
||||
*/
|
||||
abstract class AbstractSourceIndexFixture implements
|
||||
IndexFixtureInterface,
|
||||
ContainerAwareInterface
|
||||
{
|
||||
|
||||
use BaseFixtureTrait;
|
||||
|
||||
/**
|
||||
* @var SourceDocumentGenerator
|
||||
*/
|
||||
protected $generator;
|
||||
|
||||
/**
|
||||
* AbstractExternalFixture constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->generator = new SourceDocumentGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return index type for this fixture.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return self::INDEX_SOURCE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures;
|
||||
|
||||
use Faker\Factory;
|
||||
use Faker\Generator;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
|
||||
/**
|
||||
* Class BaseFixtureTrait
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
trait BaseFixtureTrait
|
||||
{
|
||||
|
||||
use ContainerAwareTrait;
|
||||
|
||||
/**
|
||||
* @var Generator
|
||||
*/
|
||||
private $faker;
|
||||
|
||||
/**
|
||||
* Check current application environment.
|
||||
*
|
||||
* @param string|string[] $expected Expected environment name or array of
|
||||
* names.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function checkEnvironment($expected)
|
||||
{
|
||||
$expected = (array) $expected;
|
||||
$environment = $this->container->getParameter('kernel.environment');
|
||||
|
||||
return in_array($environment, $expected, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Generator
|
||||
*/
|
||||
protected function getFaker()
|
||||
{
|
||||
if ($this->faker === null) {
|
||||
$this->faker = Factory::create();
|
||||
}
|
||||
|
||||
return $this->faker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\External;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractExternalFixture;
|
||||
use CacheBundle\Comment\Manager\CommentManagerInterface;
|
||||
use CacheBundle\Entity\Comment;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use IndexBundle\Index\External\InternalHoseIndex;
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use IndexBundle\Model\ArticleDocumentInterface;
|
||||
use IndexBundle\Model\DocumentInterface;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class ExternalFixture
|
||||
* @package AppBundle\DataFixtures\External
|
||||
*/
|
||||
class ExternalFixture extends AbstractExternalFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Max documents in 'stage' environment
|
||||
*/
|
||||
const MAX = 300;
|
||||
|
||||
/**
|
||||
* Load fixtures into index.
|
||||
*
|
||||
* @param IndexInterface $index A IndexInterface instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load(IndexInterface $index)
|
||||
{
|
||||
if ($this->checkEnvironment('prod')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $index instanceof InternalHoseIndex) {
|
||||
throw new \LogicException(sprintf(
|
||||
'External fixtures should be loaded into \'%s\' but \'%s\' given',
|
||||
InternalHoseIndex::class,
|
||||
get_class($index)
|
||||
));
|
||||
}
|
||||
|
||||
$patches = [
|
||||
[
|
||||
'sequence' => '1',
|
||||
'title' => 'Al Kodmani Crime Family stole millions',
|
||||
'lang' => 'en',
|
||||
'geo_country' => 'US',
|
||||
'geo_state' => 'Arizona',
|
||||
'geo_city' => 'Amazing City',
|
||||
'published' => date_create()->modify('- 10 days')->format('Y-m-d\TH:i:s\Z'),
|
||||
'source_title' => 'CNN',
|
||||
'duplicates_count' => 0,
|
||||
'image_src' => 'http://lorempixel.com/120/100/',
|
||||
'views' => 12012312,
|
||||
],
|
||||
[
|
||||
'sequence' => '2',
|
||||
'title' => 'Amazing cat',
|
||||
'lang' => 'en',
|
||||
'geo_country' => 'US',
|
||||
'geo_state' => 'Maryland',
|
||||
'published' => date_create()->modify('- 15 days')->format('Y-m-d\TH:i:s\Z'),
|
||||
'source_title' => 'Asharq Al Awsat',
|
||||
'duplicates_count' => 0,
|
||||
'image_src' => '',
|
||||
'views' => 112,
|
||||
'section' => 'Lifestyle',
|
||||
],
|
||||
[
|
||||
'sequence' => '3',
|
||||
'title' => 'More about cats',
|
||||
'lang' => 'ru',
|
||||
'geo_country' => 'US',
|
||||
'geo_state' => 'Louisiana',
|
||||
'published' => date_create()->modify('- 10 days')->format('Y-m-d\TH:i:s\Z'),
|
||||
'source_title' => 'AAAE',
|
||||
'duplicates_count' => 0,
|
||||
'image_src' => null,
|
||||
'views' => 10001,
|
||||
],
|
||||
[
|
||||
'sequence' => '4',
|
||||
'title' => 'Cat and dog market',
|
||||
'lang' => 'en',
|
||||
'geo_country' => 'RU',
|
||||
'published' => date_create()->modify('- 1 months')->format('Y-m-d\TH:i:s\Z'),
|
||||
'source_title' => 'Aaj TV',
|
||||
'duplicates_count' => 0,
|
||||
'image_src' => 'http://lorempixel.com/120/100/',
|
||||
'views' => 123,
|
||||
],
|
||||
[
|
||||
'sequence' => '5',
|
||||
'title' => 'Dogs are the best',
|
||||
'lang' => 'en',
|
||||
'published' => date_create()->modify('- 1 year')->format('Y-m-d\TH:i:s\Z'),
|
||||
'source_title' => 'AACSB',
|
||||
'duplicates_count' => 20,
|
||||
'image_src' => 'http://lorempixel.com/120/100/',
|
||||
'views' => 1123312,
|
||||
],
|
||||
[
|
||||
'sequence' => '6',
|
||||
'title' => 'Fish',
|
||||
'lang' => 'af',
|
||||
'geo_country' => 'US',
|
||||
'geo_state' => 'Maryland',
|
||||
'published' => date_create()->modify('- 25 days')->format('Y-m-d\TH:i:s\Z'),
|
||||
'source_title' => 'Armenian Assembly of America',
|
||||
'duplicates_count' => 0,
|
||||
'image_src' => 'http://lorempixel.com/120/100/',
|
||||
'views' => 100237312,
|
||||
],
|
||||
[
|
||||
'sequence' => '7',
|
||||
'title' => 'Cat and fish',
|
||||
'lang' => 'en',
|
||||
'geo_country' => 'US',
|
||||
'geo_state' => 'Arizona',
|
||||
'published' => date_create()->modify('- 3 days')->format('Y-m-d\TH:i:s\Z'),
|
||||
'source_title' => '4A\'s',
|
||||
'duplicates_count' => 10,
|
||||
'image_src' => null,
|
||||
'views' => 1543312,
|
||||
'author_name' => 'Gracie Pfeffer',
|
||||
'publisher' => 'msnbc',
|
||||
],
|
||||
[
|
||||
'sequence' => '8',
|
||||
'title' => 'Some',
|
||||
'main' => 'Cat',
|
||||
'lang' => 'af',
|
||||
'geo_country' => 'US',
|
||||
'geo_state' => 'Louisiana',
|
||||
'published' => date_create()->modify('- 15 minutes')->format('Y-m-d\TH:i:s\Z'),
|
||||
'source_title' => 'Asian American Press',
|
||||
'duplicates_count' => 5,
|
||||
'image_src' => '',
|
||||
'views' => 10312,
|
||||
],
|
||||
[
|
||||
'sequence' => '9',
|
||||
'title' => 'Some',
|
||||
'main' => 'Cat',
|
||||
'lang' => 'af',
|
||||
'geo_country' => 'US',
|
||||
'geo_state' => 'Louisiana',
|
||||
'published' => date_create()->modify('- 1 hours')->format('Y-m-d\TH:i:s\Z'),
|
||||
'source_title' => 'CNN',
|
||||
'duplicates_count' => 5,
|
||||
'image_src' => '',
|
||||
'views' => 10012,
|
||||
],
|
||||
];
|
||||
|
||||
/** @var ArticleDocumentInterface[] $documents */
|
||||
$documents = [];
|
||||
$max = $this->checkEnvironment('stage') ? self::MAX : count($patches);
|
||||
foreach (range(0, $max) as $idx) {
|
||||
$document = $this->generator->generate(10 + $idx);
|
||||
|
||||
if (isset($patches[$idx])) {
|
||||
$document = $this->applyPatch($document, $patches[$idx]);
|
||||
}
|
||||
$documents[] = $document;
|
||||
}
|
||||
|
||||
$index->index($documents);
|
||||
|
||||
//
|
||||
// Some documents we should persist into our database in order to add
|
||||
// comments for it.
|
||||
//
|
||||
/** @var EntityManagerInterface $em */
|
||||
$em = $this->container->get('doctrine.orm.default_entity_manager');
|
||||
$users = [
|
||||
$em->getReference(User::class, 1),
|
||||
$em->getReference(User::class, 2),
|
||||
$em->getReference(User::class, 3),
|
||||
];
|
||||
$faker = $this->getFaker();
|
||||
|
||||
if (! $this->checkEnvironment('test')) {
|
||||
foreach (range(0, $max, 5) as $idx) {
|
||||
$commentsCount = random_int(15, 35);
|
||||
|
||||
$entity = $documents[$idx]->toDocumentEntity()
|
||||
->setCommentsCount($commentsCount + 1);
|
||||
$em->persist($entity);
|
||||
|
||||
foreach (range(0, $commentsCount) as $commentIdx) {
|
||||
$comment = new Comment(
|
||||
$faker->randomElement($users),
|
||||
$faker->realText(),
|
||||
$faker->boolean() ? 'Comment ' . $commentIdx : ''
|
||||
);
|
||||
|
||||
$comment
|
||||
->setCreatedAt(date_create()->modify('- ' . ($commentIdx + 1) . ' minutes'))
|
||||
->setNew($commentIdx < CommentManagerInterface::NEW_COMMENT_POOL_SIZE)
|
||||
->setDocument($entity);
|
||||
$em->persist($comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DocumentInterface $document A IndexDocumentInterface instance.
|
||||
* @param array $path Array of patched properties with new values.
|
||||
*
|
||||
* @return DocumentInterface
|
||||
*/
|
||||
private function applyPatch(DocumentInterface $document, array $path)
|
||||
{
|
||||
return $document->mapRawData(function (array $data) use ($path) {
|
||||
foreach ($path as $name => $value) {
|
||||
$data[$name] = $value;
|
||||
}
|
||||
|
||||
return $data;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace AppBundle\DataFixtures\Internal;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractInternalIndexFixture;
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use IndexBundle\Index\Internal\InternalIndexInterface;
|
||||
use IndexBundle\Model\Generator\InternalDocumentGenerator;
|
||||
|
||||
/**
|
||||
* Class InternalFixture
|
||||
* @package AppBundle\DataFixtures\Internal
|
||||
*/
|
||||
class InternalFixture extends AbstractInternalIndexFixture
|
||||
{
|
||||
/**
|
||||
* @param IndexInterface $index A IndexInterface instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load(IndexInterface $index)
|
||||
{
|
||||
if (! $index instanceof InternalIndexInterface) {
|
||||
throw new \LogicException(sprintf(
|
||||
'External fixtures should be loaded into \'%s\' but \'%s\' given',
|
||||
InternalIndexInterface::class,
|
||||
get_class($index)
|
||||
));
|
||||
}
|
||||
|
||||
if (! $this->checkEnvironment([ 'dev', 'test' ])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$documentManager = new InternalDocumentGenerator();
|
||||
|
||||
$documents = [];
|
||||
for ($i = 0; $i < 100; ++$i) {
|
||||
$document = $documentManager->generate();
|
||||
$document['sequence'] = $i;
|
||||
$document['title'] = 'About cat '.$i;
|
||||
$documents[] = $document;
|
||||
}
|
||||
|
||||
$index->index($documents);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return index type for this fixture.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return self::INDEX_INTERNAL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\Source;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractSourceIndexFixture;
|
||||
use AppBundle\Manager\Source\SourceManagerInterface;
|
||||
use CacheBundle\Entity\SourceList;
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use IndexBundle\Index\Source\SourceIndexInterface;
|
||||
|
||||
/**
|
||||
* Class SourceFixture
|
||||
* @package AppBundle\DataFixtures\Source
|
||||
*/
|
||||
class SourceFixture extends AbstractSourceIndexFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Maximum created sources.
|
||||
*/
|
||||
const MAX_COUNT = 100;
|
||||
|
||||
/**
|
||||
* @param IndexInterface $index A IndexInterface instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load(IndexInterface $index)
|
||||
{
|
||||
if (! $index instanceof SourceIndexInterface) {
|
||||
throw new \LogicException(sprintf(
|
||||
'External fixtures should be loaded into \'%s\' but \'%s\' given',
|
||||
SourceIndexInterface::class,
|
||||
get_class($index)
|
||||
));
|
||||
}
|
||||
|
||||
if ($this->checkEnvironment('prod')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait to insure that all external documents was indexed.
|
||||
sleep(5);
|
||||
|
||||
/** @var SourceManagerInterface $manager */
|
||||
$manager = $this->container->get('app.source_manager');
|
||||
$manager->pullFromExternal();
|
||||
|
||||
// Wait to insure that all sources was indexed.
|
||||
sleep(5);
|
||||
|
||||
/** @var \Doctrine\ORM\EntityManagerInterface $em */
|
||||
$em = $this->container->get('doctrine.orm.default_entity_manager');
|
||||
$lists = $em->getRepository(SourceList::class)->findAll();
|
||||
$response = $index->createRequestBuilder()->setLimit(1000)->build()->execute();
|
||||
|
||||
$min = (int) floor($response->getTotalCount() / 6);
|
||||
$max = (int) floor($response->getTotalCount() / 2);
|
||||
|
||||
foreach ($lists as $list) {
|
||||
$ids = $this->uniqueRandomIds($response->getDocuments(), mt_rand($min, $max));
|
||||
$manager->addSourcesToList($ids, $list->getId());
|
||||
|
||||
// Wait to insure that all changes will be accepted and applied.
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch unique random ids from sources.
|
||||
*
|
||||
* @param array $sources Source array.
|
||||
* @param integer $count How much elements get.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function uniqueRandomIds(array $sources, $count)
|
||||
{
|
||||
$alreadyFetched = [];
|
||||
$fetchedCount = 0;
|
||||
$sourceCount = count($sources);
|
||||
$result = [];
|
||||
|
||||
while ($fetchedCount < $count) {
|
||||
$idx = mt_rand(0, $sourceCount - 1);
|
||||
if (in_array($idx, $alreadyFetched, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$alreadyFetched[] = $idx;
|
||||
$fetchedCount++;
|
||||
|
||||
$result[] = $sources[$idx]['id'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use CacheBundle\Entity\Feed\QueryFeed;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use UserBundle\Entity\Notification\Notification;
|
||||
use UserBundle\Entity\Notification\NotificationTheme;
|
||||
use UserBundle\Entity\Notification\Schedule\DailyNotificationSchedule;
|
||||
use UserBundle\Entity\Notification\Schedule\MonthlyNotificationSchedule;
|
||||
use UserBundle\Entity\Notification\Schedule\WeeklyNotificationSchedule;
|
||||
use UserBundle\Entity\Recipient\AbstractRecipient;
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\AppLimitEnum;
|
||||
use UserBundle\Enum\NotificationTypeEnum;
|
||||
use UserBundle\Enum\ThemeOptionExtractEnum;
|
||||
use UserBundle\Enum\ThemeTypeEnum;
|
||||
|
||||
/**
|
||||
* Class NotificationFixtures
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class NotificationFixtures extends AbstractFixture
|
||||
{
|
||||
|
||||
private static $scheduleClasses = [
|
||||
DailyNotificationSchedule::class,
|
||||
WeeklyNotificationSchedule::class,
|
||||
MonthlyNotificationSchedule::class,
|
||||
];
|
||||
|
||||
private static $availableDiffMap = [
|
||||
NotificationTypeEnum::ALERT => [
|
||||
ThemeTypeEnum::PLAIN => [
|
||||
'content.extract' => [
|
||||
ThemeOptionExtractEnum::NO,
|
||||
ThemeOptionExtractEnum::START,
|
||||
ThemeOptionExtractEnum::CONTEXT,
|
||||
],
|
||||
'content.highlightKeywords.highlight' => [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
'content.showInfo.sectionDivider' => [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
'content.showInfo.sourceCountry' => [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
'content.showInfo.userComments' => [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
],
|
||||
ThemeTypeEnum::ENHANCED => [
|
||||
'content.extract' => [
|
||||
ThemeOptionExtractEnum::NO,
|
||||
ThemeOptionExtractEnum::START,
|
||||
ThemeOptionExtractEnum::CONTEXT,
|
||||
],
|
||||
'content.highlightKeywords.highlight' => [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
'content.showInfo.articleSentiment' => [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
'content.showInfo.sourceCountry' => [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
'content.showInfo.userComments' => [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
switch (true) {
|
||||
case $this->checkEnvironment('dev'):
|
||||
$this->loadForDevelopment($manager);
|
||||
break;
|
||||
|
||||
case $this->checkEnvironment('test'):
|
||||
$this->loadForTesting($manager);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order of this fixture
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadForDevelopment(ObjectManager $manager)
|
||||
{
|
||||
/** @var User $testUser */
|
||||
$testUser = $this->getReference('test@email.com');
|
||||
/** @var User $masterUser */
|
||||
$masterUser = $this->getReference('master@email.com');
|
||||
|
||||
$feeds = [];
|
||||
for ($i = 0; $this->hasReference('feed_'. $i); $i++) {
|
||||
$feeds[] = $this->getReference('feed_'. $i);
|
||||
}
|
||||
|
||||
$this->createNotifications($testUser, $feeds, $manager);
|
||||
$this->createNotifications($masterUser, $feeds, $manager);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadForTesting(ObjectManager $manager)
|
||||
{
|
||||
/** @var User $testUser */
|
||||
$testUser = $this->getReference('test@email.com');
|
||||
/** @var User $masterUser */
|
||||
$masterUser = $this->getReference('master@email.com');
|
||||
|
||||
/** @var NotificationTheme $theme */
|
||||
$theme = $this->getReference('default_notification_theme');
|
||||
|
||||
/** @var QueryFeed $feedTest1 */
|
||||
$feedTest1 = $this->getReference('feed_test1');
|
||||
/** @var QueryFeed $feedTest3 */
|
||||
$feedTest3 = $this->getReference('feed_test3');
|
||||
|
||||
$notification = Notification::create()
|
||||
->setName('TestUser Notification1')
|
||||
->setSubject('TestUser Notification1 Subject')
|
||||
->setTimezone(new \DateTimeZone($this->getFaker()->timezone))
|
||||
->setOwner($testUser)
|
||||
->setBillingSubscription($testUser->getBillingSubscription())
|
||||
->setTheme($theme)
|
||||
->setNotificationType(NotificationTypeEnum::alert())
|
||||
->setThemeType(ThemeTypeEnum::plain())
|
||||
->setAutomatedSubject(false)
|
||||
->setPublished()
|
||||
->setActive()
|
||||
->setAllowUnsubscribe(false)
|
||||
->setUnsubscribeNotification(true)
|
||||
->setSendWhenEmpty(false)
|
||||
->addFeed($feedTest1)
|
||||
->setSourcesCount(1);
|
||||
$manager->persist($notification);
|
||||
|
||||
$notification = Notification::create()
|
||||
->setName('MasterUser Notification1')
|
||||
->setSubject('MasterUser Notification1 Subject')
|
||||
->setTimezone(new \DateTimeZone($this->getFaker()->timezone))
|
||||
->setOwner($masterUser)
|
||||
->setBillingSubscription($testUser->getBillingSubscription())
|
||||
->setTheme($theme)
|
||||
->setNotificationType(NotificationTypeEnum::alert())
|
||||
->setThemeType(ThemeTypeEnum::plain())
|
||||
->setAutomatedSubject(false)
|
||||
->setPublished()
|
||||
->setActive()
|
||||
->setAllowUnsubscribe(false)
|
||||
->setUnsubscribeNotification(true)
|
||||
->setSendWhenEmpty(false)
|
||||
->addFeed($feedTest3)
|
||||
->setSourcesCount(1);
|
||||
$manager->persist($notification);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create notification for specified user.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
* @param array $feeds Array of feeds.
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function createNotifications(
|
||||
User $user,
|
||||
array $feeds,
|
||||
ObjectManager $manager
|
||||
) {
|
||||
$userFeeds = \app\a\select(\nspl\op\methodCaller('isOwnedBy', [ $user ]), $feeds);
|
||||
$userFeedsCount = count($userFeeds);
|
||||
|
||||
if ($userFeedsCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$faker = $this->getFaker();
|
||||
|
||||
/** @var NotificationTheme $theme */
|
||||
$theme = $this->getReference('default_notification_theme');
|
||||
$allowedCount = ceil($user->getAllowedLimit(AppLimitEnum::alerts()) / 2);
|
||||
|
||||
$repository = $manager->getRepository(AbstractRecipient::class);
|
||||
$recipients = $repository->findBy([ 'owner' => $user->getId() ]);
|
||||
$recipientsCount = count($recipients);
|
||||
|
||||
for ($i = 0; $i < $allowedCount; ++$i) {
|
||||
$feeds = $faker->randomElements($userFeeds, random_int(1, ceil($userFeedsCount / 2)));
|
||||
$notificationType = NotificationTypeEnum::alert();
|
||||
$themeType = new ThemeTypeEnum($faker->randomElement(ThemeTypeEnum::getAvailables()));
|
||||
$count = random_int(0, $recipientsCount);
|
||||
|
||||
$notification = Notification::create()
|
||||
->setName($faker->realText($faker->numberBetween(10, 15)))
|
||||
->setSubject($faker->realText(50))
|
||||
->setTimezone(new \DateTimeZone($this->getFaker()->timezone))
|
||||
->setOwner($user)
|
||||
->setBillingSubscription($user->getBillingSubscription())
|
||||
->setTheme($theme)
|
||||
->setNotificationType($notificationType)
|
||||
->setThemeType($themeType)
|
||||
|
||||
->setAutomatedSubject($faker->boolean(35))
|
||||
->setPublished($faker->boolean(45))
|
||||
->setActive($faker->boolean(85))
|
||||
->setAllowUnsubscribe($faker->boolean(85))
|
||||
->setUnsubscribeNotification($faker->boolean(15))
|
||||
->setSendWhenEmpty($faker->boolean(15));
|
||||
|
||||
if ($themeType->is(ThemeTypeEnum::plain())) {
|
||||
$notification->setPlainThemeOptionsDiff(
|
||||
$this->generateDiff($notificationType, $themeType)
|
||||
);
|
||||
}
|
||||
|
||||
for ($j = 0; $j < $count; ++$j) {
|
||||
$notification->addRecipient($recipients[$j]);
|
||||
}
|
||||
|
||||
if ($faker->boolean(60)) {
|
||||
$notification->setSendUntil($faker->dateTimeBetween('+ 10 days', '+ 1 months'));
|
||||
}
|
||||
|
||||
foreach ($feeds as $feed) {
|
||||
$notification->addFeed($feed);
|
||||
}
|
||||
|
||||
$notification->setSourcesCount(count($feeds));
|
||||
|
||||
$schedules = $this->generateSchedules($faker->numberBetween(1, 4));
|
||||
|
||||
foreach ($schedules as $schedule) {
|
||||
$notification->addSchedule($schedule);
|
||||
$manager->persist($schedule);
|
||||
}
|
||||
|
||||
$user->useLimit(AppLimitEnum::alerts());
|
||||
|
||||
$manager->persist($notification);
|
||||
$manager->persist($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NotificationTypeEnum $notificationType A NotificationTypeEnum instance.
|
||||
* @param ThemeTypeEnum $themeType A ThemeTypeEnum instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function generateDiff(
|
||||
NotificationTypeEnum $notificationType,
|
||||
ThemeTypeEnum $themeType
|
||||
) {
|
||||
$faker = $this->getFaker();
|
||||
|
||||
$available = self::$availableDiffMap[(string) $notificationType][(string) $themeType];
|
||||
$availableCount = count($available);
|
||||
|
||||
/**
|
||||
* @return \Generator
|
||||
*/
|
||||
$generatorFn = function () use ($available, $availableCount, $faker) {
|
||||
$availableKeys = array_keys($available);
|
||||
$used = [];
|
||||
$usedCount = 0;
|
||||
|
||||
while ($availableCount > $usedCount) {
|
||||
do {
|
||||
$parameter = $faker->randomElement($availableKeys);
|
||||
} while (in_array($parameter, $used, true));
|
||||
|
||||
$used[] = $parameter;
|
||||
$usedCount++;
|
||||
|
||||
yield $parameter;
|
||||
}
|
||||
};
|
||||
|
||||
$parameters = $generatorFn();
|
||||
|
||||
$count = random_int(0, $availableCount - 1);
|
||||
$diff = [];
|
||||
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$parameter = $parameters->current();
|
||||
$diff[$parameter] = $faker->randomElement($available[$parameter]);
|
||||
|
||||
$parameters->next();
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate NotificationSchedule entity.
|
||||
*
|
||||
* @param integer $count How many schedules we need.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function generateSchedules($count)
|
||||
{
|
||||
$faker = $this->getFaker();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$class = $faker->randomElement(self::$scheduleClasses);
|
||||
$methodName = 'create'. \app\c\getShortName($class);
|
||||
yield $this->{$methodName}();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DailyNotificationSchedule
|
||||
*
|
||||
* Actually we call it.
|
||||
* @SuppressWarnings(PHPMD.UnusedPrivateMethod)
|
||||
*/
|
||||
private function createDailyNotificationSchedule()
|
||||
{
|
||||
$faker = $this->getFaker();
|
||||
|
||||
return DailyNotificationSchedule::create()
|
||||
->setDays($faker->randomElement(DailyNotificationSchedule::getAvailableDays()))
|
||||
->setTime($faker->randomElement(DailyNotificationSchedule::getAvailableTime()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WeeklyNotificationSchedule
|
||||
*
|
||||
* Actually we call it.
|
||||
* @SuppressWarnings(PHPMD.UnusedPrivateMethod)
|
||||
*/
|
||||
private function createWeeklyNotificationSchedule()
|
||||
{
|
||||
$faker = $this->getFaker();
|
||||
|
||||
return WeeklyNotificationSchedule::create()
|
||||
->setPeriod($faker->randomElement(WeeklyNotificationSchedule::getAvailablePeriod()))
|
||||
->setDay($faker->randomElement(WeeklyNotificationSchedule::getAvailableDay()))
|
||||
->setHour($faker->randomElement(range(0, 23)))
|
||||
->setMinute($faker->randomElement(range(0, 55, 5)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MonthlyNotificationSchedule
|
||||
*
|
||||
* Actually we call it.
|
||||
* @SuppressWarnings(PHPMD.UnusedPrivateMethod)
|
||||
*/
|
||||
private function createMonthlyNotificationSchedule()
|
||||
{
|
||||
$faker = $this->getFaker();
|
||||
|
||||
return MonthlyNotificationSchedule::create()
|
||||
->setDay($faker->randomElement(MonthlyNotificationSchedule::getAvailableDay()))
|
||||
->setHour($faker->randomElement(range(0, 23)))
|
||||
->setMinute($faker->randomElement(range(0, 55, 5)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use UserBundle\Entity\Notification\Notification;
|
||||
use UserBundle\Entity\Notification\NotificationSendHistory;
|
||||
use UserBundle\Entity\Notification\Schedule\AbstractNotificationSchedule;
|
||||
|
||||
/**
|
||||
* Class NotificationHistoryFixtures
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class NotificationHistoryFixtures extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager|EntityManagerInterface $manager A ObjectManager
|
||||
* instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
if (! $this->checkEnvironment('dev')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notifications = $manager->getRepository(Notification::class)
|
||||
->createQueryBuilder('Notification')
|
||||
->select('Notification, Schedule')
|
||||
->join('Notification.schedules', 'Schedule')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
$faker = $this->getFaker();
|
||||
|
||||
/** @var Notification $notification */
|
||||
foreach ($notifications as $notification) {
|
||||
$max = random_int(15, 40);
|
||||
for ($i = 0; $i < $max; ++$i) {
|
||||
$schedule = $notification->getSchedules()->toArray();
|
||||
|
||||
$historySchedule = array_map(function (AbstractNotificationSchedule $schedule) {
|
||||
$historySchedule = clone $schedule;
|
||||
$historySchedule->setNotification(null);
|
||||
|
||||
return $historySchedule;
|
||||
}, $faker->randomElements($schedule, random_int(1, count($schedule))));
|
||||
|
||||
$history = new NotificationSendHistory(
|
||||
$notification,
|
||||
$historySchedule
|
||||
);
|
||||
$history->setDate($faker->dateTimeBetween('- 1 year'));
|
||||
|
||||
$manager->persist($history);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order of this fixture
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use UserBundle\Entity\Notification\NotificationTheme;
|
||||
use UserBundle\Entity\Notification\NotificationThemeOptions;
|
||||
|
||||
/**
|
||||
* Class NotificationThemeFixtures
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class NotificationThemeFixtures extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager|EntityManagerInterface $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$defaultOptions = NotificationThemeOptions::createDefault();
|
||||
|
||||
//
|
||||
// Create default theme which should not be editable by master users.
|
||||
//
|
||||
$default = NotificationTheme::create()
|
||||
->setName('Socialhose theme')
|
||||
->setEnhanced($defaultOptions)
|
||||
->setPlain($defaultOptions)
|
||||
->setDefault(true);
|
||||
|
||||
$this->addReference('default_notification_theme', $default);
|
||||
|
||||
$manager->persist($default);
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use UserBundle\Entity\Organization;
|
||||
|
||||
/**
|
||||
* Class OrganizationFixture
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class OrganizationFixture extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
if ($this->checkEnvironment('prod')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$organization = Organization::create()
|
||||
->setName('Test Organization');
|
||||
$this->setReference('organization', $organization);
|
||||
$manager->persist($organization);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use PaymentBundle\Entity\Model\Money;
|
||||
use PaymentBundle\Entity\Payment;
|
||||
use PaymentBundle\Enum\PaymentGatewayEnum;
|
||||
use PaymentBundle\Enum\PaymentStatusEnum;
|
||||
use UserBundle\Entity\Subscription\AbstractSubscription;
|
||||
|
||||
/**
|
||||
* Class PaymentFixture
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class PaymentFixture extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
if (! $this->checkEnvironment('dev')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var AbstractSubscription[] $subscriptions */
|
||||
$subscriptions = [
|
||||
$this->getReference('first_subscription'),
|
||||
$this->getReference('second_subscription'),
|
||||
$this->getReference('personal_subscription'),
|
||||
];
|
||||
|
||||
$faker = $this->getFaker();
|
||||
for ($i = 0; $i < 50; $i++) {
|
||||
$payment = Payment::create()
|
||||
->setGateway(PaymentGatewayEnum::paypal())
|
||||
->setAmount(new Money($faker->randomFloat(2, 10, 20), 'USD'))
|
||||
->setStatus(new PaymentStatusEnum($faker->randomElement(PaymentStatusEnum::getAvailables())))
|
||||
->setSubscription($faker->randomElement($subscriptions))
|
||||
->setTransactionId($faker->md5);
|
||||
$manager->persist($payment);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order of this fixture.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use UserBundle\Entity\Plan;
|
||||
|
||||
/**
|
||||
* Class PlanFixture
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class PlanFixture extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$plan = Plan::create()
|
||||
->setTitle('Social Starter')
|
||||
->setInnerName('social_starter')
|
||||
->setSearchesPerDay(500)
|
||||
->setSavedFeeds(15)
|
||||
->setMasterAccounts(1)
|
||||
->setSubscriberAccounts(5)
|
||||
->setAlerts(5)
|
||||
->setNewsletters(1)
|
||||
->setAnalytics(false)
|
||||
->setNews(true)
|
||||
->setBlog(false)
|
||||
->setReddit(false)
|
||||
->setInstagram(true)
|
||||
->setIsDefault(true)
|
||||
->setTwitter(false)
|
||||
->setPrice(160.0);
|
||||
$manager->persist($plan);
|
||||
$this->setReference('starter_plan', $plan);
|
||||
|
||||
|
||||
$plan = Plan::create()
|
||||
->setTitle('Pr Starter')
|
||||
->setInnerName('pr_starter')
|
||||
->setSearchesPerDay(1000)
|
||||
->setSavedFeeds(20)
|
||||
->setMasterAccounts(1)
|
||||
->setSubscriberAccounts(20)
|
||||
->setAlerts(20)
|
||||
->setNewsletters(10)
|
||||
->setAnalytics(true)
|
||||
->setNews(true)
|
||||
->setBlog(false)
|
||||
->setIsDefault(true)
|
||||
->setReddit(false)
|
||||
->setInstagram(true)
|
||||
->setTwitter(false)
|
||||
->setPrice(190.0);
|
||||
$manager->persist($plan);
|
||||
$this->setReference('pr_starter', $plan);
|
||||
|
||||
|
||||
$plan = Plan::create()
|
||||
->setTitle('The Works')
|
||||
->setInnerName('the_works')
|
||||
->setSearchesPerDay(2000)
|
||||
->setSavedFeeds(25)
|
||||
->setMasterAccounts(1)
|
||||
->setSubscriberAccounts(25)
|
||||
->setAlerts(30)
|
||||
->setNewsletters(20)
|
||||
->setAnalytics(true)
|
||||
->setNews(true)
|
||||
->setBlog(true)
|
||||
->setReddit(true)
|
||||
->setInstagram(true)
|
||||
->setIsDefault(true)
|
||||
->setTwitter(true)
|
||||
->setPrice(325.0);
|
||||
$manager->persist($plan);
|
||||
$this->setReference('the_works', $plan);
|
||||
|
||||
|
||||
|
||||
$plan = Plan::create()
|
||||
->setTitle('Free')
|
||||
->setInnerName('free')
|
||||
->setSearchesPerDay(100)
|
||||
->setSavedFeeds(0)
|
||||
->setMasterAccounts(1)
|
||||
->setSubscriberAccounts(0)
|
||||
->setAlerts(5)
|
||||
->setNewsletters(5)
|
||||
->setAnalytics(true)
|
||||
->setNews(true)
|
||||
->setBlog(true)
|
||||
->setReddit(true)
|
||||
->setInstagram(true)
|
||||
->setTwitter(true)
|
||||
->setIsDefault(true)
|
||||
->setPrice(0.0);
|
||||
$manager->persist($plan);
|
||||
$this->setReference('free', $plan);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use AppBundle\Exception\LimitExceedException;
|
||||
use CacheBundle\Entity\Feed\QueryFeed;
|
||||
use CacheBundle\Entity\Feed\AbstractFeed;
|
||||
use CacheBundle\Entity\Query\StoredQuery;
|
||||
use CacheBundle\Entity\Category;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use Common\Enum\PublisherTypeEnum;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\AppLimitEnum;
|
||||
|
||||
/**
|
||||
* Class QueryFeedFixture
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class QueryFeedFixture extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Max available feeds.
|
||||
*/
|
||||
const MAX_FEEDS = 5;
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
switch (true) {
|
||||
case $this->checkEnvironment('dev'):
|
||||
$this->loadForDevelopment($manager);
|
||||
break;
|
||||
|
||||
case $this->checkEnvironment('test'):
|
||||
$this->loadForTesting($manager);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order of this fixture.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadForDevelopment(ObjectManager $manager)
|
||||
{
|
||||
/** @var User[] $users */
|
||||
$users = [
|
||||
$this->getReference('test@email.com'),
|
||||
$this->getReference('master@email.com'),
|
||||
];
|
||||
|
||||
for ($i = 0; $i < self::MAX_FEEDS; $i++) {
|
||||
$raw = strtolower($this->getFaker()->word);
|
||||
|
||||
$index = random_int(0, count($users) - 1);
|
||||
|
||||
$user = $users[$index];
|
||||
|
||||
try {
|
||||
$user->useLimit(AppLimitEnum::feeds());
|
||||
} catch (LimitExceedException $exception) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$categoriesCount = count($user->getCategories());
|
||||
|
||||
$query = $this->createQuery($raw);
|
||||
$category = $this->getCategory($user, $categoriesCount);
|
||||
$feed = $this->createQueryFeed($query, 'test'. $raw, $user, $category);
|
||||
|
||||
$manager->persist($query);
|
||||
$manager->persist($feed);
|
||||
$manager->persist($user);
|
||||
$this->addReference('feed_'. $i, $feed);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadForTesting(ObjectManager $manager)
|
||||
{
|
||||
/** @var User $testUser */
|
||||
$testUser = $this->getReference('test@email.com');
|
||||
/** @var User $masterUser */
|
||||
$masterUser = $this->getReference('master@email.com');
|
||||
|
||||
$testUser->useLimit(AppLimitEnum::feeds(), 2);
|
||||
|
||||
$query = $this->createQuery('test1');
|
||||
$feed = $this->createQueryFeed($query, 'test1', $testUser, $testUser->getCategories()->first());
|
||||
|
||||
$manager->persist($query);
|
||||
$manager->persist($feed);
|
||||
$this->addReference('feed_test1', $feed);
|
||||
|
||||
$query = $this->createQuery('test2');
|
||||
$feed = $this->createQueryFeed($query, 'test2', $testUser, $testUser->getCategories()->first());
|
||||
|
||||
$manager->persist($query);
|
||||
$manager->persist($feed);
|
||||
$this->addReference('feed_test2', $feed);
|
||||
|
||||
$masterUser->useLimit(AppLimitEnum::feeds());
|
||||
|
||||
$query = $this->createQuery('test3');
|
||||
$feed = $this->createQueryFeed($query, 'test3', $masterUser, $masterUser->getCategories()->first());
|
||||
|
||||
$manager->persist($query);
|
||||
$manager->persist($feed);
|
||||
$this->addReference('feed_test3', $feed);
|
||||
|
||||
$manager->persist($testUser);
|
||||
$manager->persist($masterUser);
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user A User entity instance.
|
||||
* @param integer $count Total count of categories.
|
||||
*
|
||||
* @return \CacheBundle\Entity\Category
|
||||
*/
|
||||
private function getCategory(User $user, $count)
|
||||
{
|
||||
static $i = 0;
|
||||
|
||||
if ($i >= $count) {
|
||||
$i = 0;
|
||||
}
|
||||
|
||||
return $user->getCategories()[$i++];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $searchString That keyword is used for searching.
|
||||
*
|
||||
* @return StoredQuery
|
||||
*/
|
||||
private function createQuery($searchString)
|
||||
{
|
||||
return StoredQuery::create()
|
||||
->setRaw($searchString)
|
||||
->setFields([
|
||||
FieldNameEnum::TITLE,
|
||||
FieldNameEnum::MAIN,
|
||||
])
|
||||
->setTotalCount($this->getFaker()->randomNumber())
|
||||
->setDate(date_create())
|
||||
->setNormalized($searchString)
|
||||
->setHash($this->getFaker()->md5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StoredQuery $query A StoredQuery instance.
|
||||
* @param string $name Feed name.
|
||||
* @param User $user Feed owner.
|
||||
* @param Category $category In which category place feed.
|
||||
*
|
||||
* @return AbstractFeed
|
||||
*/
|
||||
private function createQueryFeed(StoredQuery $query, $name, User $user, Category $category)
|
||||
{
|
||||
return QueryFeed::create()
|
||||
->setPublisherTypes($this->getFaker()
|
||||
->randomElements(PublisherTypeEnum::getAvailables(), random_int(1, 2)))
|
||||
->setQuery($query)
|
||||
->setCategory($category)
|
||||
->setName($name)
|
||||
->setUser($user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use UserBundle\Entity\Recipient\GroupRecipient;
|
||||
use UserBundle\Entity\Recipient\PersonRecipient;
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\UserRoleEnum;
|
||||
|
||||
/**
|
||||
* Class RecipientFixture
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class RecipientFixture extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
if (! $this->checkEnvironment('dev')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var User[] $users */
|
||||
$users = [
|
||||
$this->getReference('test@email.com'),
|
||||
$this->getReference('master@email.com'),
|
||||
];
|
||||
|
||||
$faker = $this->getFaker();
|
||||
|
||||
foreach ($users as $user) {
|
||||
if ($user->hasRole(UserRoleEnum::MASTER_USER)) {
|
||||
$personCount = random_int(4, 10);
|
||||
$groupCount = random_int(3, 5);
|
||||
$persons = [];
|
||||
|
||||
for ($i = 0; $i < $personCount; ++$i) {
|
||||
$person = PersonRecipient::create()
|
||||
->setFirstName($faker->firstName)
|
||||
->setLastName($faker->lastName)
|
||||
->setEmail($faker->email)
|
||||
->setOwner($user);
|
||||
|
||||
$persons[] = $person;
|
||||
|
||||
$manager->persist($person);
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $groupCount; ++$i) {
|
||||
$groupPersons = $faker->randomElements($persons, random_int(2, 4));
|
||||
|
||||
$group = GroupRecipient::create()
|
||||
->setName($faker->word)
|
||||
->setDescription($faker->realText())
|
||||
->setOwner($user);
|
||||
|
||||
foreach ($groupPersons as $person) {
|
||||
$group->addRecipient($person);
|
||||
}
|
||||
|
||||
$manager->persist($group);
|
||||
}
|
||||
|
||||
$manager->persist($user);
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order of this fixture.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken;
|
||||
|
||||
/**
|
||||
* Class RefreshTokenFixture
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class RefreshTokenFixture extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
if (! $this->checkEnvironment('test')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = new RefreshToken();
|
||||
$token
|
||||
->setRefreshToken('user1_token')
|
||||
->setUsername('test@email.com')
|
||||
->setValid(date_create()->modify('+ 10 days'));
|
||||
|
||||
$manager->persist($token);
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\AppBundleServices;
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
|
||||
/**
|
||||
* Class SiteConfigurationFixture
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class SiteConfigurationFixture extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$this->container->get(AppBundleServices::CONFIGURATION)
|
||||
->syncWithDefinitions();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use CacheBundle\Entity\SourceList;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
|
||||
/**
|
||||
* Class SourceListFixture
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class SourceListFixture extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
if (! $this->checkEnvironment('dev')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$users = [
|
||||
$this->getReference('test@email.com'),
|
||||
$this->getReference('master@email.com'),
|
||||
];
|
||||
|
||||
for ($i = 1; $i <= 25; $i++) {
|
||||
$user = $users[$i % 2];
|
||||
|
||||
$sourceList = new SourceList();
|
||||
$sourceList->setName('Source list '. $i);
|
||||
$sourceList->setUser($user);
|
||||
|
||||
if ($this->getFaker()->boolean()) {
|
||||
$sourceList
|
||||
->setUpdatedBy($user)
|
||||
->setUpdatedAt(new \DateTime());
|
||||
}
|
||||
|
||||
$manager->persist($sourceList);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order of this fixture
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DataFixtures\ORM;
|
||||
|
||||
use AppBundle\DataFixtures\AbstractFixture;
|
||||
use CacheBundle\Entity\Category;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use FOS\UserBundle\Model\UserManagerInterface;
|
||||
use PaymentBundle\Enum\PaymentGatewayEnum;
|
||||
use UserBundle\Entity\Organization;
|
||||
use UserBundle\Entity\Plan;
|
||||
use UserBundle\Entity\Subscription\OrganizationSubscription;
|
||||
use UserBundle\Entity\Subscription\PersonalSubscription;
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\UserRoleEnum;
|
||||
|
||||
/**
|
||||
* Class UserFixture
|
||||
* @package AppBundle\DataFixtures\ORM
|
||||
*/
|
||||
class UserFixture extends AbstractFixture
|
||||
{
|
||||
|
||||
/**
|
||||
* Load data fixtures with the passed EntityManager
|
||||
*
|
||||
* @param ObjectManager $manager A ObjectManager instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
/** @var UserManagerInterface $userManager */
|
||||
$userManager = $this->container->get('fos_user.user_manager');
|
||||
|
||||
if ($this->checkEnvironment('prod')) {
|
||||
/** @var User $superAdmin */
|
||||
$superAdmin = $userManager->createUser();
|
||||
$superAdmin
|
||||
->setFirstName('Super')
|
||||
->setLastName('Admin')
|
||||
->setEmail('super_admin@socialhose.io')
|
||||
->setPhoneNumber('44444444444')
|
||||
->setVerified()
|
||||
->setEnabled(true)
|
||||
->addRole(UserRoleEnum::SUPER_ADMIN)
|
||||
->setPlainPassword('FEvJNcKGk2rVDMVL');
|
||||
|
||||
$userManager->updateUser($superAdmin);
|
||||
$this->setReference('super_admin@socialhose.io', $superAdmin);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Create organization subscription and users.
|
||||
//
|
||||
/** @var Organization $organization */
|
||||
$organization = $this->getReference('organization');
|
||||
/** @var Plan $businessPlan */
|
||||
$businessPlan = $this->getReference('starter_plan');
|
||||
|
||||
/** @var Plan $basicPlan */
|
||||
$basicPlan = $this->getReference('pr_starter');
|
||||
|
||||
//
|
||||
// First department.
|
||||
//
|
||||
|
||||
$firstSubscription = OrganizationSubscription::create()
|
||||
->setGateway(PaymentGatewayEnum::paypal())
|
||||
->setPayed(true)
|
||||
->setPlan($businessPlan)
|
||||
->setOrganization($organization)
|
||||
->setOrganizationAddress('First department address')
|
||||
->setOrganizationEmail('first_department.organization@email.com')
|
||||
->setOrganizationPhone('111111111');
|
||||
|
||||
/** @var User $master */
|
||||
$master = $userManager->createUser();
|
||||
$master
|
||||
->setFirstName('John')
|
||||
->setLastName('Smith')
|
||||
->setEmail('test@email.com')
|
||||
->setPhoneNumber('11111111111')
|
||||
->setVerified()
|
||||
->setEnabled(true)
|
||||
->setBillingSubscription($firstSubscription)
|
||||
->addRole(UserRoleEnum::MASTER_USER)
|
||||
->setPlainPassword('test');
|
||||
$main = Category::createMainCategory($master);
|
||||
Category::createSharedCategory($master);
|
||||
Category::createTrashCategory($master);
|
||||
$subMain = Category::createChild($main, $master, 'Sub main');
|
||||
Category::createChild($subMain, $master, 'Sub main sub 1');
|
||||
Category::createChild($subMain, $master, 'Sub main sub 2');
|
||||
$subMainSub3 = Category::createChild($subMain, $master, 'Sub main sub 3');
|
||||
Category::createChild($subMainSub3, $master, 'Test');
|
||||
|
||||
$firstSubscription->setOwner($master);
|
||||
|
||||
$userManager->updateUser($master);
|
||||
$this->setReference('test@email.com', $master);
|
||||
$this->setReference('first_subscription', $firstSubscription);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $userManager->createUser();
|
||||
$user
|
||||
->setFirstName('John')
|
||||
->setLastName('Smith')
|
||||
->setEmail('test_subscriber@email.com')
|
||||
->setPhoneNumber('11111111112')
|
||||
->setVerified()
|
||||
->setEnabled(true)
|
||||
->setBillingSubscription($firstSubscription)
|
||||
->addRole(UserRoleEnum::SUBSCRIBER)
|
||||
->setMasterUser($master)
|
||||
->setPlainPassword('test');
|
||||
|
||||
|
||||
$manager->persist($firstSubscription);
|
||||
$userManager->updateUser($user);
|
||||
$this->setReference('test_subscriber@email.com', $user);
|
||||
|
||||
//
|
||||
// Second department.
|
||||
//
|
||||
|
||||
$secondSubscription = OrganizationSubscription::create()
|
||||
->setGateway(PaymentGatewayEnum::paypal())
|
||||
->setPayed(true)
|
||||
->setPlan($basicPlan)
|
||||
->setOrganization($organization)
|
||||
->setOrganizationAddress('Second department address')
|
||||
->setOrganizationEmail('second_department.organization@email.com')
|
||||
->setOrganizationPhone('222222222');
|
||||
|
||||
/** @var User $user */
|
||||
$user = $userManager->createUser();
|
||||
$user
|
||||
->setFirstName('Master')
|
||||
->setLastName('Smith')
|
||||
->setEmail('master@email.com')
|
||||
->setPhoneNumber('22222222222')
|
||||
->setVerified()
|
||||
->setEnabled(true)
|
||||
->setBillingSubscription($secondSubscription)
|
||||
->addRole(UserRoleEnum::MASTER_USER)
|
||||
->setPlainPassword('test');
|
||||
$main = Category::createMainCategory($user);
|
||||
Category::createSharedCategory($user);
|
||||
Category::createTrashCategory($user);
|
||||
Category::createChild($main, $user, 'Sub main');
|
||||
|
||||
$secondSubscription->setOwner($user);
|
||||
$manager->persist($secondSubscription);
|
||||
|
||||
$userManager->updateUser($user);
|
||||
$this->setReference('master@email.com', $user);
|
||||
$this->setReference('second_subscription', $secondSubscription);
|
||||
|
||||
//
|
||||
// Individual subscription.
|
||||
//
|
||||
$personSubscription = PersonalSubscription::create()
|
||||
->setGateway(PaymentGatewayEnum::paypal())
|
||||
->setPayed(true)
|
||||
->setPlan($basicPlan);
|
||||
|
||||
$user = $userManager->createUser();
|
||||
$user
|
||||
->setFirstName('Jane')
|
||||
->setLastName('Smith')
|
||||
->setEmail('jane@person.com')
|
||||
->setPhoneNumber('33333333333')
|
||||
->setVerified()
|
||||
->setEnabled(true)
|
||||
->setBillingSubscription($personSubscription)
|
||||
->addRole(UserRoleEnum::MASTER_USER)
|
||||
->setPlainPassword('test');
|
||||
Category::createMainCategory($user);
|
||||
Category::createTrashCategory($user);
|
||||
|
||||
$userManager->updateUser($user);
|
||||
$this->setReference('jane@person.com', $user);
|
||||
$this->setReference('personal_subscription', $personSubscription);
|
||||
|
||||
$personSubscription->setOwner($user);
|
||||
$manager->persist($personSubscription);
|
||||
|
||||
//
|
||||
// Admins.
|
||||
//
|
||||
|
||||
/** @var User $superAdmin */
|
||||
$superAdmin = $userManager->createUser();
|
||||
$superAdmin
|
||||
->setFirstName('Super')
|
||||
->setLastName('Admin')
|
||||
->setEmail('super_admin@socialhose.com')
|
||||
->setPhoneNumber('44444444444')
|
||||
->setVerified()
|
||||
->setEnabled(true)
|
||||
->addRole(UserRoleEnum::SUPER_ADMIN)
|
||||
->setPlainPassword('test');
|
||||
|
||||
$userManager->updateUser($superAdmin);
|
||||
$this->setReference('super_admin@socialhose.com', $superAdmin);
|
||||
|
||||
/** @var User $admin */
|
||||
$admin = $userManager->createUser();
|
||||
$admin
|
||||
->setFirstName('Just')
|
||||
->setLastName('Admin')
|
||||
->setEmail('admin@socialhose.com')
|
||||
->setPhoneNumber('55555555555')
|
||||
->setVerified()
|
||||
->setEnabled(true)
|
||||
->addRole(UserRoleEnum::ADMIN)
|
||||
->setPlainPassword('test');
|
||||
|
||||
$userManager->updateUser($admin);
|
||||
$this->setReference('admin@socialhose.com', $admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order of this fixture.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
|
||||
/**
|
||||
* Class AppExtension
|
||||
* @package ApiDocBundle\DependencyInjection
|
||||
*/
|
||||
class AppExtension extends Extension
|
||||
{
|
||||
|
||||
/**
|
||||
* Loads a specific configuration.
|
||||
*
|
||||
* @param array $configs An array of configuration values.
|
||||
* @param ContainerBuilder $container A ContainerBuilder instance.
|
||||
*
|
||||
* @throws \InvalidArgumentException When provided tag is not defined in
|
||||
* this extension.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
$loader = new YamlFileLoader(
|
||||
$container,
|
||||
new FileLocator(__DIR__.'/../Resources/config')
|
||||
);
|
||||
|
||||
$environment = $container->getParameter('kernel.environment');
|
||||
if ($environment === 'prod') {
|
||||
//
|
||||
// Inject NelmioApiDocBundle form extensions 'cause we don't load full
|
||||
// nelmio configuration on production and we got error.
|
||||
//
|
||||
$loader->load('nelmio_form.yml');
|
||||
}
|
||||
|
||||
$loader->load('services.yml');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Doctrine\DBAL\Types;
|
||||
|
||||
use AppBundle\Enum\AbstractEnum;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
/**
|
||||
* Class AbstractEnumType
|
||||
* @package AppBundle\Doctrine\DBAL\Types
|
||||
*/
|
||||
abstract class AbstractEnumType extends Type
|
||||
{
|
||||
|
||||
/**
|
||||
* Gets the SQL declaration snippet for a field of this type.
|
||||
*
|
||||
* @param array $fieldDeclaration The field declaration.
|
||||
* @param AbstractPlatform $platform The currently used database
|
||||
* platform.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value from its PHP representation to its database
|
||||
* representation of this type.
|
||||
*
|
||||
* @param mixed $value The value to convert.
|
||||
* @param AbstractPlatform $platform The currently used database platform.
|
||||
*
|
||||
* @return mixed The database representation of the value.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$class = $this->getClass();
|
||||
$value = new $class($value);
|
||||
}
|
||||
|
||||
if (! $value instanceof AbstractEnum) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid value, must be instance of '. AbstractEnum::class
|
||||
.' but '. (is_object($value) ? get_class($value) : gettype($value))
|
||||
.' given'
|
||||
);
|
||||
}
|
||||
|
||||
return $value->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return concrete enum class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getClass();
|
||||
|
||||
/**
|
||||
* Converts a value from its database representation to its PHP
|
||||
* representation of this type.
|
||||
*
|
||||
* @param mixed $value The value to convert.
|
||||
* @param AbstractPlatform $platform The currently used database platform.
|
||||
*
|
||||
* @return mixed The PHP representation of the value.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
$class = $this->getClass();
|
||||
|
||||
if ($value !== null) {
|
||||
$value = new $class($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default length of this type.
|
||||
*
|
||||
* @param AbstractPlatform $platform The currently used database platform.
|
||||
*
|
||||
* @return integer|null
|
||||
*/
|
||||
public function getDefaultLength(AbstractPlatform $platform)
|
||||
{
|
||||
return $platform->getVarcharDefaultLength();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Doctrine\DBAL\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
/**
|
||||
* Class DateTimeZoneType
|
||||
* @package AppBundle\Doctrine\DBAL\Types
|
||||
*/
|
||||
class DateTimeZoneType extends Type
|
||||
{
|
||||
/**
|
||||
* Gets the name of this type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'datetimezone';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SQL declaration snippet for a field of this type.
|
||||
*
|
||||
* @param array $fieldDeclaration The field declaration.
|
||||
* @param AbstractPlatform $platform The currently used database
|
||||
* platform.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default length of this type.
|
||||
*
|
||||
* @param AbstractPlatform $platform The currently used database platform.
|
||||
*
|
||||
* @return integer|null
|
||||
*/
|
||||
public function getDefaultLength(AbstractPlatform $platform)
|
||||
{
|
||||
return $platform->getVarcharDefaultLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value from its PHP representation to its database
|
||||
* representation of this type.
|
||||
*
|
||||
* @param mixed $value The value to convert.
|
||||
* @param AbstractPlatform $platform The currently used database platform.
|
||||
*
|
||||
* @return mixed The database representation of the value.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if (! $value instanceof \DateTimeZone) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Expects \DateTimeZone, but got '. gettype($value)
|
||||
);
|
||||
}
|
||||
|
||||
return $value->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value from its database representation to its PHP
|
||||
* representation of this type.
|
||||
*
|
||||
* @param mixed $value The value to convert.
|
||||
* @param AbstractPlatform $platform The currently used database platform.
|
||||
*
|
||||
* @return mixed The PHP representation of the value.
|
||||
*
|
||||
* @throws ConversionException If can't convert from database to php value.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
$val = new \DateTimeZone($value);
|
||||
|
||||
if (! $val instanceof \DateTimeZone) {
|
||||
throw ConversionException::conversionFailed($value, $this->getName());
|
||||
}
|
||||
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* Class BaseEntityRepository
|
||||
*
|
||||
* @package AppBundle\Doctrine\ORM
|
||||
*/
|
||||
class BaseEntityRepository extends EntityRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* Persist entity.
|
||||
*
|
||||
* @param object $entity A persisted entity. Should be have same class which
|
||||
* is used to create repository.
|
||||
* @param boolean $flush Immediately flush entity change or not.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function persist($entity, $flush = true)
|
||||
{
|
||||
if (! ClassUtils::getRealClass(get_class($entity)) === $this->_entityName) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'%s: \'$entity\' should be instance of \'%s\' but \'%s\' given',
|
||||
__CLASS__,
|
||||
$this->_entityName,
|
||||
\app\op\getPrintableType($entity)
|
||||
));
|
||||
}
|
||||
|
||||
$this->_em->persist($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->_em->flush($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Entity;
|
||||
|
||||
/**
|
||||
* Class ActivateAwareEntityTrait
|
||||
* @package AppBundle\Entity
|
||||
*/
|
||||
trait ActivateAwareEntityTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $active = true;
|
||||
|
||||
/**
|
||||
* Set active
|
||||
*
|
||||
* @param boolean $active Flag, notification will be render if set.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setActive($active = true)
|
||||
{
|
||||
$this->active = $active;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isActive()
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Entity;
|
||||
|
||||
/**
|
||||
* Trait AbstractEntity
|
||||
*
|
||||
* Base entity trait used for implementing methods from EntityInterface and some
|
||||
* standard mapping.
|
||||
*
|
||||
* @package AppBundle\Entity
|
||||
*/
|
||||
trait BaseEntityTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create entity instance for fluid interface access.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function create()
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntityType()
|
||||
{
|
||||
return \app\op\camelCaseToUnderscore(\app\c\getShortName(static::class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
|
||||
/**
|
||||
* Class CacheItem
|
||||
*
|
||||
* @ORM\Table(name="cache_items")
|
||||
* @ORM\Entity
|
||||
*
|
||||
* @package AppBundle\Entity
|
||||
*/
|
||||
class CacheItem implements CacheItemInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="`key`")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="NONE")
|
||||
*/
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*
|
||||
* @ORM\Column(type="json_array")
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private $lifetime;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*
|
||||
* @ORM\Column(type="bigint")
|
||||
*/
|
||||
private $expiresAt;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $isHit = true;
|
||||
|
||||
/**
|
||||
* CacheItem constructor.
|
||||
*
|
||||
* @param string $key Cache item key.
|
||||
* @param mixed $value Cached value.
|
||||
* @param integer $lifetime Value lifetime in seconds.
|
||||
* @param boolean $isHit Is item fetched.
|
||||
*/
|
||||
public function __construct(
|
||||
$key = null,
|
||||
$value = null,
|
||||
$lifetime = null,
|
||||
$isHit = true
|
||||
) {
|
||||
$this->key = $key;
|
||||
$this->value = $value;
|
||||
$this->lifetime = $lifetime;
|
||||
$this->expiresAt = time() + $lifetime;
|
||||
$this->isHit = $isHit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key Item key.
|
||||
*
|
||||
* @return CacheItem
|
||||
*/
|
||||
public function setKey($key)
|
||||
{
|
||||
$this->key = $key;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value A cached value.
|
||||
*
|
||||
* @return CacheItem
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the item from the cache associated with this
|
||||
* object's key.
|
||||
*
|
||||
* The value returned must be identical to the value originally stored by
|
||||
* set().
|
||||
*
|
||||
* If isHit() returns false, this method MUST return null. Note that null
|
||||
* is a legitimate cached value, so the isHit() method SHOULD be used to
|
||||
* differentiate between "null value was found" and "no value was found."
|
||||
*
|
||||
* @return mixed
|
||||
* The value corresponding to this cache item's key, or null if not
|
||||
* found.
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value represented by this cache item.
|
||||
*
|
||||
* The $value argument may be any item that can be serialized by PHP,
|
||||
* although the method of serialization is left up to the Implementing
|
||||
* Library.
|
||||
*
|
||||
* @param mixed $value The value to be stored.
|
||||
*
|
||||
* @return static
|
||||
* The invoked object.
|
||||
*/
|
||||
public function set($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms if the cache item lookup resulted in a cache hit.
|
||||
*
|
||||
* Note: This method MUST NOT have a race condition between calling isHit()
|
||||
* and calling get().
|
||||
*
|
||||
* @return boolean
|
||||
* True if the request resulted in a cache hit. False otherwise.
|
||||
*/
|
||||
public function isHit()
|
||||
{
|
||||
return $this->isHit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $lifetime How long this item is valid in seconds.
|
||||
*
|
||||
* @return CacheItem
|
||||
*/
|
||||
public function setLifetime($lifetime)
|
||||
{
|
||||
$this->lifetime = $lifetime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getExpiresAt()
|
||||
{
|
||||
return $this->expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expiration time for this cache item.
|
||||
*
|
||||
* @param \DateTimeInterface|null $expiration The point in time after which
|
||||
* the item MUST be considered expired.
|
||||
* If null is passed explicitly,
|
||||
* a default value MAY be used.
|
||||
* If none is set, the value should
|
||||
* be stored permanently or for as
|
||||
* long as the implementation allows.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function expiresAt($expiration)
|
||||
{
|
||||
if (null === $expiration) {
|
||||
$this->expiresAt = $this->lifetime > 0 ? time() + $this->lifetime : null;
|
||||
} elseif ($expiration instanceof \DateTimeInterface) {
|
||||
$this->expiresAt = (int) $expiration->format('U');
|
||||
} else {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Expiration date must implement DateTimeInterface or be null, "%s" given',
|
||||
is_object($expiration) ? get_class($expiration) : gettype($expiration)
|
||||
));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expiration time for this cache item.
|
||||
*
|
||||
* @param integer|\DateInterval|null $time The period of time from the present
|
||||
* after which the item MUST be considered
|
||||
* expired. An integer parameter is
|
||||
* understood to be the time in seconds
|
||||
* until expiration. If null is passed
|
||||
* explicitly, a default value MAY be
|
||||
* used. If none is set, the value
|
||||
* should be stored permanently or for
|
||||
* as long as the implementation allows.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function expiresAfter($time)
|
||||
{
|
||||
if (null === $time) {
|
||||
$this->expiresAt = $this->lifetime > 0 ? time() + $this->lifetime : null;
|
||||
} elseif ($time instanceof \DateInterval) {
|
||||
$this->expiresAt = (int) \DateTime::createFromFormat('U', time())->add($time)->format('U');
|
||||
} elseif (is_int($time)) {
|
||||
$this->expiresAt = $time + time();
|
||||
} else {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Expiration date must be an integer, a DateInterval or null, "%s" given',
|
||||
is_object($time) ? get_class($time) : gettype($time)
|
||||
));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* EmailedDocument
|
||||
*
|
||||
* @ORM\Table(name="emailed_documents")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class EmailedDocument implements EntityInterface
|
||||
{
|
||||
|
||||
use BaseEntityTrait;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*
|
||||
* @ORM\Column(type="array")
|
||||
*
|
||||
* @Assert\Count(min=1)
|
||||
*/
|
||||
private $emailTo;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column
|
||||
*
|
||||
* @Assert\NotBlank
|
||||
*/
|
||||
private $emailReplyTo;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(nullable=true)
|
||||
*/
|
||||
private $subject;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="text")
|
||||
*
|
||||
* @Assert\NotBlank
|
||||
*/
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* Set emailTo
|
||||
*
|
||||
* @param array|mixed $emailTo List of recipients.
|
||||
*
|
||||
* @return EmailedDocument
|
||||
*/
|
||||
public function setEmailTo($emailTo)
|
||||
{
|
||||
$this->emailTo = (array) $emailTo;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get emailTo
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getEmailTo()
|
||||
{
|
||||
return $this->emailTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set emailReplyTo
|
||||
*
|
||||
* @param string $emailReplyTo Reply to.
|
||||
*
|
||||
* @return EmailedDocument
|
||||
*/
|
||||
public function setEmailReplyTo($emailReplyTo)
|
||||
{
|
||||
$this->emailReplyTo = $emailReplyTo;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get emailReplyTo
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEmailReplyTo()
|
||||
{
|
||||
return $this->emailReplyTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set subject
|
||||
*
|
||||
* @param string $subject Email subject.
|
||||
*
|
||||
* @return EmailedDocument
|
||||
*/
|
||||
public function setSubject($subject)
|
||||
{
|
||||
$this->subject = $subject;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subject
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSubject()
|
||||
{
|
||||
return $this->subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content
|
||||
*
|
||||
* @param string $content Email content.
|
||||
*
|
||||
* @return EmailedDocument
|
||||
*/
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->content = $content;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Entity;
|
||||
|
||||
/**
|
||||
* Interface EntityInterface
|
||||
* @package AppBundle\Entity
|
||||
*/
|
||||
interface EntityInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId();
|
||||
|
||||
/**
|
||||
* Get entity type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntityType();
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Trait OwnerAwareEntityTrait
|
||||
*
|
||||
* Contains mapping for entities which should have owner relation with some user.
|
||||
*
|
||||
* @package AppBundle\Entity
|
||||
*/
|
||||
trait OwnerAwareEntityTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* The user who created this notification.
|
||||
*
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="UserBundle\Entity\User")
|
||||
* @ORM\JoinColumn(name="owner_id", referencedColumnName="id", onDelete="SET NULL")
|
||||
*/
|
||||
protected $owner;
|
||||
|
||||
/**
|
||||
* Set owner
|
||||
*
|
||||
* @param User $owner The owner of this notification.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setOwner(User $owner = null)
|
||||
{
|
||||
$this->owner = $owner;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get owner
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getOwner()
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that this entity is owned by specified user.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOwnedBy(User $user)
|
||||
{
|
||||
return $this->owner->getId() === $user->getId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Enum;
|
||||
|
||||
/**
|
||||
* Class AbstractEnum
|
||||
* @package AppBundle\Enum
|
||||
*/
|
||||
abstract class AbstractEnum
|
||||
{
|
||||
|
||||
/**
|
||||
* Cached constants.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $cache = [];
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* AbstractEnum constructor.
|
||||
*
|
||||
* @param mixed $value One of availables enum values.
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
if (! self::isValid($value)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Unknown value %s for enum %s. Expects one of %s',
|
||||
$value,
|
||||
static::class,
|
||||
implode(', ', self::getAvailables())
|
||||
));
|
||||
}
|
||||
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available enum values.
|
||||
*
|
||||
* @return AbstractEnum[]
|
||||
*/
|
||||
public static function getValues()
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach (static::getAvailables() as $available) {
|
||||
//
|
||||
// Code sniffer says that: 'Use parentheses when instantiating classes'
|
||||
// but, obviously, we did it.
|
||||
//
|
||||
// @codingStandardsIgnoreStart
|
||||
$values[] = new static($available);
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available constants values.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getAvailables()
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
if (! isset(self::$cache[$class])) {
|
||||
$reflection = new \ReflectionClass($class);
|
||||
self::$cache[$class] = $reflection->getConstants();
|
||||
}
|
||||
|
||||
return self::$cache[$class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that specified value is valid for current enum.
|
||||
*
|
||||
* @param mixed $value Maybe one of enum value.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isValid($value)
|
||||
{
|
||||
return in_array($value, self::getAvailables(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Method name.
|
||||
* @param mixed $arguments Method arguments.
|
||||
*
|
||||
* @return static
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
// camelCase to underscore.
|
||||
$name = strtoupper(preg_replace('/([^A-Z-])([A-Z])/', '$1_$2', $name));
|
||||
$availables = self::getAvailables();
|
||||
|
||||
if (array_key_exists($name, $availables)) {
|
||||
//
|
||||
// Code sniffer says that: 'Use parentheses when instantiating classes'
|
||||
// but, obviously, we did it.
|
||||
//
|
||||
// @codingStandardsIgnoreStart
|
||||
return new static($availables[$name]);
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
|
||||
throw new \RuntimeException("Unknown enum value '{$name}'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that current value is equal to specified.
|
||||
*
|
||||
* @param AbstractEnum|string $enum One of availables enum values.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ShortMethodName)
|
||||
*/
|
||||
public function is($enum)
|
||||
{
|
||||
if (is_scalar($enum)) {
|
||||
//
|
||||
// Code sniffer says that: 'Use parentheses when instantiating classes'
|
||||
// but, obviously, we did it.
|
||||
//
|
||||
// @codingStandardsIgnoreStart
|
||||
$enum = new static($enum);
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
|
||||
if (! $enum instanceof static) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Unknown value %s for enum %s. Expects one of %s',
|
||||
$enum,
|
||||
static::class,
|
||||
implode(', ', self::getAvailables())
|
||||
));
|
||||
}
|
||||
|
||||
return $this->value === $enum->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Enum;
|
||||
|
||||
/**
|
||||
* Class UnhandledEnumException
|
||||
*
|
||||
* @package AppBundle\Enum
|
||||
*/
|
||||
class UnhandledEnumException extends \RuntimeException
|
||||
{
|
||||
|
||||
/**
|
||||
* UnhandledEnumException constructor.
|
||||
*
|
||||
* @param string $enumClass Enumeration class.
|
||||
* @param mixed $value Unhandled value.
|
||||
*/
|
||||
public function __construct($enumClass, $value)
|
||||
{
|
||||
parent::__construct(sprintf('Unhandled \'%s\' enum value \'%s\'', $enumClass, $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractEnum $enum A AbstractEnum instance.
|
||||
*
|
||||
* @return UnhandledEnumException
|
||||
*/
|
||||
public static function fromInstance(AbstractEnum $enum)
|
||||
{
|
||||
return new self(get_class($enum), $enum->getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\EventListener;
|
||||
|
||||
use AppBundle\Response\SearchResponseInterface;
|
||||
use Knp\Component\Pager\Event\ItemsEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Class ResponsePagination
|
||||
* Need for knp paginator.
|
||||
*
|
||||
* @package SearchBundle\EventListener
|
||||
*/
|
||||
class ResponsePagination implements EventSubscriberInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns an array of event names this subscriber wants to listen to.
|
||||
*
|
||||
* The array keys are event names and the value can be:
|
||||
*
|
||||
* * The method name to call (priority defaults to 0)
|
||||
* * An array composed of the method name to call and the priority
|
||||
* * An array of arrays composed of the method names to call and
|
||||
* respective
|
||||
* priorities, or 0 if unset
|
||||
*
|
||||
* For instance:
|
||||
*
|
||||
* * array('eventName' => 'methodName')
|
||||
* * array('eventName' => array('methodName', $priority))
|
||||
* * array('eventName' => array(array('methodName1', $priority),
|
||||
* array('methodName2')))
|
||||
*
|
||||
* @return array The event names to listen to
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [ 'knp_pager.items' => [ 'handle', 0 ] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ItemsEvent $event A ItemsEvent instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(ItemsEvent $event)
|
||||
{
|
||||
$response = $event->target;
|
||||
if (! $response instanceof SearchResponseInterface) {
|
||||
return;
|
||||
}
|
||||
$event->stopPropagation();
|
||||
|
||||
$event->items = $response->getDocuments();
|
||||
$event->count = $response->getTotalCount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Exception;
|
||||
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\AppLimitEnum;
|
||||
|
||||
/**
|
||||
* Class LimitExceedException
|
||||
*
|
||||
* Occurred when we user try to reserve more limits that allowed.
|
||||
*
|
||||
* @package AppBundle\Exception
|
||||
*/
|
||||
class LimitExceedException extends \RuntimeException
|
||||
{
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @var AppLimitEnum
|
||||
*/
|
||||
private $limit;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $currValue;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $requested;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $max;
|
||||
|
||||
/**
|
||||
* NotAllowedException constructor.
|
||||
*
|
||||
* @param User $user Who try to make not allowed action.
|
||||
* @param AppLimitEnum $appLimit Which limit is requested.
|
||||
* @param integer $currValue Current limit value.
|
||||
* @param integer $requested How much limit is requested.
|
||||
* @param integer $max Max value of limit.
|
||||
*/
|
||||
public function __construct(
|
||||
User $user,
|
||||
AppLimitEnum $appLimit,
|
||||
$currValue,
|
||||
$requested,
|
||||
$max
|
||||
) {
|
||||
parent::__construct(sprintf(
|
||||
'User \'%s\' is exceed limit \'%s\'. Current limit value %s, request %s but limit is %s',
|
||||
$user->getId(),
|
||||
$appLimit->getValue(),
|
||||
$currValue,
|
||||
$requested,
|
||||
$max
|
||||
));
|
||||
|
||||
$this->user = $user;
|
||||
$this->limit = $appLimit;
|
||||
$this->currValue = $currValue;
|
||||
$this->requested = $requested;
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AppLimitEnum
|
||||
*/
|
||||
public function getLimit()
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getCurrValue()
|
||||
{
|
||||
return $this->currValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getRequested()
|
||||
{
|
||||
return $this->requested;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getMax()
|
||||
{
|
||||
return $this->max;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Exception;
|
||||
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\AppPermissionEnum;
|
||||
|
||||
/**
|
||||
* Class NotAllowedException
|
||||
*
|
||||
* Occurred when we user try to make operations which is not allowed for him.
|
||||
*
|
||||
* @package AppBundle\Exception
|
||||
*/
|
||||
class NotAllowedException extends \RuntimeException
|
||||
{
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @var AppPermissionEnum
|
||||
*/
|
||||
private $permission;
|
||||
|
||||
/**
|
||||
* NotAllowedException constructor.
|
||||
*
|
||||
* @param User $user Who try to make not allowed action.
|
||||
* @param AppPermissionEnum $appPermission Which permission is required.
|
||||
*/
|
||||
public function __construct(User $user, AppPermissionEnum $appPermission)
|
||||
{
|
||||
parent::__construct(sprintf(
|
||||
'User \'%s\' is don\'t have \'%s\' permission',
|
||||
$user->getId(),
|
||||
$appPermission->getValue()
|
||||
));
|
||||
|
||||
$this->user = $user;
|
||||
$this->permission = $appPermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AppPermissionEnum
|
||||
*/
|
||||
public function getPermission()
|
||||
{
|
||||
return $this->permission;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form;
|
||||
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class AbstractConnectionAwareType
|
||||
* @package AppBundle\Form
|
||||
*/
|
||||
abstract class AbstractConnectionAwareType extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* @var IndexInterface
|
||||
*/
|
||||
protected $index;
|
||||
|
||||
/**
|
||||
* Max documents per page.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $perPage;
|
||||
|
||||
/**
|
||||
* SimpleQueryType constructor.
|
||||
*
|
||||
* @param IndexInterface $index A IndexInterface instance.
|
||||
* @param integer $perPage Max documents per page.
|
||||
*/
|
||||
public function __construct(IndexInterface $index, $perPage)
|
||||
{
|
||||
$this->index = $index;
|
||||
$this->perPage = $perPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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', SearchRequestBuilderInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create initial search request builder
|
||||
*
|
||||
* @return \IndexBundle\SearchRequest\SearchRequestBuilderInterface
|
||||
*/
|
||||
protected function createSearchRequestBuilder()
|
||||
{
|
||||
return $this->index
|
||||
->createRequestBuilder()
|
||||
->setLimit($this->perPage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form;
|
||||
|
||||
use AppBundle\Entity\EmailedDocument;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class EmailedDocumentType
|
||||
* @package AppBundle\Form
|
||||
*/
|
||||
class EmailedDocumentType 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('emailTo', CollectionType::class, [
|
||||
'entry_type' => EmailType::class,
|
||||
'allow_add' => true,
|
||||
])
|
||||
->add('emailReplyTo')
|
||||
->add('subject', null, [ 'required' => false ])
|
||||
->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', EmailedDocument::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Factory;
|
||||
|
||||
use AppBundle\Form\AbstractConnectionAwareType;
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use IndexBundle\Index\Internal\InternalIndexInterface;
|
||||
|
||||
/**
|
||||
* Class FilterFactoryAwareTypeFactory
|
||||
* @package AppBundle\Form\Factory
|
||||
*/
|
||||
class FilterFactoryAwareTypeFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* Max documents per page.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $perPage;
|
||||
|
||||
/**
|
||||
* @var InternalIndexInterface
|
||||
*/
|
||||
private $index;
|
||||
|
||||
/**
|
||||
* SimpleQueryType constructor.
|
||||
*
|
||||
* @param IndexInterface $index A IndexInterface interface.
|
||||
* @param integer $perPage Max documents per page.
|
||||
*/
|
||||
public function __construct(
|
||||
IndexInterface $index,
|
||||
$perPage
|
||||
) {
|
||||
$this->index = $index;
|
||||
$this->perPage = $perPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create search type instance.
|
||||
*
|
||||
* @param string $class Concrete form fqcn.
|
||||
*
|
||||
* @return AbstractConnectionAwareType
|
||||
*/
|
||||
public function create($class)
|
||||
{
|
||||
$form = new $class($this->index, $this->perPage);
|
||||
|
||||
if (! $form instanceof AbstractConnectionAwareType) {
|
||||
$message = 'Invalid form class, expects: '
|
||||
. AbstractConnectionAwareType::class;
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form;
|
||||
|
||||
use AppBundle\AdvancedFilters\AdvancedFiltersConfig;
|
||||
use AppBundle\Form\Type\AdvancedFiltersType;
|
||||
use Common\Enum\AFSourceEnum;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
|
||||
/**
|
||||
* Class FeedDocumentSearchType
|
||||
* @package AppBundle\Form
|
||||
*/
|
||||
class FeedDocumentSearchType extends AbstractConnectionAwareType 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)
|
||||
{
|
||||
$builder
|
||||
// Page field, default value - 1
|
||||
->add('page', null, [
|
||||
'description' => 'Requested page number. Default value is 1.',
|
||||
'empty_data' => 1,
|
||||
])
|
||||
// Collection of available advanced filters.
|
||||
->add('advancedFilters', AdvancedFiltersType::class, [
|
||||
'description' => 'Advanced filters.',
|
||||
'config' => AdvancedFiltersConfig::getConfig(AFSourceEnum::FEED),
|
||||
'empty_data' => [],
|
||||
'connection' => $this->index,
|
||||
'required' => false,
|
||||
])
|
||||
->setEmptyData($this->createSearchRequestBuilder())
|
||||
->setDataMapper($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
->setPage($forms['page']->getData())
|
||||
->setFilters($forms['advancedFilters']->getData());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form;
|
||||
|
||||
use AppBundle\Form\SearchRequest\StoredQuerySearchRequestType;
|
||||
use CacheBundle\Entity\Feed\ClipFeed;
|
||||
use CacheBundle\Form\FeedInfoType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Validator\Constraints\Valid;
|
||||
|
||||
/**
|
||||
* Class FeedType
|
||||
* @package AppBundle\Form
|
||||
*/
|
||||
class FeedType 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('feed', FeedInfoType::class, [
|
||||
'constraints' => new Valid(),
|
||||
])
|
||||
->add('search', StoredQuerySearchRequestType::class)
|
||||
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
|
||||
$data = $event->getData();
|
||||
$form = $event->getForm()->get('search');
|
||||
|
||||
if (! isset($data['feed']['subType'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data['feed']['subType'] === ClipFeed::getSubType()) {
|
||||
$form
|
||||
->remove('query')
|
||||
->remove('filters');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\SearchRequest;
|
||||
|
||||
use AppBundle\AdvancedFilters\AdvancedFiltersConfig;
|
||||
use AppBundle\Form\AbstractConnectionAwareType;
|
||||
use AppBundle\Form\Type\AdvancedFiltersType;
|
||||
use AppBundle\Form\Type\Filter as QueryFilter;
|
||||
use AppBundle\Form\Type\FiltersType;
|
||||
use Common\Enum\AFSourceEnum;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
/**
|
||||
* Class AbstractSearchRequestType
|
||||
* @package AppBundle\Form\SearchRequest
|
||||
*/
|
||||
abstract class AbstractSearchRequestType extends AbstractConnectionAwareType implements
|
||||
DataMapperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Available search request filters for articles.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $filters = [
|
||||
'headline' => [
|
||||
'type' => QueryFilter\HeadlineFilterType::class,
|
||||
'description' => 'Addition title filtering',
|
||||
],
|
||||
'publisher' => [
|
||||
'type' => QueryFilter\PublisherFilterType::class,
|
||||
'description' => 'Filter by publisher type.',
|
||||
],
|
||||
'source' => [
|
||||
'type' => QueryFilter\SourceFilterType::class,
|
||||
'description' => 'Filter by source.',
|
||||
],
|
||||
'sourceList' => [
|
||||
'type' => QueryFilter\SourceListFilterType::class,
|
||||
'description' => 'Filter by source lists.',
|
||||
],
|
||||
'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.',
|
||||
],
|
||||
'date' => [
|
||||
'type' => QueryFilter\DateFilterType::class,
|
||||
'description' => 'Filter by date, may be two types',
|
||||
],
|
||||
'hasImage' => [
|
||||
'type' => QueryFilter\HasImageFilterType::class,
|
||||
'description' => 'Boolean flag, if true get document only with image.',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* 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
|
||||
// Raw search query typed by user.
|
||||
->add('query', null, [
|
||||
'constraints' => [
|
||||
new NotBlank(),
|
||||
new Length([ 'min' => 3 ]),
|
||||
],
|
||||
'required' => true,
|
||||
'description' => 'Search query.',
|
||||
])
|
||||
|
||||
// Collection of available filters.
|
||||
->add('filters', FiltersType::class, [
|
||||
'filter_factory' => $this->index->getFilterFactory(),
|
||||
'description' => 'Search filters.',
|
||||
'empty_data' => [],
|
||||
'filters' => self::$filters,
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
// Collection of available advanced filters.
|
||||
->add('advancedFilters', AdvancedFiltersType::class, [
|
||||
'description' => 'Advanced filters.',
|
||||
'config' => AdvancedFiltersConfig::getConfig(AFSourceEnum::FEED),
|
||||
'empty_data' => [],
|
||||
'connection' => $this->index,
|
||||
'required' => false,
|
||||
])
|
||||
->setEmptyData($this->createSearchRequestBuilder())
|
||||
->setDataMapper($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->setDefaults([
|
||||
'data_class' => SearchRequestBuilderInterface::class,
|
||||
'key' => 'createFeed',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
if (isset($forms['query'])) {
|
||||
$data->setQuery($forms['query']->getData());
|
||||
}
|
||||
|
||||
$filters = $forms['advancedFilters']->getData() ?: [];
|
||||
if (isset($forms['filters'])) {
|
||||
$filters = array_merge($filters, $forms['filters']->getData());
|
||||
}
|
||||
$data->setFilters($filters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\SearchRequest;
|
||||
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\Test\FormInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class ClipFeedSearchRequestType
|
||||
* @package AppBundle\Form\SearchRequest
|
||||
*/
|
||||
class ClipFeedSearchRequestType extends AbstractSearchRequestType
|
||||
{
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder
|
||||
->remove('query')
|
||||
->remove('filters');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->setDefaults([
|
||||
'cascade_validation' => true,
|
||||
'key' => 'search',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->setFilters($forms['advancedFilters']->getData());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\SearchRequest;
|
||||
|
||||
use AppBundle\Form\Type\Filter as QueryFilter;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class SimpleQuerySearchRequestType
|
||||
* @package AppBundle\Form\SearchRequest
|
||||
*/
|
||||
class SimpleQuerySearchRequestType extends AbstractSearchRequestType
|
||||
{
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$builder
|
||||
// Page field, default value - 1
|
||||
->add('page', null, [
|
||||
'description' => 'Requested page number. Default value is 1.',
|
||||
'empty_data' => 1,
|
||||
]);
|
||||
parent::buildForm($builder, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->setDefaults([
|
||||
'cascade_validation' => true,
|
||||
'key' => 'search',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
parent::mapFormsToData($forms, $data);
|
||||
|
||||
$forms = iterator_to_array($forms);
|
||||
$data->setPage($forms['page']->getData());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\SearchRequest;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class StoredQuerySearchRequestType
|
||||
* @package AppBundle\Form\SearchRequest
|
||||
*/
|
||||
class StoredQuerySearchRequestType extends AbstractSearchRequestType
|
||||
{
|
||||
|
||||
/**
|
||||
* 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', 'createFeed');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Transformer;
|
||||
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
|
||||
/**
|
||||
* Class OnlyReverseTransformer
|
||||
* @package AppBundle\Form\Transformer
|
||||
*/
|
||||
class OnlyReverseTransformer extends CallbackTransformer
|
||||
{
|
||||
|
||||
/**
|
||||
* OnlyReverseTransformer constructor.
|
||||
*
|
||||
* @param callable $reverseTransform The reverse transform callback.
|
||||
*/
|
||||
public function __construct(callable $reverseTransform)
|
||||
{
|
||||
parent::__construct(function ($value) {
|
||||
return $value;
|
||||
}, $reverseTransform);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Transformer;
|
||||
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
/**
|
||||
* Trait OnlyReverseTransformer
|
||||
* @package AppBundle\Form\Transformer
|
||||
*/
|
||||
trait OnlyReverseTransformerTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* Transforms a value from the original representation to a transformed
|
||||
* representation.
|
||||
*
|
||||
* This method is called on two occasions inside a form field:
|
||||
*
|
||||
* 1. When the form field is initialized with the data attached from the
|
||||
* datasource (object or array).
|
||||
* 2. When data from a request is submitted using {@link Form::submit()} to
|
||||
* transform the new input data back into the renderable format. For
|
||||
* example if you have a date field and submit '2009-10-10' you might
|
||||
* accept this value because its easily parsed, but the transformer still
|
||||
* writes back
|
||||
* "2009/10/10" onto the form field (for further displaying or other
|
||||
* purposes).
|
||||
*
|
||||
* This method must be able to deal with empty values. Usually this will
|
||||
* be NULL, but depending on your implementation other empty values are
|
||||
* possible as well (such as empty strings). The reasoning behind this is
|
||||
* that value transformers must be chainable. If the transform() method
|
||||
* of the first value transformer outputs NULL, the second value
|
||||
* transformer
|
||||
* must be able to process that value.
|
||||
*
|
||||
* By convention, transform() should return an empty string if NULL is
|
||||
* passed.
|
||||
*
|
||||
* @param mixed $value The value in the original representation.
|
||||
*
|
||||
* @return mixed The value in the transformed representation
|
||||
*
|
||||
* @throws TransformationFailedException When the transformation fails.
|
||||
*/
|
||||
public function transform($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type\AdvancedFilter;
|
||||
|
||||
use IndexBundle\Filter\Factory\FilterFactoryInterface;
|
||||
use IndexBundle\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Class AdvancedFilterParameters
|
||||
* @package AppBundle\Form\Type\AdvancedFilter
|
||||
*/
|
||||
class AdvancedFilterParameters
|
||||
{
|
||||
|
||||
/**
|
||||
* Array of values which MUST be present in the search result.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $included;
|
||||
|
||||
/**
|
||||
* Array of values which MUST NOT be present in the search result.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $excluded;
|
||||
|
||||
/**
|
||||
* AdvancedFilterParameters constructor.
|
||||
*
|
||||
* @param string|string[] $included Array of values which MUST be present in
|
||||
* the search result.
|
||||
* @param string|string[] $excluded Array of values which MUST NOT be present
|
||||
* in the search result.
|
||||
*/
|
||||
public function __construct($included, $excluded)
|
||||
{
|
||||
$this->included = (array) $included;
|
||||
$this->excluded = (array) $excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value Additional query.
|
||||
*
|
||||
* @return AdvancedFilterParameters
|
||||
*/
|
||||
public static function queryFilterParameters($value)
|
||||
{
|
||||
// @codingStandardsIgnoreStart
|
||||
return new static([ $value ], []);
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of values which MUST be present in the search result.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getIncluded()
|
||||
{
|
||||
return $this->included;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of values which MUST NOT be present in the search result.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getExcluded()
|
||||
{
|
||||
return $this->excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $names Array of used field names.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function createQueryFilter(array $names, FilterFactoryInterface $factory)
|
||||
{
|
||||
return $factory->orX(array_map(function ($name) use ($factory) {
|
||||
return $factory->eq($name, current($this->included));
|
||||
}, $names));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create filter for range advanced filter.
|
||||
*
|
||||
* @param string $name A Field name.
|
||||
* @param array $ranges Available field ranges.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function createRangeFilter($name, array $ranges, FilterFactoryInterface $factory)
|
||||
{
|
||||
$availables = array_keys($ranges);
|
||||
(($value = current($this->included)) !== false) || ($value = current($this->excluded));
|
||||
|
||||
if (! in_array($value, $availables, true)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Invalid value \'%s\'. Expects one of %s.',
|
||||
$value,
|
||||
implode(', ', $availables)
|
||||
));
|
||||
}
|
||||
|
||||
//
|
||||
// Get start and end bound for given value.
|
||||
//
|
||||
$start = $ranges[$value]['from'];
|
||||
$end = isset($ranges[$value]['to']) ? $ranges[$value]['to'] : null;
|
||||
|
||||
// Firstly create 'gte' filter.
|
||||
$filter = $factory->gte($name, $start);
|
||||
|
||||
if ($end !== null) {
|
||||
//
|
||||
// We have end bound, so we should use 'lte' filter and wrap
|
||||
// both into 'andX'.
|
||||
//
|
||||
$filter = $factory->andX([
|
||||
$filter,
|
||||
$factory->lte($name, $end),
|
||||
]);
|
||||
}
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create filter for simple advanced filter.
|
||||
*
|
||||
* @param string $name A Field name.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return FilterInterface|null
|
||||
*/
|
||||
public function createSimpleFilter($name, FilterFactoryInterface $factory)
|
||||
{
|
||||
$eqFactory = \nspl\f\partial([ $factory, 'eq' ], $name);
|
||||
$eqFilters = \nspl\a\map($eqFactory, $this->included);
|
||||
|
||||
//
|
||||
// If we got some positive statements we should use only them.
|
||||
//
|
||||
if (count($eqFilters) > 0) {
|
||||
return $factory->orX($eqFilters);
|
||||
}
|
||||
|
||||
$neqFactory = \nspl\f\compose(
|
||||
[ $factory, 'not' ],
|
||||
$eqFactory
|
||||
);
|
||||
$neqFilters = \nspl\a\map($neqFactory, $this->excluded);
|
||||
|
||||
if (count($neqFilters) > 0) {
|
||||
return $factory->andX($neqFilters);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type\AdvancedFilter;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class AdvancedFilterType
|
||||
*
|
||||
* Convert filter parameters passed from frontend into internal representation.
|
||||
*
|
||||
* @package AppBundle\Form\Type\AdvancedFilte
|
||||
*
|
||||
* @see \AppBundle\Form\Type\AdvancedFilter\AdvancedFilterParameters
|
||||
*/
|
||||
class AdvancedFilterType 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)
|
||||
{
|
||||
$choices = $options['choices'];
|
||||
|
||||
$transformer = function (FormEvent $event) use ($choices) {
|
||||
$values = $event->getData();
|
||||
if ($values instanceof AdvancedFilterParameters) {
|
||||
$values = $event->getForm()->getExtraData();
|
||||
}
|
||||
|
||||
$include = [];
|
||||
$exclude = [];
|
||||
|
||||
switch (true) {
|
||||
case is_string($values):
|
||||
$data = AdvancedFilterParameters::queryFilterParameters($values);
|
||||
break;
|
||||
|
||||
case is_array($values):
|
||||
if (count($choices) > 0) {
|
||||
// Range type.
|
||||
$include = key($values);
|
||||
} else {
|
||||
// Simple type.
|
||||
foreach ($values as $value => $type) {
|
||||
switch ($type) {
|
||||
case 1:
|
||||
$include[] = $value;
|
||||
break;
|
||||
|
||||
case -1:
|
||||
$exclude[] = $value;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \RuntimeException('Invalid value type, should be 1 or -1');
|
||||
}
|
||||
}
|
||||
}
|
||||
$data = new AdvancedFilterParameters($include, $exclude);
|
||||
break;
|
||||
|
||||
default:
|
||||
$event->getForm()->addError(new FormError('Invalid value, expects object or string'));
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setData($data);
|
||||
};
|
||||
|
||||
$builder->addEventListener(FormEvents::SUBMIT, $transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => AdvancedFilterParameters::class,
|
||||
'empty_data' => new AdvancedFilterParameters([], []),
|
||||
'allow_extra_fields' => true,
|
||||
'choices' => [],
|
||||
'compound' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type;
|
||||
|
||||
use AppBundle\Form\Transformer\OnlyReverseTransformer;
|
||||
use AppBundle\Form\Type\AdvancedFilter\AdvancedFilterParameters;
|
||||
use AppBundle\Form\Type\AdvancedFilter\AdvancedFilterType;
|
||||
use AppBundle\Form\Type\Traits\CleanFormTrait;
|
||||
use Common\Enum\AFTypeEnum;
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
/**
|
||||
* Class AdvancedFiltersType
|
||||
* @package AppBundle\Form\Type
|
||||
*/
|
||||
class AdvancedFiltersType extends AbstractType
|
||||
{
|
||||
|
||||
use CleanFormTrait;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$index = $options['connection'];
|
||||
if (! $index instanceof IndexInterface) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'\'connection\' options should be instance of %s',
|
||||
IndexInterface::class
|
||||
));
|
||||
}
|
||||
$config = array_filter($options['config']);
|
||||
|
||||
//
|
||||
// Add all available filters to form.
|
||||
//
|
||||
foreach ($config as $name => $params) {
|
||||
$parameters = [];
|
||||
|
||||
if ($params['type'] === AFTypeEnum::RANGE) {
|
||||
$parameters['choices'] = array_keys($params['ranges']);
|
||||
} elseif ($params['type'] === AFTypeEnum::QUERY) {
|
||||
$parameters['compound'] = false;
|
||||
}
|
||||
|
||||
$parameters['description'] = $params['description'];
|
||||
$parameters['constraints'] = new NotBlank();
|
||||
|
||||
$builder->add($name, AdvancedFilterType::class, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform filter names and values to concrete filters.
|
||||
*
|
||||
* @param array $filters Array of advanced filters values.
|
||||
*
|
||||
* @return \IndexBundle\Filter\FilterInterface[]
|
||||
*/
|
||||
$transformationFn = function (array $filters) use ($index, $config) {
|
||||
$resolver = $index->getAFResolver();
|
||||
$resolvedFilters = [];
|
||||
|
||||
/** @var AdvancedFilterParameters $params */
|
||||
foreach ($filters as $name => $params) {
|
||||
try {
|
||||
$resolvedFilters[] = $resolver->generateFilter($config, $name, $params);
|
||||
} catch (\Exception $exception) {
|
||||
throw new TransformationFailedException($exception->getMessage(), $exception->getCode(), $exception);
|
||||
}
|
||||
}
|
||||
|
||||
return $resolvedFilters;
|
||||
};
|
||||
|
||||
$builder
|
||||
->addEventListener(FormEvents::PRE_SUBMIT, [ $this, 'clean' ])
|
||||
->addModelTransformer(new OnlyReverseTransformer($transformationFn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setRequired('connection')
|
||||
->setRequired('config')
|
||||
->setAllowedTypes('connection', IndexInterface::class)
|
||||
->setAllowedTypes('config', 'array')
|
||||
->setDefaults([
|
||||
'allow_extra_fields' => true, // We handle this situation in pre
|
||||
// submit listener and make more
|
||||
// detailed error message.
|
||||
'connection' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type;
|
||||
|
||||
use AppBundle\Enum\AbstractEnum;
|
||||
use AppBundle\Form\Transformer\OnlyReverseTransformer;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class EnumType
|
||||
*
|
||||
* @package AppBundle\Form\Type
|
||||
*/
|
||||
class EnumType 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)
|
||||
{
|
||||
$class = $options['enum_class'];
|
||||
|
||||
$builder->addModelTransformer(new OnlyReverseTransformer(function ($value) use ($class) {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new $class($value);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setRequired('enum_class')
|
||||
->setAllowedTypes('enum_class', 'string')
|
||||
->setDefault('choices', function (Options $options) {
|
||||
$class = $options['enum_class'];
|
||||
|
||||
if (! class_exists($class)) {
|
||||
throw new \InvalidArgumentException('Can\'t find class: '. $class);
|
||||
}
|
||||
|
||||
$reflection = new \ReflectionClass($class);
|
||||
|
||||
if ($reflection->isSubclassOf(AbstractEnum::class)) {
|
||||
return $class::getAvailables();
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the parent type.
|
||||
*
|
||||
* @return string|null The name of the parent type if any, null otherwise.
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type\Extension;
|
||||
|
||||
use AppBundle\Utils\TransKey\ConstTransKeyGenerator;
|
||||
use AppBundle\Utils\TransKey\RecursiveTransKeyGenerator;
|
||||
use AppBundle\Utils\TransKey\TransKeyGeneratorInterface;
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class LocalizationTypeExtension
|
||||
* @package AppBundle\Form\Type\Extension
|
||||
*/
|
||||
class LocalizationTypeExtension extends AbstractTypeExtension
|
||||
{
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setDefault('key', new RecursiveTransKeyGenerator())
|
||||
->addAllowedTypes('key', [ 'string', TransKeyGeneratorInterface::class ])
|
||||
->setNormalizer('key', function (Options $options, $key) {
|
||||
//
|
||||
// We should insure that key is always be an instance of trans key
|
||||
// generator.
|
||||
//
|
||||
if (is_string($key)) {
|
||||
$key = new ConstTransKeyGenerator($key);
|
||||
}
|
||||
|
||||
return $key;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the type being extended.
|
||||
*
|
||||
* @return string The name of the type being extended.
|
||||
*/
|
||||
public function getExtendedType()
|
||||
{
|
||||
return FormType::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type\Filter;
|
||||
|
||||
use AppBundle\Form\Transformer\OnlyReverseTransformer;
|
||||
use IndexBundle\Filter\Factory\FilterFactoryInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class AbstractFilterType
|
||||
* @package AppBundle\Form\Type\Filter
|
||||
*/
|
||||
abstract class AbstractFilterType 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)
|
||||
{
|
||||
/** @var FilterFactoryInterface $factory */
|
||||
$factory = $options['filter_factory'];
|
||||
if (! $factory instanceof FilterFactoryInterface) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'\'filter_factory\' option should be instance of %s',
|
||||
FilterFactoryInterface::class
|
||||
));
|
||||
}
|
||||
|
||||
$builder
|
||||
->addModelTransformer(new OnlyReverseTransformer(function ($value) use ($factory) {
|
||||
return $this->transform($value, $factory);
|
||||
}))
|
||||
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
|
||||
$this->preSubmit($event);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setRequired('filter_factory')
|
||||
->setAllowedTypes('filter_factory', FilterFactoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make some manipulation with form and data before submit.
|
||||
*
|
||||
* @param FormEvent $event A FormEvent instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function preSubmit(FormEvent $event)
|
||||
{
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input values into proper filters.
|
||||
*
|
||||
* @param mixed $value Value to be transformed.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return \IndexBundle\Filter\FilterInterface|null
|
||||
*/
|
||||
abstract protected function transform($value, FilterFactoryInterface $factory);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type\Filter;
|
||||
|
||||
use Common\Enum\CountryEnum;
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use IndexBundle\Filter\Factory\FilterFactoryInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* Class CountryFilterType
|
||||
* @package AppBundle\Form\Type\Filter
|
||||
*/
|
||||
class CountryFilterType extends AbstractFilterType
|
||||
{
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder
|
||||
->add('include', ChoiceType::class, [
|
||||
'choices' => CountryEnum::getAvailables(),
|
||||
'multiple' => true,
|
||||
'description' => 'Get document within specified countries',
|
||||
])
|
||||
->add('exclude', ChoiceType::class, [
|
||||
'choices' => CountryEnum::getAvailables(),
|
||||
'multiple' => true,
|
||||
'description' => 'Get document not within specified countries',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input values into proper filters.
|
||||
*
|
||||
* @param mixed $value Value to be transformed.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return \IndexBundle\Filter\FilterInterface|null
|
||||
*/
|
||||
protected function transform($value, FilterFactoryInterface $factory)
|
||||
{
|
||||
if (! isset($value['include'], $value['exclude'])) {
|
||||
//
|
||||
// One of value is not valid, don't make any transformations.
|
||||
//
|
||||
return null;
|
||||
}
|
||||
|
||||
$include = $value['include'];
|
||||
$exclude = $value['exclude'];
|
||||
|
||||
$condition = $factory->andX();
|
||||
|
||||
if (count($include) > 0) {
|
||||
$condition->add($factory->in(FieldNameEnum::COUNTRY, $include));
|
||||
}
|
||||
|
||||
if (count($exclude) > 0) {
|
||||
$condition->add($factory->not($factory->in(FieldNameEnum::COUNTRY, $exclude)));
|
||||
}
|
||||
|
||||
return $condition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type\Filter;
|
||||
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use IndexBundle\Filter\Factory\FilterFactoryInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Validator\Constraints\GreaterThan;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
/**
|
||||
* Class DateFilterType
|
||||
* @package AppBundle\Form\Type\Filter
|
||||
*/
|
||||
class DateFilterType extends AbstractFilterType
|
||||
{
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder
|
||||
->add('type', ChoiceType::class, [
|
||||
'choices' => [ 'last', 'between' ],
|
||||
'description' => 'Date filter type.',
|
||||
])
|
||||
->add('days', IntegerType::class, [
|
||||
'constraints' => [
|
||||
new GreaterThan([
|
||||
'value' => 0,
|
||||
'message' => 'This value should be integer greater than 0.',
|
||||
]),
|
||||
new NotBlank(),
|
||||
],
|
||||
'invalid_message' => 'This value should be integer greater than 0.',
|
||||
'description' => 'How many days ago document found. Used only for \'last\' type.',
|
||||
])
|
||||
->add('start', DateType::class, [
|
||||
'widget' => 'single_text',
|
||||
'input' => 'string',
|
||||
'description' => 'Start of searched period, format: \'YYYY-MM-DD\'. Used only for \'between\' type.',
|
||||
'constraints' => new NotBlank(),
|
||||
])
|
||||
->add('end', DateType::class, [
|
||||
'widget' => 'single_text',
|
||||
'input' => 'string',
|
||||
'description' => 'End of searched period, format: \'YYYY-MM-DD\'. Used only for \'between\' type.',
|
||||
'constraints' => new NotBlank(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change form based on selected type.
|
||||
*
|
||||
* @param FormEvent $event A FormEvent instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function preSubmit(FormEvent $event)
|
||||
{
|
||||
$data = $event->getData();
|
||||
$form = $event->getForm();
|
||||
|
||||
switch (true) {
|
||||
//
|
||||
// Stop processing because type not set.
|
||||
// Remove all fields to avoid unnecessary validation errors.
|
||||
//
|
||||
case ! isset($data['type']) || (($data['type'] !== 'last')
|
||||
&& ($data['type'] !== 'between')):
|
||||
$form
|
||||
->remove('days')
|
||||
->remove('start')
|
||||
->remove('end');
|
||||
break;
|
||||
|
||||
//
|
||||
// For last 'type' we should remove 'start' and 'end' field because
|
||||
// its unnecessary.
|
||||
//
|
||||
case $data['type'] === 'last':
|
||||
$form
|
||||
->remove('start')
|
||||
->remove('end');
|
||||
break;
|
||||
|
||||
//
|
||||
// Make additional validation for 'between' type.
|
||||
//
|
||||
// Try to convert 'start' and 'end' values into datetime instances
|
||||
// and check that 'start' not greater than 'end' and if it so add
|
||||
// proper error message to 'start' field.
|
||||
//
|
||||
default:
|
||||
if (isset($data['start'], $data['end'])) {
|
||||
$start = date_create_from_format('Y-m-d', $data['start'])->setTime(0, 0);
|
||||
$end = date_create_from_format('Y-m-d', $data['end'])->setTime(0, 0);
|
||||
|
||||
if ((($start !== false) && ($end !== false)) && ($start > $end)) {
|
||||
$form->addError(new FormError(
|
||||
'\'start\' value should be less than \'end\'.'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$form->remove('days');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input values into proper filters.
|
||||
*
|
||||
* @param mixed $value Value to be transformed.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return \IndexBundle\Filter\FilterInterface|null
|
||||
*/
|
||||
protected function transform($value, FilterFactoryInterface $factory)
|
||||
{
|
||||
//
|
||||
// Don't make any transformations if 'type' is not set.
|
||||
//
|
||||
if (! is_array($value) || ! isset($value['type'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
// Because transformation occurred before validation we should check all
|
||||
// values and if some of them is invalid we just return null.
|
||||
//
|
||||
// All validation error messages will be added in form validation
|
||||
// listener so we should'nt worry about it.
|
||||
//
|
||||
|
||||
switch ($value['type']) {
|
||||
case 'last':
|
||||
return $this->transformLast($value, $factory);
|
||||
|
||||
case 'between':
|
||||
return $this->transformBetween($value, $factory);
|
||||
}
|
||||
|
||||
throw new \LogicException(sprintf(
|
||||
'Unhandled date filter type \'%s\'',
|
||||
$value['type']
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $value Date filter value.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return \IndexBundle\Filter\Filters\AndFilter|null
|
||||
*/
|
||||
private function transformLast(array $value, FilterFactoryInterface $factory)
|
||||
{
|
||||
if (! isset($value['days']) || ($value['days'] < 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $factory->andX([
|
||||
$factory->gte(FieldNameEnum::PUBLISHED, date_create(sprintf(
|
||||
'- %d days 00:00:00',
|
||||
$value['days']
|
||||
))),
|
||||
$factory->lte(FieldNameEnum::PUBLISHED, date_create('23:59:59')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $value Date filter value.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return \IndexBundle\Filter\Filters\AndFilter|null
|
||||
*/
|
||||
private function transformBetween(array $value, FilterFactoryInterface $factory)
|
||||
{
|
||||
if (! isset($value['start'], $value['end'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to create datetime instances from 'start' and 'end' fields and
|
||||
// if we got error we should stop further transformations and return
|
||||
// null.
|
||||
$start = date_create_from_format('Y-m-d', $value['start'])->setTime(0, 0);
|
||||
$end = date_create_from_format('Y-m-d', $value['end'])->setTime(23, 59, 59);
|
||||
|
||||
if (($start === false) || ($end === false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $factory->andX([
|
||||
$factory->gte(FieldNameEnum::PUBLISHED, $start),
|
||||
$factory->lte(FieldNameEnum::PUBLISHED, $end),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type\Filter;
|
||||
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use IndexBundle\Filter\Factory\FilterFactoryInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
|
||||
/**
|
||||
* Class HasImageFilterType
|
||||
*
|
||||
* If false we should search documents with images and without.
|
||||
* If true we should search documents only with images.
|
||||
*
|
||||
* @package AppBundle\Form\Type\Filter
|
||||
*/
|
||||
class HasImageFilterType extends AbstractFilterType
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns the name of the parent type.
|
||||
*
|
||||
* @return string|null The name of the parent type if any, null otherwise.
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return CheckboxType::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make some manipulation with form and data before submit.
|
||||
*
|
||||
* @param FormEvent $event A FormEvent instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function preSubmit(FormEvent $event)
|
||||
{
|
||||
$data = trim($event->getData());
|
||||
|
||||
if (($data !== '0') && ($data !== '') && ($data !== '1')) {
|
||||
$event->getForm()->addError(
|
||||
new FormError('This value should be of type boolean.')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input values into proper filters.
|
||||
*
|
||||
* @param mixed $value Value to be transformed.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return \IndexBundle\Filter\FilterInterface|null
|
||||
*/
|
||||
protected function transform($value, FilterFactoryInterface $factory)
|
||||
{
|
||||
return $value === true
|
||||
? $factory->eq(FieldNameEnum::IMAGE_SRC, '/.+/')
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type\Filter;
|
||||
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use IndexBundle\Filter\Factory\FilterFactoryInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* Class HeadlineFilterType
|
||||
* @package AppBundle\Form\Type\Filter
|
||||
*/
|
||||
class HeadlineFilterType extends AbstractFilterType
|
||||
{
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder
|
||||
->add('include', null, [
|
||||
'description' => 'Comma separated list of words which should be in title.',
|
||||
])
|
||||
->add('exclude', null, [
|
||||
'description' => 'Comma separated list of words which should not be in title.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input values into proper filters.
|
||||
*
|
||||
* @param mixed $value Value to be transformed.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return \IndexBundle\Filter\FilterInterface|null
|
||||
*/
|
||||
protected function transform($value, FilterFactoryInterface $factory)
|
||||
{
|
||||
if (! isset($value['include']) && !isset($value['exclude'])) {
|
||||
//
|
||||
// One of value is not valid, don't make any transformations.
|
||||
//
|
||||
return null;
|
||||
}
|
||||
|
||||
$include = array_filter(array_map('trim', explode(',', $value['include'])));
|
||||
$exclude = array_filter(array_map('trim', explode(',', $value['exclude'])));
|
||||
|
||||
$condition = $factory->andX();
|
||||
|
||||
if (count($include) > 0) {
|
||||
$condition->add($factory->in(FieldNameEnum::TITLE, $include));
|
||||
}
|
||||
|
||||
if (count($exclude) > 0) {
|
||||
$condition->add($factory->not($factory->in(FieldNameEnum::TITLE, $exclude)));
|
||||
}
|
||||
|
||||
return $condition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Form\Type\Filter;
|
||||
|
||||
use Common\Enum\FieldNameEnum;
|
||||
use Common\Enum\LanguageEnum;
|
||||
use IndexBundle\Filter\Factory\FilterFactoryInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class LanguageFilterType
|
||||
* @package AppBundle\Form\Type\Filter
|
||||
*/
|
||||
class LanguageFilterType extends AbstractFilterType
|
||||
{
|
||||
|
||||
/**
|
||||
* 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->setDefaults([
|
||||
'choices' => LanguageEnum::getAvailables(),
|
||||
'multiple' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the parent type.
|
||||
*
|
||||
* @return string|null The name of the parent type if any, null otherwise.
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input values into proper filters.
|
||||
*
|
||||
* @param mixed $value Value to be transformed.
|
||||
* @param FilterFactoryInterface $factory A FilterFactoryInterface instance.
|
||||
*
|
||||
* @return \IndexBundle\Filter\FilterInterface|null
|
||||
*/
|
||||
protected function transform($value, FilterFactoryInterface $factory)
|
||||
{
|
||||
if (! is_array($value) || (count($value) === 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $factory->in(FieldNameEnum::LANG, $value);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user