diff --git a/src/Oro/Bundle/ActivityBundle/Routing/ActivityAssociationRouteOptionsResolver.php b/src/Oro/Bundle/ActivityBundle/Routing/ActivityAssociationRouteOptionsResolver.php
index 1d80e6dc0bf..3e2ac257d2f 100644
--- a/src/Oro/Bundle/ActivityBundle/Routing/ActivityAssociationRouteOptionsResolver.php
+++ b/src/Oro/Bundle/ActivityBundle/Routing/ActivityAssociationRouteOptionsResolver.php
@@ -30,6 +30,9 @@ class ActivityAssociationRouteOptionsResolver implements RouteOptionsResolverInt
/** @var EntityAliasResolver */
protected $entityAliasResolver;
+ /** @var array */
+ private $supportedActivities;
+
/**
* @param ConfigProvider $groupingConfigProvider
* @param EntityAliasResolver $entityAliasResolver
@@ -50,7 +53,25 @@ public function resolve(Route $route, RouteCollectionAccessor $routes)
}
if ($this->hasAttribute($route, self::ACTIVITY_PLACEHOLDER)) {
- $activities = array_map(
+ $activities = $this->getSupportedActivities();
+ if (!empty($activities)) {
+ $this->adjustRoutes($route, $routes, $activities);
+ }
+
+ $this->completeRouteRequirements($route);
+ $route->setOption('hidden', true);
+ } elseif ($this->hasAttribute($route, self::ENTITY_PLACEHOLDER)) {
+ $this->completeRouteRequirements($route);
+ }
+ }
+
+ /**
+ * @return string[]
+ */
+ protected function getSupportedActivities()
+ {
+ if (null === $this->supportedActivities) {
+ $this->supportedActivities = array_map(
function (ConfigInterface $config) {
// convert to entity alias
return $this->entityAliasResolver->getPluralAlias(
@@ -68,15 +89,9 @@ function (ConfigInterface $config) {
}
)
);
-
- if (!empty($activities)) {
- $this->adjustRoutes($route, $routes, $activities);
- $route->setRequirement(self::ACTIVITY_ATTRIBUTE, implode('|', $activities));
- }
- $this->completeRouteRequirements($route);
- } elseif ($this->hasAttribute($route, self::ENTITY_PLACEHOLDER)) {
- $this->completeRouteRequirements($route);
}
+
+ return $this->supportedActivities;
}
/**
@@ -126,6 +141,11 @@ protected function adjustRoutes(Route $route, RouteCollectionAccessor $routes, $
*/
protected function completeRouteRequirements(Route $route)
{
+ if (null === $route->getRequirement(self::ACTIVITY_ATTRIBUTE)
+ && $this->hasAttribute($route, self::ACTIVITY_PLACEHOLDER)
+ ) {
+ $route->setRequirement(self::ACTIVITY_ATTRIBUTE, '\w+');
+ }
if (null === $route->getRequirement(self::ACTIVITY_ID_ATTRIBUTE)
&& $this->hasAttribute($route, self::ACTIVITY_ID_PLACEHOLDER)
) {
diff --git a/src/Oro/Bundle/ActivityBundle/Tests/Unit/Routing/ActivityAssociationRouteOptionsResolverTest.php b/src/Oro/Bundle/ActivityBundle/Tests/Unit/Routing/ActivityAssociationRouteOptionsResolverTest.php
index 5a4fb9b4117..2d88c9cf6ee 100644
--- a/src/Oro/Bundle/ActivityBundle/Tests/Unit/Routing/ActivityAssociationRouteOptionsResolverTest.php
+++ b/src/Oro/Bundle/ActivityBundle/Tests/Unit/Routing/ActivityAssociationRouteOptionsResolverTest.php
@@ -110,7 +110,7 @@ public function testResolve()
$this->routeOptionsResolver->resolve($route, $this->routeCollectionAccessor);
$this->assertEquals(
- ['activity' => 'emails|calls|tasks|events'],
+ ['activity' => '\w+'],
$route->getRequirements()
);
@@ -130,7 +130,7 @@ public function testResolve()
);
$this->assertEquals(
- 'emails|calls|tasks|events',
+ '\w+',
$this->routeCollection->get('tested_route')->getRequirement('activity')
);
$this->assertEquals(
diff --git a/src/Oro/Bundle/ActivityListBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/ActivityListBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..b158015cb7f
--- /dev/null
+++ b/src/Oro/Bundle/ActivityListBundle/Resources/config/oro/api.yml
@@ -0,0 +1,4 @@
+oro_api:
+ entities:
+ Oro\Bundle\ActivityListBundle\Entity\ActivityList: ~
+ Oro\Bundle\ActivityListBundle\Entity\ActivityOwner: ~
diff --git a/src/Oro/Bundle/AddressBundle/Entity/AbstractAddress.php b/src/Oro/Bundle/AddressBundle/Entity/AbstractAddress.php
index 19c575706ee..2701b34afa6 100644
--- a/src/Oro/Bundle/AddressBundle/Entity/AbstractAddress.php
+++ b/src/Oro/Bundle/AddressBundle/Entity/AbstractAddress.php
@@ -768,7 +768,7 @@ public function beforeSave()
/**
* @param ExecutionContextInterface $context
- * @deprecated Use \Oro\Bundle\AddressBundle\Validator\Constraints\ValidRegionValidator instead
+ * @deprecated since 1.9 Use \Oro\Bundle\AddressBundle\Validator\Constraints\ValidRegionValidator instead
*/
public function isRegionValid(ExecutionContextInterface $context)
{
diff --git a/src/Oro/Bundle/AddressBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/AddressBundle/Resources/config/oro/api.yml
index 20021fa4cfc..bc566c3276c 100644
--- a/src/Oro/Bundle/AddressBundle/Resources/config/oro/api.yml
+++ b/src/Oro/Bundle/AddressBundle/Resources/config/oro/api.yml
@@ -1,4 +1,7 @@
oro_api:
+ entities:
+ Oro\Bundle\AddressBundle\Entity\Address: ~
+
relations:
Oro\Bundle\AddressBundle\Entity\AbstractAddress:
definition:
diff --git a/src/Oro/Bundle/AddressBundle/Resources/config/validation.yml b/src/Oro/Bundle/AddressBundle/Resources/config/validation.yml
index f02c6b1a9b7..775b3cfdebc 100644
--- a/src/Oro/Bundle/AddressBundle/Resources/config/validation.yml
+++ b/src/Oro/Bundle/AddressBundle/Resources/config/validation.yml
@@ -49,6 +49,8 @@ Oro\Bundle\AddressBundle\Entity\AbstractAddress:
# Prevent required values for all child of AbstractAddress
Oro\Bundle\AddressBundle\Entity\Address:
+ constraints:
+ - Oro\Bundle\AddressBundle\Validator\Constraints\ValidRegion: { groups: ['RequirePeriod'] }
properties:
street:
- NotBlank: ~
@@ -56,8 +58,6 @@ Oro\Bundle\AddressBundle\Entity\Address:
- NotBlank: ~
postalCode:
- NotBlank: ~
- constraints:
- - Oro\Bundle\AddressBundle\Validator\Constraints\ValidRegion: ~
Oro\Bundle\AddressBundle\Entity\AbstractEmail:
properties:
diff --git a/src/Oro/Bundle/ApiBundle/Command/DebugCommand.php b/src/Oro/Bundle/ApiBundle/Command/DebugCommand.php
index 541eb4fb750..d821974d317 100644
--- a/src/Oro/Bundle/ApiBundle/Command/DebugCommand.php
+++ b/src/Oro/Bundle/ApiBundle/Command/DebugCommand.php
@@ -4,6 +4,7 @@
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator;
@@ -12,6 +13,7 @@
use Oro\Component\ChainProcessor\ChainApplicableChecker;
use Oro\Component\ChainProcessor\Context;
use Oro\Component\ChainProcessor\ProcessorBagInterface;
+use Oro\Bundle\ApiBundle\Request\RequestType;
class DebugCommand extends ContainerAwareCommand
{
@@ -27,6 +29,12 @@ protected function configure()
'action',
InputArgument::OPTIONAL,
'Shows a list of processors for a specified action in the order they are executed'
+ )
+ ->addOption(
+ 'request-type',
+ null,
+ InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'API request type'
);
}
@@ -39,7 +47,7 @@ public function execute(InputInterface $input, OutputInterface $output)
if (empty($action)) {
$this->dumpActions($output);
} else {
- $this->dumpProcessors($output, $action);
+ $this->dumpProcessors($output, $action, $input->getOption('request-type'));
}
}
@@ -77,9 +85,12 @@ protected function dumpActions(OutputInterface $output)
/**
* @param OutputInterface $output
* @param string $action
+ * @param string[] $requestType
*/
- protected function dumpProcessors(OutputInterface $output, $action)
+ protected function dumpProcessors(OutputInterface $output, $action, array $requestType)
{
+ $output->writeln('The processors are displayed in order they are executed.');
+
/** @var ProcessorBagInterface $processorBag */
$processorBag = $this->getContainer()->get('oro_api.processor_bag');
@@ -88,8 +99,15 @@ protected function dumpProcessors(OutputInterface $output, $action)
$context = new Context();
$context->setAction($action);
+ if (!empty($requestType)) {
+ $context->set('requestType', $requestType);
+ }
$processors = $processorBag->getProcessors($context);
- $processors->setApplicableChecker(new ChainApplicableChecker());
+
+ $applicableChecker = new ChainApplicableChecker();
+ $applicableChecker->addChecker(new RequestTypeApplicableChecker());
+ $processors->setApplicableChecker($applicableChecker);
+
$i = 0;
foreach ($processors as $processor) {
if ($i > 0) {
diff --git a/src/Oro/Bundle/ApiBundle/Command/DumpConfigCommand.php b/src/Oro/Bundle/ApiBundle/Command/DumpConfigCommand.php
index 9249cdd0609..caa91403024 100644
--- a/src/Oro/Bundle/ApiBundle/Command/DumpConfigCommand.php
+++ b/src/Oro/Bundle/ApiBundle/Command/DumpConfigCommand.php
@@ -70,7 +70,7 @@ public function execute(InputInterface $input, OutputInterface $output)
/** @var EntityClassNameHelper $entityClassNameHelper */
$entityClassNameHelper = $this->getContainer()->get('oro_entity.entity_class_name_helper');
- $entityClass = $entityClassNameHelper->resolveEntityClass($input->getArgument('entity'));
+ $entityClass = $entityClassNameHelper->resolveEntityClass($input->getArgument('entity'), true);
$requestType = $input->getOption('request-type');
// @todo: API version is not supported for now
//$version = $input->getArgument('version');
diff --git a/src/Oro/Bundle/ApiBundle/Command/DumpMetadataCommand.php b/src/Oro/Bundle/ApiBundle/Command/DumpMetadataCommand.php
index a9cf571cc27..7a7da1be841 100644
--- a/src/Oro/Bundle/ApiBundle/Command/DumpMetadataCommand.php
+++ b/src/Oro/Bundle/ApiBundle/Command/DumpMetadataCommand.php
@@ -55,7 +55,7 @@ public function execute(InputInterface $input, OutputInterface $output)
/** @var EntityClassNameHelper $entityClassNameHelper */
$entityClassNameHelper = $this->getContainer()->get('oro_entity.entity_class_name_helper');
- $entityClass = $entityClassNameHelper->resolveEntityClass($input->getArgument('entity'));
+ $entityClass = $entityClassNameHelper->resolveEntityClass($input->getArgument('entity'), true);
$requestType = $input->getOption('request-type');
// @todo: API version is not supported for now
//$version = $input->getArgument('version');
diff --git a/src/Oro/Bundle/ApiBundle/Command/DumpPublicResourcesCommand.php b/src/Oro/Bundle/ApiBundle/Command/DumpPublicResourcesCommand.php
new file mode 100644
index 00000000000..ebd03e68d76
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Command/DumpPublicResourcesCommand.php
@@ -0,0 +1,122 @@
+setName('oro:api:resources:dump')
+ ->setDescription('Dumps all public API resources.')
+ // @todo: API version is not supported for now
+ //->addArgument(
+ // 'version',
+ // InputArgument::OPTIONAL,
+ // 'API version',
+ // Version::LATEST
+ //)
+ ->addOption(
+ 'request-type',
+ null,
+ InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'API request type',
+ [RequestType::REST, RequestType::JSON_API]
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ $requestType = $input->getOption('request-type');
+ // @todo: API version is not supported for now
+ //$version = $input->getArgument('version');
+ $version = Version::LATEST;
+
+ /** @var PublicResourcesLoader $resourcesLoader */
+ $resourcesLoader = $this->getContainer()->get('oro_api.public_resources_loader');
+ $resources = $resourcesLoader->getResources($version, $requestType);
+
+ $table = new Table($output);
+ $table->setHeaders(['Entity', 'Attributes']);
+
+ $i = 0;
+ foreach ($resources as $resource) {
+ if ($i > 0) {
+ $table->addRow(new TableSeparator());
+ }
+ $table->addRow(
+ [
+ $resource->getEntityClass(),
+ $this->convertResourceAttributesToString($this->getResourceAttributes($resource))
+ ]
+ );
+ $i++;
+ }
+
+ $table->render();
+ }
+
+ /**
+ * @param PublicResource $resource
+ *
+ * @return array
+ */
+ protected function getResourceAttributes(PublicResource $resource)
+ {
+ $result = [];
+
+ $entityClass = $resource->getEntityClass();
+
+ /** @var EntityAliasResolver $entityAliasResolver */
+ $entityAliasResolver = $this->getContainer()->get('oro_entity.entity_alias_resolver');
+ $result['Alias'] = $entityAliasResolver->getPluralAlias($entityClass);
+
+ /** @var EntityClassNameProviderInterface $entityClassNameProvider */
+ $entityClassNameProvider = $this->getContainer()->get('oro_entity.entity_class_name_provider');
+ $result['Name'] = $entityClassNameProvider->getEntityClassName($entityClass);
+ $result['Plural Name'] = $entityClassNameProvider->getEntityClassPluralName($entityClass);
+
+ return $result;
+ }
+
+ /**
+ * @param array $attributes
+ *
+ * @return string
+ */
+ protected function convertResourceAttributesToString(array $attributes)
+ {
+ $result = '';
+
+ $i = 0;
+ foreach ($attributes as $name => $value) {
+ if ($i > 0) {
+ $result .= PHP_EOL;
+ }
+ $result .= sprintf('%s: %s', $name, $value);
+ $i++;
+ }
+
+ return $result;
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Command/RequestTypeApplicableChecker.php b/src/Oro/Bundle/ApiBundle/Command/RequestTypeApplicableChecker.php
new file mode 100644
index 00000000000..8091e4d6f76
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Command/RequestTypeApplicableChecker.php
@@ -0,0 +1,44 @@
+has($attrName)) {
+ if (!$this->isMatch($processorAttributes[$attrName], $context->get($attrName))) {
+ $result = self::NOT_APPLICABLE;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Checks if a value of a processor attribute matches a corresponding value from the context
+ *
+ * @param mixed $value
+ * @param mixed $contextValue
+ *
+ * @return bool
+ */
+ protected function isMatch($value, $contextValue)
+ {
+ if (is_array($contextValue)) {
+ return is_array($value)
+ ? count(array_intersect($value, $contextValue)) === count($value)
+ : in_array($value, $contextValue, true);
+ }
+
+ return $contextValue === $value;
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConfigurationCompilerPass.php b/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConfigurationCompilerPass.php
index 77c0a596682..3ad40aebe41 100644
--- a/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConfigurationCompilerPass.php
+++ b/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConfigurationCompilerPass.php
@@ -16,7 +16,7 @@ class ConfigurationCompilerPass implements CompilerPassInterface
const ACTION_PROCESSOR_BAG_SERVICE_ID = 'oro_api.action_processor_bag';
const PROCESSOR_BAG_SERVICE_ID = 'oro_api.processor_bag';
const FILTER_FACTORY_TAG = 'oro.api.filter_factory';
- const FILTER_FACTORY_SERVICE_ID = 'oro.api.filter_factory';
+ const FILTER_FACTORY_SERVICE_ID = 'oro_api.filter_factory';
const EXCLUSION_PROVIDER_TAG = 'oro_entity.exclusion_provider.api';
const EXCLUSION_PROVIDER_SERVICE_ID = 'oro_api.entity_exclusion_provider';
const VIRTUAL_FIELD_PROVIDER_TAG = 'oro_entity.virtual_field_provider.api';
diff --git a/src/Oro/Bundle/ApiBundle/DependencyInjection/OroApiExtension.php b/src/Oro/Bundle/ApiBundle/DependencyInjection/OroApiExtension.php
index 27d5a3e6141..af3967f7d76 100644
--- a/src/Oro/Bundle/ApiBundle/DependencyInjection/OroApiExtension.php
+++ b/src/Oro/Bundle/ApiBundle/DependencyInjection/OroApiExtension.php
@@ -21,6 +21,7 @@ public function load(array $configs, ContainerBuilder $container)
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
$loader->load('processors.normalize_value.yml');
+ $loader->load('processors.collect_public_resources.yml');
$loader->load('processors.get_config.yml');
$loader->load('processors.get_metadata.yml');
$loader->load('processors.get_list.yml');
@@ -44,20 +45,18 @@ protected function loadApiConfiguration(ContainerBuilder $container)
$apiConfig = $this->mergeApiConfiguration($resource, $apiConfig);
}
- $exclusions = [];
- if (array_key_exists('exclusions', $apiConfig)) {
- $exclusions = $apiConfig['exclusions'];
- unset($apiConfig['exclusions']);
- }
+ $apiConfig = $this->normalizeApiConfiguration($apiConfig);
$configBagDef = $container->getDefinition('oro_api.config_bag');
- $configBagDef->replaceArgument(0, $apiConfig);
+ $configBagDef->replaceArgument(0, $apiConfig['config']);
$exclusionProviderDef = $container->getDefinition('oro_api.entity_exclusion_provider.config');
- $exclusionProviderDef->replaceArgument(1, $exclusions);
+ $exclusionProviderDef->replaceArgument(1, $apiConfig['exclusions']);
}
/**
+ * @todo: this merge should be replaced with Symfony configuration tree (see BAP-9757)
+ *
* @param CumulativeResourceInfo $resource
* @param array $data
*
@@ -66,12 +65,211 @@ protected function loadApiConfiguration(ContainerBuilder $container)
protected function mergeApiConfiguration(CumulativeResourceInfo $resource, array $data)
{
if (!empty($resource->data['oro_api'])) {
- $data = array_merge(
- $data,
- $resource->data['oro_api']
- );
+ foreach (['entities', 'relations', 'metadata'] as $section) {
+ if (!empty($resource->data['oro_api'][$section])) {
+ $merged = $this->mergeApiConfigurationSection(
+ $data,
+ $resource->data['oro_api'],
+ [$section]
+ );
+ $data[$section] = $merged[$section];
+ }
+ }
+ $section = 'exclusions';
+ if (!empty($resource->data['oro_api'][$section])) {
+ $data[$section] = array_merge(
+ $this->normalizeArray($data, $section),
+ $resource->data['oro_api'][$section]
+ );
+ }
}
return $data;
}
+
+ /**
+ * @param array|null $data1
+ * @param array|null $data2
+ * @param string[] $sections
+ *
+ * @return array
+ */
+ protected function mergeApiConfigurationSection($data1, $data2, array $sections)
+ {
+ $result = [];
+
+ $data1 = $this->normalizeArray($data1);
+ $data2 = $this->normalizeArray($data2);
+ foreach ($sections as $section) {
+ $array1 = $this->normalizeArray($data1, $section);
+ $array2 = $this->normalizeArray($data2, $section);
+
+ $sectionData = [];
+ foreach ($array1 as $key => $val) {
+ $val = $this->normalizeArray($val);
+ if (array_key_exists($key, $array2)) {
+ if (!empty($array2[$key])) {
+ $sectionData[$key] = call_user_func(
+ $this->getMergeCallback($section, $key),
+ $val,
+ $array2[$key]
+ );
+ } else {
+ $sectionData[$key] = $val;
+ }
+ unset($array2[$key]);
+ } else {
+ $sectionData[$key] = $val;
+ }
+ }
+ foreach ($array2 as $key => $val) {
+ $sectionData[$key] = $this->normalizeArray($val);
+ }
+
+ $result[$section] = $sectionData;
+
+ unset($data1[$section]);
+ unset($data2[$section]);
+ }
+ foreach ($data1 as $key => $val) {
+ $result[$key] = $val;
+ }
+ foreach ($data2 as $key => $val) {
+ $result[$key] = $val;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $parentSection
+ * @param string $section
+ *
+ * @return callable
+ */
+ protected function getMergeCallback($parentSection, $section)
+ {
+ if (in_array($parentSection, ['entities', 'relations', 'metadata'], true)) {
+ return function (array $array1, array $array2) {
+ $result = $this->mergeApiConfigurationSection(
+ $array1,
+ $array2,
+ ['definition']
+ );
+ if (empty($result['definition'])) {
+ unset($result['definition']);
+ }
+
+ return $result;
+ };
+ }
+ if ('definition' === $parentSection) {
+ if ('fields' === $section) {
+ return function (array $array1, array $array2) {
+ $result = $this->mergeApiConfigurationSection(
+ ['fields' => $array1],
+ ['fields' => $array2],
+ ['fields']
+ );
+ if (!empty($result['fields'])) {
+ $result['fields'] = $this->normalizeFields($result['fields']);
+ }
+
+ return $result['fields'];
+ };
+ } elseif (in_array($section, ['filters', 'sorters'], true)) {
+ return function (array $array1, array $array2) {
+ $result = $this->mergeApiConfigurationSection(
+ $array1,
+ $array2,
+ ['fields']
+ );
+ if (empty($result['fields'])) {
+ unset($result['fields']);
+ } else {
+ $result['fields'] = $this->normalizeFields($result['fields']);
+ }
+
+ return $result;
+ };
+ }
+ }
+
+ return 'array_merge';
+ }
+
+ /**
+ * @param array|null $data
+ * @param string $section
+ *
+ * @return array
+ */
+ protected function normalizeArray($data, $section = null)
+ {
+ if (null === $section) {
+ return null !== $data ? $data : [];
+ }
+
+ return !empty($data[$section]) ? $data[$section] : [];
+ }
+
+ /**
+ * @param array $array
+ *
+ * @return array
+ */
+ protected function normalizeArrayValues(array $array)
+ {
+ return array_map(
+ function ($item) {
+ return null !== $item ? $item : [];
+ },
+ $array
+ );
+ }
+
+ /**
+ * @param array $array
+ *
+ * @return array
+ */
+ protected function normalizeFields(array $array)
+ {
+ return array_map(
+ function ($item) {
+ return !empty($item) ? $item : null;
+ },
+ $array
+ );
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ */
+ protected function normalizeApiConfiguration(array $data)
+ {
+ $exclusions = [];
+ if (array_key_exists('exclusions', $data)) {
+ $exclusions = $data['exclusions'];
+ unset($data['exclusions']);
+ }
+
+ if (!empty($data['entities'])) {
+ foreach ($data['entities'] as $entityClass => &$entityConfig) {
+ if (!empty($entityConfig) && array_key_exists('exclude', $entityConfig)) {
+ if ($entityConfig['exclude']) {
+ $exclusions[] = ['entity' => $entityClass];
+ }
+ unset($entityConfig['exclude']);
+ }
+ }
+ }
+
+ return [
+ 'exclusions' => $exclusions,
+ 'config' => $data
+ ];
+ }
}
diff --git a/src/Oro/Bundle/ApiBundle/Metadata/AssociationMetadata.php b/src/Oro/Bundle/ApiBundle/Metadata/AssociationMetadata.php
index 8bdfadf6be7..7aafdfd7946 100644
--- a/src/Oro/Bundle/ApiBundle/Metadata/AssociationMetadata.php
+++ b/src/Oro/Bundle/ApiBundle/Metadata/AssociationMetadata.php
@@ -7,6 +7,9 @@ class AssociationMetadata extends PropertyMetadata
/** FQCN of an association target */
const TARGET_CLASS_NAME = 'targetClass';
+ /** FQCN of acceptable association targets */
+ const ACCEPTABLE_TARGET_CLASS_NAMES = 'acceptableTargetClasses';
+
/** a flag indicates if an association represents "to-many" or "to-one" relation */
const COLLECTION = 'collection';
@@ -53,6 +56,59 @@ public function setTargetClassName($className)
$this->set(self::TARGET_CLASS_NAME, $className);
}
+ /**
+ * Gets FQCN of acceptable association targets.
+ *
+ * @return string[]
+ */
+ public function getAcceptableTargetClassNames()
+ {
+ $classNames = $this->get(self::ACCEPTABLE_TARGET_CLASS_NAMES);
+
+ return null !== $classNames
+ ? $classNames
+ : [];
+ }
+
+ /**
+ * Sets FQCN of acceptable association targets.
+ *
+ * @param string[] $classNames
+ */
+ public function setAcceptableTargetClassNames(array $classNames)
+ {
+ $this->set(self::ACCEPTABLE_TARGET_CLASS_NAMES, $classNames);
+ }
+
+ /**
+ * Adds new acceptable association target.
+ *
+ * @param string $className
+ */
+ public function addAcceptableTargetClassName($className)
+ {
+ $classNames = $this->getAcceptableTargetClassNames();
+ if (!in_array($className, $classNames, true)) {
+ $classNames[] = $className;
+ }
+ $this->set(self::ACCEPTABLE_TARGET_CLASS_NAMES, $classNames);
+ }
+
+ /**
+ * Removes acceptable association target.
+ *
+ * @param string $className
+ */
+ public function removeAcceptableTargetClassName($className)
+ {
+ $classNames = $this->getAcceptableTargetClassNames();
+ $key = array_search($className, $classNames, true);
+ if (false !== $key) {
+ unset($classNames[$key]);
+ $this->set(self::ACCEPTABLE_TARGET_CLASS_NAMES, array_values($classNames));
+ }
+ }
+
/**
* Whether an association represents "to-many" or "to-one" relation.
*
diff --git a/src/Oro/Bundle/ApiBundle/Metadata/EntityMetadata.php b/src/Oro/Bundle/ApiBundle/Metadata/EntityMetadata.php
index 71e01c5b7aa..d702e60a25c 100644
--- a/src/Oro/Bundle/ApiBundle/Metadata/EntityMetadata.php
+++ b/src/Oro/Bundle/ApiBundle/Metadata/EntityMetadata.php
@@ -9,6 +9,9 @@ class EntityMetadata extends ParameterBag
/** FQCN of an entity */
const CLASS_NAME = 'class';
+ /** entity inheritance flag */
+ const INHERITED = 'inherited';
+
/** @var string[] */
private $identifiers = [];
@@ -58,6 +61,28 @@ public function setIdentifierFieldNames(array $fieldNames)
$this->identifiers = $fieldNames;
}
+ /**
+ * Checks whether an entity is inherited object.
+ * It can be an entity implemented by Doctrine table inheritance
+ * or by another feature, for example by associations provided by OroPlatform.
+ *
+ * @return bool
+ */
+ public function isInheritedType()
+ {
+ return (bool)$this->get(self::INHERITED);
+ }
+
+ /**
+ * Sets inheritance flag.
+ *
+ * @param bool $inherited
+ */
+ public function setInheritedType($inherited)
+ {
+ $this->set(self::INHERITED, $inherited);
+ }
+
/**
* Checks whether metadata of the given field or association exists.
*
diff --git a/src/Oro/Bundle/ApiBundle/Metadata/EntityMetadataFactory.php b/src/Oro/Bundle/ApiBundle/Metadata/EntityMetadataFactory.php
index 54cc08276d8..4eccd198612 100644
--- a/src/Oro/Bundle/ApiBundle/Metadata/EntityMetadataFactory.php
+++ b/src/Oro/Bundle/ApiBundle/Metadata/EntityMetadataFactory.php
@@ -30,6 +30,7 @@ public function createEntityMetadata(ClassMetadata $classMetadata)
$entityMetadata = new EntityMetadata();
$entityMetadata->setClassName($classMetadata->name);
$entityMetadata->setIdentifierFieldNames($classMetadata->getIdentifierFieldNames());
+ $entityMetadata->setInheritedType($classMetadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE);
return $entityMetadata;
}
@@ -72,6 +73,12 @@ public function createAssociationMetadata(ClassMetadata $classMetadata, $associa
$associationMetadata->setDataType(DataType::STRING);
}
+ if ($targetMetadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
+ $associationMetadata->setAcceptableTargetClassNames($targetMetadata->subClasses);
+ } else {
+ $associationMetadata->addAcceptableTargetClassName($targetClass);
+ }
+
return $associationMetadata;
}
}
diff --git a/src/Oro/Bundle/ApiBundle/Normalizer/ObjectNormalizer.php b/src/Oro/Bundle/ApiBundle/Normalizer/ObjectNormalizer.php
index 7f450521bdf..ba9475d674b 100644
--- a/src/Oro/Bundle/ApiBundle/Normalizer/ObjectNormalizer.php
+++ b/src/Oro/Bundle/ApiBundle/Normalizer/ObjectNormalizer.php
@@ -154,9 +154,7 @@ protected function normalizeObjectByConfig($object, $config, $level)
if (ConfigUtil::isExclude($fieldConfig)) {
continue;
}
- $propertyPath = !empty($fieldConfig[ConfigUtil::PROPERTY_PATH])
- ? $fieldConfig[ConfigUtil::PROPERTY_PATH]
- : $fieldName;
+ $propertyPath = ConfigUtil::getPropertyPath($fieldConfig, $fieldName);
if ($this->dataAccessor->tryGetValue($object, $propertyPath, $value) && null !== $value) {
$childFields = isset($fieldConfig[ConfigUtil::FIELDS])
? $fieldConfig[ConfigUtil::FIELDS]
diff --git a/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/CollectPublicResourcesContext.php b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/CollectPublicResourcesContext.php
new file mode 100644
index 00000000000..48daed8250b
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/CollectPublicResourcesContext.php
@@ -0,0 +1,17 @@
+setResult(new PublicResourceCollection());
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/LoadCustomEntities.php b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/LoadCustomEntities.php
new file mode 100644
index 00000000000..416a19acd39
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/LoadCustomEntities.php
@@ -0,0 +1,42 @@
+configManager = $configManager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContextInterface $context)
+ {
+ /** @var CollectPublicResourcesContext $context */
+
+ $resources = $context->getResult();
+ $configs = $this->configManager->getConfigs('extend', null, true);
+ foreach ($configs as $config) {
+ if ($config->is('is_extend') && $config->is('owner', ExtendScope::OWNER_CUSTOM)) {
+ $resources->add(new PublicResource($config->getId()->getClassName()));
+ }
+ }
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/LoadDictionaries.php b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/LoadDictionaries.php
new file mode 100644
index 00000000000..f27da2d2078
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/LoadDictionaries.php
@@ -0,0 +1,39 @@
+dictionaryProvider = $dictionaryProvider;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContextInterface $context)
+ {
+ /** @var CollectPublicResourcesContext $context */
+
+ $resources = $context->getResult();
+ $entities = $this->dictionaryProvider->getSupportedEntityClasses();
+ foreach ($entities as $entityClass) {
+ $resources->add(new PublicResource($entityClass));
+ }
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/LoadFromConfigBag.php b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/LoadFromConfigBag.php
new file mode 100644
index 00000000000..e539df2a2bf
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/LoadFromConfigBag.php
@@ -0,0 +1,39 @@
+configBag = $configBag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContextInterface $context)
+ {
+ /** @var CollectPublicResourcesContext $context */
+
+ $resources = $context->getResult();
+ $configs = $this->configBag->getConfigs($context->getVersion());
+ foreach ($configs as $entityClass => $config) {
+ $resources->add(new PublicResource($entityClass));
+ }
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/RemoveExcludedEntities.php b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/RemoveExcludedEntities.php
new file mode 100644
index 00000000000..80045583281
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResources/RemoveExcludedEntities.php
@@ -0,0 +1,41 @@
+entityExclusionProvider = $entityExclusionProvider;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContextInterface $context)
+ {
+ /** @var CollectPublicResourcesContext $context */
+
+ $context->setResult(
+ $context->getResult()->filter(
+ function (PublicResource $resource) {
+ return !$this->entityExclusionProvider->isIgnoredEntity($resource->getEntityClass());
+ }
+ )
+ );
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResourcesProcessor.php b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResourcesProcessor.php
new file mode 100644
index 00000000000..4854dc60d75
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/CollectPublicResourcesProcessor.php
@@ -0,0 +1,17 @@
+configBag->getConfig($entityClass, $version);
- if (null === $config || ConfigUtil::isInherit($config)) {
+ if (empty($config) || ConfigUtil::isInherit($config)) {
$parentClasses = $this->entityHierarchyProvider->getHierarchyForClassName($entityClass);
foreach ($parentClasses as $parentClass) {
$parentConfig = $this->configBag->getConfig($parentClass, $version);
@@ -67,7 +67,7 @@ protected function loadConfig($entityClass, $version)
}
}
- return $config;
+ return !empty($config) ? $config : null;
}
/**
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/NormalizeChildSection.php b/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/NormalizeChildSection.php
index bbdde9a454e..ea040d72461 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/NormalizeChildSection.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/NormalizeChildSection.php
@@ -57,9 +57,7 @@ protected function collectNested(
$this->updatePropertyPath($childSectionConfig, $definition);
$fields = ConfigUtil::getArrayValue($childSectionConfig, ConfigUtil::FIELDS);
foreach ($fields as $fieldName => $config) {
- $fieldPath = !empty($config[ConfigUtil::PROPERTY_PATH])
- ? $config[ConfigUtil::PROPERTY_PATH]
- : $fieldName;
+ $fieldPath = ConfigUtil::getPropertyPath($config, $fieldName);
$field = $fieldPrefix . $fieldName;
if (!isset($sectionConfig[ConfigUtil::FIELDS][$field])) {
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/SetDataItemCustomizationHandler.php b/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/SetDataItemCustomizationHandler.php
index 1988e51090e..7c91ebf48c0 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/SetDataItemCustomizationHandler.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/SetDataItemCustomizationHandler.php
@@ -55,14 +55,13 @@ public function process(ContextInterface $context)
*/
protected function setCustomizationHandler(array &$definition, ConfigContext $context)
{
- if (isset($definition[ConfigUtil::POST_SERIALIZE])) {
- // a customization handler already exists
- return;
- }
-
$entityClass = $context->getClassName();
- $definition[ConfigUtil::POST_SERIALIZE] = $this->getRootCustomizationHandler($context, $entityClass);
+ $definition[ConfigUtil::POST_SERIALIZE] = $this->getRootCustomizationHandler(
+ $context,
+ $entityClass,
+ isset($definition[ConfigUtil::POST_SERIALIZE]) ? $definition[ConfigUtil::POST_SERIALIZE] : null
+ );
if (!$this->doctrineHelper->isManageableEntityClass($entityClass)) {
// we can set customization handlers for associations only for manageable entity,
@@ -147,14 +146,13 @@ protected function setFieldCustomizationHandler(
&& is_array($fieldConfig[ConfigUtil::FIELDS])
&& $metadata->hasAssociation($fieldName)
) {
- if (!isset($definition[ConfigUtil::POST_SERIALIZE])) {
- $fieldConfig[ConfigUtil::POST_SERIALIZE] = $this->getCustomizationHandler(
- $context,
- $rootEntityClass,
- $fieldPath,
- $metadata->getAssociationTargetClass($fieldName)
- );
- }
+ $fieldConfig[ConfigUtil::POST_SERIALIZE] = $this->getCustomizationHandler(
+ $context,
+ $rootEntityClass,
+ $fieldPath,
+ $metadata->getAssociationTargetClass($fieldName),
+ isset($fieldConfig[ConfigUtil::POST_SERIALIZE]) ? $fieldConfig[ConfigUtil::POST_SERIALIZE] : null
+ );
$this->processFields(
$context,
$fieldConfig[ConfigUtil::FIELDS],
@@ -196,15 +194,23 @@ protected function createCustomizationContext(ConfigContext $context)
/**
* @param ConfigContext $context
* @param string $entityClass
+ * @param callable|null $previousHandler
*
* @return callable
*/
- protected function getRootCustomizationHandler(ConfigContext $context, $entityClass)
- {
- return function (array $dataItem) use ($context, $entityClass) {
+ protected function getRootCustomizationHandler(
+ ConfigContext $context,
+ $entityClass,
+ $previousHandler
+ ) {
+ return function (array $item) use ($context, $entityClass, $previousHandler) {
+ if (null !== $previousHandler) {
+ $item = call_user_func($previousHandler, $item);
+ }
+
$customizationContext = $this->createCustomizationContext($context);
$customizationContext->setClassName($entityClass);
- $customizationContext->setResult($dataItem);
+ $customizationContext->setResult($item);
$this->customizationProcessor->process($customizationContext);
return $customizationContext->getResult();
@@ -216,17 +222,27 @@ protected function getRootCustomizationHandler(ConfigContext $context, $entityCl
* @param string $rootEntityClass
* @param string $propertyPath
* @param string $entityClass
+ * @param callable|null $previousHandler
*
* @return callable
*/
- protected function getCustomizationHandler(ConfigContext $context, $rootEntityClass, $propertyPath, $entityClass)
- {
- return function (array $dataItem) use ($context, $rootEntityClass, $propertyPath, $entityClass) {
+ protected function getCustomizationHandler(
+ ConfigContext $context,
+ $rootEntityClass,
+ $propertyPath,
+ $entityClass,
+ $previousHandler
+ ) {
+ return function (array $item) use ($context, $rootEntityClass, $propertyPath, $entityClass, $previousHandler) {
+ if (null !== $previousHandler) {
+ $item = call_user_func($previousHandler, $item);
+ }
+
$customizationContext = $this->createCustomizationContext($context);
$customizationContext->setRootClassName($rootEntityClass);
$customizationContext->setPropertyPath($propertyPath);
$customizationContext->setClassName($entityClass);
- $customizationContext->setResult($dataItem);
+ $customizationContext->setResult($item);
$this->customizationProcessor->process($customizationContext);
return $customizationContext->getResult();
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/SetMaxRelatedEntities.php b/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/SetMaxRelatedEntities.php
index 5da74b80660..de3872e6899 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/SetMaxRelatedEntities.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/Config/GetConfig/SetMaxRelatedEntities.php
@@ -62,9 +62,7 @@ protected function setLimits(array &$definition, $entityClass, $limit)
$metadata = $this->doctrineHelper->getEntityMetadataForClass($entityClass);
foreach ($definition[ConfigUtil::FIELDS] as $fieldName => &$fieldConfig) {
if (is_array($fieldConfig)) {
- $propertyPath = !empty($fieldConfig[ConfigUtil::PROPERTY_PATH])
- ? $fieldConfig[ConfigUtil::PROPERTY_PATH]
- : $fieldName;
+ $propertyPath = ConfigUtil::getPropertyPath($fieldConfig, $fieldName);
$path = ConfigUtil::explodePropertyPath($propertyPath);
if (count($path) === 1) {
$this->setFieldLimit($fieldConfig, $metadata, $propertyPath, $limit);
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Config/GetRelationConfig/LoadFromConfigBag.php b/src/Oro/Bundle/ApiBundle/Processor/Config/GetRelationConfig/LoadFromConfigBag.php
index 8a32c0467ea..96b0535cdfd 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/Config/GetRelationConfig/LoadFromConfigBag.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/Config/GetRelationConfig/LoadFromConfigBag.php
@@ -80,7 +80,7 @@ protected function addConfigToContext(RelationConfigContext $context, array $con
protected function loadConfig($entityClass, $version)
{
$config = $this->configBag->getRelationConfig($entityClass, $version);
- if (null === $config || ConfigUtil::isInherit($config)) {
+ if (empty($config) || ConfigUtil::isInherit($config)) {
$parentClasses = $this->entityHierarchyProvider->getHierarchyForClassName($entityClass);
foreach ($parentClasses as $parentClass) {
$parentConfig = $this->configBag->getRelationConfig($parentClass, $version);
@@ -93,7 +93,7 @@ protected function loadConfig($entityClass, $version)
}
}
- return $config;
+ return !empty($config) ? $config : null;
}
/**
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/CompleteDefinition.php b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/CompleteDefinition.php
index a8737c2cec5..a70c3ce9e3c 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/CompleteDefinition.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/CompleteDefinition.php
@@ -4,10 +4,10 @@
use Doctrine\ORM\Mapping\ClassMetadata;
-use Oro\Bundle\ApiBundle\Provider\ConfigProvider;
use Oro\Component\ChainProcessor\ContextInterface;
use Oro\Component\ChainProcessor\ProcessorInterface;
use Oro\Bundle\ApiBundle\Processor\Config\ConfigContext;
+use Oro\Bundle\ApiBundle\Provider\ConfigProvider;
use Oro\Bundle\ApiBundle\Provider\FieldConfigProvider;
use Oro\Bundle\ApiBundle\Util\ConfigUtil;
use Oro\Bundle\ApiBundle\Util\DoctrineHelper;
@@ -39,10 +39,10 @@ public function __construct(
ConfigProvider $configProvider,
FieldConfigProvider $fieldConfigProvider
) {
- $this->doctrineHelper = $doctrineHelper;
- $this->exclusionProvider = $exclusionProvider;
- $this->configProvider = $configProvider;
- $this->fieldConfigProvider = $fieldConfigProvider;
+ $this->doctrineHelper = $doctrineHelper;
+ $this->exclusionProvider = $exclusionProvider;
+ $this->configProvider = $configProvider;
+ $this->fieldConfigProvider = $fieldConfigProvider;
}
/**
@@ -170,18 +170,18 @@ protected function getAssociations(
continue;
}
- $config[ConfigUtil::DEFINITION] = [
- ConfigUtil::EXCLUSION_POLICY => ConfigUtil::EXCLUSION_POLICY_ALL,
- ConfigUtil::FIELDS => null
- ];
- $config[ConfigUtil::DEFINITION][ConfigUtil::EXCLUSION_POLICY] = ConfigUtil::EXCLUSION_POLICY_ALL;
-
$identifierFieldNames = $this->doctrineHelper->getEntityIdentifierFieldNamesForClass(
$mapping['targetEntity']
);
- $config[ConfigUtil::DEFINITION][ConfigUtil::FIELDS] = count($identifierFieldNames) === 1
- ? reset($identifierFieldNames)
- : array_fill_keys($identifierFieldNames, null);
+
+ $config = [
+ ConfigUtil::DEFINITION => [
+ ConfigUtil::EXCLUSION_POLICY => ConfigUtil::EXCLUSION_POLICY_ALL,
+ ConfigUtil::FIELDS => count($identifierFieldNames) === 1
+ ? reset($identifierFieldNames)
+ : array_fill_keys($identifierFieldNames, null)
+ ]
+ ];
if (isset($definition[$fieldName]) && is_array($definition[$fieldName])) {
$config = array_merge_recursive($config, [ConfigUtil::DEFINITION => $definition[$fieldName]]);
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/CompleteDefinitionByExtra.php b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/CompleteDefinitionByExtra.php
index 06ad2816f3b..656e8bb2a50 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/CompleteDefinitionByExtra.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/CompleteDefinitionByExtra.php
@@ -37,7 +37,7 @@ public function process(ContextInterface $context)
continue;
}
- if (!in_array($fieldName, $expandRelations)) {
+ if (!in_array($fieldName, $expandRelations, true)) {
continue;
}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/ExcludeNotAccessibleRelations.php b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/ExcludeNotAccessibleRelations.php
new file mode 100644
index 00000000000..3e77a0d789c
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/ExcludeNotAccessibleRelations.php
@@ -0,0 +1,210 @@
+doctrineHelper = $doctrineHelper;
+ $this->router = $router;
+ $this->entityAliasResolver = $entityAliasResolver;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContextInterface $context)
+ {
+ /** @var ConfigContext $context */
+
+ $definition = $context->getResult();
+ if (!isset($definition[ConfigUtil::FIELDS])
+ || !is_array($definition[ConfigUtil::FIELDS])
+ || !ConfigUtil::isExcludeAll($definition)
+ ) {
+ // expected normalized configs
+ return;
+ }
+
+ $entityClass = $context->getClassName();
+ if (!$this->doctrineHelper->isManageableEntityClass($entityClass)) {
+ // only manageable entities are supported
+ return;
+ }
+
+ if ($this->updateRelations($definition, $entityClass)) {
+ $context->setResult($definition);
+ }
+ }
+
+ /**
+ * @param array $definition
+ * @param string $entityClass
+ *
+ * @return bool
+ */
+ protected function updateRelations(array &$definition, $entityClass)
+ {
+ $hasChanges = false;
+
+ $metadata = $this->doctrineHelper->getEntityMetadataForClass($entityClass);
+ foreach ($definition[ConfigUtil::FIELDS] as $fieldName => &$fieldConfig) {
+ if (!is_array($fieldConfig) || empty($fieldConfig[ConfigUtil::DEFINITION][ConfigUtil::FIELDS])) {
+ continue;
+ }
+
+ $fieldDefinition = $fieldConfig[ConfigUtil::DEFINITION];
+ if (ConfigUtil::isExclude($fieldDefinition)) {
+ continue;
+ }
+
+ $propertyPath = ConfigUtil::getPropertyPath($fieldDefinition, $fieldName);
+ if (!$metadata->hasAssociation($propertyPath)) {
+ continue;
+ }
+
+ $mapping = $metadata->getAssociationMapping($propertyPath);
+ $targetMetadata = $this->doctrineHelper->getEntityMetadataForClass($mapping['targetEntity']);
+ if ($this->isResourceForRelatedEntityAccessible($targetMetadata)) {
+ continue;
+ }
+
+ $fieldDefinition[ConfigUtil::EXCLUDE] = true;
+
+ $fieldConfig[ConfigUtil::DEFINITION] = $fieldDefinition;
+
+ $hasChanges = true;
+ }
+
+ return $hasChanges;
+ }
+
+ /**
+ * @param ClassMetadata $targetMetadata
+ *
+ * @return bool
+ */
+ protected function isResourceForRelatedEntityAccessible(ClassMetadata $targetMetadata)
+ {
+ if ($this->isResourceAccessible($targetMetadata->name)) {
+ return true;
+ }
+ if ($targetMetadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
+ // check that at least one inhetited entity has API resource
+ foreach ($targetMetadata->subClasses as $inheritedEntityClass) {
+ if ($this->isResourceAccessible($inheritedEntityClass)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string $entityClass
+ *
+ * @return bool
+ */
+ protected function isResourceAccessible($entityClass)
+ {
+ $result = false;
+
+ $uri = $this->getEntityResourceUri($entityClass);
+ if ($uri) {
+ $matchingContext = $this->router->getContext();
+
+ $prevMethod = $matchingContext->getMethod();
+ $matchingContext->setMethod('GET');
+ try {
+ $match = $this->router->match($uri);
+ $matchingContext->setMethod($prevMethod);
+ if ($this->isAcceptableMatch($match)) {
+ $result = true;
+ }
+ } catch (RoutingException $e) {
+ // any exception from UrlMatcher means that the requested resource is not accessible
+ $matchingContext->setMethod($prevMethod);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $entityClass
+ *
+ * @return string|null
+ */
+ protected function getEntityResourceUri($entityClass)
+ {
+ $uri = null;
+ if ($this->entityAliasResolver->hasAlias($entityClass)) {
+ try {
+ $uri = $this->router->generate(
+ 'oro_rest_api_cget',
+ ['entity' => $this->entityAliasResolver->getPluralAlias($entityClass)]
+ );
+ } catch (RoutingException $e) {
+ // ignore any exceptions
+ }
+ }
+
+ if ($uri) {
+ $baseUrl = $this->router->getContext()->getBaseUrl();
+ if ($baseUrl) {
+ $uri = substr($uri, strlen($baseUrl));
+ }
+ }
+
+ return $uri;
+ }
+
+ /**
+ * @param array $match
+ *
+ * @return bool
+ */
+ protected function isAcceptableMatch(array $match)
+ {
+ // @todo: need to investigate how to avoid "'_webservice_definition' !== $match['_route']" check (BAP-8996)
+ return
+ isset($match['_route'])
+ && '_webservice_definition' !== $match['_route'];
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/FilterFieldsByExtra.php b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/FilterFieldsByExtra.php
index 33170fa4284..e073bad81d0 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/FilterFieldsByExtra.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/FilterFieldsByExtra.php
@@ -70,16 +70,19 @@ protected function filterFields($entityClass, &$fieldsDefinition, &$filterFields
{
$entityAlias = $this->entityClassTransformer->transform($entityClass);
$rootEntityIdentifiers = $this->doctrineHelper->getEntityIdentifierFieldNamesForClass($entityClass);
- if (in_array($entityAlias, array_keys($filterFieldsConfig))) {
+ if (array_key_exists($entityAlias, $filterFieldsConfig)) {
$allowedFields = $filterFieldsConfig[$entityAlias];
foreach ($fieldsDefinition as $name => &$def) {
if (isset($def[ConfigUtil::DEFINITION][ConfigUtil::FIELDS])
- && in_array($name, array_keys($filterFieldsConfig))
+ && array_key_exists($name, $filterFieldsConfig)
) {
continue;
}
- if (!in_array($name, $allowedFields) && !in_array($name, $rootEntityIdentifiers)) {
+ if (!in_array($name, $allowedFields, true)
+ && !in_array($name, $rootEntityIdentifiers, true)
+ && !ConfigUtil::isMetadataProperty($name)
+ ) {
$def[ConfigUtil::DEFINITION][ConfigUtil::EXCLUDE] = true;
}
}
@@ -113,11 +116,11 @@ protected function filterAssociations($entityClass, &$fieldsDefinition, &$filter
$associationAllowedFields = $filterFieldsConfig[$fieldName];
foreach ($fieldsDefinition[$fieldName][ConfigUtil::DEFINITION][ConfigUtil::FIELDS] as $name => &$def) {
- if (in_array($name, $identifierFieldNames)) {
+ if (in_array($name, $identifierFieldNames, true)) {
continue;
}
- if (!in_array($name, $associationAllowedFields)) {
+ if (!in_array($name, $associationAllowedFields, true) && !ConfigUtil::isMetadataProperty($name)) {
if (is_array($def)) {
$def = array_merge($def, [ConfigUtil::EXCLUDE => true]);
} else {
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/SetDescriptionForFilters.php b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/SetDescriptionForFilters.php
index 5683ff5c9ae..43cba7ec8da 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/SetDescriptionForFilters.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/SetDescriptionForFilters.php
@@ -71,7 +71,7 @@ public function process(ContextInterface $context)
protected function findFieldConfig($entityClass, $filterKey, $filterConfig)
{
$path = ConfigUtil::explodePropertyPath(
- isset($filterConfig[ConfigUtil::PROPERTY_PATH]) ? $filterConfig[ConfigUtil::PROPERTY_PATH] : $filterKey
+ ConfigUtil::getPropertyPath($filterConfig, $filterKey)
);
if (count($path) === 1) {
return $this->getFieldConfig($entityClass, reset($path));
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/SetTypeForTableInheritanceRelations.php b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/SetTypeForTableInheritanceRelations.php
new file mode 100644
index 00000000000..e157f6003f1
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/Config/Shared/SetTypeForTableInheritanceRelations.php
@@ -0,0 +1,101 @@
+doctrineHelper = $doctrineHelper;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContextInterface $context)
+ {
+ /** @var ConfigContext $context */
+
+ $definition = $context->getResult();
+ if (!isset($definition[ConfigUtil::FIELDS])
+ || !is_array($definition[ConfigUtil::FIELDS])
+ || !ConfigUtil::isExcludeAll($definition)
+ ) {
+ // expected normalized configs
+ return;
+ }
+
+ $entityClass = $context->getClassName();
+ if (!$this->doctrineHelper->isManageableEntityClass($entityClass)) {
+ // only manageable entities are supported
+ return;
+ }
+
+ if ($this->updateRelations($definition, $entityClass)) {
+ $context->setResult($definition);
+ }
+ }
+
+ /**
+ * @param array $definition
+ * @param string $entityClass
+ *
+ * @return bool
+ */
+ protected function updateRelations(array &$definition, $entityClass)
+ {
+ $hasChanges = false;
+
+ $metadata = $this->doctrineHelper->getEntityMetadataForClass($entityClass);
+ foreach ($definition[ConfigUtil::FIELDS] as $fieldName => &$fieldConfig) {
+ if (!is_array($fieldConfig) || empty($fieldConfig[ConfigUtil::DEFINITION][ConfigUtil::FIELDS])) {
+ continue;
+ }
+
+ $fieldDefinition = $fieldConfig[ConfigUtil::DEFINITION];
+
+ $propertyPath = ConfigUtil::getPropertyPath($fieldDefinition, $fieldName);
+ if (!$metadata->hasAssociation($propertyPath)) {
+ continue;
+ }
+
+ $mapping = $metadata->getAssociationMapping($propertyPath);
+ $targetMetadata = $this->doctrineHelper->getEntityMetadataForClass($mapping['targetEntity']);
+ if ($targetMetadata->inheritanceType === ClassMetadata::INHERITANCE_TYPE_NONE) {
+ continue;
+ }
+
+ if (!is_array($fieldDefinition[ConfigUtil::FIELDS])) {
+ $fieldDefinition[ConfigUtil::FIELDS] = [
+ $fieldDefinition[ConfigUtil::FIELDS] => null
+ ];
+ }
+
+ $fieldDefinition[ConfigUtil::FIELDS][ConfigUtil::CLASS_NAME] = null;
+
+ $fieldConfig[ConfigUtil::DEFINITION] = $fieldDefinition;
+
+ $hasChanges = true;
+ }
+
+ return $hasChanges;
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Get/JsonApi/BuildJsonApiDocument.php b/src/Oro/Bundle/ApiBundle/Processor/Get/JsonApi/BuildJsonApiDocument.php
index 896a96dc4e0..90894fe3599 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/Get/JsonApi/BuildJsonApiDocument.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/Get/JsonApi/BuildJsonApiDocument.php
@@ -37,7 +37,7 @@ public function process(ContextInterface $context)
try {
if ($context->hasErrors()) {
- $documentBuilder->setErrors($context->getErrors());
+ $documentBuilder->setErrorCollection($context->getErrors());
// remove errors from the Context to avoid processing them by other processors
$context->resetErrors();
} elseif ($context->hasResult()) {
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/BuildJsonApiDocument.php b/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/BuildJsonApiDocument.php
index 50704060d77..2020e3ba68f 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/BuildJsonApiDocument.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/BuildJsonApiDocument.php
@@ -37,7 +37,7 @@ public function process(ContextInterface $context)
try {
if ($context->hasErrors()) {
- $documentBuilder->setErrors($context->getErrors());
+ $documentBuilder->setErrorCollection($context->getErrors());
// remove errors from the Context to avoid processing them by other processors
$context->resetErrors();
} elseif ($context->hasResult()) {
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/CorrectSortValue.php b/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/CorrectSortValue.php
new file mode 100644
index 00000000000..006fb948879
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/CorrectSortValue.php
@@ -0,0 +1,99 @@
+doctrineHelper = $doctrineHelper;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContextInterface $context)
+ {
+ /** @var GetListContext $context */
+
+ if ($context->hasQuery()) {
+ // a query is already built
+ return;
+ }
+
+ $entityClass = $context->getClassName();
+ if (!$this->doctrineHelper->isManageableEntityClass($entityClass)) {
+ // only manageable entities are supported
+ return;
+ }
+
+ $filterValues = $context->getFilterValues();
+ if ($filterValues->has(self::SORT_FILTER_KEY)) {
+ $filterValue = $filterValues->get(self::SORT_FILTER_KEY);
+ $filterValue->setValue(
+ $this->normalizeValue($filterValue->getValue(), $entityClass)
+ );
+ }
+ }
+
+ /**
+ * @param mixed $value
+ * @param string $entityClass
+ *
+ * @return mixed
+ */
+ protected function normalizeValue($value, $entityClass)
+ {
+ if (empty($value) || !is_string($value)) {
+ return $value;
+ }
+
+ $result = [];
+ $items = explode(self::ARRAY_DELIMITER, $value);
+ foreach ($items as $item) {
+ switch (trim($item)) {
+ case 'id':
+ $this->addEntityIdentifierFieldNames($result, $entityClass);
+ break;
+ case '-id':
+ $this->addEntityIdentifierFieldNames($result, $entityClass, true);
+ break;
+ default:
+ $result[] = $item;
+ break;
+ }
+ }
+
+ return implode(self::ARRAY_DELIMITER, $result);
+ }
+
+ /**
+ * @param string[] $result
+ * @param string $entityClass
+ * @param bool $desc
+ */
+ protected function addEntityIdentifierFieldNames(array &$result, $entityClass, $desc = false)
+ {
+ $idFieldNames = $this->doctrineHelper->getEntityIdentifierFieldNamesForClass($entityClass);
+ foreach ($idFieldNames as $fieldName) {
+ $result[] = $desc ? '-' . $fieldName : $fieldName;
+ }
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/NormalizeFilterKeys.php b/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/NormalizeFilterKeys.php
index 5ac076599b6..1406339157b 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/NormalizeFilterKeys.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/NormalizeFilterKeys.php
@@ -2,14 +2,35 @@
namespace Oro\Bundle\ApiBundle\Processor\GetList\JsonApi;
+use Symfony\Component\Translation\TranslatorInterface;
+
use Oro\Component\ChainProcessor\ContextInterface;
use Oro\Component\ChainProcessor\ProcessorInterface;
+use Oro\Bundle\ApiBundle\Filter\ComparisonFilter;
+use Oro\Bundle\ApiBundle\Model\Label;
use Oro\Bundle\ApiBundle\Processor\GetList\GetListContext;
+use Oro\Bundle\ApiBundle\Util\DoctrineHelper;
class NormalizeFilterKeys implements ProcessorInterface
{
const FILTER_KEY_TEMPLATE = 'filter[%s]';
+ /** @var DoctrineHelper */
+ protected $doctrineHelper;
+
+ /** @var TranslatorInterface */
+ protected $translator;
+
+ /**
+ * @param DoctrineHelper $doctrineHelper
+ * @param TranslatorInterface $translator
+ */
+ public function __construct(DoctrineHelper $doctrineHelper, TranslatorInterface $translator)
+ {
+ $this->doctrineHelper = $doctrineHelper;
+ $this->translator = $translator;
+ }
+
/**
* {@inheritdoc}
*/
@@ -18,14 +39,45 @@ public function process(ContextInterface $context)
/** @var GetListContext $context */
$filterCollection = $context->getFilters();
+ $idFieldName = $this->getIdFieldName($context->getClassName());
$filters = $filterCollection->all();
foreach ($filters as $filterKey => $filter) {
$filterCollection->remove($filterKey);
+ if ($filter instanceof ComparisonFilter && $filter->getField() === $idFieldName) {
+ $filterKey = 'id';
+ $filter->setDescription($this->getIdFieldDescription());
+ }
$filterCollection->add(
sprintf(self::FILTER_KEY_TEMPLATE, $filterKey),
$filter
);
}
}
+
+ /**
+ * @param string $entityClass
+ *
+ * @return string|null
+ */
+ protected function getIdFieldName($entityClass)
+ {
+ if (!$this->doctrineHelper->isManageableEntityClass($entityClass)) {
+ return null;
+ }
+
+ $idFieldNames = $this->doctrineHelper->getEntityIdentifierFieldNamesForClass($entityClass);
+
+ return reset($idFieldNames);
+ }
+
+ /**
+ * @return string
+ */
+ protected function getIdFieldDescription()
+ {
+ $label = new Label('oro.entity.identifier_field');
+
+ return $label->trans($this->translator);
+ }
}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/SetDefaultPaging.php b/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/SetDefaultPaging.php
index 2532717ecb8..aeae53c264d 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/SetDefaultPaging.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/SetDefaultPaging.php
@@ -23,11 +23,6 @@ public function process(ContextInterface $context)
{
/** @var GetListContext $context */
- if ($context->hasQuery()) {
- // a query is already built
- return;
- }
-
if (!in_array(RequestType::REST, $context->getRequestType(), true)) {
parent::process($context);
} else {
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/SetDefaultSorting.php b/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/SetDefaultSorting.php
new file mode 100644
index 00000000000..70decc394cb
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetList/JsonApi/SetDefaultSorting.php
@@ -0,0 +1,77 @@
+getRequestType(), true)) {
+ parent::process($context);
+ } else {
+ // reuse REST API sorting filter
+ $filters = $context->getFilters();
+ $sortFilterKey = $this->getSortFilterKey();
+ if ($sortFilterKey !== RestSetDefaultSorting::SORT_FILTER_KEY
+ && $filters->has(RestSetDefaultSorting::SORT_FILTER_KEY)
+ ) {
+ $filter = $filters->get(RestSetDefaultSorting::SORT_FILTER_KEY);
+ $filters->remove(RestSetDefaultSorting::SORT_FILTER_KEY);
+ $filters->add($sortFilterKey, $filter);
+ }
+ if ($filters->has($sortFilterKey)) {
+ $filter = $filters->get($sortFilterKey);
+ if ($filter instanceof SortFilter) {
+ $entityClass = $context->getClassName();
+ $filter->setDefaultValue(
+ function () use ($entityClass) {
+ return $this->getDefaultValue($entityClass);
+ }
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getSortFilterKey()
+ {
+ return self::SORT_FILTER_KEY;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultValue($entityClass)
+ {
+ $result = [];
+
+ $idFieldNames = $this->doctrineHelper->getEntityIdentifierFieldNamesForClass($entityClass);
+ foreach ($idFieldNames as $fieldName) {
+ $result[$fieldName] = Criteria::ASC;
+ }
+
+ return $result;
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetList/RegisterFilters.php b/src/Oro/Bundle/ApiBundle/Processor/GetList/RegisterFilters.php
index 87feae8511c..9154536d19f 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/GetList/RegisterFilters.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetList/RegisterFilters.php
@@ -52,7 +52,7 @@ public function process(ContextInterface $context)
continue;
}
$filter = $this->createFilter(
- !empty($fieldConfig[ConfigUtil::PROPERTY_PATH]) ? $fieldConfig[ConfigUtil::PROPERTY_PATH] : $field,
+ ConfigUtil::getPropertyPath($fieldConfig, $field),
$fieldConfig
);
if (null !== $filter) {
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetList/Rest/SetDefaultSorting.php b/src/Oro/Bundle/ApiBundle/Processor/GetList/Rest/SetDefaultSorting.php
index 35206865fe9..c482269b883 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/GetList/Rest/SetDefaultSorting.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetList/Rest/SetDefaultSorting.php
@@ -2,74 +2,20 @@
namespace Oro\Bundle\ApiBundle\Processor\GetList\Rest;
-use Doctrine\Common\Collections\Criteria;
-
-use Oro\Component\ChainProcessor\ContextInterface;
-use Oro\Component\ChainProcessor\ProcessorInterface;
-use Oro\Bundle\ApiBundle\Filter\SortFilter;
-use Oro\Bundle\ApiBundle\Processor\GetList\GetListContext;
-use Oro\Bundle\ApiBundle\Request\DataType;
-use Oro\Bundle\ApiBundle\Request\RestRequest;
-use Oro\Bundle\ApiBundle\Util\DoctrineHelper;
+use Oro\Bundle\ApiBundle\Processor\GetList\SetDefaultSorting as BaseSetDefaultSorting;
/**
* Sets default sorting for REST API requests: sort = identifier field ASC.
*/
-class SetDefaultSorting implements ProcessorInterface
+class SetDefaultSorting extends BaseSetDefaultSorting
{
const SORT_FILTER_KEY = 'sort';
- /** @var DoctrineHelper */
- protected $doctrineHelper;
-
- /**
- * @param DoctrineHelper $doctrineHelper
- */
- public function __construct(DoctrineHelper $doctrineHelper)
- {
- $this->doctrineHelper = $doctrineHelper;
- }
-
/**
* {@inheritdoc}
*/
- public function process(ContextInterface $context)
+ protected function getSortFilterKey()
{
- /** @var GetListContext $context */
-
- if ($context->hasQuery()) {
- // a query is already built
- return;
- }
-
- $entityClass = $context->getClassName();
- if (!$this->doctrineHelper->isManageableEntityClass($entityClass)) {
- // only manageable entities are supported
- return;
- }
-
- $filters = $context->getFilters();
- if (!$filters->has(self::SORT_FILTER_KEY)) {
- $filters->add(
- self::SORT_FILTER_KEY,
- new SortFilter(
- DataType::ORDER_BY,
- 'Result sorting. One or several fields separated by comma, for example \'field1,-field2\'.',
- function () use ($entityClass) {
- return $this->doctrineHelper->getOrderByIdentifier($entityClass);
- },
- function ($value) {
- $result = [];
- if (null !== $value) {
- foreach ($value as $field => $order) {
- $result[] = (Criteria::DESC === $order ? '-' : '') . $field;
- }
- }
-
- return implode(RestRequest::ARRAY_DELIMITER, $result);
- }
- )
- );
- }
+ return self::SORT_FILTER_KEY;
}
}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetList/SetDefaultSorting.php b/src/Oro/Bundle/ApiBundle/Processor/GetList/SetDefaultSorting.php
new file mode 100644
index 00000000000..c49b6b81ef0
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetList/SetDefaultSorting.php
@@ -0,0 +1,116 @@
+doctrineHelper = $doctrineHelper;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContextInterface $context)
+ {
+ /** @var GetListContext $context */
+
+ if ($context->hasQuery()) {
+ // a query is already built
+ return;
+ }
+
+ $entityClass = $context->getClassName();
+ if (!$this->doctrineHelper->isManageableEntityClass($entityClass)) {
+ // only manageable entities are supported
+ return;
+ }
+
+ $this->addSortFilter($context->getFilters(), $entityClass);
+ }
+
+ /**
+ * @param FilterCollection $filters
+ * @param string $entityClass
+ */
+ protected function addSortFilter(FilterCollection $filters, $entityClass)
+ {
+ $sortFilterKey = $this->getSortFilterKey();
+ if (!$filters->has($sortFilterKey)) {
+ $filters->add(
+ $sortFilterKey,
+ new SortFilter(
+ DataType::ORDER_BY,
+ $this->getSortFilterDescription(),
+ function () use ($entityClass) {
+ return $this->getDefaultValue($entityClass);
+ },
+ function ($value) {
+ return $this->convertDefaultValueToString($value);
+ }
+ )
+ );
+ }
+ }
+
+ /**
+ * @return string
+ */
+ abstract protected function getSortFilterKey();
+
+ /**
+ * @return string
+ */
+ protected function getSortFilterDescription()
+ {
+ return 'Result sorting. One or several fields separated by comma, for example \'field1,-field2\'.';
+ }
+
+ /**
+ * @param string $entityClass
+ *
+ * @return string
+ */
+ protected function getDefaultValue($entityClass)
+ {
+ return $this->doctrineHelper->getOrderByIdentifier($entityClass);
+ }
+
+ /**
+ * @param array|null $value
+ *
+ * @return string
+ */
+ protected function convertDefaultValueToString($value)
+ {
+ $result = [];
+ if (null !== $value) {
+ foreach ($value as $field => $order) {
+ $result[] = (Criteria::DESC === $order ? '-' : '') . $field;
+ }
+ }
+
+ return implode(RestRequest::ARRAY_DELIMITER, $result);
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadEntityMetadata.php b/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadEntityMetadata.php
index 078e5e81df1..bb07f44e80b 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadEntityMetadata.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadEntityMetadata.php
@@ -2,6 +2,7 @@
namespace Oro\Bundle\ApiBundle\Processor\GetMetadata;
+use Oro\Bundle\ApiBundle\Util\ConfigUtil;
use Oro\Component\ChainProcessor\ContextInterface;
use Oro\Component\ChainProcessor\ProcessorInterface;
use Oro\Bundle\ApiBundle\Metadata\EntityMetadataFactory;
@@ -46,11 +47,18 @@ public function process(ContextInterface $context)
return;
}
+ // filter excluded fields on this stage though there is another processor doing the same
+ // it is done due to performance reasons
+ $allowedFields = $this->getAllowedFields($context->getConfig());
+
$classMetadata = $this->doctrineHelper->getEntityMetadataForClass($entityClass);
$entityMetadata = $this->entityMetadataFactory->createEntityMetadata($classMetadata);
$fields = $classMetadata->getFieldNames();
foreach ($fields as $fieldName) {
+ if (!isset($allowedFields[$fieldName])) {
+ continue;
+ }
$entityMetadata->addField(
$this->entityMetadataFactory->createFieldMetadata($classMetadata, $fieldName)
);
@@ -58,6 +66,9 @@ public function process(ContextInterface $context)
$associations = $classMetadata->getAssociationNames();
foreach ($associations as $associationName) {
+ if (!isset($allowedFields[$associationName])) {
+ continue;
+ }
$entityMetadata->addAssociation(
$this->entityMetadataFactory->createAssociationMetadata($classMetadata, $associationName)
);
@@ -65,4 +76,28 @@ public function process(ContextInterface $context)
$context->setResult($entityMetadata);
}
+
+ /**
+ * @param array|null $config
+ *
+ * @return array
+ */
+ protected function getAllowedFields($config)
+ {
+ $fields = [];
+ if (!empty($config[ConfigUtil::FIELDS])) {
+ if (is_array($config[ConfigUtil::FIELDS])) {
+ foreach ($config[ConfigUtil::FIELDS] as $fieldName => $fieldConfig) {
+ if (!is_array($fieldConfig) || !ConfigUtil::isExclude($fieldConfig)) {
+ $propertyPath = ConfigUtil::getPropertyPath($fieldConfig, $fieldName);
+ $fields[$propertyPath] = $fieldName;
+ }
+ }
+ } elseif (is_string($config[ConfigUtil::FIELDS])) {
+ $fields[$config[ConfigUtil::FIELDS]] = $config[ConfigUtil::FIELDS];
+ }
+ }
+
+ return $fields;
+ }
}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadFromConfigBag.php b/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadFromConfigBag.php
index d93ad20216d..cf40bf2d10a 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadFromConfigBag.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadFromConfigBag.php
@@ -35,7 +35,7 @@ public function process(ContextInterface $context)
}
$config = $this->configBag->getMetadata($context->getClassName(), $context->getVersion());
- if (null !== $config) {
+ if (!empty($config)) {
$context->setResult($config);
}
}
diff --git a/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadRelatedEntityMetadata.php b/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadRelatedEntityMetadata.php
index 23beb1932bf..8d0efa50232 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadRelatedEntityMetadata.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/GetMetadata/LoadRelatedEntityMetadata.php
@@ -65,10 +65,6 @@ protected function loadMetadataForRelatedEntities(
// a configuration of an association fields does not exist
continue;
}
- if (is_string($config[ConfigUtil::FIELDS][$associationName][ConfigUtil::FIELDS])) {
- // a single field association
- continue;
- }
$relatedEntityMetadata = $this->metadataProvider->getMetadata(
$association->getTargetClassName(),
diff --git a/src/Oro/Bundle/ApiBundle/Processor/Shared/JsonApi/SetFieldsFilter.php b/src/Oro/Bundle/ApiBundle/Processor/Shared/JsonApi/SetFieldsFilter.php
index 81a54b5f852..04998299341 100644
--- a/src/Oro/Bundle/ApiBundle/Processor/Shared/JsonApi/SetFieldsFilter.php
+++ b/src/Oro/Bundle/ApiBundle/Processor/Shared/JsonApi/SetFieldsFilter.php
@@ -2,14 +2,13 @@
namespace Oro\Bundle\ApiBundle\Processor\Shared\JsonApi;
-use Oro\Bundle\ApiBundle\Request\EntityClassTransformerInterface;
use Oro\Component\ChainProcessor\ContextInterface;
use Oro\Component\ChainProcessor\ProcessorInterface;
use Oro\Bundle\ApiBundle\Filter\FieldsFilter;
use Oro\Bundle\ApiBundle\Processor\Context;
use Oro\Bundle\ApiBundle\Request\DataType;
+use Oro\Bundle\ApiBundle\Request\EntityClassTransformerInterface;
use Oro\Bundle\ApiBundle\Util\DoctrineHelper;
-use Oro\Bundle\EntityBundle\ORM\EntityAliasResolver;
class SetFieldsFilter implements ProcessorInterface
{
@@ -66,7 +65,7 @@ public function process(ContextInterface $context)
$fieldFilter
);
- $associations = $this->doctrineHelper->getEntityMetadata($entityClass)->getAssociationMappings();
+ $associations = $context->getMetadata()->getAssociations();
if (!$associations) {
// no associations - no sense to add associations fields filters
return;
diff --git a/src/Oro/Bundle/ApiBundle/Provider/ConfigBag.php b/src/Oro/Bundle/ApiBundle/Provider/ConfigBag.php
index ff557303f75..63cef5fcfee 100644
--- a/src/Oro/Bundle/ApiBundle/Provider/ConfigBag.php
+++ b/src/Oro/Bundle/ApiBundle/Provider/ConfigBag.php
@@ -30,6 +30,18 @@ public function getMetadata($className, $version)
return $this->findConfig('metadata', $className, $version);
}
+ /**
+ * Gets a configuration for all entities for the given version
+ *
+ * @param string $version The version of a config
+ *
+ * @return array [entity class => config, ...]
+ */
+ public function getConfigs($version)
+ {
+ return $this->findConfigs('entities', $version);
+ }
+
/**
* Gets a configuration for the given version of a class
*
@@ -56,6 +68,24 @@ public function getRelationConfig($className, $version)
return $this->findConfig('relations', $className, $version);
}
+ /**
+ * @param string $section
+ * @param string $version
+ *
+ * @return array
+ */
+ protected function findConfigs($section, $version)
+ {
+ if (!isset($this->config[$section])) {
+ return [];
+ }
+ $result = $this->config[$section];
+
+ // @todo: API version is not supported for now. Implement filtering by the version here
+
+ return $result;
+ }
+
/**
* @param string $section
* @param string $className
diff --git a/src/Oro/Bundle/ApiBundle/Provider/MetadataProvider.php b/src/Oro/Bundle/ApiBundle/Provider/MetadataProvider.php
index 150f4d7268a..a6497b34bab 100644
--- a/src/Oro/Bundle/ApiBundle/Provider/MetadataProvider.php
+++ b/src/Oro/Bundle/ApiBundle/Provider/MetadataProvider.php
@@ -12,9 +12,6 @@ class MetadataProvider
/** @var MetadataProcessor */
protected $processor;
- /** @var array */
- protected $cache = [];
-
/**
* @param MetadataProcessor $processor
*/
@@ -40,11 +37,6 @@ public function getMetadata($className, $version, array $requestType, array $ext
throw new \InvalidArgumentException('$className must not be empty.');
}
- $cacheKey = implode('', $requestType) . $version . $className;
- if (array_key_exists($cacheKey, $this->cache)) {
- return $this->cache[$cacheKey];
- }
-
/** @var MetadataContext $context */
$context = $this->processor->createContext();
$context->setVersion($version);
@@ -62,8 +54,6 @@ public function getMetadata($className, $version, array $requestType, array $ext
$result = $context->getResult();
}
- $this->cache[$cacheKey] = $result;
-
return $result;
}
}
diff --git a/src/Oro/Bundle/ApiBundle/Provider/PublicResourcesLoader.php b/src/Oro/Bundle/ApiBundle/Provider/PublicResourcesLoader.php
new file mode 100644
index 00000000000..d6de08e65e0
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Provider/PublicResourcesLoader.php
@@ -0,0 +1,41 @@
+processor = $processor;
+ }
+
+ /**
+ * Gets all public resources available for the requested API version.
+ *
+ * @param string $version The version of API
+ * @param string[] $requestType The type of API request, for example "rest", "soap", "odata", etc.
+ *
+ * @return PublicResource[]
+ */
+ public function getResources($version, array $requestType)
+ {
+ /** @var CollectPublicResourcesContext $context */
+ $context = $this->processor->createContext();
+ $context->setVersion($version);
+ $context->setRequestType($requestType);
+
+ $this->processor->process($context);
+
+ return array_values($context->getResult()->toArray());
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Request/EntityClassTransformerInterface.php b/src/Oro/Bundle/ApiBundle/Request/EntityClassTransformerInterface.php
index ac21377d869..b68859db7ea 100644
--- a/src/Oro/Bundle/ApiBundle/Request/EntityClassTransformerInterface.php
+++ b/src/Oro/Bundle/ApiBundle/Request/EntityClassTransformerInterface.php
@@ -7,18 +7,20 @@ interface EntityClassTransformerInterface
/**
* Transforms the FQCN of an entity to the type of an entity.
*
- * @param string $entityClass The FQCN of an entity
+ * @param string $entityClass The FQCN of an entity
+ * @param bool $throwException Whether to throw exception in case the the transformation failed
*
- * @return string The type of an entity
+ * @return string|null The type of an entity
*/
- public function transform($entityClass);
+ public function transform($entityClass, $throwException = true);
/**
* Transforms an entity type to the FQCN of an entity.
*
- * @param string $entityType The type of an entity
+ * @param string $entityType The type of an entity
+ * @param bool $throwException Whether to throw exception in case the the transformation failed
*
- * @return mixed The FQCN of an entity
+ * @return mixed|null The FQCN of an entity
*/
- public function reverseTransform($entityType);
+ public function reverseTransform($entityType, $throwException = true);
}
diff --git a/src/Oro/Bundle/ApiBundle/Request/JsonApi/EntityClassTransformer.php b/src/Oro/Bundle/ApiBundle/Request/JsonApi/EntityClassTransformer.php
index aa8e808f92b..fb9544ee9ba 100644
--- a/src/Oro/Bundle/ApiBundle/Request/JsonApi/EntityClassTransformer.php
+++ b/src/Oro/Bundle/ApiBundle/Request/JsonApi/EntityClassTransformer.php
@@ -3,6 +3,7 @@
namespace Oro\Bundle\ApiBundle\Request\JsonApi;
use Oro\Bundle\ApiBundle\Request\EntityClassTransformerInterface;
+use Oro\Bundle\EntityBundle\Exception\EntityAliasNotFoundException;
use Oro\Bundle\EntityBundle\ORM\EntityAliasResolver;
class EntityClassTransformer implements EntityClassTransformerInterface
@@ -21,16 +22,32 @@ public function __construct(EntityAliasResolver $entityAliasResolver)
/**
* {@inheritdoc}
*/
- public function transform($entityClass)
+ public function transform($entityClass, $throwException = true)
{
- return $this->entityAliasResolver->getPluralAlias($entityClass);
+ try {
+ return $this->entityAliasResolver->getPluralAlias($entityClass);
+ } catch (EntityAliasNotFoundException $e) {
+ if ($throwException) {
+ throw $e;
+ }
+ }
+
+ return null;
}
/**
* {@inheritdoc}
*/
- public function reverseTransform($entityType)
+ public function reverseTransform($entityType, $throwException = true)
{
- return $this->entityAliasResolver->getClassByPluralAlias($entityType);
+ try {
+ return $this->entityAliasResolver->getClassByPluralAlias($entityType);
+ } catch (EntityAliasNotFoundException $e) {
+ if ($throwException) {
+ throw $e;
+ }
+ }
+
+ return null;
}
}
diff --git a/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/ArrayAccessor.php b/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/ArrayAccessor.php
new file mode 100644
index 00000000000..55d2fcb41ee
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/ArrayAccessor.php
@@ -0,0 +1,54 @@
+hasProperty($object, $propertyName)) {
+ throw new \OutOfBoundsException(sprintf('The "%s" property does not exist.', $propertyName));
+ }
+
+ return $object[$propertyName];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasProperty($object, $propertyName)
+ {
+ // ignore "metadata" items
+ if (ConfigUtil::CLASS_NAME === $propertyName) {
+ return false;
+ }
+
+ return array_key_exists($propertyName, $object);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toArray($object)
+ {
+ // remove "metadata" items
+ unset($object[ConfigUtil::CLASS_NAME]);
+
+ return $object;
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/EntityIdAccessor.php b/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/EntityIdAccessor.php
new file mode 100644
index 00000000000..69b6a2fb02b
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/EntityIdAccessor.php
@@ -0,0 +1,91 @@
+objectAccessor = $objectAccessor;
+ $this->entityIdTransformer = $entityIdTransformer;
+ }
+
+ /**
+ * Returns a string representation of the identifier of a given entity.
+ *
+ * @param mixed $entity
+ * @param EntityMetadata $metadata
+ *
+ * @return string
+ */
+ public function getEntityId($entity, EntityMetadata $metadata)
+ {
+ $result = null;
+
+ $idFieldNames = $metadata->getIdentifierFieldNames();
+ $idFieldNamesCount = count($idFieldNames);
+ if ($idFieldNamesCount === 1) {
+ $fieldName = reset($idFieldNames);
+ if (!$this->objectAccessor->hasProperty($entity, $fieldName)) {
+ throw new \RuntimeException(
+ sprintf(
+ 'An object of the type "%s" does not have the identifier property "%s".',
+ $metadata->getClassName(),
+ $fieldName
+ )
+ );
+ }
+ $result = $this->entityIdTransformer->transform(
+ $this->objectAccessor->getValue($entity, $fieldName)
+ );
+ } elseif ($idFieldNamesCount > 1) {
+ $id = [];
+ foreach ($idFieldNames as $fieldName) {
+ if (!$this->objectAccessor->hasProperty($entity, $fieldName)) {
+ throw new \RuntimeException(
+ sprintf(
+ 'An object of the type "%s" does not have the identifier property "%s".',
+ $metadata->getClassName(),
+ $fieldName
+ )
+ );
+ }
+ $id[$fieldName] = $this->objectAccessor->getValue($entity, $fieldName);
+ }
+ $result = $this->entityIdTransformer->transform($id);
+ } else {
+ throw new \RuntimeException(
+ sprintf(
+ 'The "%s" entity does not have an identifier.',
+ $metadata->getClassName()
+ )
+ );
+ }
+
+ if (empty($result)) {
+ throw new \RuntimeException(
+ sprintf(
+ 'The identifier value for "%s" entity must not be empty.',
+ $metadata->getClassName()
+ )
+ );
+ }
+
+ return $result;
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/ObjectAccessor.php b/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/ObjectAccessor.php
new file mode 100644
index 00000000000..4e7fd1804ea
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/ObjectAccessor.php
@@ -0,0 +1,72 @@
+getObjectAccessor($object)->getClassName($object);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValue($object, $propertyName)
+ {
+ return $this->getObjectAccessor($object)->getValue($object, $propertyName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasProperty($object, $propertyName)
+ {
+ return $this->getObjectAccessor($object)->hasProperty($object, $propertyName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toArray($object)
+ {
+ return $this->getObjectAccessor($object)->toArray($object);
+ }
+
+ /**
+ * @param mixed $object
+ *
+ * @return ObjectAccessorInterface
+ */
+ protected function getObjectAccessor($object)
+ {
+ $objectType = is_object($object)
+ ? get_class($object)
+ : gettype($object);
+
+ if (isset($this->accessors[$objectType])) {
+ return $this->accessors[$objectType];
+ }
+
+ // currently only ArrayAccessor was implemented. It is enough for now.
+ // in the future may be ArrayAccessAccessor will be implemented.
+ if (is_array($object)) {
+ $this->accessors[$objectType] = new ArrayAccessor();
+ } else {
+ throw new \RuntimeException(
+ sprintf(
+ 'The object accessor for "%s" type does not exist.',
+ is_object($object) ? get_class($object) : gettype($object)
+ )
+ );
+ }
+
+ return $this->accessors[$objectType];
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/ObjectAccessorInterface.php b/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/ObjectAccessorInterface.php
new file mode 100644
index 00000000000..009c9ede126
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Request/JsonApi/JsonApiDocument/ObjectAccessorInterface.php
@@ -0,0 +1,44 @@
+entityClassTransformer = $entityClassTransformer;
$this->entityIdTransformer = $entityIdTransformer;
+
+ $this->objectAccessor = new ObjectAccessor();
+ $this->entityIdAccessor = new EntityIdAccessor(
+ $this->objectAccessor,
+ $this->entityIdTransformer
+ );
}
/**
@@ -90,12 +105,7 @@ public function setDataCollection($collection, EntityMetadata $metadata = null)
$this->result[self::DATA][] = $this->handleObject($object, $metadata);
}
} else {
- throw new \UnexpectedValueException(
- sprintf(
- 'Expected argument of type "array or \Traversable", "%s" given',
- is_object($collection) ? get_class($collection) : gettype($collection)
- )
- );
+ throw $this->createUnexpectedValueException('array or \Traversable', $collection);
}
return $this;
@@ -124,7 +134,7 @@ public function setErrorObject(Error $error)
*
* @return self
*/
- public function setErrors(array $errors)
+ public function setErrorCollection(array $errors)
{
$this->assertNoData();
@@ -145,7 +155,7 @@ public function setErrors(array $errors)
*
* @return self
*/
- public function addRelatedObject($object, EntityMetadata $metadata = null)
+ protected function addRelatedObject($object, EntityMetadata $metadata = null)
{
$this->result[self::INCLUDED][] = $this->handleObject($object, $metadata);
@@ -159,36 +169,23 @@ public function addRelatedObject($object, EntityMetadata $metadata = null)
* @return array
*/
protected function handleObject($object, EntityMetadata $metadata = null)
- {
- if (is_array($object)) {
- $result = $this->handleArrayObject($object, $metadata);
- } else {
- throw new \UnexpectedValueException(
- sprintf(
- 'Expected argument of type "array", "%s" given',
- is_object($object) ? get_class($object) : gettype($object)
- )
- );
- }
-
- return $result;
- }
-
- /**
- * @param array $object
- * @param EntityMetadata|null $metadata
- *
- * @return array
- */
- protected function handleArrayObject(array $object, EntityMetadata $metadata = null)
{
$result = [];
if (null === $metadata) {
- $result[self::ATTRIBUTES] = $object;
+ $result[self::ATTRIBUTES] = $this->objectAccessor->toArray($object);
} else {
- $result[self::TYPE] = $this->getEntityType($metadata->getClassName());
- $result[self::ID] = $this->getEntityIdFromArrayObject($object, $metadata);
- foreach ($object as $name => $value) {
+ $className = $this->objectAccessor->getClassName($object);
+ $entityType = $className
+ ? $this->getEntityType($className, $metadata->getClassName())
+ : $this->getEntityType($metadata->getClassName());
+
+ $result = $this->getResourceIdObject(
+ $entityType,
+ $this->entityIdAccessor->getEntityId($object, $metadata)
+ );
+
+ $data = $this->objectAccessor->toArray($object);
+ foreach ($data as $name => $value) {
if (in_array($name, $metadata->getIdentifierFieldNames(), true)) {
continue;
}
@@ -196,8 +193,8 @@ protected function handleArrayObject(array $object, EntityMetadata $metadata = n
$associationMetadata = $metadata->getAssociation($name);
$result[self::RELATIONSHIPS][$name][self::DATA] = $associationMetadata->isCollection()
- ? $this->processRelatedArrayCollection($associationMetadata, $value)
- : $this->processRelatedArrayObject($associationMetadata, $value);
+ ? $this->handleRelatedCollection($value, $associationMetadata)
+ : $this->handleRelatedObject($value, $associationMetadata);
} else {
$result[self::ATTRIBUTES][$name] = $value;
}
@@ -208,121 +205,110 @@ protected function handleArrayObject(array $object, EntityMetadata $metadata = n
}
/**
+ * @param mixed $object
* @param AssociationMetadata $associationMetadata
- * @param mixed $value
*
* @return array|null
*/
- protected function processRelatedArrayObject(AssociationMetadata $associationMetadata, $value)
+ protected function handleRelatedObject($object, AssociationMetadata $associationMetadata)
{
- $targetMetadata = $associationMetadata->getTargetMetadata();
- $targetEntityType = $this->getEntityType($associationMetadata->getTargetClassName());
-
- $data = null;
- if (null !== $value) {
- if (null === $targetMetadata) {
- $data = $this->getResourceIdObject($targetEntityType, $value);
- } else {
- $data = $this->getResourceIdObject(
- $targetEntityType,
- $this->getEntityIdFromArrayObject($value, $targetMetadata)
- );
- $this->addRelatedObject($value, $targetMetadata);
- }
+ if (null === $object) {
+ return null;
}
- return $data;
+ return $this->processRelatedObject($object, $associationMetadata);
}
/**
+ * @param mixed $collection
* @param AssociationMetadata $associationMetadata
- * @param mixed $value
*
* @return array
*/
- protected function processRelatedArrayCollection(AssociationMetadata $associationMetadata, $value)
+ protected function handleRelatedCollection($collection, AssociationMetadata $associationMetadata)
{
- $targetMetadata = $associationMetadata->getTargetMetadata();
- $targetEntityType = $this->getEntityType($associationMetadata->getTargetClassName());
+ if (null === $collection) {
+ return [];
+ }
$data = [];
- if (null !== $value) {
- if (null === $targetMetadata) {
- foreach ($value as $val) {
- $data[] = $this->getResourceIdObject($targetEntityType, $val);
- }
- } else {
- foreach ($value as $val) {
- $data[] = $this->getResourceIdObject(
- $targetEntityType,
- $this->getEntityIdFromArrayObject($val, $targetMetadata)
- );
- $this->addRelatedObject($val, $targetMetadata);
- }
- }
+ foreach ($collection as $object) {
+ $data[] = $this->processRelatedObject($object, $associationMetadata);
}
return $data;
}
/**
- * @param array $object
- * @param EntityMetadata $metadata
+ * @param mixed $object
+ * @param AssociationMetadata $associationMetadata
*
- * @return string
+ * @return array The resource identifier
*/
- protected function getEntityIdFromArrayObject(array $object, EntityMetadata $metadata)
+ protected function processRelatedObject($object, AssociationMetadata $associationMetadata)
{
- $result = null;
-
- $idFieldNames = $metadata->getIdentifierFieldNames();
- $idFieldNamesCount = count($idFieldNames);
- if ($idFieldNamesCount === 1) {
- $fieldName = reset($idFieldNames);
- if (!array_key_exists($fieldName, $object)) {
- throw new \RuntimeException(
- sprintf(
- 'An object of the type "%s" does not have the identifier property "%s".',
- $metadata->getClassName(),
- $fieldName
- )
+ $targetMetadata = $associationMetadata->getTargetMetadata();
+
+ $preparedValue = $this->prepareRelatedValue(
+ $object,
+ $associationMetadata->getTargetClassName(),
+ $targetMetadata
+ );
+ if ($preparedValue['idOnly']) {
+ $resourceId = $this->getResourceIdObject(
+ $preparedValue['entityType'],
+ $this->entityIdTransformer->transform($preparedValue['value'])
+ );
+ } else {
+ $resourceId = $this->getResourceIdObject(
+ $preparedValue['entityType'],
+ $this->entityIdAccessor->getEntityId($preparedValue['value'], $targetMetadata)
+ );
+ $this->addRelatedObject($preparedValue['value'], $targetMetadata);
+ }
+
+ return $resourceId;
+ }
+
+ /**
+ * @param mixed $object
+ * @param string $targetClassName
+ * @param EntityMetadata|null $targetMetadata
+ *
+ * @return array
+ */
+ protected function prepareRelatedValue($object, $targetClassName, EntityMetadata $targetMetadata = null)
+ {
+ $idOnly = false;
+ $targetEntityType = null;
+ if (is_array($object) || is_object($object)) {
+ if (null !== $targetMetadata && $targetMetadata->isInheritedType()) {
+ $targetEntityType = $this->getEntityType(
+ $this->objectAccessor->getClassName($object),
+ $targetClassName
);
- }
- $result = $this->entityIdTransformer->transform($object[$fieldName]);
- } elseif ($idFieldNamesCount > 1) {
- $id = [];
- foreach ($idFieldNames as $fieldName) {
- if (!array_key_exists($fieldName, $object)) {
- throw new \RuntimeException(
- sprintf(
- 'An object of the type "%s" does not have the identifier property "%s".',
- $metadata->getClassName(),
- $fieldName
- )
- );
+
+ $data = $this->objectAccessor->toArray($object);
+ if ($this->isIdentity($data, $targetMetadata)) {
+ $idOnly = true;
+
+ $object = count($data) === 1
+ ? reset($data)
+ : $data;
}
- $id[$fieldName] = $object[$fieldName];
}
- $result = $this->entityIdTransformer->transform($id);
} else {
- throw new \RuntimeException(
- sprintf(
- 'The "%s" entity does not have an identifier.',
- $metadata->getClassName()
- )
- );
+ $idOnly = true;
}
-
- if (empty($result)) {
- throw new \RuntimeException(
- sprintf(
- 'The identifier value for "%s" entity must not be empty.',
- $metadata->getClassName()
- )
- );
+ if (!$targetEntityType) {
+ $targetEntityType = $this->getEntityType($targetClassName);
}
- return $result;
+ return [
+ 'value' => $object,
+ 'entityType' => $targetEntityType,
+ 'idOnly' => $idOnly
+ ];
}
/**
@@ -340,13 +326,41 @@ protected function getResourceIdObject($entityType, $entityId)
}
/**
- * @param string $entityClass
+ * @param string $entityClass
+ * @param string|null $fallbackEntityClass
*
* @return string
*/
- protected function getEntityType($entityClass)
+ protected function getEntityType($entityClass, $fallbackEntityClass = null)
{
- return $this->entityClassTransformer->transform($entityClass);
+ if (null === $fallbackEntityClass) {
+ $entityType = $this->entityClassTransformer->transform($entityClass);
+ } else {
+ $entityType = $this->entityClassTransformer->transform($entityClass, false);
+ if (!$entityType) {
+ $entityType = $this->entityClassTransformer->transform($fallbackEntityClass);
+ }
+ }
+
+ return $entityType;
+ }
+
+ /**
+ * Checks whether a given object has only identity property(s)
+ * or any other properties as well.
+ *
+ * @param array $object
+ * @param EntityMetadata $metadata
+ *
+ * @return bool
+ */
+ protected function isIdentity(array $object, EntityMetadata $metadata)
+ {
+ $idFields = $metadata->getIdentifierFieldNames();
+
+ return
+ count($object) === count($idFields)
+ && count(array_diff_key($object, array_flip($idFields))) === 0;
}
/**
@@ -359,6 +373,23 @@ protected function assertNoData()
}
}
+ /**
+ * @param string $expectedType
+ * @param mixed $value
+ *
+ * @return \UnexpectedValueException
+ */
+ protected function createUnexpectedValueException($expectedType, $value)
+ {
+ return new \UnexpectedValueException(
+ sprintf(
+ 'Expected argument of type "%s", "%s" given.',
+ $expectedType,
+ is_object($value) ? get_class($value) : gettype($value)
+ )
+ );
+ }
+
/**
* @param Error $error
*
@@ -368,7 +399,7 @@ protected function handleError(Error $error)
{
$result = [];
if ($error->getStatusCode()) {
- $result['code'] = (string) $error->getStatusCode();
+ $result['code'] = (string)$error->getStatusCode();
}
if ($error->getDetail()) {
$result['detail'] = $error->getDetail();
diff --git a/src/Oro/Bundle/ApiBundle/Request/PublicResource.php b/src/Oro/Bundle/ApiBundle/Request/PublicResource.php
new file mode 100644
index 00000000000..c8b0ecab153
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Request/PublicResource.php
@@ -0,0 +1,35 @@
+entityClass = $entityClass;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEntityClass()
+ {
+ return $this->entityClass;
+ }
+
+ /**
+ * Returns a string representation of this resource.
+ *
+ * @return string A string representation of the Resource
+ */
+ public function __toString()
+ {
+ return $this->entityClass;
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Request/PublicResourceCollection.php b/src/Oro/Bundle/ApiBundle/Request/PublicResourceCollection.php
new file mode 100644
index 00000000000..024619e4578
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Request/PublicResourceCollection.php
@@ -0,0 +1,92 @@
+ true, ...]
+ */
+ protected $keys;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(array $elements = [])
+ {
+ parent::__construct($elements);
+
+ $this->keys = [];
+ foreach ($elements as $element) {
+ $this->keys[(string)$element] = true;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add($value)
+ {
+ $key = (string)$value;
+ if (!isset($this->keys[$key])) {
+ parent::add($value);
+ $this->keys[$key] = true;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($key)
+ {
+ $removedElement = parent::remove($key);
+ if (null !== $removedElement) {
+ unset($this->keys[(string)$removedElement]);
+ }
+
+ return $removedElement;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeElement($element)
+ {
+ $removed = parent::removeElement($element);
+ if ($removed) {
+ unset($this->keys[(string)$element]);
+ }
+
+ return $removed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($key, $value)
+ {
+ $existingElement = $this->get($key);
+ if (null !== $existingElement) {
+ unset($this->keys[(string)$existingElement]);
+ }
+ $resKey = (string)$value;
+ if (isset($this->keys[$resKey])) {
+ $elements = $this->toArray();
+ foreach ($elements as $elementKey => $element) {
+ if ($resKey === (string)$element) {
+ $this->remove($elementKey);
+ break;
+ }
+ }
+ }
+
+ parent::set($key, $value);
+ $this->keys[$resKey] = true;
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Resources/config/oro/app.yml b/src/Oro/Bundle/ApiBundle/Resources/config/oro/app.yml
index 4422c5a796c..595cdf4d18b 100644
--- a/src/Oro/Bundle/ApiBundle/Resources/config/oro/app.yml
+++ b/src/Oro/Bundle/ApiBundle/Resources/config/oro/app.yml
@@ -20,29 +20,33 @@ oro_api:
priority: -10
security_check:
priority: -20
- build_query:
+ normalize_input:
priority: -30
- load_data:
+ build_query:
priority: -40
- normalize_data:
+ load_data:
priority: -50
- finalize:
+ normalize_data:
priority: -60
- normalize_result:
+ finalize:
priority: -70
+ normalize_result:
+ priority: -80
get:
processing_groups:
initialize:
priority: -10
security_check:
priority: -20
- build_query:
+ normalize_input:
priority: -30
- load_data:
+ build_query:
priority: -40
- normalize_data:
+ load_data:
priority: -50
- finalize:
+ normalize_data:
priority: -60
- normalize_result:
+ finalize:
priority: -70
+ normalize_result:
+ priority: -80
diff --git a/src/Oro/Bundle/ApiBundle/Resources/config/processors.collect_public_resources.yml b/src/Oro/Bundle/ApiBundle/Resources/config/processors.collect_public_resources.yml
new file mode 100644
index 00000000000..834d5f17b3f
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Resources/config/processors.collect_public_resources.yml
@@ -0,0 +1,39 @@
+services:
+ oro_api.collect_public_resources.processor:
+ class: Oro\Bundle\ApiBundle\Processor\CollectPublicResourcesProcessor
+ public: false
+ arguments:
+ - @oro_api.processor_bag
+ - collect_public_resources
+
+ #
+ # collect_public_resources
+ #
+
+ oro_api.collect_public_resources.load_dictionaries:
+ class: Oro\Bundle\ApiBundle\Processor\CollectPublicResources\LoadDictionaries
+ arguments:
+ - @oro_entity.dictionary_value_list_provider
+ tags:
+ - { name: oro.api.processor, action: collect_public_resources, priority: -10 }
+
+ oro_api.collect_public_resources.load_custom_entities:
+ class: Oro\Bundle\ApiBundle\Processor\CollectPublicResources\LoadCustomEntities
+ arguments:
+ - @oro_entity_config.config_manager
+ tags:
+ - { name: oro.api.processor, action: collect_public_resources, priority: -10 }
+
+ oro_api.collect_public_resources.load_from_config_bag:
+ class: Oro\Bundle\ApiBundle\Processor\CollectPublicResources\LoadFromConfigBag
+ arguments:
+ - @oro_api.config_bag
+ tags:
+ - { name: oro.api.processor, action: collect_public_resources, priority: -50 }
+
+ oro_api.collect_public_resources.remove_excluded_entities:
+ class: Oro\Bundle\ApiBundle\Processor\CollectPublicResources\RemoveExcludedEntities
+ arguments:
+ - @oro_api.entity_exclusion_provider
+ tags:
+ - { name: oro.api.processor, action: collect_public_resources, priority: -100 }
diff --git a/src/Oro/Bundle/ApiBundle/Resources/config/processors.get.yml b/src/Oro/Bundle/ApiBundle/Resources/config/processors.get.yml
index dc2ccadf556..78177713d12 100644
--- a/src/Oro/Bundle/ApiBundle/Resources/config/processors.get.yml
+++ b/src/Oro/Bundle/ApiBundle/Resources/config/processors.get.yml
@@ -14,7 +14,7 @@ services:
# initialize
#
- oro_api.get.json_api.check_request_type:
+ oro_api.get.check_request_type:
class: Oro\Bundle\ApiBundle\Processor\Shared\JsonApi\CheckRequestType
tags:
- { name: oro.api.processor, action: get, group: initialize, priority: 250 }
@@ -46,14 +46,14 @@ services:
tags:
- { name: oro.api.processor, action: get, group: initialize, priority: 50 }
- oro_api.get.normalize_include_parameter:
+ oro_api.get.json_api.normalize_include_parameter:
class: Oro\Bundle\ApiBundle\Processor\Shared\JsonApi\NormalizeIncludeParameter
arguments:
- @oro_api.value_normalizer
tags:
- { name: oro.api.processor, action: get, group: initialize, requestType: json_api, priority: 50 }
- oro_api.get.normalize_fields_parameter:
+ oro_api.get.json_api.normalize_fields_parameter:
class: Oro\Bundle\ApiBundle\Processor\Shared\JsonApi\NormalizeFieldsParameter
arguments:
- @oro_api.value_normalizer
@@ -101,20 +101,24 @@ services:
- { name: oro.api.processor, action: get, group: security_check, priority: 10 }
#
- # build_query
+ # normalize_input
#
oro_api.get.validate_entity_id_exists:
class: Oro\Bundle\ApiBundle\Processor\Shared\ValidateEntityIdExists
tags:
- - { name: oro.api.processor, action: get, group: build_query, priority: 255 }
+ - { name: oro.api.processor, action: get, group: normalize_input, priority: -100 }
oro_api.get.rest.normalize_entity_id:
class: Oro\Bundle\ApiBundle\Processor\Shared\NormalizeEntityId
arguments:
- @oro_api.rest.entity_id_transformer
tags:
- - { name: oro.api.processor, action: get, group: build_query, requestType: rest, priority: 250 }
+ - { name: oro.api.processor, action: get, group: normalize_input, requestType: rest, priority: -110 }
+
+ #
+ # build_query
+ #
oro_api.get.complete_criteria:
class: Oro\Bundle\ApiBundle\Processor\Shared\CompleteCriteria
diff --git a/src/Oro/Bundle/ApiBundle/Resources/config/processors.get_config.yml b/src/Oro/Bundle/ApiBundle/Resources/config/processors.get_config.yml
index c819992e7e3..6fa678d287b 100644
--- a/src/Oro/Bundle/ApiBundle/Resources/config/processors.get_config.yml
+++ b/src/Oro/Bundle/ApiBundle/Resources/config/processors.get_config.yml
@@ -57,26 +57,26 @@ services:
oro_api.get_config.normalize_filters:
class: Oro\Bundle\ApiBundle\Processor\Config\GetConfig\NormalizeFilters
tags:
- - { name: oro.api.processor, action: get_config, extra: filters, priority: -100 }
+ - { name: oro.api.processor, action: get_config, extra: filters, priority: -130 }
oro_api.get_config.normalize_sorters:
class: Oro\Bundle\ApiBundle\Processor\Config\GetConfig\NormalizeSorters
tags:
- - { name: oro.api.processor, action: get_config, extra: sorters, priority: -100 }
+ - { name: oro.api.processor, action: get_config, extra: sorters, priority: -130 }
oro_api.get_config.remove_duplicate_filters:
class: Oro\Bundle\ApiBundle\Processor\Config\GetConfig\RemoveDuplicateFilters
arguments:
- @oro_api.doctrine_helper
tags:
- - { name: oro.api.processor, action: get_config, extra: filters, priority: -110 }
+ - { name: oro.api.processor, action: get_config, extra: filters, priority: -140 }
oro_api.get_config.remove_duplicate_sorters:
class: Oro\Bundle\ApiBundle\Processor\Config\GetConfig\RemoveDuplicateSorters
arguments:
- @oro_api.doctrine_helper
tags:
- - { name: oro.api.processor, action: get_config, extra: sorters, priority: -110 }
+ - { name: oro.api.processor, action: get_config, extra: sorters, priority: -140 }
oro_api.get_config.set_max_related_entities:
class: Oro\Bundle\ApiBundle\Processor\Config\GetConfig\SetMaxRelatedEntities
@@ -154,7 +154,7 @@ services:
- @oro_api.config_provider
- @oro_api.field_config_provider
tags:
- - { name: oro.api.processor, action: get_config, requstType: json_api, priority: -40 }
+ - { name: oro.api.processor, action: get_config, priority: -40 }
- { name: oro.api.processor, action: get_field_config, priority: -40 }
- { name: oro.api.processor, action: get_relation_config, priority: -40 }
@@ -166,22 +166,41 @@ services:
tags:
- { name: oro.api.processor, action: get_config, priority: -50 }
+ oro_api.get_config.exclude_not_accessible_relations:
+ class: Oro\Bundle\ApiBundle\Processor\Config\Shared\ExcludeNotAccessibleRelations
+ arguments:
+ - @oro_api.doctrine_helper
+ - @router
+ - @oro_entity.entity_alias_resolver
+ tags:
+ - { name: oro.api.processor, action: get_config, requestType: rest, priority: -60 }
+ - { name: oro.api.processor, action: get_relation_config, requestType: rest, priority: -60 }
+
+ oro_api.get_config.set_type_for_table_inheritance_relations:
+ class: Oro\Bundle\ApiBundle\Processor\Config\Shared\SetTypeForTableInheritanceRelations
+ arguments:
+ - @oro_api.doctrine_helper
+ tags:
+ - { name: oro.api.processor, action: get_config, requestType: json_api, priority: -70 }
+ - { name: oro.api.processor, action: get_relation_config, requestType: json_api, priority: -70 }
+
oro_api.get_config.complete_filters:
class: Oro\Bundle\ApiBundle\Processor\Config\Shared\CompleteFilters
arguments:
- @oro_api.doctrine_helper
tags:
- - { name: oro.api.processor, action: get_config, extra: filters, priority: -60 }
- - { name: oro.api.processor, action: get_field_config, extra: filters, priority: -60 }
- - { name: oro.api.processor, action: get_relation_config, extra: filters, priority: -60 }
+ - { name: oro.api.processor, action: get_config, extra: filters, priority: -80 }
+ - { name: oro.api.processor, action: get_field_config, extra: filters, priority: -80 }
+ - { name: oro.api.processor, action: get_relation_config, extra: filters, priority: -80 }
oro_api.get_config.complete_sorters:
class: Oro\Bundle\ApiBundle\Processor\Config\Shared\CompleteSorters
arguments:
- @oro_api.doctrine_helper
tags:
- - { name: oro.api.processor, action: get_config, extra: sorters, priority: -60 }
- - { name: oro.api.processor, action: get_relation_config, extra: filters, priority: -60 }
+ - { name: oro.api.processor, action: get_config, extra: sorters, priority: -80 }
+ - { name: oro.api.processor, action: get_field_config, extra: filters, priority: -80 }
+ - { name: oro.api.processor, action: get_relation_config, extra: filters, priority: -80 }
oro_api.get_config.filter_fields_by_extra:
class: Oro\Bundle\ApiBundle\Processor\Config\Shared\FilterFieldsByExtra
@@ -189,16 +208,16 @@ services:
- @oro_api.doctrine_helper
- @oro_api.json_api.entity_class_transformer
tags:
- - { name: oro.api.processor, action: get_config, extra: filter_fields, priority: -80 }
+ - { name: oro.api.processor, action: get_config, extra: filter_fields, priority: -100 }
oro_api.get_config.json_api.fix_field_naming:
class: Oro\Bundle\ApiBundle\Processor\Config\Shared\JsonApi\FixFieldNaming
arguments:
- @oro_api.doctrine_helper
tags:
- - { name: oro.api.processor, action: get_config, requestType: json_api, priority: -90 }
- - { name: oro.api.processor, action: get_field_config, requestType: json_api, priority: -90 }
- - { name: oro.api.processor, action: get_relation_config, requestType: json_api, priority: -90 }
+ - { name: oro.api.processor, action: get_config, requestType: json_api, priority: -110 }
+ - { name: oro.api.processor, action: get_field_config, requestType: json_api, priority: -110 }
+ - { name: oro.api.processor, action: get_relation_config, requestType: json_api, priority: -110 }
oro_api.get_config.normalize_definition:
class: Oro\Bundle\ApiBundle\Processor\Config\Shared\NormalizeDefinition
diff --git a/src/Oro/Bundle/ApiBundle/Resources/config/processors.get_list.yml b/src/Oro/Bundle/ApiBundle/Resources/config/processors.get_list.yml
index dc16fbab4c2..569dfdd2684 100644
--- a/src/Oro/Bundle/ApiBundle/Resources/config/processors.get_list.yml
+++ b/src/Oro/Bundle/ApiBundle/Resources/config/processors.get_list.yml
@@ -14,7 +14,7 @@ services:
# initialize
#
- oro_api.get_list.json_api.check_request_type:
+ oro_api.get_list.check_request_type:
class: Oro\Bundle\ApiBundle\Processor\Shared\JsonApi\CheckRequestType
tags:
- { name: oro.api.processor, action: get_list, group: initialize, priority: 250 }
@@ -46,14 +46,14 @@ services:
tags:
- { name: oro.api.processor, action: get_list, group: initialize, priority: 50 }
- oro_api.get_list.normalize_include_parameter:
+ oro_api.get_list.json_api.normalize_include_parameter:
class: Oro\Bundle\ApiBundle\Processor\Shared\JsonApi\NormalizeIncludeParameter
arguments:
- @oro_api.value_normalizer
tags:
- { name: oro.api.processor, action: get_list, group: initialize, requestType: json_api, priority: 50 }
- oro_api.get_list.normalize_fields_parameter:
+ oro_api.get_list.json_api.normalize_fields_parameter:
class: Oro\Bundle\ApiBundle\Processor\Shared\JsonApi\NormalizeFieldsParameter
arguments:
- @oro_api.value_normalizer
@@ -75,12 +75,15 @@ services:
oro_api.get_list.register_filters:
class: Oro\Bundle\ApiBundle\Processor\GetList\RegisterFilters
arguments:
- - @oro.api.filter_factory
+ - @oro_api.filter_factory
tags:
- { name: oro.api.processor, action: get_list, group: initialize, priority: -50 }
oro_api.get_list.json_api.normalize_filter_keys:
class: Oro\Bundle\ApiBundle\Processor\GetList\JsonApi\NormalizeFilterKeys
+ arguments:
+ - @oro_api.doctrine_helper
+ - @translator
tags:
- { name: oro.api.processor, action: get_list, group: initialize, requestType: json_api, priority: -50 }
@@ -96,6 +99,13 @@ services:
tags:
- { name: oro.api.processor, action: get_list, group: initialize, requestType: rest, priority: -100 }
+ oro_api.get_list.json_api.set_default_sorting:
+ class: Oro\Bundle\ApiBundle\Processor\GetList\JsonApi\SetDefaultSorting
+ arguments:
+ - @oro_api.doctrine_helper
+ tags:
+ - { name: oro.api.processor, action: get_list, group: initialize, requestType: json_api, priority: -105 }
+
oro_api.get_list.json_api.set_default_paging:
class: Oro\Bundle\ApiBundle\Processor\GetList\JsonApi\SetDefaultPaging
tags:
@@ -130,20 +140,31 @@ services:
- { name: oro.api.processor, action: get_list, group: security_check, priority: 10 }
#
- # build_query
+ # normalize_input
#
- oro_api.get_list.json_api.normalize_filter_values:
+ oro_api.get_list.json_api.correct_sort_value:
+ class: Oro\Bundle\ApiBundle\Processor\GetList\JsonApi\CorrectSortValue
+ arguments:
+ - @oro_api.doctrine_helper
+ tags:
+ - { name: oro.api.processor, action: get_list, group: normalize_input, requestType: json_api, priority: 10 }
+
+ oro_api.get_list.normalize_filter_values:
class: Oro\Bundle\ApiBundle\Processor\GetList\NormalizeFilterValues
arguments:
- @oro_api.value_normalizer
tags:
- - { name: oro.api.processor, action: get_list, group: build_query, priority: 100 }
+ - { name: oro.api.processor, action: get_list, group: normalize_input, priority: -10 }
oro_api.get_list.validate_sorting:
class: Oro\Bundle\ApiBundle\Processor\GetList\ValidateSorting
tags:
- - { name: oro.api.processor, action: get_list, group: build_query, priority: 60 }
+ - { name: oro.api.processor, action: get_list, group: normalize_input, priority: -100 }
+
+ #
+ # build_query
+ #
oro_api.get_list.build_criteria:
class: Oro\Bundle\ApiBundle\Processor\GetList\BuildCriteria
diff --git a/src/Oro/Bundle/ApiBundle/Resources/config/services.yml b/src/Oro/Bundle/ApiBundle/Resources/config/services.yml
index 08220878202..aa390e0b9b6 100644
--- a/src/Oro/Bundle/ApiBundle/Resources/config/services.yml
+++ b/src/Oro/Bundle/ApiBundle/Resources/config/services.yml
@@ -78,6 +78,11 @@ services:
- @oro_api.processor_bag
- customize_data_item
+ oro_api.public_resources_loader:
+ class: Oro\Bundle\ApiBundle\Provider\PublicResourcesLoader
+ arguments:
+ - @oro_api.collect_public_resources.processor
+
oro_api.config_bag:
class: Oro\Bundle\ApiBundle\Provider\ConfigBag
public: false
@@ -190,8 +195,8 @@ services:
class: Oro\Bundle\ApiBundle\Routing\RestRouteOptionsResolver
public: false
arguments:
- - @oro_entity_config.entity_manager_bag
- - @oro_api.entity_exclusion_provider
+ - %installed%
+ - @oro_api.public_resources_loader
- @oro_entity.entity_alias_resolver
- @oro_api.doctrine_helper
- @oro_api.value_normalizer
@@ -219,11 +224,11 @@ services:
arguments:
- @request_stack
- oro.api.filter_factory:
+ oro_api.filter_factory:
class: Oro\Bundle\ApiBundle\Filter\ChainFilterFactory
public: false
- oro.api.filter_factory.default:
+ oro_api.filter_factory.default:
class: Oro\Bundle\ApiBundle\Filter\SimpleFilterFactory
public: false
calls:
diff --git a/src/Oro/Bundle/ApiBundle/Resources/translations/messages.en.yml b/src/Oro/Bundle/ApiBundle/Resources/translations/messages.en.yml
new file mode 100644
index 00000000000..8b29a952127
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Resources/translations/messages.en.yml
@@ -0,0 +1,3 @@
+oro:
+ entity:
+ identifier_field: Id
diff --git a/src/Oro/Bundle/ApiBundle/Routing/RestRouteOptionsResolver.php b/src/Oro/Bundle/ApiBundle/Routing/RestRouteOptionsResolver.php
index 99539765eb6..0c1f892ce24 100644
--- a/src/Oro/Bundle/ApiBundle/Routing/RestRouteOptionsResolver.php
+++ b/src/Oro/Bundle/ApiBundle/Routing/RestRouteOptionsResolver.php
@@ -6,29 +6,29 @@
use Oro\Component\Routing\Resolver\RouteCollectionAccessor;
use Oro\Component\Routing\Resolver\RouteOptionsResolverInterface;
+
+use Oro\Bundle\ApiBundle\Provider\PublicResourcesLoader;
use Oro\Bundle\ApiBundle\Request\RequestType;
use Oro\Bundle\ApiBundle\Request\RestRequest;
use Oro\Bundle\ApiBundle\Request\ValueNormalizer;
+use Oro\Bundle\ApiBundle\Request\Version;
use Oro\Bundle\ApiBundle\Util\DoctrineHelper;
use Oro\Bundle\EntityBundle\ORM\EntityAliasResolver;
-use Oro\Bundle\EntityBundle\Tools\SafeDatabaseChecker;
-use Oro\Bundle\EntityBundle\Provider\ExclusionProviderInterface;
-use Oro\Bundle\EntityConfigBundle\Config\EntityManagerBag;
class RestRouteOptionsResolver implements RouteOptionsResolverInterface
{
- const ROUTE_GROUP = 'rest_api';
- const ENTITY_ATTRIBUTE = 'entity';
- const ENTITY_PLACEHOLDER = '{entity}';
- const ID_ATTRIBUTE = 'id';
- const ID_PLACEHOLDER = '{id}';
- const FORMAT_ATTRIBUTE = '_format';
+ const ROUTE_GROUP = 'rest_api';
+ const ENTITY_ATTRIBUTE = 'entity';
+ const ENTITY_PLACEHOLDER = '{entity}';
+ const ID_ATTRIBUTE = 'id';
+ const ID_PLACEHOLDER = '{id}';
+ const FORMAT_ATTRIBUTE = '_format';
- /** @var EntityManagerBag */
- protected $entityManagerBag;
+ /** @var bool */
+ protected $isApplicationInstalled;
- /** @var ExclusionProviderInterface */
- protected $entityExclusionProvider;
+ /** @var PublicResourcesLoader */
+ protected $resourcesLoader;
/** @var EntityAliasResolver */
protected $entityAliasResolver;
@@ -45,31 +45,34 @@ class RestRouteOptionsResolver implements RouteOptionsResolverInterface
/** @var string[] */
protected $defaultFormat;
+ /** @var array */
+ private $supportedEntities;
+
/**
- * @param EntityManagerBag $entityManagerBag
- * @param ExclusionProviderInterface $entityExclusionProvider
- * @param EntityAliasResolver $entityAliasResolver
- * @param DoctrineHelper $doctrineHelper
- * @param ValueNormalizer $valueNormalizer
- * @param string $formats
- * @param string $defaultFormat
+ * @param bool|string|null $isApplicationInstalled
+ * @param PublicResourcesLoader $resourcesLoader
+ * @param EntityAliasResolver $entityAliasResolver
+ * @param DoctrineHelper $doctrineHelper
+ * @param ValueNormalizer $valueNormalizer
+ * @param string $formats
+ * @param string $defaultFormat
*/
public function __construct(
- EntityManagerBag $entityManagerBag,
- ExclusionProviderInterface $entityExclusionProvider,
+ $isApplicationInstalled,
+ PublicResourcesLoader $resourcesLoader,
EntityAliasResolver $entityAliasResolver,
DoctrineHelper $doctrineHelper,
ValueNormalizer $valueNormalizer,
$formats,
$defaultFormat
) {
- $this->entityManagerBag = $entityManagerBag;
- $this->entityExclusionProvider = $entityExclusionProvider;
- $this->entityAliasResolver = $entityAliasResolver;
- $this->doctrineHelper = $doctrineHelper;
- $this->valueNormalizer = $valueNormalizer;
- $this->formats = $formats;
- $this->defaultFormat = $defaultFormat;
+ $this->isApplicationInstalled = !empty($isApplicationInstalled);
+ $this->resourcesLoader = $resourcesLoader;
+ $this->entityAliasResolver = $entityAliasResolver;
+ $this->doctrineHelper = $doctrineHelper;
+ $this->valueNormalizer = $valueNormalizer;
+ $this->formats = $formats;
+ $this->defaultFormat = $defaultFormat;
}
/**
@@ -77,61 +80,64 @@ public function __construct(
*/
public function resolve(Route $route, RouteCollectionAccessor $routes)
{
- if ($route->getOption('group') !== self::ROUTE_GROUP) {
+ if (!$this->isApplicationInstalled || $route->getOption('group') !== self::ROUTE_GROUP) {
return;
}
if ($this->hasAttribute($route, self::ENTITY_PLACEHOLDER)) {
$this->setFormatAttribute($route);
- $entities = $this->getSupportedEntityClasses();
-
+ $entities = $this->getSupportedEntities();
if (!empty($entities)) {
$this->adjustRoutes($route, $routes, $entities);
}
+ $route->setRequirement(self::ENTITY_ATTRIBUTE, '\w+');
+
+ $route->setOption('hidden', true);
}
}
/**
- * @return string[]
+ * @return array [[entity class, entity plural alias], ...]
*/
- protected function getSupportedEntityClasses()
+ protected function getSupportedEntities()
{
- $entities = [];
- $entityManagers = $this->entityManagerBag->getEntityManagers();
- foreach ($entityManagers as $em) {
- $allMetadata = SafeDatabaseChecker::getAllMetadata($em);
- foreach ($allMetadata as $metadata) {
- if ($metadata->isMappedSuperclass) {
- continue;
- }
- if ($this->entityExclusionProvider->isIgnoredEntity($metadata->name)) {
- continue;
+ if (null === $this->supportedEntities) {
+ $resources = $this->resourcesLoader->getResources(
+ Version::LATEST,
+ [RequestType::REST, RequestType::JSON_API]
+ );
+
+ $this->supportedEntities = [];
+ foreach ($resources as $resource) {
+ $className = $resource->getEntityClass();
+ $pluralAlias = $this->entityAliasResolver->getPluralAlias($className);
+ if (!empty($pluralAlias)) {
+ $this->supportedEntities[] = [
+ $className,
+ $pluralAlias
+ ];
}
- $entities[] = $metadata->name;
}
}
- return $entities;
+ return $this->supportedEntities;
}
/**
* @param Route $route
* @param RouteCollectionAccessor $routes
- * @param string[] $entities
+ * @param array $entities [[entity class, entity plural alias], ...]
*/
protected function adjustRoutes(Route $route, RouteCollectionAccessor $routes, $entities)
{
$routeName = $routes->getName($route);
- foreach ($entities as $className) {
- $entity = $this->entityAliasResolver->getPluralAlias($className);
- if (empty($entity)) {
- continue;
- }
+ foreach ($entities as $entity) {
+ list($className, $pluralAlias) = $entity;
$existingRoute = $routes->getByPath(
- str_replace(self::ENTITY_PLACEHOLDER, $entity, $route->getPath()),
+ str_replace(self::ENTITY_PLACEHOLDER, $pluralAlias, $route->getPath()),
$route->getMethods()
);
if ($existingRoute) {
@@ -142,8 +148,8 @@ protected function adjustRoutes(Route $route, RouteCollectionAccessor $routes, $
} else {
// add an additional strict route based on the base route and current entity
$strictRoute = $routes->cloneRoute($route);
- $strictRoute->setPath(str_replace(self::ENTITY_PLACEHOLDER, $entity, $strictRoute->getPath()));
- $strictRoute->setDefault(self::ENTITY_ATTRIBUTE, $entity);
+ $strictRoute->setPath(str_replace(self::ENTITY_PLACEHOLDER, $pluralAlias, $strictRoute->getPath()));
+ $strictRoute->setDefault(self::ENTITY_ATTRIBUTE, $pluralAlias);
$requirements = $strictRoute->getRequirements();
unset($requirements[self::ENTITY_ATTRIBUTE]);
$strictRoute->setRequirements($requirements);
@@ -182,20 +188,13 @@ protected function setIdRequirement(Route $route, $entityClass)
// single identifier
$route->setRequirement(
self::ID_ATTRIBUTE,
- $this->valueNormalizer->getRequirement(
- $metadata->getTypeOfField(reset($idFields)),
- [RequestType::REST, RequestType::JSON_API]
- )
+ $this->getIdFieldRequirement($metadata->getTypeOfField(reset($idFields)))
);
} elseif ($idFieldCount > 1) {
// combined identifier
$requirements = [];
foreach ($idFields as $field) {
- $requirements[] = $field . '='
- . $this->valueNormalizer->getRequirement(
- $metadata->getTypeOfField($field),
- [RequestType::REST, RequestType::JSON_API]
- );
+ $requirements[] = $field . '=' . $this->getIdFieldRequirement($metadata->getTypeOfField($field));
}
$route->setRequirement(
self::ID_ATTRIBUTE,
@@ -204,6 +203,25 @@ protected function setIdRequirement(Route $route, $entityClass)
}
}
+ /**
+ * @param string $fieldType
+ *
+ * @return string
+ */
+ protected function getIdFieldRequirement($fieldType)
+ {
+ $result = $this->valueNormalizer->getRequirement(
+ $fieldType,
+ [RequestType::REST, RequestType::JSON_API]
+ );
+
+ if (ValueNormalizer::DEFAULT_REQUIREMENT === $result) {
+ $result = '[^\.]+';
+ }
+
+ return $result;
+ }
+
/**
* Checks if a route has the given placeholder in a path.
*
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Functional/ApiTestCase.php b/src/Oro/Bundle/ApiBundle/Tests/Functional/ApiTestCase.php
index d30554f3e2b..be47b764dac 100644
--- a/src/Oro/Bundle/ApiBundle/Tests/Functional/ApiTestCase.php
+++ b/src/Oro/Bundle/ApiBundle/Tests/Functional/ApiTestCase.php
@@ -4,13 +4,15 @@
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Yaml\Parser;
use Oro\Bundle\ApiBundle\Request\JsonApi\EntityClassTransformer;
use Oro\Bundle\ApiBundle\Request\RestRequest;
+use Oro\Bundle\ApiBundle\Request\Version;
use Oro\Bundle\ApiBundle\Util\DoctrineHelper;
use Oro\Bundle\TestFrameworkBundle\Test\WebTestCase;
-class ApiTestCase extends WebTestCase
+abstract class ApiTestCase extends WebTestCase
{
/** @var DoctrineHelper */
protected $doctrineHelper;
@@ -18,39 +20,44 @@ class ApiTestCase extends WebTestCase
/** @var EntityClassTransformer */
protected $entityClassTransformer;
+ /**
+ * Local cache for expectations
+ *
+ * @var array
+ */
+ private $expectations = [];
+
/**
* {@inheritdoc}
*/
protected function setUp()
{
/** @var ContainerInterface $container */
- $container = $this->getContainer();
+ $container = $this->getContainer();
$this->entityClassTransformer = $container->get('oro_api.json_api.entity_class_transformer');
$this->doctrineHelper = $container->get('oro_api.doctrine_helper');
}
+ /**
+ * @return string[]
+ */
+ abstract protected function getRequestType();
+
/**
* @return array
*/
public function getEntities()
{
$this->initClient();
- $entities = [];
- $container = $this->getContainer();
- $entityManagers = $container->get('oro_entity_config.entity_manager_bag')->getEntityManagers();
- $entityExclusionProvider = $container->get('oro_api.entity_exclusion_provider');
- foreach ($entityManagers as $em) {
- $allMetadata = $em->getMetadataFactory()->getAllMetadata();
- foreach ($allMetadata as $metadata) {
- if ($metadata->isMappedSuperclass) {
- continue;
- }
- if ($entityExclusionProvider->isIgnoredEntity($metadata->name)) {
- continue;
- }
- $entities[$metadata->name] = [$metadata->name];
- }
+ $entities = [];
+ $container = $this->getContainer();
+ $resourcesLoader = $container->get('oro_api.public_resources_loader');
+ $resources = $resourcesLoader->getResources(Version::LATEST, $this->getRequestType());
+ foreach ($resources as $resource) {
+ $entityClass = $resource->getEntityClass();
+
+ $entities[$entityClass] = [$entityClass];
}
return $entities;
@@ -82,6 +89,26 @@ protected function getGetRequestConfig($entityClass, $content)
}
}
+ /**
+ * @param string $filename
+ *
+ * @return array
+ */
+ protected function loadExpectation($filename)
+ {
+ if (!isset($this->expectations[$filename])) {
+ $expectedContent = file_get_contents(
+ __DIR__ . DIRECTORY_SEPARATOR . 'Stub' . DIRECTORY_SEPARATOR . $filename
+ );
+
+ $ymlParser = new Parser();
+
+ $this->expectations[$filename] = $ymlParser->parse($expectedContent);
+ }
+
+ return $this->expectations[$filename];
+ }
+
/**
* @param Response $response
* @param integer $statusCode
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Functional/DataFixtures/LoadAuditData.php b/src/Oro/Bundle/ApiBundle/Tests/Functional/DataFixtures/LoadAuditData.php
new file mode 100644
index 00000000000..7a75a9e0e6e
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Functional/DataFixtures/LoadAuditData.php
@@ -0,0 +1,51 @@
+container = $container;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function load(ObjectManager $manager)
+ {
+ /** @var User $user */
+ $user = $manager->getRepository('OroUserBundle:User')->findOneBy(['username' => 'admin']);
+
+ $logEntry = new Audit();
+ $logEntry->setAction('update');
+ $logEntry->setObjectClass('Oro\Bundle\UserBundle\Entity\User');
+ $logEntry->setLoggedAt();
+ $logEntry->setUser($user);
+ $logEntry->setOrganization($user->getOrganization());
+ $logEntry->setObjectName('test_user');
+ $logEntry->setObjectId($user->getId());
+ $logEntry->createField('username', 'text', 'new_value', 'old_value');
+ $logEntry->setVersion(1);
+
+ $manager->persist($logEntry);
+ $manager->flush();
+
+ $this->setReference('audit_log_entry', $logEntry);
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiTest.php b/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiTest.php
index 1c1bf61b2e5..43a13fd2d5f 100644
--- a/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiTest.php
+++ b/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiTest.php
@@ -2,6 +2,8 @@
namespace Oro\Bundle\ApiBundle\Tests\Functional;
+use Oro\Bundle\ApiBundle\Request\RequestType;
+
class GetRestJsonApiTest extends ApiTestCase
{
/**
@@ -20,6 +22,14 @@ protected function setUp()
parent::setUp();
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getRequestType()
+ {
+ return [RequestType::REST, RequestType::JSON_API];
+ }
+
/**
* @param string $entityClass
*
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiWithIncludeFieldsTest.php b/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiWithIncludeFieldsTest.php
index 75b9968e2dc..90ff24856c5 100644
--- a/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiWithIncludeFieldsTest.php
+++ b/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiWithIncludeFieldsTest.php
@@ -2,7 +2,7 @@
namespace Oro\Bundle\ApiBundle\Tests\Functional;
-use Symfony\Component\Yaml\Parser;
+use Oro\Bundle\ApiBundle\Request\RequestType;
class GetRestJsonApiWithIncludeFieldsTest extends ApiTestCase
{
@@ -11,13 +11,6 @@ class GetRestJsonApiWithIncludeFieldsTest extends ApiTestCase
*/
const ENTITY_CLASS = 'Oro\Bundle\UserBundle\Entity\User';
- /**
- * Local cache for expectations
- *
- * @var array
- */
- protected $expectations = [];
-
/**
* {@inheritdoc}
*/
@@ -34,6 +27,14 @@ protected function setUp()
parent::setUp();
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getRequestType()
+ {
+ return [RequestType::REST, RequestType::JSON_API];
+ }
+
/**
* @param array $params
* @param array $expects
@@ -126,23 +127,4 @@ public function getParamsAndExpectation()
]
];
}
-
- /**
- * @param $filename
- *
- * @return array
- */
- protected function loadExpectation($filename)
- {
- if (!isset($this->expectations[$filename])) {
- $expectedContent = file_get_contents(
- __DIR__ . DIRECTORY_SEPARATOR . 'Stub' . DIRECTORY_SEPARATOR . $filename
- );
- $ymlParser = new Parser();
-
- $this->expectations[$filename] = $ymlParser->parse($expectedContent);
- }
-
- return $this->expectations[$filename];
- }
}
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiWithTableInheritanceTest.php b/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiWithTableInheritanceTest.php
new file mode 100644
index 00000000000..bcff1e71516
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestJsonApiWithTableInheritanceTest.php
@@ -0,0 +1,111 @@
+initClient(
+ [],
+ array_replace(
+ $this->generateWsseAuthHeader(),
+ ['CONTENT_TYPE' => 'application/vnd.api+json']
+ )
+ );
+
+ parent::setUp();
+
+ $this->loadFixtures(['Oro\Bundle\ApiBundle\Tests\Functional\DataFixtures\LoadAuditData']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getRequestType()
+ {
+ return [RequestType::REST, RequestType::JSON_API];
+ }
+
+ /**
+ * @param array $params
+ * @param array $expects
+ *
+ * @dataProvider getParamsAndExpectation
+ */
+ public function testGetEntityWithTableInheritance($params, $expects)
+ {
+ /** @var Audit $auditLogEntry */
+ $auditLogEntry = $this->getReference('audit_log_entry');
+
+ $expects['data'][0]['id'] = (string)$auditLogEntry->getField('username')->getId();
+
+ $expects['data'][0]['relationships']['audit']['data']['id'] = (string)$auditLogEntry->getId();
+ if (isset($expects['included'][0]['id'])) {
+ $expects['included'][0]['id'] = (string)$auditLogEntry->getId();
+
+ }
+
+ $entityAlias = $this->entityClassTransformer->transform(self::ENTITY_CLASS);
+
+ // test get list request
+ $this->client->request(
+ 'GET',
+ $this->getUrl('oro_rest_api_cget', ['entity' => $entityAlias, 'page[size]' => 1]),
+ $params,
+ [],
+ array_replace(
+ $this->generateWsseAuthHeader(),
+ ['CONTENT_TYPE' => 'application/vnd.api+json']
+ )
+ );
+
+ $response = $this->client->getResponse();
+
+ $this->assertApiResponseStatusCodeEquals($response, 200, $entityAlias, 'get list');
+ $this->assertEquals($expects, json_decode($response->getContent(), true));
+ }
+
+ /**
+ * @return array
+ */
+ public function getParamsAndExpectation()
+ {
+ return [
+ 'Related entity with table inheritance' => [
+ 'params' => [
+ 'fields' => [
+ 'auditfields' => 'oldText,newText,audit'
+ ],
+ 'sort' => '-id'
+ ],
+ 'expects' => $this->loadExpectation('output_inheritance_1.yml')
+ ],
+ 'Related entity with table inheritance (expanded)' => [
+ 'params' => [
+ 'include' => 'audit',
+ 'fields' => [
+ 'auditfields' => 'oldText,newText,audit',
+ 'audit' => 'objectClass'
+ ],
+ 'sort' => '-id'
+ ],
+ 'expects' => $this->loadExpectation('output_inheritance_2.yml')
+ ],
+ ];
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestPlainApiTest.php b/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestPlainApiTest.php
index 120191af947..5559d2719fb 100644
--- a/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestPlainApiTest.php
+++ b/src/Oro/Bundle/ApiBundle/Tests/Functional/GetRestPlainApiTest.php
@@ -2,6 +2,8 @@
namespace Oro\Bundle\ApiBundle\Tests\Functional;
+use Oro\Bundle\ApiBundle\Request\RequestType;
+
class GetRestPlainApiTest extends ApiTestCase
{
/**
@@ -14,6 +16,14 @@ protected function setUp()
parent::setUp();
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getRequestType()
+ {
+ return [RequestType::REST];
+ }
+
/**
* @param string $entityClass
*
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Functional/Stub/output_inheritance_1.yml b/src/Oro/Bundle/ApiBundle/Tests/Functional/Stub/output_inheritance_1.yml
new file mode 100644
index 00000000000..42c7d05bd90
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Functional/Stub/output_inheritance_1.yml
@@ -0,0 +1,12 @@
+data:
+ 0:
+ type: "auditfields"
+ id: auto
+ attributes:
+ oldText: "old_value"
+ newText: "new_value"
+ relationships:
+ audit:
+ data:
+ type: "audits"
+ id: auto
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Functional/Stub/output_inheritance_2.yml b/src/Oro/Bundle/ApiBundle/Tests/Functional/Stub/output_inheritance_2.yml
new file mode 100644
index 00000000000..45819953c05
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Functional/Stub/output_inheritance_2.yml
@@ -0,0 +1,18 @@
+data:
+ 0:
+ type: "auditfields"
+ id: auto
+ attributes:
+ oldText: "old_value"
+ newText: "new_value"
+ relationships:
+ audit:
+ data:
+ type: "audits"
+ id: auto
+included:
+ 0:
+ type: "audits"
+ id: auto
+ attributes:
+ objectClass: "Oro\Bundle\UserBundle\Entity\User"
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/DependencyInjection/Fixtures/BarBundle/BarBundle.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/DependencyInjection/Fixtures/BarBundle/BarBundle.php
new file mode 100644
index 00000000000..f07ca40a7d7
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/DependencyInjection/Fixtures/BarBundle/BarBundle.php
@@ -0,0 +1,9 @@
+clear()
+ ->setBundles(
+ [
+ $bundle1->getName() => get_class($bundle1),
+ $bundle2->getName() => get_class($bundle2),
+ $bundle3->getName() => get_class($bundle3)
+ ]
+ );
+
+ $extension = new OroApiExtension();
+
+ $container = new ContainerBuilder();
+
+ $extension->load([], $container);
+
+ $this->assertNotNull(
+ $container->getDefinition('oro_api.config_bag'),
+ 'Expected oro_api.config_bag service'
+ );
+ $this->assertNotNull(
+ $container->getDefinition('oro_api.entity_exclusion_provider.config'),
+ 'Expected oro_api.entity_exclusion_provider.config service'
+ );
+
+ $this->assertEquals(
+ [
+ 'entities' => [
+ 'Test\Entity1' => [],
+ 'Test\Entity2' => [],
+ 'Test\Entity3' => [],
+ 'Test\Entity4' => [
+ 'definition' => [
+ 'fields' => [
+ 'field1' => null,
+ 'field2' => [
+ 'exclude' => true
+ ],
+ 'field3' => [
+ 'exclude' => true
+ ]
+ ],
+ 'filters' => [
+ 'fields' => [
+ 'field1' => null,
+ 'field2' => [
+ 'data_type' => 'string',
+ 'exclude' => true
+ ],
+ 'field3' => [
+ 'exclude' => true
+ ]
+ ]
+ ],
+ 'sorters' => [
+ 'fields' => [
+ 'field1' => null,
+ 'field2' => [
+ 'exclude' => true
+ ]
+ ]
+ ],
+ ]
+ ],
+ 'Test\Entity5' => [
+ 'definition' => [
+ 'fields' => [
+ 'field1' => null
+ ]
+ ]
+ ],
+ 'Test\Entity6' => [
+ 'definition' => [
+ 'fields' => [
+ 'field1' => null
+ ]
+ ]
+ ],
+ 'Test\Entity10' => [],
+ 'Test\Entity11' => [],
+ ]
+ ],
+ $container->getDefinition('oro_api.config_bag')->getArgument(0)
+ );
+
+ $this->assertEquals(
+ [
+ ['entity' => 'Test\Entity1'],
+ ['entity' => 'Test\Entity2'],
+ ['entity' => 'Test\Entity3'],
+ ],
+ $container->getDefinition('oro_api.entity_exclusion_provider.config')->getArgument(1)
+ );
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/LoadCustomEntitiesTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/LoadCustomEntitiesTest.php
new file mode 100644
index 00000000000..cbf3f0808a0
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/LoadCustomEntitiesTest.php
@@ -0,0 +1,68 @@
+configManager = $this->getMockBuilder('Oro\Bundle\EntityConfigBundle\Config\ConfigManager')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->processor = new LoadCustomEntities($this->configManager);
+ }
+
+ public function testProcess()
+ {
+ $context = new CollectPublicResourcesContext();
+
+ $this->configManager->expects($this->once())
+ ->method('getConfigs')
+ ->with('extend', null, true)
+ ->willReturn(
+ [
+ $this->getEntityConfig('Test\Entity1', ['is_extend' => true, 'owner' => ExtendScope::OWNER_CUSTOM]),
+ $this->getEntityConfig('Test\Entity2', ['is_extend' => true, 'owner' => ExtendScope::OWNER_SYSTEM]),
+ $this->getEntityConfig('Test\Entity3'),
+ ]
+ );
+
+ $this->processor->process($context);
+
+ $this->assertEquals(
+ [
+ new PublicResource('Test\Entity1'),
+ ],
+ $context->getResult()->toArray()
+ );
+ }
+
+ /**
+ * @param string $className
+ * @param array $values
+ *
+ * @return Config
+ */
+ protected function getEntityConfig($className, $values = [])
+ {
+ $configId = new EntityConfigId('extend', $className);
+ $config = new Config($configId);
+ $config->setValues($values);
+
+ return $config;
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/LoadDictionariesTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/LoadDictionariesTest.php
new file mode 100644
index 00000000000..4e965f255a8
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/LoadDictionariesTest.php
@@ -0,0 +1,45 @@
+dictionaryProvider = $this
+ ->getMockBuilder('Oro\Bundle\EntityBundle\Provider\ChainDictionaryValueListProvider')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->processor = new LoadDictionaries($this->dictionaryProvider);
+ }
+
+ public function testProcess()
+ {
+ $context = new CollectPublicResourcesContext();
+
+ $this->dictionaryProvider->expects($this->once())
+ ->method('getSupportedEntityClasses')
+ ->willReturn(['Test\Entity1', 'Test\Entity2']);
+
+ $this->processor->process($context);
+
+ $this->assertEquals(
+ [
+ new PublicResource('Test\Entity1'),
+ new PublicResource('Test\Entity2'),
+ ],
+ $context->getResult()->toArray()
+ );
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/LoadFromConfigBagTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/LoadFromConfigBagTest.php
new file mode 100644
index 00000000000..ee3b9b11c08
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/LoadFromConfigBagTest.php
@@ -0,0 +1,52 @@
+configBag = $this->getMockBuilder('Oro\Bundle\ApiBundle\Provider\ConfigBag')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->processor = new LoadFromConfigBag($this->configBag);
+ }
+
+ public function testProcess()
+ {
+ $context = new CollectPublicResourcesContext();
+ $context->setVersion(Version::LATEST);
+
+ $this->configBag->expects($this->once())
+ ->method('getConfigs')
+ ->with(Version::LATEST)
+ ->willReturn(
+ [
+ 'Test\Entity1' => null,
+ 'Test\Entity2' => null,
+ ]
+ );
+
+ $this->processor->process($context);
+
+ $this->assertEquals(
+ [
+ new PublicResource('Test\Entity1'),
+ new PublicResource('Test\Entity2'),
+ ],
+ $context->getResult()->toArray()
+ );
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/RemoveExcludedEntitiesTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/RemoveExcludedEntitiesTest.php
new file mode 100644
index 00000000000..5d9e5510987
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/CollectPublicResources/RemoveExcludedEntitiesTest.php
@@ -0,0 +1,51 @@
+entityExclusionProvider = $this->getMock('Oro\Bundle\EntityBundle\Provider\ExclusionProviderInterface');
+
+ $this->processor = new RemoveExcludedEntities($this->entityExclusionProvider);
+ }
+
+ public function testProcess()
+ {
+ $context = new CollectPublicResourcesContext();
+ $context->setVersion(Version::LATEST);
+
+ $context->getResult()->add(new PublicResource('Test\Entity1'));
+ $context->getResult()->add(new PublicResource('Test\Entity2'));
+
+ $this->entityExclusionProvider->expects($this->exactly(2))
+ ->method('isIgnoredEntity')
+ ->willReturnMap(
+ [
+ ['Test\Entity1', true],
+ ['Test\Entity1', false],
+ ]
+ );
+
+ $this->processor->process($context);
+
+ $this->assertEquals(
+ [
+ 1 => new PublicResource('Test\Entity2'),
+ ],
+ $context->getResult()->toArray()
+ );
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Provider/ConfigBagTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Provider/ConfigBagTest.php
index c270b01a50e..d745a083636 100644
--- a/src/Oro/Bundle/ApiBundle/Tests/Unit/Provider/ConfigBagTest.php
+++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Provider/ConfigBagTest.php
@@ -49,6 +49,38 @@ public function testNoMetadata($className, $version)
);
}
+ /**
+ * @dataProvider getConfigsProvider
+ */
+ public function testGetConfigs($version, $expectedConfig)
+ {
+ $this->assertEquals(
+ $expectedConfig,
+ $this->configBag->getConfigs($version)
+ );
+ }
+
+ public function getConfigsProvider()
+ {
+ return [
+ /* @todo: API version is not supported for now. Add data to test versioning here */
+ [
+ '1.0',
+ [
+ 'Test\Class1' => [ConfigUtil::DEFINITION => [ConfigUtil::FIELDS => ['class1_v0' => []]]],
+ 'Test\Class2' => [ConfigUtil::DEFINITION => [ConfigUtil::FIELDS => ['class2_v2.0' => []]]],
+ ]
+ ],
+ [
+ Version::LATEST,
+ [
+ 'Test\Class1' => [ConfigUtil::DEFINITION => [ConfigUtil::FIELDS => ['class1_v0' => []]]],
+ 'Test\Class2' => [ConfigUtil::DEFINITION => [ConfigUtil::FIELDS => ['class2_v2.0' => []]]],
+ ]
+ ],
+ ];
+ }
+
/**
* @dataProvider noConfigProvider
*/
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Provider/PublicResourcesLoaderTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Provider/PublicResourcesLoaderTest.php
new file mode 100644
index 00000000000..314fc88d256
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Provider/PublicResourcesLoaderTest.php
@@ -0,0 +1,52 @@
+processor = $this->getMockBuilder('Oro\Bundle\ApiBundle\Processor\CollectPublicResourcesProcessor')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->loader = new PublicResourcesLoader($this->processor);
+ }
+
+ public function testGetResources()
+ {
+ $version = '1.2.3';
+ $requestType = [RequestType::REST, RequestType::JSON_API];
+
+ $this->processor->expects($this->once())
+ ->method('process')
+ ->willReturnCallback(
+ function (CollectPublicResourcesContext $context) use ($version, $requestType) {
+ $this->assertEquals($version, $context->getVersion());
+ $this->assertEquals($requestType, $context->getRequestType());
+
+ $context->getResult()->add(new PublicResource('Test\Entity1'));
+ $context->getResult()->set(2, new PublicResource('Test\Entity3'));
+ }
+ );
+
+ $this->assertEquals(
+ [
+ new PublicResource('Test\Entity1'),
+ new PublicResource('Test\Entity3'),
+ ],
+ $this->loader->getResources($version, $requestType)
+ );
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/PublicResourceCollectionTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/PublicResourceCollectionTest.php
new file mode 100644
index 00000000000..99513d2ad59
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/PublicResourceCollectionTest.php
@@ -0,0 +1,140 @@
+assertEquals(0, $collection->count());
+ $this->assertAttributeEquals([], 'keys', $collection);
+ }
+
+ public function testConstructor()
+ {
+ $resource1 = new PublicResource('Test\Class1');
+ $resource2 = new PublicResource('Test\Class2');
+
+ $collection = new PublicResourceCollection([$resource1, $resource2]);
+ $this->assertEquals(2, $collection->count());
+ $this->assertAttributeEquals(
+ [(string)$resource1 => true, (string)$resource2 => true],
+ 'keys',
+ $collection
+ );
+ }
+
+ public function testAdd()
+ {
+ $resource1 = new PublicResource('Test\Class1');
+ $resource2 = new PublicResource('Test\Class2');
+
+ $collection = new PublicResourceCollection();
+ $collection->add($resource1);
+ $collection->add($resource2);
+ $collection->add($resource1);
+
+ $this->assertEquals(2, $collection->count());
+ $this->assertAttributeEquals(
+ [(string)$resource1 => true, (string)$resource2 => true],
+ 'keys',
+ $collection
+ );
+ }
+
+ public function testRemove()
+ {
+ $resource1 = new PublicResource('Test\Class1');
+ $resource2 = new PublicResource('Test\Class2');
+
+ $collection = new PublicResourceCollection([$resource1, $resource2]);
+
+ $this->assertSame(
+ $resource1,
+ $collection->remove(0)
+ );
+ $this->assertEquals(1, $collection->count());
+ $this->assertAttributeEquals(
+ [(string)$resource2 => true],
+ 'keys',
+ $collection
+ );
+
+ $this->assertNull(
+ $collection->remove(0)
+ );
+ $this->assertEquals(1, $collection->count());
+ $this->assertAttributeEquals(
+ [(string)$resource2 => true],
+ 'keys',
+ $collection
+ );
+ }
+
+ public function testRemoveElement()
+ {
+ $resource1 = new PublicResource('Test\Class1');
+ $resource2 = new PublicResource('Test\Class2');
+
+ $collection = new PublicResourceCollection([$resource1, $resource2]);
+
+ $this->assertTrue(
+ $collection->removeElement($resource1)
+ );
+ $this->assertEquals(1, $collection->count());
+ $this->assertAttributeEquals(
+ [(string)$resource2 => true],
+ 'keys',
+ $collection
+ );
+
+ $this->assertFalse(
+ $collection->removeElement($resource1)
+ );
+ $this->assertEquals(1, $collection->count());
+ $this->assertAttributeEquals(
+ [(string)$resource2 => true],
+ 'keys',
+ $collection
+ );
+ }
+
+ public function testSet()
+ {
+ $resource1 = new PublicResource('Test\Class1');
+ $resource2 = new PublicResource('Test\Class2');
+
+ $collection = new PublicResourceCollection([$resource1]);
+
+ $collection->set(0, $resource2);
+ $this->assertEquals(1, $collection->count());
+ $this->assertAttributeEquals(
+ [(string)$resource2 => true],
+ 'keys',
+ $collection
+ );
+ $this->assertSame($resource2, $collection->get(0));
+
+ $collection->set(1, $resource1);
+ $this->assertEquals(2, $collection->count());
+ $this->assertAttributeEquals(
+ [(string)$resource2 => true, (string)$resource1 => true],
+ 'keys',
+ $collection
+ );
+ $this->assertSame($resource1, $collection->get(1));
+
+ $collection->set(0, $resource1);
+ $this->assertEquals(1, $collection->count());
+ $this->assertAttributeEquals(
+ [(string)$resource1 => true],
+ 'keys',
+ $collection
+ );
+ $this->assertSame($resource1, $collection->get(0));
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/PublicResourceTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/PublicResourceTest.php
new file mode 100644
index 00000000000..33141f2416e
--- /dev/null
+++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/PublicResourceTest.php
@@ -0,0 +1,24 @@
+assertEquals($className, $resource->getEntityClass());
+ }
+
+ public function testToString()
+ {
+ $className = 'Test\Class';
+
+ $resource = new PublicResource($className);
+ $this->assertEquals($className, (string)$resource);
+ }
+}
diff --git a/src/Oro/Bundle/ApiBundle/Util/ConfigUtil.php b/src/Oro/Bundle/ApiBundle/Util/ConfigUtil.php
index 6a66f03c0fe..a87fa450a64 100644
--- a/src/Oro/Bundle/ApiBundle/Util/ConfigUtil.php
+++ b/src/Oro/Bundle/ApiBundle/Util/ConfigUtil.php
@@ -118,4 +118,19 @@ public static function isExcludedField(array $config, $field)
return $result;
}
+
+ /**
+ * Returns the property path to the field.
+ *
+ * @param array|null $fieldConfig
+ * @param string $fieldName
+ *
+ * @return string
+ */
+ public static function getPropertyPath($fieldConfig, $fieldName)
+ {
+ return !empty($fieldConfig[ConfigUtil::PROPERTY_PATH])
+ ? $fieldConfig[ConfigUtil::PROPERTY_PATH]
+ : $fieldName;
+ }
}
diff --git a/src/Oro/Bundle/ApiBundle/Util/DoctrineHelper.php b/src/Oro/Bundle/ApiBundle/Util/DoctrineHelper.php
index 5043ce23a32..ac265f649cb 100644
--- a/src/Oro/Bundle/ApiBundle/Util/DoctrineHelper.php
+++ b/src/Oro/Bundle/ApiBundle/Util/DoctrineHelper.php
@@ -71,19 +71,21 @@ public function findEntityMetadataByPath($entityClass, array $associationPath)
* Gets ORDER BY expression that can be used to sort a collection by entity identifier.
*
* @param string $entityClass
+ * @param bool $desc
*
* @return array|null
*/
- public function getOrderByIdentifier($entityClass)
+ public function getOrderByIdentifier($entityClass, $desc = false)
{
- $ids = $this->getEntityMetadata($entityClass)->getIdentifierFieldNames();
- if (empty($ids)) {
+ $idFieldNames = $this->getEntityIdentifierFieldNamesForClass($entityClass);
+ if (empty($idFieldNames)) {
return null;
}
$orderBy = [];
- foreach ($ids as $pk) {
- $orderBy[$pk] = Criteria::ASC;
+ $order = $desc ? Criteria::DESC : Criteria::ASC;
+ foreach ($idFieldNames as $idFieldName) {
+ $orderBy[$idFieldName] = $order;
}
return $orderBy;
@@ -102,7 +104,9 @@ public function getIndexedFields(ClassMetadata $metadata)
$idFieldNames = $metadata->getIdentifierFieldNames();
if (count($idFieldNames) > 0) {
- $indexedColumns[reset($idFieldNames)] = true;
+ $mapping = $metadata->getFieldMapping(reset($idFieldNames));
+
+ $indexedColumns[$mapping['columnName']] = true;
}
if (isset($metadata->table['indexes'])) {
diff --git a/src/Oro/Bundle/AttachmentBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/AttachmentBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..b0c0a75ecb9
--- /dev/null
+++ b/src/Oro/Bundle/AttachmentBundle/Resources/config/oro/api.yml
@@ -0,0 +1,4 @@
+oro_api:
+ entities:
+ Oro\Bundle\AttachmentBundle\Entity\Attachment: ~
+ Oro\Bundle\AttachmentBundle\Entity\File: ~
diff --git a/src/Oro/Bundle/CalendarBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/CalendarBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..b1e32cc8431
--- /dev/null
+++ b/src/Oro/Bundle/CalendarBundle/Resources/config/oro/api.yml
@@ -0,0 +1,6 @@
+oro_api:
+ entities:
+ Oro\Bundle\CalendarBundle\Entity\Calendar: ~
+ Oro\Bundle\CalendarBundle\Entity\CalendarEvent: ~
+ Oro\Bundle\CalendarBundle\Entity\CalendarProperty: ~
+ Oro\Bundle\CalendarBundle\Entity\SystemCalendar: ~
diff --git a/src/Oro/Bundle/CommentBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/CommentBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..65c34e2079e
--- /dev/null
+++ b/src/Oro/Bundle/CommentBundle/Resources/config/oro/api.yml
@@ -0,0 +1,3 @@
+oro_api:
+ entities:
+ Oro\Bundle\CommentBundle\Entity\Comment: ~
diff --git a/src/Oro/Bundle/ConfigBundle/Config/ConfigManager.php b/src/Oro/Bundle/ConfigBundle/Config/ConfigManager.php
index 763e591ef8b..1c48fe0e90d 100644
--- a/src/Oro/Bundle/ConfigBundle/Config/ConfigManager.php
+++ b/src/Oro/Bundle/ConfigBundle/Config/ConfigManager.php
@@ -283,9 +283,14 @@ protected function getValue($name, $default = false, $full = false)
{
$value = null;
$managers = $this->getScopeManagersToGetValue($default);
- foreach ($managers as $manager) {
+ foreach ($managers as $scopeName => $manager) {
$value = $manager->getSettingValue($name, $full);
if (null !== $value) {
+ // in case if we get value not from current scope,
+ // we should mark value that it was get from another scope
+ if ($full && $this->scope !== $scopeName) {
+ $value['use_parent_scope_value'] = true;
+ }
break;
}
}
diff --git a/src/Oro/Bundle/ConfigBundle/Config/UserScopeManager.php b/src/Oro/Bundle/ConfigBundle/Config/UserScopeManager.php
index 04fa09f8f15..5450a68f5a8 100644
--- a/src/Oro/Bundle/ConfigBundle/Config/UserScopeManager.php
+++ b/src/Oro/Bundle/ConfigBundle/Config/UserScopeManager.php
@@ -58,7 +58,7 @@ public function setScopeId($scopeId)
*/
protected function ensureScopeIdInitialized()
{
- if (null === $this->scopeId) {
+ if (!$this->scopeId) {
$scopeId = 0;
$token = $this->securityContext->getToken();
diff --git a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Config/ConfigManagerTest.php b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Config/ConfigManagerTest.php
index 1f32a26a409..d3a69fc84a7 100644
--- a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Config/ConfigManagerTest.php
+++ b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Config/ConfigManagerTest.php
@@ -287,7 +287,7 @@ public function getFromParentParamProvider()
'expectedResult' => [
'scope' => 'global',
'value' => ['foo' => 'bar'],
- 'use_parent_scope_value' => false
+ 'use_parent_scope_value' => true
]
],
[
diff --git a/src/Oro/Bundle/CronBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/CronBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..acaba7fb15b
--- /dev/null
+++ b/src/Oro/Bundle/CronBundle/Resources/config/oro/api.yml
@@ -0,0 +1,3 @@
+oro_api:
+ entities:
+ JMS\JobQueueBundle\Entity\Job: ~
diff --git a/src/Oro/Bundle/DashboardBundle/Helper/DateHelper.php b/src/Oro/Bundle/DashboardBundle/Helper/DateHelper.php
index ac0434fb5f0..bfc51285a2d 100644
--- a/src/Oro/Bundle/DashboardBundle/Helper/DateHelper.php
+++ b/src/Oro/Bundle/DashboardBundle/Helper/DateHelper.php
@@ -3,6 +3,7 @@
namespace Oro\Bundle\DashboardBundle\Helper;
use \DateTime;
+use \DateInterval;
use Doctrine\ORM\QueryBuilder;
@@ -310,6 +311,58 @@ public function getPeriod($dateRange, $entity, $field)
return [$start, $end];
}
+ /**
+ * @return DateTime
+ */
+ public function getCurrentDateTime()
+ {
+ $now = new DateTime('now', new \DateTimeZone($this->localeSettings->getTimeZone()));
+
+ return $now;
+ }
+
+ /**
+ * Gets date interval, depends on the user timezone and $interval.
+ *
+ * @param string $interval
+ *
+ * @return array
+ */
+ public function getDateTimeInterval($interval = 'P1M')
+ {
+ $start = $this->getCurrentDateTime();
+ $start->setTime(0, 0, 0);
+
+ $end = $this->getCurrentDateTime();
+ $end->setTime(23, 59, 59);
+
+ $start = $start->sub(new DateInterval($interval));
+
+ return [$start, $end];
+ }
+
+ /**
+ * Gets previous date interval
+ *
+ * @param DateTime $from
+ * @param DateTime $to
+ *
+ * @return array
+ */
+ public function getPreviousDateTimeInterval(DateTime $from, DateTime $to)
+ {
+ $interval = $from->diff($to);
+ $start = clone $from;
+ $start = $start->sub($interval);
+ $start = $start->sub(new DateInterval('PT1S'));
+
+ $end = clone $to;
+ $end = $end->sub($interval);
+ $end = $end->sub(new DateInterval('PT1S'));
+
+ return [$start, $end];
+ }
+
/**
* @param array $config
* @param DateTime $date
diff --git a/src/Oro/Bundle/DashboardBundle/Provider/BigNumber/BigNumberDateHelper.php b/src/Oro/Bundle/DashboardBundle/Provider/BigNumber/BigNumberDateHelper.php
index 0f5d9179669..eab99164130 100644
--- a/src/Oro/Bundle/DashboardBundle/Provider/BigNumber/BigNumberDateHelper.php
+++ b/src/Oro/Bundle/DashboardBundle/Provider/BigNumber/BigNumberDateHelper.php
@@ -6,6 +6,7 @@
use Oro\Bundle\FilterBundle\Form\Type\Filter\AbstractDateFilterType;
use Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper;
+use Oro\Bundle\LocaleBundle\Model\LocaleSettings;
class BigNumberDateHelper
{
@@ -15,14 +16,19 @@ class BigNumberDateHelper
/** @var AclHelper */
protected $aclHelper;
+ /** @var LocaleSettings */
+ protected $localeSettings;
+
/**
* @param RegistryInterface $doctrine
- * @param AclHelper $aclHelper
+ * @param AclHelper $aclHelper
+ * @param LocaleSettings $localeSettings
*/
- public function __construct(RegistryInterface $doctrine, AclHelper $aclHelper)
+ public function __construct(RegistryInterface $doctrine, AclHelper $aclHelper, LocaleSettings $localeSettings)
{
- $this->doctrine = $doctrine;
- $this->aclHelper = $aclHelper;
+ $this->doctrine = $doctrine;
+ $this->aclHelper = $aclHelper;
+ $this->localeSettings = $localeSettings;
}
/**
@@ -36,14 +42,14 @@ public function getPeriod($dateRange, $entity, $field)
{
$start = $dateRange['start'];
$end = $dateRange['end'];
-
- if ($dateRange['type'] === AbstractDateFilterType::TYPE_LESS_THAN) {
+ if (isset($dateRange['type']) && $dateRange['type'] === AbstractDateFilterType::TYPE_LESS_THAN) {
$qb = $this->doctrine
->getRepository($entity)
->createQueryBuilder('e')
->select(sprintf('MIN(e.%s) as val', $field));
$start = $this->aclHelper->apply($qb)->getSingleScalarResult();
$start = new \DateTime($start, new \DateTimeZone('UTC'));
+ $start->setTimezone(new \DateTimeZone($this->localeSettings->getTimeZone()));
}
return [$start, $end];
@@ -56,12 +62,11 @@ public function getPeriod($dateRange, $entity, $field)
*/
public function getLastWeekPeriod($weeksDiff = 0)
{
- $end = new \DateTime('last Saturday', new \DateTimeZone('UTC'));
- $end->setTime(23, 59, 59);
+ $end = new \DateTime('last Saturday', new \DateTimeZone($this->localeSettings->getTimeZone()));
$start = clone $end;
- $start->modify('-6 days');
- $start->setTime(0, 0, 0);
+ $start->modify('-7 days');
+ $end->setTime(23, 59, 59);
if ($weeksDiff) {
$days = $weeksDiff * 7;
diff --git a/src/Oro/Bundle/DashboardBundle/Provider/Converters/FilterDateTimeRangeConverter.php b/src/Oro/Bundle/DashboardBundle/Provider/Converters/FilterDateTimeRangeConverter.php
index 2e02b7501e0..5d0ca3d00e9 100644
--- a/src/Oro/Bundle/DashboardBundle/Provider/Converters/FilterDateTimeRangeConverter.php
+++ b/src/Oro/Bundle/DashboardBundle/Provider/Converters/FilterDateTimeRangeConverter.php
@@ -11,6 +11,8 @@
use Oro\Bundle\FilterBundle\Form\Type\Filter\AbstractDateFilterType;
use Oro\Bundle\LocaleBundle\Formatter\DateTimeFormatter;
+use Oro\Bundle\DashboardBundle\Helper\DateHelper;
+
class FilterDateTimeRangeConverter extends ConfigValueConverterAbstract
{
const MIN_DATE = '1900-01-01';
@@ -24,16 +26,25 @@ class FilterDateTimeRangeConverter extends ConfigValueConverterAbstract
/** @var TranslatorInterface */
protected $translator;
+ /** @var DateHelper */
+ protected $dateHelper;
+
/**
* @param DateTimeFormatter $formatter
* @param Compiler $dateCompiler
* @param TranslatorInterface $translator
+ * @param DateHelper $dateHelper
*/
- public function __construct(DateTimeFormatter $formatter, Compiler $dateCompiler, TranslatorInterface $translator)
- {
+ public function __construct(
+ DateTimeFormatter $formatter,
+ Compiler $dateCompiler,
+ TranslatorInterface $translator,
+ DateHelper $dateHelper
+ ) {
$this->formatter = $formatter;
$this->dateCompiler = $dateCompiler;
$this->translator = $translator;
+ $this->dateHelper = $dateHelper;
}
/**
@@ -44,15 +55,17 @@ public function getConvertedValue(array $widgetConfig, $value = null, array $con
if (is_null($value)
|| ($value['value']['start'] === null && $value['value']['end'] === null)
) {
- $end = new DateTime('now', new \DateTimeZone('UTC'));
- $start = clone $end;
- $start = $start->sub(new \DateInterval('P1M'));
+ list($start, $end) = $this->dateHelper->getDateTimeInterval('P1M');
+
$type = AbstractDateFilterType::TYPE_BETWEEN;
} else {
list($startValue, $endValue, $type) = $this->getPeriodValues($value);
$start = $startValue instanceof DateTime ? $startValue : $this->dateCompiler->compile($startValue);
- $end = $endValue instanceof DateTime ? $endValue : $this->dateCompiler->compile($endValue);
+ $start->setTime(0, 0, 0);
+
+ $end = $endValue instanceof DateTime ? $endValue : $this->dateCompiler->compile($endValue);
+ $end->setTime(23, 59, 59);
}
return [
diff --git a/src/Oro/Bundle/DashboardBundle/Provider/Converters/PreviousFilterDateRangeConverter.php b/src/Oro/Bundle/DashboardBundle/Provider/Converters/PreviousFilterDateRangeConverter.php
index 3b19b6b91e7..58720fed879 100644
--- a/src/Oro/Bundle/DashboardBundle/Provider/Converters/PreviousFilterDateRangeConverter.php
+++ b/src/Oro/Bundle/DashboardBundle/Provider/Converters/PreviousFilterDateRangeConverter.php
@@ -31,17 +31,13 @@ public function getConvertedValue(array $widgetConfig, $value = null, array $con
}
if ($currentDateRange['type'] !== AbstractDateFilterType::TYPE_LESS_THAN) {
- /**
- * @var \DateTime $from
- * @var \DateTime $to
- */
- $from = $currentDateRange['start'];
- $to = $currentDateRange['end'];
+ list($start, $end) = $this->dateHelper->getPreviousDateTimeInterval(
+ $currentDateRange['start'],
+ $currentDateRange['end']
+ );
- $interval = $from->diff($to);
- $fromDate = clone $from;
- $result['start'] = $fromDate->sub($interval);
- $result['end'] = clone $from;
+ $result['start'] = $start;
+ $result['end'] = $end;
$result['type'] = AbstractDateFilterType::TYPE_BETWEEN;
}
}
diff --git a/src/Oro/Bundle/DashboardBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/DashboardBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..7ec0b42f9b2
--- /dev/null
+++ b/src/Oro/Bundle/DashboardBundle/Resources/config/oro/api.yml
@@ -0,0 +1,6 @@
+oro_api:
+ entities:
+ Oro\Bundle\DashboardBundle\Entity\ActiveDashboard: ~
+ Oro\Bundle\DashboardBundle\Entity\Dashboard: ~
+ Oro\Bundle\DashboardBundle\Entity\Widget: ~
+ Oro\Bundle\DashboardBundle\Entity\WidgetState: ~
diff --git a/src/Oro/Bundle/DashboardBundle/Resources/config/services.yml b/src/Oro/Bundle/DashboardBundle/Resources/config/services.yml
index ea0ddc65258..08a4c8bed4a 100644
--- a/src/Oro/Bundle/DashboardBundle/Resources/config/services.yml
+++ b/src/Oro/Bundle/DashboardBundle/Resources/config/services.yml
@@ -193,15 +193,13 @@ services:
- @oro_locale.formatter.date_time
- @oro_filter.expression.date.compiler
- @translator
+ - @oro_dashboard.datetime.helper
tags:
- { name: oro_dashboard.value.converter, alias: "oro_type_widget_date_range" }
oro_dashboard.widget_config_value.previous_date_range.converter:
class: %oro_dashboard.widget_config_value.previous_date_range.converter.class%
- arguments:
- - @oro_locale.formatter.date_time
- - @oro_filter.expression.date.compiler
- - @translator
+ parent: oro_dashboard.widget_config_value.date_range.converter
tags:
- { name: oro_dashboard.value.converter, alias: "oro_type_widget_previous_date_range" }
@@ -266,3 +264,4 @@ services:
arguments:
- @doctrine
- @oro_security.acl_helper
+ - @oro_locale.settings
diff --git a/src/Oro/Bundle/DashboardBundle/Resources/views/Dashboard/bigNumberSubwidget.html.twig b/src/Oro/Bundle/DashboardBundle/Resources/views/Dashboard/bigNumberSubwidget.html.twig
index f5390bed4b8..ad46c25231d 100644
--- a/src/Oro/Bundle/DashboardBundle/Resources/views/Dashboard/bigNumberSubwidget.html.twig
+++ b/src/Oro/Bundle/DashboardBundle/Resources/views/Dashboard/bigNumberSubwidget.html.twig
@@ -14,6 +14,6 @@
{{ 'oro.dashboard.widget.big_number.compare_to.label'|trans }}:
- {{ item.value.previousRange.start|oro_format_date }} - {{ item.value.previousRange.end|oro_format_date }}
+ {{ item.value.previousRange.start|oro_format_date({timeZone: oro_timezone()}) }} - {{ item.value.previousRange.end|oro_format_date({timeZone: oro_timezone()}) }}
{% endif %}
diff --git a/src/Oro/Bundle/DashboardBundle/Tests/Unit/Helper/DateHelperTest.php b/src/Oro/Bundle/DashboardBundle/Tests/Unit/Helper/DateHelperTest.php
index 3f80bc424d7..87acdfc7282 100644
--- a/src/Oro/Bundle/DashboardBundle/Tests/Unit/Helper/DateHelperTest.php
+++ b/src/Oro/Bundle/DashboardBundle/Tests/Unit/Helper/DateHelperTest.php
@@ -9,6 +9,9 @@
use Oro\Bundle\DashboardBundle\Helper\DateHelper;
use Oro\Bundle\TestFrameworkBundle\Test\Doctrine\ORM\OrmTestCase;
+/**
+ * @SuppressWarnings(PHPMD.ExcessivePublicCount)
+ */
class DateHelperTest extends OrmTestCase
{
/** @var DateHelper */
@@ -31,13 +34,13 @@ public function setUp()
$this->settings->expects($this->any())
->method('getTimeZone')
->willReturn('UTC');
- $this->doctrine = $this->getMockBuilder('Doctrine\Bundle\DoctrineBundle\Registry')
+ $this->doctrine = $this->getMockBuilder('Doctrine\Bundle\DoctrineBundle\Registry')
->disableOriginalConstructor()
->getMock();
$this->aclHelper = $this->getMockBuilder('Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper')
->disableOriginalConstructor()
->getMock();
- $this->helper = new DateHelper($this->settings, $this->doctrine, $this->aclHelper);
+ $this->helper = new DateHelper($this->settings, $this->doctrine, $this->aclHelper);
}
/**
@@ -211,37 +214,37 @@ public function addDatePartsSelectProvider()
'2007-01-01',
'2011-01-01',
'SELECT id, YEAR(t.createdAt) as yearCreated '
- . 'FROM Oro\Bundle\DashboardBundle\Tests\Unit\Fixtures\FirstTestBundle\Entity\TestEntity t '
- . 'GROUP BY yearCreated'
+ . 'FROM Oro\Bundle\DashboardBundle\Tests\Unit\Fixtures\FirstTestBundle\Entity\TestEntity t '
+ . 'GROUP BY yearCreated'
],
'month' => [
'2000-01-01',
'2000-05-01',
'SELECT id, YEAR(t.createdAt) as yearCreated, MONTH(t.createdAt) as monthCreated '
- . 'FROM Oro\Bundle\DashboardBundle\Tests\Unit\Fixtures\FirstTestBundle\Entity\TestEntity t '
- . 'GROUP BY yearCreated, monthCreated'
+ . 'FROM Oro\Bundle\DashboardBundle\Tests\Unit\Fixtures\FirstTestBundle\Entity\TestEntity t '
+ . 'GROUP BY yearCreated, monthCreated'
],
'week' => [
'2000-03-01',
'2000-05-01',
'SELECT id, YEAR(t.createdAt) as yearCreated, WEEK(t.createdAt) as weekCreated '
- . 'FROM Oro\Bundle\DashboardBundle\Tests\Unit\Fixtures\FirstTestBundle\Entity\TestEntity t '
- . 'GROUP BY yearCreated, weekCreated'
+ . 'FROM Oro\Bundle\DashboardBundle\Tests\Unit\Fixtures\FirstTestBundle\Entity\TestEntity t '
+ . 'GROUP BY yearCreated, weekCreated'
],
'day' => [
'2000-03-01',
'2000-03-04',
'SELECT id, YEAR(t.createdAt) as yearCreated, MONTH(t.createdAt) as monthCreated, '
- . 'DAY(t.createdAt) as dayCreated '
- . 'FROM Oro\Bundle\DashboardBundle\Tests\Unit\Fixtures\FirstTestBundle\Entity\TestEntity t '
- . 'GROUP BY yearCreated, monthCreated, dayCreated'
+ . 'DAY(t.createdAt) as dayCreated '
+ . 'FROM Oro\Bundle\DashboardBundle\Tests\Unit\Fixtures\FirstTestBundle\Entity\TestEntity t '
+ . 'GROUP BY yearCreated, monthCreated, dayCreated'
],
'hour' => [
'2000-03-01',
'2000-03-02',
'SELECT id, DATE(t.createdAt) as dateCreated, HOUR(t.createdAt) as hourCreated '
- . 'FROM Oro\Bundle\DashboardBundle\Tests\Unit\Fixtures\FirstTestBundle\Entity\TestEntity t '
- . 'GROUP BY dateCreated, hourCreated'
+ . 'FROM Oro\Bundle\DashboardBundle\Tests\Unit\Fixtures\FirstTestBundle\Entity\TestEntity t '
+ . 'GROUP BY dateCreated, hourCreated'
]
];
}
@@ -262,9 +265,9 @@ public function testConvertToCurrentPeriodShouldReturnEmptyArrayIfDataAreEmpty()
public function testConvertToCurrentPeriod()
{
$from = new DateTime('2015-05-10');
- $to = new DateTime('2015-05-15');
+ $to = new DateTime('2015-05-15');
- $data = [
+ $data = [
[
'yearCreated' => '2015',
'monthCreated' => '05',
@@ -308,7 +311,7 @@ public function testCombinePreviousDataWithCurrentPeriodShouldReturnEmptyArrayIf
public function combinePreviousDataWithCurrentPeriodDataProvider()
{
return [
- 'general' => [
+ 'general' => [
new DateTime('2015-05-05'),
new DateTime('2015-05-10'),
[
@@ -328,13 +331,13 @@ public function combinePreviousDataWithCurrentPeriodDataProvider()
['date' => '2015-05-15'],
]
],
- 'empty_data_returns_empty_array' => [
+ 'empty_data_returns_empty_array' => [
new DateTime(),
new DateTime(),
[],
[]
],
- 'long_period_last_days_of_month' => [
+ 'long_period_last_days_of_month' => [
new DateTime('2015-05-19 23:00:00'),
new DateTime('2015-08-30 00:00:00'),
[
@@ -390,4 +393,38 @@ public function testCombinePreviousDataWithCurrentPeriod($previousFrom, $previou
$this->assertEquals($expectedData, $actualData);
}
+
+ /**
+ * @dataProvider getPreviousDateTimeIntervalDataProvider
+ *
+ * @param Datetime $from
+ * @param Datetime $to
+ * @param Datetime $expectedStart
+ * @param Datetime $expectedEnd
+ */
+ public function testGetPreviousDateTimeInterval(
+ \Datetime $from,
+ \Datetime $to,
+ \Datetime $expectedStart,
+ \Datetime $expectedEnd
+ ) {
+ list($start, $end) = $this->helper->getPreviousDateTimeInterval($from, $to);
+
+ $this->assertEquals($start, $expectedStart);
+ $this->assertEquals($end, $expectedEnd);
+ }
+
+ public function getPreviousDateTimeIntervalDataProvider()
+ {
+ $timezone = new \DateTimeZone('UTC');
+
+ return [
+ [
+ 'from' => new \DateTime('2014-01-01 00:00:00', $timezone),
+ 'to' => new \DateTime('2014-01-02 23:59:59', $timezone),
+ 'expectedStart' => new \DateTime('2013-12-30 00:00:00', $timezone),
+ 'expectedEnd' => new \DateTime('2013-12-31 23:59:59', $timezone),
+ ]
+ ];
+ }
}
diff --git a/src/Oro/Bundle/DashboardBundle/Tests/Unit/Provider/Converters/FilterDateTimeRangeConverterTest.php b/src/Oro/Bundle/DashboardBundle/Tests/Unit/Provider/Converters/FilterDateTimeRangeConverterTest.php
index 24ecd5e1281..524f8d8605f 100644
--- a/src/Oro/Bundle/DashboardBundle/Tests/Unit/Provider/Converters/FilterDateTimeRangeConverterTest.php
+++ b/src/Oro/Bundle/DashboardBundle/Tests/Unit/Provider/Converters/FilterDateTimeRangeConverterTest.php
@@ -3,6 +3,7 @@
namespace Oro\Bundle\DashboardBundle\Tests\Unit\Provider\Converters;
use Oro\Bundle\DashboardBundle\Provider\Converters\FilterDateTimeRangeConverter;
+use Oro\Bundle\DashboardBundle\Helper\DateHelper;
use Oro\Bundle\FilterBundle\Form\Type\Filter\AbstractDateFilterType;
class FilterDateTimeRangeConverterTest extends \PHPUnit_Framework_TestCase
@@ -19,6 +20,9 @@ class FilterDateTimeRangeConverterTest extends \PHPUnit_Framework_TestCase
/** @var \PHPUnit_Framework_MockObject_MockObject */
protected $translator;
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ protected $dateHelper;
+
public function setUp()
{
$this->formatter = $this->getMockBuilder('Oro\Bundle\LocaleBundle\Formatter\DateTimeFormatter')
@@ -33,7 +37,26 @@ public function setUp()
->disableOriginalConstructor()
->getMock();
- $this->converter = new FilterDateTimeRangeConverter($this->formatter, $this->converter, $this->translator);
+ $settings = $this->getMockBuilder('Oro\Bundle\LocaleBundle\Model\LocaleSettings')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $settings->expects($this->any())
+ ->method('getTimeZone')
+ ->willReturn('UTC');
+ $doctrine = $this->getMockBuilder('Doctrine\Bundle\DoctrineBundle\Registry')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $aclHelper = $this->getMockBuilder('Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->dateHelper = new DateHelper($settings, $doctrine, $aclHelper);
+
+ $this->converter = new FilterDateTimeRangeConverter(
+ $this->formatter,
+ $this->converter,
+ $this->translator,
+ $this->dateHelper
+ );
}
public function testGetConvertedValueDefaultValues()
diff --git a/src/Oro/Bundle/DashboardBundle/Tests/Unit/Provider/Converters/PreviousFilterDateRangeConverterTest.php b/src/Oro/Bundle/DashboardBundle/Tests/Unit/Provider/Converters/PreviousFilterDateRangeConverterTest.php
index 55130340cf1..35885b2d7be 100644
--- a/src/Oro/Bundle/DashboardBundle/Tests/Unit/Provider/Converters/PreviousFilterDateRangeConverterTest.php
+++ b/src/Oro/Bundle/DashboardBundle/Tests/Unit/Provider/Converters/PreviousFilterDateRangeConverterTest.php
@@ -3,6 +3,7 @@
namespace Oro\Bundle\DashboardBundle\Tests\Unit\Provider\Converters;
use Oro\Bundle\DashboardBundle\Provider\Converters\PreviousFilterDateRangeConverter;
+use Oro\Bundle\DashboardBundle\Helper\DateHelper;
use Oro\Bundle\FilterBundle\Form\Type\Filter\AbstractDateFilterType;
class PreviousFilterDateRangeConverterTest extends \PHPUnit_Framework_TestCase
@@ -19,6 +20,9 @@ class PreviousFilterDateRangeConverterTest extends \PHPUnit_Framework_TestCase
/** @var \PHPUnit_Framework_MockObject_MockObject */
protected $formatter;
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ protected $dateHelper;
+
public function setUp()
{
$this->converter = $this->getMockBuilder('Oro\Bundle\FilterBundle\Expression\Date\Compiler')
@@ -32,13 +36,33 @@ public function setUp()
$this->translator = $this->getMockBuilder('Oro\Bundle\TranslationBundle\Translation\Translator')
->disableOriginalConstructor()
->getMock();
- $this->converter = new PreviousFilterDateRangeConverter($this->formatter, $this->converter, $this->translator);
+
+ $settings = $this->getMockBuilder('Oro\Bundle\LocaleBundle\Model\LocaleSettings')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $settings->expects($this->any())
+ ->method('getTimeZone')
+ ->willReturn('UTC');
+ $doctrine = $this->getMockBuilder('Doctrine\Bundle\DoctrineBundle\Registry')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $aclHelper = $this->getMockBuilder('Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->dateHelper = new DateHelper($settings, $doctrine, $aclHelper);
+
+ $this->converter = new PreviousFilterDateRangeConverter(
+ $this->formatter,
+ $this->converter,
+ $this->translator,
+ $this->dateHelper
+ );
}
public function testGetConvertedValueBetween()
{
$start = new \DateTime('2014-01-01', new \DateTimeZone('UTC'));
- $end = new \DateTime('2015-01-01', new \DateTimeZone('UTC'));
+ $end = new \DateTime('2014-12-31', new \DateTimeZone('UTC'));
$result = $this->converter->getConvertedValue(
[],
@@ -60,6 +84,6 @@ public function testGetConvertedValueBetween()
);
$this->assertEquals('2013-01-01', $result['start']->format('Y-m-d'));
- $this->assertEquals('2014-01-01', $result['end']->format('Y-m-d'));
+ $this->assertEquals('2013-12-31', $result['end']->format('Y-m-d'));
}
}
diff --git a/src/Oro/Bundle/DataAuditBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/DataAuditBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..e0fea1ca28d
--- /dev/null
+++ b/src/Oro/Bundle/DataAuditBundle/Resources/config/oro/api.yml
@@ -0,0 +1,4 @@
+oro_api:
+ entities:
+ Oro\Bundle\DataAuditBundle\Entity\Audit: ~
+ Oro\Bundle\DataAuditBundle\Entity\AuditField: ~
diff --git a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/listener/column-form-listener.js b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/listener/column-form-listener.js
index 6639d69caa6..7ee00e79754 100644
--- a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/listener/column-form-listener.js
+++ b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/listener/column-form-listener.js
@@ -71,6 +71,7 @@ define([
var original = this.get('original');
var included = this.get('included');
var excluded = this.get('excluded');
+ id = parseInt(id);
var isActive = model.get(this.columnName);
var originallyActive;
@@ -153,6 +154,7 @@ define([
_restoreState: function() {
var included = '';
var excluded = '';
+ var columnName = this.columnName;
if (this.selectors.included && $(this.selectors.included).length) {
included = this._explode($(this.selectors.included).val());
this.set('included', included);
@@ -161,6 +163,16 @@ define([
excluded = this._explode($(this.selectors.excluded).val());
this.set('excluded', excluded);
}
+ _.each(this.grid.collection.models, function(model) {
+ var isActive = model.get(columnName);
+ var modelId = parseInt(model.id);
+ if (!isActive && _.contains(included, modelId)) {
+ model.set(columnName, true);
+ }
+ if (isActive && _.contains(excluded, modelId)) {
+ model.set(columnName, false);
+ }
+ });
if (included || excluded) {
mediator.trigger('datagrid:setParam:' + this.gridName, 'data_in', included);
mediator.trigger('datagrid:setParam:' + this.gridName, 'data_not_in', excluded);
diff --git a/src/Oro/Bundle/DistributionBundle/DependencyInjection/Compiler/HiddenRoutesPass.php b/src/Oro/Bundle/DistributionBundle/DependencyInjection/Compiler/HiddenRoutesPass.php
new file mode 100644
index 00000000000..0b170e09dae
--- /dev/null
+++ b/src/Oro/Bundle/DistributionBundle/DependencyInjection/Compiler/HiddenRoutesPass.php
@@ -0,0 +1,72 @@
+hasParameter(self::MATCHER_DUMPER_CLASS_PARAM)) {
+ $newClass = $this->getNewRoutingMatcherDumperClass(
+ $container->getParameter(self::MATCHER_DUMPER_CLASS_PARAM)
+ );
+ if ($newClass) {
+ $container->setParameter(self::MATCHER_DUMPER_CLASS_PARAM, $newClass);
+ }
+ }
+
+ if ($container->hasParameter(self::API_DOC_EXTRACTOR_CLASS_PARAM)) {
+ $newClass = $this->getNewApiDocExtractorClass(
+ $container->getParameter(self::API_DOC_EXTRACTOR_CLASS_PARAM)
+ );
+ if ($newClass) {
+ $container->setParameter(self::API_DOC_EXTRACTOR_CLASS_PARAM, $newClass);
+ }
+ }
+ }
+
+ /**
+ * @param string $currentClass
+ *
+ * @return string|null
+ */
+ protected function getNewRoutingMatcherDumperClass($currentClass)
+ {
+ return self::EXPECTED_MATCHER_DUMPER_CLASS === $currentClass
+ ? self::NEW_MATCHER_DUMPER_CLASS
+ : null;
+ }
+
+ /**
+ * @param string $currentClass
+ *
+ * @return string|null
+ */
+ protected function getNewApiDocExtractorClass($currentClass)
+ {
+ switch ($currentClass) {
+ case self::EXPECTED_CACHING_API_DOC_EXTRACTOR_CLASS:
+ return self::NEW_CACHING_API_DOC_EXTRACTOR_CLASS;
+ case self::EXPECTED_API_DOC_EXTRACTOR_CLASS:
+ return self::NEW_API_DOC_EXTRACTOR_CLASS;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/Oro/Bundle/DistributionBundle/OroDistributionBundle.php b/src/Oro/Bundle/DistributionBundle/OroDistributionBundle.php
index 796dcce4d54..7939e1f6101 100644
--- a/src/Oro/Bundle/DistributionBundle/OroDistributionBundle.php
+++ b/src/Oro/Bundle/DistributionBundle/OroDistributionBundle.php
@@ -5,6 +5,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
+use Oro\Bundle\DistributionBundle\DependencyInjection\Compiler\HiddenRoutesPass;
use Oro\Bundle\DistributionBundle\DependencyInjection\Compiler\RoutingOptionsResolverPass;
class OroDistributionBundle extends Bundle
@@ -17,5 +18,6 @@ public function build(ContainerBuilder $container)
parent::build($container);
$container->addCompilerPass(new RoutingOptionsResolverPass());
+ $container->addCompilerPass(new HiddenRoutesPass());
}
}
diff --git a/src/Oro/Bundle/DistributionBundle/Tests/Unit/DependencyInjection/Compiler/HiddenRoutesPassTest.php b/src/Oro/Bundle/DistributionBundle/Tests/Unit/DependencyInjection/Compiler/HiddenRoutesPassTest.php
new file mode 100644
index 00000000000..61dacb8ba79
--- /dev/null
+++ b/src/Oro/Bundle/DistributionBundle/Tests/Unit/DependencyInjection/Compiler/HiddenRoutesPassTest.php
@@ -0,0 +1,89 @@
+compilerPass = new HiddenRoutesPass();
+ }
+
+ /**
+ * @dataProvider processDataProvider
+ */
+ public function testProcess($params, $expectedParams)
+ {
+ $container = new ContainerBuilder();
+ foreach ($params as $name => $val) {
+ $container->getParameterBag()->set($name, $val);
+ }
+
+ $this->compilerPass->process($container);
+
+ $this->assertEquals(
+ $expectedParams,
+ $container->getParameterBag()->all()
+ );
+ }
+
+ public function processDataProvider()
+ {
+ return [
+ [[], []],
+ [
+ [
+ HiddenRoutesPass::MATCHER_DUMPER_CLASS_PARAM =>
+ HiddenRoutesPass::EXPECTED_MATCHER_DUMPER_CLASS
+ ],
+ [
+ HiddenRoutesPass::MATCHER_DUMPER_CLASS_PARAM =>
+ HiddenRoutesPass::NEW_MATCHER_DUMPER_CLASS
+ ]
+ ],
+ [
+ [
+ HiddenRoutesPass::MATCHER_DUMPER_CLASS_PARAM => 'OtherMatcherDumper'
+ ],
+ [
+ HiddenRoutesPass::MATCHER_DUMPER_CLASS_PARAM => 'OtherMatcherDumper'
+ ]
+ ],
+ [
+ [
+ HiddenRoutesPass::API_DOC_EXTRACTOR_CLASS_PARAM =>
+ HiddenRoutesPass::EXPECTED_API_DOC_EXTRACTOR_CLASS
+ ],
+ [
+ HiddenRoutesPass::API_DOC_EXTRACTOR_CLASS_PARAM =>
+ HiddenRoutesPass::NEW_API_DOC_EXTRACTOR_CLASS
+ ]
+ ],
+ [
+ [
+ HiddenRoutesPass::API_DOC_EXTRACTOR_CLASS_PARAM =>
+ HiddenRoutesPass::EXPECTED_CACHING_API_DOC_EXTRACTOR_CLASS
+ ],
+ [
+ HiddenRoutesPass::API_DOC_EXTRACTOR_CLASS_PARAM =>
+ HiddenRoutesPass::NEW_CACHING_API_DOC_EXTRACTOR_CLASS
+ ]
+ ],
+ [
+ [
+ HiddenRoutesPass::API_DOC_EXTRACTOR_CLASS_PARAM => 'OtherApiDocExtractor'
+ ],
+ [
+ HiddenRoutesPass::API_DOC_EXTRACTOR_CLASS_PARAM => 'OtherApiDocExtractor'
+ ]
+ ],
+ ];
+ }
+}
diff --git a/src/Oro/Bundle/EmailBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/EmailBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..c32b5452735
--- /dev/null
+++ b/src/Oro/Bundle/EmailBundle/Resources/config/oro/api.yml
@@ -0,0 +1,15 @@
+oro_api:
+ entities:
+ Oro\Bundle\EmailBundle\Entity\EmailTemplate: ~
+ Oro\Bundle\EmailBundle\Entity\AutoResponseRule: ~
+ Oro\Bundle\EmailBundle\Entity\AutoResponseRuleCondition: ~
+ Oro\Bundle\EmailBundle\Entity\Email: ~
+ Oro\Bundle\EmailBundle\Entity\EmailAttachment: ~
+ Oro\Bundle\EmailBundle\Entity\EmailAttachmentContent: ~
+ Oro\Bundle\EmailBundle\Entity\EmailBody: ~
+ Oro\Bundle\EmailBundle\Entity\EmailFolder: ~
+ Oro\Bundle\EmailBundle\Entity\EmailOrigin: ~
+ Oro\Bundle\EmailBundle\Entity\EmailRecipient: ~
+ Oro\Bundle\EmailBundle\Entity\EmailThread: ~
+ Oro\Bundle\EmailBundle\Entity\EmailUser: ~
+ Oro\Bundle\EmailBundle\Entity\Mailbox: ~
diff --git a/src/Oro/Bundle/EmbeddedFormBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/EmbeddedFormBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..b9e7f2de362
--- /dev/null
+++ b/src/Oro/Bundle/EmbeddedFormBundle/Resources/config/oro/api.yml
@@ -0,0 +1,3 @@
+oro_api:
+ entities:
+ Oro\Bundle\EmbeddedFormBundle\Entity\EmbeddedForm: ~
diff --git a/src/Oro/Bundle/EntityBundle/EntityConfig/DatagridScope.php b/src/Oro/Bundle/EntityBundle/EntityConfig/DatagridScope.php
index f0aa6cf1ed2..00e0ccb27d5 100644
--- a/src/Oro/Bundle/EntityBundle/EntityConfig/DatagridScope.php
+++ b/src/Oro/Bundle/EntityBundle/EntityConfig/DatagridScope.php
@@ -4,7 +4,8 @@
class DatagridScope
{
- const IS_VISIBLE_FALSE = 0;
- const IS_VISIBLE_TRUE = 1;
- const IS_VISIBLE_MANDATORY = 2;
+ const IS_VISIBLE_FALSE = 0; // do not show on grid and do not allow to manage
+ const IS_VISIBLE_TRUE = 1; // show on grid by default and allow to manage
+ const IS_VISIBLE_MANDATORY = 2; // show on grid and do not allow to manage
+ const IS_VISIBLE_HIDDEN = 3; // do not show on grid and allow to manage
}
diff --git a/src/Oro/Bundle/EntityBundle/Resources/config/entity_config.yml b/src/Oro/Bundle/EntityBundle/Resources/config/entity_config.yml
index e1a43562150..56eae3dd734 100755
--- a/src/Oro/Bundle/EntityBundle/Resources/config/entity_config.yml
+++ b/src/Oro/Bundle/EntityBundle/Resources/config/entity_config.yml
@@ -140,7 +140,8 @@ oro_entity_config:
options:
choices:
0: 'No'
- 1: 'Yes'
+ 1: oro.entity.entity_config.datagrid.field.items.is_visible.shown
+ 3: oro.entity.entity_config.datagrid.field.items.is_visible.hidden
2: oro.entity.entity_config.datagrid.field.items.is_visible.mandatory
empty_value: false
block: other
diff --git a/src/Oro/Bundle/EntityBundle/Resources/translations/messages.en.yml b/src/Oro/Bundle/EntityBundle/Resources/translations/messages.en.yml
index 658e52eda20..3fefdd59c51 100644
--- a/src/Oro/Bundle/EntityBundle/Resources/translations/messages.en.yml
+++ b/src/Oro/Bundle/EntityBundle/Resources/translations/messages.en.yml
@@ -39,8 +39,10 @@ oro:
field:
items:
is_visible: Add to grid settings
+ is_visible.shown: Yes and display
+ is_visible.hidden: Yes and do not display
is_visible.mandatory: Yes as mandatory
- is_visible.tooltip: Controls the availability of a field in the Grid Settings. "Yes" makes it available (hidden by default) and "Yes as mandatory" adds it permanently to all grid views
+ is_visible.tooltip: Controls the availability of a field in the Grid Settings. "Yes and display" makes it available (shown by default), "Yes and do not display" the same but hidden by default and "Yes as mandatory" adds it permanently to all grid views
show_filter: Show grid filter
form:
field:
diff --git a/src/Oro/Bundle/EntityBundle/Routing/DictionaryEntityRouteOptionsResolver.php b/src/Oro/Bundle/EntityBundle/Routing/DictionaryEntityRouteOptionsResolver.php
index 9884d208f84..e61aae07234 100644
--- a/src/Oro/Bundle/EntityBundle/Routing/DictionaryEntityRouteOptionsResolver.php
+++ b/src/Oro/Bundle/EntityBundle/Routing/DictionaryEntityRouteOptionsResolver.php
@@ -26,6 +26,9 @@ class DictionaryEntityRouteOptionsResolver implements RouteOptionsResolverInterf
/** @var EntityClassNameHelper */
protected $entityClassNameHelper;
+ /** @var array */
+ private $supportedEntities;
+
/**
* @param ChainDictionaryValueListProvider $dictionaryProvider
* @param EntityAliasResolver $entityAliasResolver
@@ -51,37 +54,50 @@ public function resolve(Route $route, RouteCollectionAccessor $routes)
}
if ($this->hasAttribute($route, self::ENTITY_PLACEHOLDER)) {
+ $entities = $this->getSupportedEntities();
+ if (!empty($entities)) {
+ $this->adjustRoutes($route, $routes, $entities);
+ }
+ $route->setRequirement(self::ENTITY_ATTRIBUTE, '\w+');
+
+ $route->setOption('hidden', true);
+ }
+ }
+
+ /**
+ * @return array [[entity plural alias, url safe class name], ...]
+ */
+ protected function getSupportedEntities()
+ {
+ if (null === $this->supportedEntities) {
$entities = $this->dictionaryProvider->getSupportedEntityClasses();
- if (!empty($entities)) {
- $entities = $this->adjustRoutes($route, $routes, $entities);
- if (!empty($entities)) {
- $route->setRequirement(self::ENTITY_ATTRIBUTE, implode('|', $entities));
- }
+ $this->supportedEntities = [];
+ foreach ($entities as $className) {
+ $this->supportedEntities[] = [
+ $this->entityAliasResolver->getPluralAlias($className),
+ $this->entityClassNameHelper->getUrlSafeClassName($className)
+ ];
}
}
+
+ return $this->supportedEntities;
}
/**
* @param Route $route
* @param RouteCollectionAccessor $routes
- * @param string[] $entities
- *
- * @return string[] Entity requirements for the default controller
+ * @param array $entities [[entity plural alias, url safe class name], ...]
*/
protected function adjustRoutes(Route $route, RouteCollectionAccessor $routes, $entities)
{
- $result = [];
$routeName = $routes->getName($route);
- foreach ($entities as $className) {
- $entity = $this->entityAliasResolver->getPluralAlias($className);
-
- $result[] = $entity;
- $result[] = $this->entityClassNameHelper->getUrlSafeClassName($className);
+ foreach ($entities as $entity) {
+ list($pluralAlias, $urlSafeClassName) = $entity;
$existingRoute = $routes->getByPath(
- str_replace(self::ENTITY_PLACEHOLDER, $entity, $route->getPath()),
+ str_replace(self::ENTITY_PLACEHOLDER, $pluralAlias, $route->getPath()),
$route->getMethods()
);
if ($existingRoute) {
@@ -89,16 +105,12 @@ protected function adjustRoutes(Route $route, RouteCollectionAccessor $routes, $
$existingRouteName = $routes->getName($existingRoute);
$routes->remove($existingRouteName);
$routes->insert($existingRouteName, $existingRoute, $routeName, true);
- //additional route for entities which has api, but it not recognize urls like
+ // additional route for entities which has api, but it not recognize urls like
// /api/rest/latest/Oro_Bundle_AddressBundle_Entity_Country
- //TODO: This should be removed in scope of https://magecore.atlassian.net/browse/BAP-8650
+ // TODO: This should be removed in scope of https://magecore.atlassian.net/browse/BAP-8650
$dictionaryRoute = $routes->cloneRoute($existingRoute);
$dictionaryRoute->setPath(
- str_replace(
- self::ENTITY_PLACEHOLDER,
- $this->entityClassNameHelper->getUrlSafeClassName($className),
- $route->getPath()
- )
+ str_replace(self::ENTITY_PLACEHOLDER, $urlSafeClassName, $route->getPath())
);
$routes->insert(
$routes->generateRouteName($existingRouteName),
@@ -109,8 +121,10 @@ protected function adjustRoutes(Route $route, RouteCollectionAccessor $routes, $
} else {
// add an additional strict route based on the base route and current entity
$strictRoute = $routes->cloneRoute($route);
- $strictRoute->setPath(str_replace(self::ENTITY_PLACEHOLDER, $entity, $strictRoute->getPath()));
- $strictRoute->setDefault(self::ENTITY_ATTRIBUTE, $entity);
+ $strictRoute->setPath(
+ str_replace(self::ENTITY_PLACEHOLDER, $pluralAlias, $strictRoute->getPath())
+ );
+ $strictRoute->setDefault(self::ENTITY_ATTRIBUTE, $pluralAlias);
$routes->insert(
$routes->generateRouteName($routeName),
$strictRoute,
@@ -119,8 +133,6 @@ protected function adjustRoutes(Route $route, RouteCollectionAccessor $routes, $
);
}
}
-
- return $result;
}
/**
diff --git a/src/Oro/Bundle/EntityBundle/Tests/Unit/Routing/DictionaryEntityRouteOptionsResolverTest.php b/src/Oro/Bundle/EntityBundle/Tests/Unit/Routing/DictionaryEntityRouteOptionsResolverTest.php
index fe545448853..d53b73bf141 100644
--- a/src/Oro/Bundle/EntityBundle/Tests/Unit/Routing/DictionaryEntityRouteOptionsResolverTest.php
+++ b/src/Oro/Bundle/EntityBundle/Tests/Unit/Routing/DictionaryEntityRouteOptionsResolverTest.php
@@ -116,7 +116,7 @@ function ($className) {
$this->routeOptionsResolver->resolve($route, $this->routeCollectionAccessor);
$this->assertEquals(
- ['dictionary' => 'statuses|Test_Status|priorities|Test_Priority|sources|Test_Source|groups|Test_Group'],
+ ['dictionary' => '\w+'],
$route->getRequirements()
);
@@ -138,7 +138,7 @@ function ($className) {
);
$this->assertEquals(
- 'statuses|Test_Status|priorities|Test_Priority|sources|Test_Source|groups|Test_Group',
+ '\w+',
$this->routeCollection->get('tested_route')->getRequirement('dictionary')
);
$this->assertEquals(
diff --git a/src/Oro/Bundle/EntityExtendBundle/Grid/AbstractFieldsExtension.php b/src/Oro/Bundle/EntityExtendBundle/Grid/AbstractFieldsExtension.php
index fbde93dc7c4..df64acd50d4 100644
--- a/src/Oro/Bundle/EntityExtendBundle/Grid/AbstractFieldsExtension.php
+++ b/src/Oro/Bundle/EntityExtendBundle/Grid/AbstractFieldsExtension.php
@@ -173,16 +173,20 @@ abstract protected function getFields(DatagridConfiguration $config);
protected function prepareColumnOptions(FieldConfigId $field, array &$columnOptions)
{
$fieldName = $field->getFieldName();
- // if field is visible as mandatory it is required in grid settings and rendered
- // and not required and hidden by default otherwise.
- $isMandatory = $this->getFieldConfig('datagrid', $field)
- ->get('is_visible') === DatagridScope::IS_VISIBLE_MANDATORY;
+
+ // if field is "visible as mandatory" it is required in grid settings and rendered
+ // if field is just "visible" it's rendered by default and manageable in grid settings
+ // otherwise - not required and hidden by default.
+ $gridVisibilityValue = $this->getFieldConfig('datagrid', $field)->get('is_visible');
+
+ $isRequired = $gridVisibilityValue === DatagridScope::IS_VISIBLE_MANDATORY;
+ $isRenderable = $isRequired ? : $gridVisibilityValue === DatagridScope::IS_VISIBLE_TRUE;
$columnOptions = [
DatagridGuesser::FORMATTER => [
'label' => $this->getFieldConfig('entity', $field)->get('label') ? : $fieldName,
- 'renderable' => $isMandatory,
- 'required' => $isMandatory
+ 'renderable' => $isRenderable,
+ 'required' => $isRequired
],
DatagridGuesser::SORTER => [
'data_name' => $fieldName
diff --git a/src/Oro/Bundle/EntityExtendBundle/Provider/EnumExclusionProvider.php b/src/Oro/Bundle/EntityExtendBundle/Provider/EnumExclusionProvider.php
new file mode 100644
index 00000000000..e0f0ca9b921
--- /dev/null
+++ b/src/Oro/Bundle/EntityExtendBundle/Provider/EnumExclusionProvider.php
@@ -0,0 +1,83 @@
+configManager = $configManager;
+ $this->snapshotSuffixOffset = -strlen(ExtendHelper::ENUM_SNAPSHOT_SUFFIX);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isIgnoredEntity($className)
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isIgnoredField(ClassMetadata $metadata, $fieldName)
+ {
+ // check for "snapshot" field of multi-enum type
+ if (substr($fieldName, $this->snapshotSuffixOffset) === ExtendHelper::ENUM_SNAPSHOT_SUFFIX) {
+ $guessedName = substr($fieldName, 0, $this->snapshotSuffixOffset);
+ if (!empty($guessedName) && $this->isMultiEnumField($metadata->name, $guessedName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isIgnoredRelation(ClassMetadata $metadata, $associationName)
+ {
+ return false;
+ }
+
+ /**
+ * @param string $className
+ * @param string $fieldName
+ *
+ * @return bool
+ */
+ protected function isMultiEnumField($className, $fieldName)
+ {
+ if ($this->configManager->hasConfig($className, $fieldName)) {
+ /** @var FieldConfigId $fieldId */
+ $fieldId = $this->configManager->getId('extend', $className, $fieldName);
+ if ($fieldId->getFieldType() === 'multiEnum') {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/Oro/Bundle/EntityExtendBundle/Provider/ExtendExclusionProvider.php b/src/Oro/Bundle/EntityExtendBundle/Provider/ExtendExclusionProvider.php
index 31915bf046f..cd1777ff55e 100644
--- a/src/Oro/Bundle/EntityExtendBundle/Provider/ExtendExclusionProvider.php
+++ b/src/Oro/Bundle/EntityExtendBundle/Provider/ExtendExclusionProvider.php
@@ -17,12 +17,25 @@ class ExtendExclusionProvider implements ExclusionProviderInterface
/** @var ConfigManager */
protected $configManager;
+ /** @var bool */
+ protected $excludeHiddenEntities;
+
+ /** @var bool */
+ protected $excludeHiddenFields;
+
/**
* @param ConfigManager $configManager
+ * @param bool $excludeHiddenEntities
+ * @param bool $excludeHiddenFields
*/
- public function __construct(ConfigManager $configManager)
- {
- $this->configManager = $configManager;
+ public function __construct(
+ ConfigManager $configManager,
+ $excludeHiddenEntities = false,
+ $excludeHiddenFields = false
+ ) {
+ $this->configManager = $configManager;
+ $this->excludeHiddenEntities = $excludeHiddenEntities;
+ $this->excludeHiddenFields = $excludeHiddenFields;
}
/**
@@ -38,7 +51,7 @@ public function isIgnoredEntity($className)
return
!ExtendHelper::isEntityAccessible($extendConfig)
- || $this->configManager->isHiddenModel($className);
+ || ($this->excludeHiddenEntities && $this->configManager->isHiddenModel($className));
}
/**
@@ -54,7 +67,7 @@ public function isIgnoredField(ClassMetadata $metadata, $fieldName)
return
!ExtendHelper::isFieldAccessible($extendFieldConfig)
- || $this->configManager->isHiddenModel($metadata->name, $fieldName);
+ || ($this->excludeHiddenFields && $this->configManager->isHiddenModel($metadata->name, $fieldName));
}
/**
@@ -68,14 +81,22 @@ public function isIgnoredRelation(ClassMetadata $metadata, $associationName)
$extendFieldConfig = $this->configManager->getFieldConfig('extend', $metadata->name, $associationName);
- return
- !ExtendHelper::isFieldAccessible($extendFieldConfig)
- || $this->configManager->isHiddenModel($metadata->name, $associationName)
- || (
- $extendFieldConfig->has('target_entity')
- && !ExtendHelper::isEntityAccessible(
- $this->configManager->getEntityConfig('extend', $extendFieldConfig->get('target_entity'))
- )
- );
+ if (!ExtendHelper::isFieldAccessible($extendFieldConfig)) {
+ return true;
+ }
+ if ($this->excludeHiddenFields && $this->configManager->isHiddenModel($metadata->name, $associationName)) {
+ return true;
+ }
+ if ($extendFieldConfig->has('target_entity')) {
+ $targetEntity = $extendFieldConfig->get('target_entity');
+ if (!ExtendHelper::isEntityAccessible($this->configManager->getEntityConfig('extend', $targetEntity))) {
+ return true;
+ }
+ if ($this->excludeHiddenEntities && $this->configManager->isHiddenModel($targetEntity)) {
+ return true;
+ }
+ }
+
+ return false;
}
}
diff --git a/src/Oro/Bundle/EntityExtendBundle/Resources/config/services.yml b/src/Oro/Bundle/EntityExtendBundle/Resources/config/services.yml
index 0ae3fb70f11..8be3b273696 100644
--- a/src/Oro/Bundle/EntityExtendBundle/Resources/config/services.yml
+++ b/src/Oro/Bundle/EntityExtendBundle/Resources/config/services.yml
@@ -391,6 +391,14 @@ services:
tags:
- { name: oro_entity.exclusion_provider.api, priority: 10 }
+ oro_entity_extend.exclusion_provider.enum:
+ class: Oro\Bundle\EntityExtendBundle\Provider\EnumExclusionProvider
+ public: false
+ arguments:
+ - @oro_entity_config.config_manager
+ tags:
+ - { name: oro_entity.exclusion_provider.api, priority: -50 }
+
oro_entity_extend.entity_alias_provider:
class: %oro_entity_extend.entity_alias_provider.class%
public: false
diff --git a/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Provider/EnumExclusionProviderTest.php b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Provider/EnumExclusionProviderTest.php
new file mode 100644
index 00000000000..e4adb762dc0
--- /dev/null
+++ b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Provider/EnumExclusionProviderTest.php
@@ -0,0 +1,112 @@
+configManager = $this->getMockBuilder('Oro\Bundle\EntityConfigBundle\Config\ConfigManager')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->exclusionProvider = new EnumExclusionProvider($this->configManager);
+ }
+
+ public function testIsIgnoredEntity()
+ {
+ $this->assertFalse(
+ $this->exclusionProvider->isIgnoredEntity(self::ENTITY_CLASS)
+ );
+ }
+
+ public function testIsIgnoredFieldWithoutMultiEnumSnapshotSuffix()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->assertFalse(
+ $this->exclusionProvider->isIgnoredField($metadata, 'test')
+ );
+ }
+
+ public function testIsIgnoredFieldForSnapshotFieldName()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->assertFalse(
+ $this->exclusionProvider->isIgnoredField($metadata, 'Snapshot')
+ );
+ }
+
+ public function testIsIgnoredFieldForNonConfigurableGuessedField()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS, 'test')
+ ->willReturn(false);
+
+ $this->assertFalse(
+ $this->exclusionProvider->isIgnoredField($metadata, 'testSnapshot')
+ );
+ }
+
+ public function testIsIgnoredFieldForNotMultiEnumSnapshot()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS, 'test')
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getId')
+ ->with('extend', self::ENTITY_CLASS, 'test')
+ ->willReturn(new FieldConfigId('extend', self::ENTITY_CLASS, 'test', 'string'));
+
+ $this->assertFalse(
+ $this->exclusionProvider->isIgnoredField($metadata, 'testSnapshot')
+ );
+ }
+
+ public function testIsIgnoredFieldForMultiEnumSnapshot()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS, 'test')
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getId')
+ ->with('extend', self::ENTITY_CLASS, 'test')
+ ->willReturn(new FieldConfigId('extend', self::ENTITY_CLASS, 'test', 'multiEnum'));
+
+ $this->assertTrue(
+ $this->exclusionProvider->isIgnoredField($metadata, 'testSnapshot')
+ );
+ }
+
+ public function testIsIgnoredRelation()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->assertFalse(
+ $this->exclusionProvider->isIgnoredRelation($metadata, 'testRelation')
+ );
+ }
+}
diff --git a/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Provider/ExtendExclusionProviderTest.php b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Provider/ExtendExclusionProviderTest.php
new file mode 100644
index 00000000000..b479ec42450
--- /dev/null
+++ b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Provider/ExtendExclusionProviderTest.php
@@ -0,0 +1,513 @@
+configManager = $this->getMockBuilder('Oro\Bundle\EntityConfigBundle\Config\ConfigManager')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function testIsIgnoredEntityForNonConfigurableEntity()
+ {
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(false);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredEntity(self::ENTITY_CLASS)
+ );
+ }
+
+ public function testIsIgnoredEntityForNotAccessibleEntity()
+ {
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getEntityConfig')
+ ->with('extend', self::ENTITY_CLASS)
+ ->willReturn($this->getEntityConfig(self::ENTITY_CLASS, ['is_extend' => true, 'is_deleted' => true]));
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertTrue(
+ $exclusionProvider->isIgnoredEntity(self::ENTITY_CLASS)
+ );
+ }
+
+ public function testIsIgnoredEntityForHiddenEntity()
+ {
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getEntityConfig')
+ ->with('extend', self::ENTITY_CLASS)
+ ->willReturn($this->getEntityConfig(self::ENTITY_CLASS));
+ $this->configManager->expects($this->never())
+ ->method('isHiddenModel');
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredEntity(self::ENTITY_CLASS)
+ );
+ }
+
+ public function testIsIgnoredEntityForHiddenEntityAndExcludeHiddenEntitiesRequested()
+ {
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getEntityConfig')
+ ->with('extend', self::ENTITY_CLASS)
+ ->willReturn($this->getEntityConfig(self::ENTITY_CLASS));
+ $this->configManager->expects($this->once())
+ ->method('isHiddenModel')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager, true);
+
+ $this->assertTrue(
+ $exclusionProvider->isIgnoredEntity(self::ENTITY_CLASS)
+ );
+ }
+
+ public function testIsIgnoredEntityForRegularEntityAndExcludeHiddenEntitiesRequested()
+ {
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getEntityConfig')
+ ->with('extend', self::ENTITY_CLASS)
+ ->willReturn($this->getEntityConfig(self::ENTITY_CLASS));
+ $this->configManager->expects($this->once())
+ ->method('isHiddenModel')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(false);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager, true);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredEntity(self::ENTITY_CLASS)
+ );
+ }
+
+ public function testIsIgnoredFieldForNonConfigurableField()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(false);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredField($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredFieldForNotAccessibleField()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(
+ $this->getFieldConfig(
+ self::ENTITY_CLASS,
+ self::FIELD_NAME,
+ ['is_extend' => true, 'is_deleted' => true]
+ )
+ );
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertTrue(
+ $exclusionProvider->isIgnoredField($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredFieldForHiddenField()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn($this->getFieldConfig(self::ENTITY_CLASS, self::FIELD_NAME));
+ $this->configManager->expects($this->never())
+ ->method('isHiddenModel');
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredField($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredFieldForHiddenFieldAndExcludeHiddenFieldsRequested()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn($this->getFieldConfig(self::ENTITY_CLASS, self::FIELD_NAME));
+ $this->configManager->expects($this->once())
+ ->method('isHiddenModel')
+ ->with(self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(true);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager, false, true);
+
+ $this->assertTrue(
+ $exclusionProvider->isIgnoredField($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredFieldForRegularFieldAndExcludeHiddenFieldsRequested()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn($this->getFieldConfig(self::ENTITY_CLASS, self::FIELD_NAME));
+ $this->configManager->expects($this->once())
+ ->method('isHiddenModel')
+ ->with(self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(false);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager, false, true);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredField($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredRelationForNonConfigurableField()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(false);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredRelation($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredRelationForNotAccessibleField()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(
+ $this->getFieldConfig(
+ self::ENTITY_CLASS,
+ self::FIELD_NAME,
+ ['is_extend' => true, 'is_deleted' => true]
+ )
+ );
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertTrue(
+ $exclusionProvider->isIgnoredRelation($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredRelationForHiddenField()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn($this->getFieldConfig(self::ENTITY_CLASS, self::FIELD_NAME));
+ $this->configManager->expects($this->never())
+ ->method('isHiddenModel');
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredRelation($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredRelationForHiddenFieldAndExcludeHiddenFieldsRequested()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn($this->getFieldConfig(self::ENTITY_CLASS, self::FIELD_NAME));
+ $this->configManager->expects($this->once())
+ ->method('isHiddenModel')
+ ->with(self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(true);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager, false, true);
+
+ $this->assertTrue(
+ $exclusionProvider->isIgnoredRelation($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredRelationForRegularFieldAndExcludeHiddenFieldsRequested()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn($this->getFieldConfig(self::ENTITY_CLASS, self::FIELD_NAME));
+ $this->configManager->expects($this->once())
+ ->method('isHiddenModel')
+ ->with(self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(false);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager, false, true);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredRelation($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredRelationForNotAccessibleTargetEntity()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(
+ $this->getFieldConfig(
+ self::ENTITY_CLASS,
+ self::FIELD_NAME,
+ ['target_entity' => 'Test\TargetEntity']
+ )
+ );
+ $this->configManager->expects($this->once())
+ ->method('getEntityConfig')
+ ->with('extend', 'Test\TargetEntity')
+ ->willReturn(
+ $this->getEntityConfig('Test\TargetEntity', ['is_extend' => true, 'is_deleted' => true])
+ );
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertTrue(
+ $exclusionProvider->isIgnoredRelation($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredRelationWithTargetEntity()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(
+ $this->getFieldConfig(
+ self::ENTITY_CLASS,
+ self::FIELD_NAME,
+ ['target_entity' => 'Test\TargetEntity']
+ )
+ );
+ $this->configManager->expects($this->once())
+ ->method('getEntityConfig')
+ ->with('extend', 'Test\TargetEntity')
+ ->willReturn(
+ $this->getEntityConfig('Test\TargetEntity')
+ );
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredRelation($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredRelationWithTargetEntityAndExcludeHiddenEntitiesRequested()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(
+ $this->getFieldConfig(
+ self::ENTITY_CLASS,
+ self::FIELD_NAME,
+ ['target_entity' => 'Test\TargetEntity']
+ )
+ );
+ $this->configManager->expects($this->once())
+ ->method('getEntityConfig')
+ ->with('extend', 'Test\TargetEntity')
+ ->willReturn(
+ $this->getEntityConfig('Test\TargetEntity')
+ );
+ $this->configManager->expects($this->once())
+ ->method('isHiddenModel')
+ ->with('Test\TargetEntity')
+ ->willReturn(false);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager, true);
+
+ $this->assertFalse(
+ $exclusionProvider->isIgnoredRelation($metadata, self::FIELD_NAME)
+ );
+ }
+
+ public function testIsIgnoredRelationWithHiddenTargetEntityAndExcludeHiddenEntitiesRequested()
+ {
+ $metadata = new ClassMetadata(self::ENTITY_CLASS);
+
+ $this->configManager->expects($this->once())
+ ->method('hasConfig')
+ ->with(self::ENTITY_CLASS)
+ ->willReturn(true);
+ $this->configManager->expects($this->once())
+ ->method('getFieldConfig')
+ ->with('extend', self::ENTITY_CLASS, self::FIELD_NAME)
+ ->willReturn(
+ $this->getFieldConfig(
+ self::ENTITY_CLASS,
+ self::FIELD_NAME,
+ ['target_entity' => 'Test\TargetEntity']
+ )
+ );
+ $this->configManager->expects($this->once())
+ ->method('getEntityConfig')
+ ->with('extend', 'Test\TargetEntity')
+ ->willReturn(
+ $this->getEntityConfig('Test\TargetEntity')
+ );
+ $this->configManager->expects($this->once())
+ ->method('isHiddenModel')
+ ->with('Test\TargetEntity')
+ ->willReturn(true);
+
+ $exclusionProvider = new ExtendExclusionProvider($this->configManager, true);
+
+ $this->assertTrue(
+ $exclusionProvider->isIgnoredRelation($metadata, self::FIELD_NAME)
+ );
+ }
+
+ /**
+ * @param string $className
+ * @param array $values
+ *
+ * @return Config
+ */
+ protected function getEntityConfig($className, $values = [])
+ {
+ $configId = new EntityConfigId('extend', $className);
+ $config = new Config($configId);
+ $config->setValues($values);
+
+ return $config;
+ }
+
+ /**
+ * @param string $className
+ * @param string $fieldName
+ * @param array $values
+ *
+ * @return Config
+ */
+ protected function getFieldConfig($className, $fieldName, $values = [])
+ {
+ $configId = new FieldConfigId('extend', $className, $fieldName);
+ $config = new Config($configId);
+ $config->setValues($values);
+
+ return $config;
+ }
+}
diff --git a/src/Oro/Bundle/EntityExtendBundle/Tools/ExtendConfigLoader.php b/src/Oro/Bundle/EntityExtendBundle/Tools/ExtendConfigLoader.php
index 40e7ca39fc7..b183e7e2c43 100644
--- a/src/Oro/Bundle/EntityExtendBundle/Tools/ExtendConfigLoader.php
+++ b/src/Oro/Bundle/EntityExtendBundle/Tools/ExtendConfigLoader.php
@@ -4,10 +4,26 @@
use Doctrine\ORM\Mapping\ClassMetadata;
+use Oro\Bundle\EntityConfigBundle\Config\EntityManagerBag;
+use Oro\Bundle\EntityConfigBundle\Config\ConfigManager;
+use Oro\Bundle\EntityConfigBundle\Config\Id\FieldConfigId;
use Oro\Bundle\EntityConfigBundle\Tools\ConfigLoader;
class ExtendConfigLoader extends ConfigLoader
{
+ /** @var int */
+ private $snapshotSuffixOffset;
+
+ /**
+ * @param ConfigManager $configManager
+ * @param EntityManagerBag $entityManagerBag
+ */
+ public function __construct(ConfigManager $configManager, EntityManagerBag $entityManagerBag)
+ {
+ parent::__construct($configManager, $entityManagerBag);
+ $this->snapshotSuffixOffset = -strlen(ExtendHelper::ENUM_SNAPSHOT_SUFFIX);
+ }
+
/**
* {@inheritdoc}
*/
@@ -21,16 +37,14 @@ protected function hasEntityConfigs(ClassMetadata $metadata)
*/
protected function hasFieldConfigs(ClassMetadata $metadata, $fieldName)
{
- $className = $metadata->getName();
- if ($this->isExtendField($className, $fieldName)) {
+ if ($this->isExtendField($metadata->name, $fieldName)) {
return false;
}
// check for "snapshot" field of multi-enum type
- $snapshotSuffixOffset = -strlen(ExtendHelper::ENUM_SNAPSHOT_SUFFIX);
- if (substr($fieldName, $snapshotSuffixOffset) === ExtendHelper::ENUM_SNAPSHOT_SUFFIX) {
- $guessedName = substr($fieldName, 0, $snapshotSuffixOffset);
- if (!empty($guessedName) && $this->isExtendField($className, $guessedName)) {
+ if (substr($fieldName, $this->snapshotSuffixOffset) === ExtendHelper::ENUM_SNAPSHOT_SUFFIX) {
+ $guessedName = substr($fieldName, 0, $this->snapshotSuffixOffset);
+ if (!empty($guessedName) && $this->isMultiEnumField($metadata->name, $guessedName)) {
return false;
}
}
@@ -43,15 +57,14 @@ protected function hasFieldConfigs(ClassMetadata $metadata, $fieldName)
*/
protected function hasAssociationConfigs(ClassMetadata $metadata, $associationName)
{
- $className = $metadata->getName();
- if ($this->isExtendField($className, $associationName)) {
+ if ($this->isExtendField($metadata->name, $associationName)) {
return false;
}
// check for default field of oneToMany or manyToMany relation
if (strpos($associationName, ExtendConfigDumper::DEFAULT_PREFIX) === 0) {
$guessedName = substr($associationName, strlen(ExtendConfigDumper::DEFAULT_PREFIX));
- if (!empty($guessedName) && $this->isExtendField($className, $guessedName)) {
+ if (!empty($guessedName) && $this->isExtendField($metadata->name, $guessedName)) {
return false;
}
}
@@ -87,4 +100,23 @@ protected function isExtendField($className, $fieldName)
return false;
}
+
+ /**
+ * @param string $className
+ * @param string $fieldName
+ *
+ * @return bool
+ */
+ protected function isMultiEnumField($className, $fieldName)
+ {
+ if ($this->configManager->hasConfig($className, $fieldName)) {
+ /** @var FieldConfigId $fieldId */
+ $fieldId = $this->configManager->getId('extend', $className, $fieldName);
+ if ($fieldId->getFieldType() === 'multiEnum') {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/src/Oro/Bundle/FilterBundle/Filter/BooleanFilter.php b/src/Oro/Bundle/FilterBundle/Filter/BooleanFilter.php
index 77bbe612747..190b0aa7af3 100644
--- a/src/Oro/Bundle/FilterBundle/Filter/BooleanFilter.php
+++ b/src/Oro/Bundle/FilterBundle/Filter/BooleanFilter.php
@@ -3,12 +3,16 @@
namespace Oro\Bundle\FilterBundle\Filter;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
+use Symfony\Component\Translation\TranslatorInterface;
use Oro\Bundle\FilterBundle\Form\Type\Filter\BooleanFilterType;
use Oro\Bundle\FilterBundle\Datasource\FilterDatasourceAdapterInterface;
class BooleanFilter extends AbstractFilter
{
+ /** @var TranslatorInterface */
+ protected $translator;
+
/**
* {@inheritdoc}
*/
@@ -71,6 +75,10 @@ function (ChoiceView $choice) {
$metadata = parent::getMetadata();
$metadata['choices'] = $choices;
+ if (!empty($metadata['placeholder'])) {
+ $metadata['placeholder'] = $this->translator->trans($metadata['placeholder']);
+ }
+
return $metadata;
}
@@ -113,4 +121,12 @@ protected function buildComparisonExpr(
return $ds->expr()->neq($fieldName, 'true');
}
}
+
+ /**
+ * @param TranslatorInterface $translator
+ */
+ public function setTranslator(TranslatorInterface $translator)
+ {
+ $this->translator = $translator;
+ }
}
diff --git a/src/Oro/Bundle/FilterBundle/Resources/config/filters.yml b/src/Oro/Bundle/FilterBundle/Resources/config/filters.yml
index 2aa8d7e7277..dbdacd0f78f 100644
--- a/src/Oro/Bundle/FilterBundle/Resources/config/filters.yml
+++ b/src/Oro/Bundle/FilterBundle/Resources/config/filters.yml
@@ -100,6 +100,8 @@ services:
arguments:
- @form.factory
- @oro_filter.filter_utility
+ calls:
+ - [setTranslator, [@translator]]
tags:
- { name: oro_filter.extension.orm_filter.filter, type: boolean }
diff --git a/src/Oro/Bundle/GoogleIntegrationBundle/Resources/translations/messages.en.yml b/src/Oro/Bundle/GoogleIntegrationBundle/Resources/translations/messages.en.yml
index f4bd4a8082e..6dec5e8341a 100644
--- a/src/Oro/Bundle/GoogleIntegrationBundle/Resources/translations/messages.en.yml
+++ b/src/Oro/Bundle/GoogleIntegrationBundle/Resources/translations/messages.en.yml
@@ -9,5 +9,5 @@ oro:
domains.tooltip: Comma separated list of allowed domains
client_id:
label: Client Id
- tooltip: Please read instructions for obtaining credentials.
+ tooltip: Please read instructions for obtaining credentials. Make sure that your OroCRM domain is included into `Authorized JavaScript origins` and `Authorized redirect URIs`.
client_secret.label: Client secret
diff --git a/src/Oro/Bundle/ImapBundle/Resources/translations/messages.en.yml b/src/Oro/Bundle/ImapBundle/Resources/translations/messages.en.yml
index a1eb652bb93..017646214b4 100644
--- a/src/Oro/Bundle/ImapBundle/Resources/translations/messages.en.yml
+++ b/src/Oro/Bundle/ImapBundle/Resources/translations/messages.en.yml
@@ -5,7 +5,7 @@ oro.imap:
fields:
enable_google_imap:
label: Enable
- tooltip: Please make sure that Gmail API is enabled in Google Developers Console. Make sure that your OroCRM domain is included into `Authorized JavaScript origins` and `Authorized redirect URIs`.
+ tooltip: Please make sure that Gmail API is enabled in Google Developers Console.
error.label: Error during Authorizing
success.label: Authorization was successfull
warning.label: If you disable this option, all users' mailboxes, configured with Gmail OAuth 2.0, will be unavailable for synchronization.
@@ -24,7 +24,7 @@ oro.imap:
smtp_encryption.label: Encryption
use_imap:
label: Enable IMAP
- tooltip: Enable imap sync with your mailbox to send and receive emails from OroCRM. If you don't know your imap credentials, contact your administrator.
+ tooltip: Enable imap sync with your mailbox to receive emails on OroCRM. If you don't know your imap credentials, contact your administrator.
use_smtp:
label: Enable SMTP
tooltip: Enable smtp sync to synchronize emails sent from OroCRM to your mailbox so you can see them in other email clients.
diff --git a/src/Oro/Bundle/IntegrationBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/IntegrationBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..3e2cc950d3c
--- /dev/null
+++ b/src/Oro/Bundle/IntegrationBundle/Resources/config/oro/api.yml
@@ -0,0 +1,5 @@
+oro_api:
+ entities:
+ Oro\Bundle\IntegrationBundle\Entity\Channel: ~
+ Oro\Bundle\IntegrationBundle\Entity\FieldsChanges: ~
+ Oro\Bundle\IntegrationBundle\Entity\Status: ~
diff --git a/src/Oro/Bundle/NavigationBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/NavigationBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..80584b72f8c
--- /dev/null
+++ b/src/Oro/Bundle/NavigationBundle/Resources/config/oro/api.yml
@@ -0,0 +1,7 @@
+oro_api:
+ entities:
+ Oro\Bundle\NavigationBundle\Entity\NavigationHistoryItem: ~
+ Oro\Bundle\NavigationBundle\Entity\NavigationItem: ~
+ Oro\Bundle\NavigationBundle\Entity\PageState: ~
+ Oro\Bundle\NavigationBundle\Entity\PinbarTab: ~
+ Oro\Bundle\NavigationBundle\Entity\Title: ~
diff --git a/src/Oro/Bundle/NoteBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/NoteBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..5d66249b5dc
--- /dev/null
+++ b/src/Oro/Bundle/NoteBundle/Resources/config/oro/api.yml
@@ -0,0 +1,3 @@
+oro_api:
+ entities:
+ Oro\Bundle\NoteBundle\Entity\Note: ~
diff --git a/src/Oro/Bundle/NotificationBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/NotificationBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..865fac8f8f5
--- /dev/null
+++ b/src/Oro/Bundle/NotificationBundle/Resources/config/oro/api.yml
@@ -0,0 +1,5 @@
+oro_api:
+ entities:
+ Oro\Bundle\NotificationBundle\Entity\EmailNotification: ~
+ Oro\Bundle\NotificationBundle\Entity\Event: ~
+ Oro\Bundle\NotificationBundle\Entity\RecipientList: ~
diff --git a/src/Oro/Bundle/OrganizationBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/OrganizationBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..d0afd62c8c5
--- /dev/null
+++ b/src/Oro/Bundle/OrganizationBundle/Resources/config/oro/api.yml
@@ -0,0 +1,4 @@
+oro_api:
+ entities:
+ Oro\Bundle\OrganizationBundle\Entity\BusinessUnit: ~
+ Oro\Bundle\OrganizationBundle\Entity\Organization: ~
diff --git a/src/Oro/Bundle/QueryDesignerBundle/QueryDesigner/Configuration.php b/src/Oro/Bundle/QueryDesignerBundle/QueryDesigner/Configuration.php
index 8dff578dbce..f0148a8d744 100644
--- a/src/Oro/Bundle/QueryDesignerBundle/QueryDesigner/Configuration.php
+++ b/src/Oro/Bundle/QueryDesignerBundle/QueryDesigner/Configuration.php
@@ -96,6 +96,7 @@ protected function getFiltersConfigTree()
->requiresAtLeastOneElement()
->prototype('scalar')->cannotBeEmpty()->end()
->end()
+ ->scalarNode('placeholder')->end()
->end()
->validate()
->always(
diff --git a/src/Oro/Bundle/ReminderBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/ReminderBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..1ae894890ca
--- /dev/null
+++ b/src/Oro/Bundle/ReminderBundle/Resources/config/oro/api.yml
@@ -0,0 +1,3 @@
+oro_api:
+ entities:
+ Oro\Bundle\ReminderBundle\Entity\Reminder: ~
diff --git a/src/Oro/Bundle/ReportBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/ReportBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..df31f48c090
--- /dev/null
+++ b/src/Oro/Bundle/ReportBundle/Resources/config/oro/api.yml
@@ -0,0 +1,4 @@
+oro_api:
+ entities:
+ Oro\Bundle\ReportBundle\Entity\Report: ~
+ Oro\Bundle\ReportBundle\Entity\ReportType: ~
diff --git a/src/Oro/Bundle/ReportBundle/Resources/config/query_designer.yml b/src/Oro/Bundle/ReportBundle/Resources/config/query_designer.yml
index ebbde9b0357..afbe0a21e52 100644
--- a/src/Oro/Bundle/ReportBundle/Resources/config/query_designer.yml
+++ b/src/Oro/Bundle/ReportBundle/Resources/config/query_designer.yml
@@ -26,3 +26,7 @@ query_designer:
- { name: Min, expr: MIN($column) }
- { name: Max, expr: MAX($column) }
query_type: [report]
+
+ filters:
+ boolean:
+ placeholder: oro.query_designer.filter.boolean.select_value.label
diff --git a/src/Oro/Bundle/ReportBundle/Resources/translations/messages.en.yml b/src/Oro/Bundle/ReportBundle/Resources/translations/messages.en.yml
index a3fe22134ce..9aff540e496 100644
--- a/src/Oro/Bundle/ReportBundle/Resources/translations/messages.en.yml
+++ b/src/Oro/Bundle/ReportBundle/Resources/translations/messages.en.yml
@@ -36,6 +36,9 @@ oro:
name: Max
hint: Maximum value
+ filter:
+ boolean.select_value.label: Select Value
+
report:
menu:
manage_reports.label: Manage Custom Reports
diff --git a/src/Oro/Bundle/SegmentBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/SegmentBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..2c239a8ae29
--- /dev/null
+++ b/src/Oro/Bundle/SegmentBundle/Resources/config/oro/api.yml
@@ -0,0 +1,4 @@
+oro_api:
+ entities:
+ Oro\Bundle\SegmentBundle\Entity\Segment: ~
+ Oro\Bundle\SegmentBundle\Entity\SegmentType: ~
diff --git a/src/Oro/Bundle/SidebarBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/SidebarBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..3395b192c28
--- /dev/null
+++ b/src/Oro/Bundle/SidebarBundle/Resources/config/oro/api.yml
@@ -0,0 +1,4 @@
+oro_api:
+ entities:
+ Oro\Bundle\SidebarBundle\Entity\SidebarState: ~
+ Oro\Bundle\SidebarBundle\Entity\Widget: ~
diff --git a/src/Oro/Bundle/TagBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/TagBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..8299d3d7df4
--- /dev/null
+++ b/src/Oro/Bundle/TagBundle/Resources/config/oro/api.yml
@@ -0,0 +1,3 @@
+oro_api:
+ entities:
+ Oro\Bundle\TagBundle\Entity\Tag: ~
diff --git a/src/Oro/Bundle/TrackingBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/TrackingBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..f1605cb0235
--- /dev/null
+++ b/src/Oro/Bundle/TrackingBundle/Resources/config/oro/api.yml
@@ -0,0 +1,5 @@
+oro_api:
+ entities:
+ Oro\Bundle\TrackingBundle\Entity\TrackingEvent: ~
+ Oro\Bundle\TrackingBundle\Entity\TrackingVisitEvent: ~
+ Oro\Bundle\TrackingBundle\Entity\TrackingWebsite: ~
diff --git a/src/Oro/Bundle/TranslationBundle/Provider/TranslationStatisticProvider.php b/src/Oro/Bundle/TranslationBundle/Provider/TranslationStatisticProvider.php
index 7b68ec637fc..8049d6eadb4 100644
--- a/src/Oro/Bundle/TranslationBundle/Provider/TranslationStatisticProvider.php
+++ b/src/Oro/Bundle/TranslationBundle/Provider/TranslationStatisticProvider.php
@@ -36,8 +36,9 @@ public function get()
if (false === $data) {
$data = $this->fetch();
-
- $this->cache->save(static::CACHE_KEY, $data, static::CACHE_TTL);
+ if (!empty($data)) {
+ $this->cache->save(static::CACHE_KEY, $data, static::CACHE_TTL);
+ }
}
return $data;
diff --git a/src/Oro/Bundle/TranslationBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/TranslationBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..ae8de444607
--- /dev/null
+++ b/src/Oro/Bundle/TranslationBundle/Resources/config/oro/api.yml
@@ -0,0 +1,3 @@
+oro_api:
+ entities:
+ Oro\Bundle\TranslationBundle\Entity\Translation: ~
diff --git a/src/Oro/Bundle/TranslationBundle/Resources/config/services.yml b/src/Oro/Bundle/TranslationBundle/Resources/config/services.yml
index 69e9e0b475c..9f2957dc592 100644
--- a/src/Oro/Bundle/TranslationBundle/Resources/config/services.yml
+++ b/src/Oro/Bundle/TranslationBundle/Resources/config/services.yml
@@ -80,6 +80,7 @@ services:
- @oro_translation.guzzle_oro_client
calls:
- [ setApiKey, [ %oro_translation.api.oro_service.key% ] ]
+ - [ setLogger, [ @logger ] ]
oro_translation.database_translation.metadata.cache:
class: %oro_translation.dynamic_translation.metadata.cache.class%
diff --git a/src/Oro/Bundle/TranslationBundle/Tests/Unit/Provider/TranslationStatisticProviderTest.php b/src/Oro/Bundle/TranslationBundle/Tests/Unit/Provider/TranslationStatisticProviderTest.php
index 5f6307147fc..94f9b1f2a20 100644
--- a/src/Oro/Bundle/TranslationBundle/Tests/Unit/Provider/TranslationStatisticProviderTest.php
+++ b/src/Oro/Bundle/TranslationBundle/Tests/Unit/Provider/TranslationStatisticProviderTest.php
@@ -51,29 +51,36 @@ public function testClear()
/**
* @dataProvider getProvider
*
- * @param mixed $cachedData
- * @param array $resultExpected
- * @param bool $fetchExpected
- * @param array $fetchedResult
- * @param bool $isException
+ * @param mixed $cachedData
+ * @param array $resultExpected
+ * @param bool $fetchExpected
+ * @param array $fetchedResult
+ * @param \Exception $exception
+ *
*/
- public function testGet($cachedData, $resultExpected, $fetchExpected, $fetchedResult = [], $isException = false)
- {
+ public function testGet(
+ $cachedData,
+ $resultExpected,
+ $fetchExpected,
+ $fetchedResult = [],
+ \Exception $exception = null
+ ) {
$this->cache->expects($this->once())->method('fetch')
->with($this->equalTo(TranslationStatisticProvider::CACHE_KEY))
->will($this->returnValue($cachedData));
if ($fetchExpected) {
- if ($isException) {
+ if (null !== $exception) {
$this->adapter->expects($this->once())->method('fetchStatistic')
- ->will($this->throwException($fetchedResult));
+ ->will($this->throwException($exception));
} else {
$this->adapter->expects($this->once())->method('fetchStatistic')
->will($this->returnValue($fetchedResult));
}
-
- $this->cache->expects($this->once())->method('save')
- ->with($this->equalTo(TranslationStatisticProvider::CACHE_KEY));
+ if (!empty($fetchedResult)) {
+ $this->cache->expects($this->once())->method('save')
+ ->with($this->equalTo(TranslationStatisticProvider::CACHE_KEY));
+ }
} else {
$this->adapter->expects($this->never())->method('fetchStatistic');
}
@@ -90,9 +97,9 @@ public function getProvider()
$testDataSet = [['code' => 'en']];
return [
- 'no cache data, fetch expected' => [false, $testDataSet, true, $testDataSet],
- 'cache data found , no fetch needed' => [$testDataSet, $testDataSet, false],
- 'exception should be caught' => [false, [], true, new \Exception(), true]
+ 'no cache data, fetch expected' => [false, $testDataSet, true, $testDataSet],
+ 'cache data found , no fetch needed' => [$testDataSet, $testDataSet, false],
+ 'exception should be caught no data saved' => [false, [], true, [], new \Exception()]
];
}
}
diff --git a/src/Oro/Bundle/UserBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/UserBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..dcb58668354
--- /dev/null
+++ b/src/Oro/Bundle/UserBundle/Resources/config/oro/api.yml
@@ -0,0 +1,6 @@
+oro_api:
+ entities:
+ Oro\Bundle\UserBundle\Entity\Email: ~
+ Oro\Bundle\UserBundle\Entity\Group: ~
+ Oro\Bundle\UserBundle\Entity\Role: ~
+ Oro\Bundle\UserBundle\Entity\User: ~
diff --git a/src/Oro/Bundle/UserBundle/Resources/config/oro/entity.yml b/src/Oro/Bundle/UserBundle/Resources/config/oro/entity.yml
index 39936e24ed4..e75e39b5c91 100644
--- a/src/Oro/Bundle/UserBundle/Resources/config/oro/entity.yml
+++ b/src/Oro/Bundle/UserBundle/Resources/config/oro/entity.yml
@@ -1,4 +1,8 @@
oro_entity:
+ exclusions:
+ - { entity: Oro\Bundle\UserBundle\Entity\User, field: password }
+ - { entity: Oro\Bundle\UserBundle\Entity\User, field: salt }
+
entity_aliases:
Oro\Bundle\UserBundle\Entity\Email:
alias: useremail
diff --git a/src/Oro/Bundle/WindowsBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/WindowsBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..798d66c90ba
--- /dev/null
+++ b/src/Oro/Bundle/WindowsBundle/Resources/config/oro/api.yml
@@ -0,0 +1,3 @@
+oro_api:
+ entities:
+ Oro\Bundle\WindowsBundle\Entity\WindowsState: ~
diff --git a/src/Oro/Bundle/WorkflowBundle/Resources/config/oro/api.yml b/src/Oro/Bundle/WorkflowBundle/Resources/config/oro/api.yml
new file mode 100644
index 00000000000..14a773b85ef
--- /dev/null
+++ b/src/Oro/Bundle/WorkflowBundle/Resources/config/oro/api.yml
@@ -0,0 +1,11 @@
+oro_api:
+ entities:
+ Oro\Bundle\WorkflowBundle\Entity\ProcessDefinition: ~
+ Oro\Bundle\WorkflowBundle\Entity\ProcessJob: ~
+ Oro\Bundle\WorkflowBundle\Entity\ProcessTrigger: ~
+ Oro\Bundle\WorkflowBundle\Entity\WorkflowDefinition: ~
+ Oro\Bundle\WorkflowBundle\Entity\WorkflowEntityAcl: ~
+ Oro\Bundle\WorkflowBundle\Entity\WorkflowEntityAclIdentity: ~
+ Oro\Bundle\WorkflowBundle\Entity\WorkflowItem: ~
+ Oro\Bundle\WorkflowBundle\Entity\WorkflowStep: ~
+ Oro\Bundle\WorkflowBundle\Entity\WorkflowTransitionRecord: ~
diff --git a/src/Oro/Component/ChainProcessor/DependencyInjection/LoadProcessorsCompilerPass.php b/src/Oro/Component/ChainProcessor/DependencyInjection/LoadProcessorsCompilerPass.php
index 1a52ceed2e7..0a47a5e1df5 100644
--- a/src/Oro/Component/ChainProcessor/DependencyInjection/LoadProcessorsCompilerPass.php
+++ b/src/Oro/Component/ChainProcessor/DependencyInjection/LoadProcessorsCompilerPass.php
@@ -60,6 +60,7 @@ public function process(ContainerBuilder $container)
*/
protected function registerProcessors(ContainerBuilder $container, Definition $processorBagServiceDef)
{
+ $isDebug = $container->getParameter('kernel.debug');
$taggedServices = $container->findTaggedServiceIds($this->processorTagName);
foreach ($taggedServices as $id => $taggedAttributes) {
foreach ($taggedAttributes as $attributes) {
@@ -77,7 +78,10 @@ protected function registerProcessors(ContainerBuilder $container, Definition $p
);
}
- unset($attributes['action'], $attributes['group'], $attributes['priority']);
+ unset($attributes['action'], $attributes['group']);
+ if (!$isDebug) {
+ unset($attributes['priority']);
+ }
$attributes = array_map(
function ($val) {
return is_string($val) && strpos($val, '&') ? explode('&', $val) : $val;
diff --git a/src/Oro/Component/ChainProcessor/Tests/Unit/DependencyInjection/LoadProcessorsCompilerPassTest.php b/src/Oro/Component/ChainProcessor/Tests/Unit/DependencyInjection/LoadProcessorsCompilerPassTest.php
index 3bab88a4d90..62cf5e0587e 100644
--- a/src/Oro/Component/ChainProcessor/Tests/Unit/DependencyInjection/LoadProcessorsCompilerPassTest.php
+++ b/src/Oro/Component/ChainProcessor/Tests/Unit/DependencyInjection/LoadProcessorsCompilerPassTest.php
@@ -4,9 +4,9 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
use Oro\Component\ChainProcessor\DependencyInjection\LoadProcessorsCompilerPass;
-use Symfony\Component\DependencyInjection\Reference;
class LoadProcessorsCompilerPassTest extends \PHPUnit_Framework_TestCase
{
@@ -38,6 +38,7 @@ public function testProcessWithoutProcessorBag()
public function testProcess()
{
$container = new ContainerBuilder();
+ $container->setParameter('kernel.debug', false);
$processorBag = new Definition('Test\ProcessorBag');
@@ -175,6 +176,7 @@ public function testProcess()
public function testProcessWithInvalidConfigurationOfCommonProcessor()
{
$container = new ContainerBuilder();
+ $container->setParameter('kernel.debug', false);
$processorBag = new Definition('Test\ProcessorBag');
diff --git a/src/Oro/Component/EntitySerializer/ConfigUtil.php b/src/Oro/Component/EntitySerializer/ConfigUtil.php
index ec56473ee34..4c7f305d4ce 100644
--- a/src/Oro/Component/EntitySerializer/ConfigUtil.php
+++ b/src/Oro/Component/EntitySerializer/ConfigUtil.php
@@ -48,9 +48,24 @@ class ConfigUtil
*/
public static function getArrayValue(array $config, $key)
{
- return isset($config[$key])
- ? $config[$key]
- : [];
+ if (!isset($config[$key])) {
+ return [];
+ }
+
+ $value = $config[$key];
+ if (is_string($value)) {
+ return [$value => null];
+ }
+ if (is_array($value)) {
+ return $value;
+ }
+
+ throw new \UnexpectedValueException(
+ sprintf(
+ 'Expected value of type "array, string or nothing", "%s" given.',
+ is_object($value) ? get_class($value) : gettype($value)
+ )
+ );
}
/**
diff --git a/src/Oro/Component/Routing/ApiDoc/ApiDocExtractor.php b/src/Oro/Component/Routing/ApiDoc/ApiDocExtractor.php
new file mode 100644
index 00000000000..3ebf03ac384
--- /dev/null
+++ b/src/Oro/Component/Routing/ApiDoc/ApiDocExtractor.php
@@ -0,0 +1,17 @@
+hasParameter(self::MATCHER_DUMPER_CLASS_PARAM)) {
+ $newClass = $this->getNewRoutingMatcherDumperClass(
+ $container->getParameter(self::MATCHER_DUMPER_CLASS_PARAM)
+ );
+ if ($newClass) {
+ $container->setParameter(self::MATCHER_DUMPER_CLASS_PARAM, $newClass);
+ }
+ }
+
+ if ($container->hasParameter(self::API_DOC_EXTRACTOR_CLASS_PARAM)) {
+ $newClass = $this->getNewApiDocExtractorClass(
+ $container->getParameter(self::API_DOC_EXTRACTOR_CLASS_PARAM)
+ );
+ if ($newClass) {
+ $container->setParameter(self::API_DOC_EXTRACTOR_CLASS_PARAM, $newClass);
+ }
+ }
+ }
+
+ /**
+ * @param string $currentClass
+ *
+ * @return string|null
+ */
+ protected function getNewRoutingMatcherDumperClass($currentClass)
+ {
+ return self::EXPECTED_MATCHER_DUMPER_CLASS === $currentClass
+ ? self::NEW_MATCHER_DUMPER_CLASS
+ : null;
+ }
+
+ /**
+ * @param string $currentClass
+ *
+ * @return string|null
+ */
+ protected function getNewApiDocExtractorClass($currentClass)
+ {
+ switch ($currentClass) {
+ case self::EXPECTED_CACHING_API_DOC_EXTRACTOR_CLASS:
+ return self::NEW_CACHING_API_DOC_EXTRACTOR_CLASS;
+ case self::EXPECTED_API_DOC_EXTRACTOR_CLASS:
+ return self::NEW_API_DOC_EXTRACTOR_CLASS;
+ default:
+ return null;
+ }
+ }
+}
+```
+
+``` php
+addCompilerPass(new HiddenRoutesPass());
+ }
+}
+```
+
+Now to hide any route just set `hidden` option to `true` for it.
+
+Here is an example of a route options resolver where this feature can be helpful:
+
+``` php
+getOption('group') !== self::ROUTE_GROUP) {
+ return;
+ }
+
+ if ($this->hasAttribute($route, self::ENTITY_PLACEHOLDER)) {
+ // generate routes for concrete entities
+ $entities = $this->getSupportedEntities();
+ if (!empty($entities)) {
+ $this->adjustRoutes($route, $routes, $entities);
+ }
+ $route->setRequirement(self::ENTITY_ATTRIBUTE, '\w+');
+
+ // mark the common route as hidden
+ $route->setOption('hidden', true);
+ }
+ }
+
+ /**
+ * @return string[]
+ */
+ protected function getSupportedEntities()
+ {
+ if (null === $this->supportedEntities) {
+ $entities = ... get supported entities ...
+
+ $this->supportedEntities = [];
+ foreach ($entities as $className) {
+ $pluralAlias = ... get entity plural alias ...
+ $this->supportedEntities[] = $pluralAlias;
+ }
+ }
+
+ return $this->supportedEntities;
+ }
+
+ /**
+ * @param Route $route
+ * @param RouteCollectionAccessor $routes
+ * @param string[] $entities
+ */
+ protected function adjustRoutes(Route $route, RouteCollectionAccessor $routes, $entities)
+ {
+ $routeName = $routes->getName($route);
+
+ foreach ($entities as $pluralAlias) {
+ $existingRoute = $routes->getByPath(
+ str_replace(self::ENTITY_PLACEHOLDER, $pluralAlias, $route->getPath()),
+ $route->getMethods()
+ );
+ if ($existingRoute) {
+ // move existing route before the common route
+ $existingRouteName = $routes->getName($existingRoute);
+ $routes->remove($existingRouteName);
+ $routes->insert($existingRouteName, $existingRoute, $routeName, true);
+ } else {
+ // add an additional strict route based on the common route and current entity
+ $strictRoute = $routes->cloneRoute($route);
+ $strictRoute->setPath(
+ str_replace(self::ENTITY_PLACEHOLDER, $pluralAlias, $strictRoute->getPath())
+ );
+ $strictRoute->setDefault(self::ENTITY_ATTRIBUTE, $pluralAlias);
+ $routes->insert(
+ $routes->generateRouteName($routeName),
+ $strictRoute,
+ $routeName,
+ true
+ );
+ }
+ }
+ }
+
+ /**
+ * Checks if a route has the given attribute
+ *
+ * @param Route $route
+ * @param string $placeholder
+ *
+ * @return bool
+ */
+ protected function hasAttribute(Route $route, $placeholder)
+ {
+ return false !== strpos($route->getPath(), $placeholder);
+ }
+}
+```
+
+The common route can be registered in `routing.yml` file, for example:
+
+``` yaml
+acme_dictionary_api:
+ resource: "@AcmeProductBundle/Controller/Api/Rest/DictionaryController.php"
+ type: rest
+ prefix: api/rest/{version}
+ requirements:
+ version: latest|v1
+ _format: json
+ defaults:
+ version: latest
+ options:
+ group: dictionary_entity
+```
diff --git a/src/Oro/Component/Routing/RouteCollectionUtil.php b/src/Oro/Component/Routing/RouteCollectionUtil.php
new file mode 100644
index 00000000000..25e3a3bfd31
--- /dev/null
+++ b/src/Oro/Component/Routing/RouteCollectionUtil.php
@@ -0,0 +1,58 @@
+all();
+ foreach ($routes as $name => $route) {
+ if (!$route->getOption('hidden')) {
+ $dest->add($name, $route);
+ }
+ }
+
+ $resources = $src->getResources();
+ foreach ($resources as $resource) {
+ $dest->addResource($resource);
+ }
+
+ return $dest;
+ }
+
+ /**
+ * Returns a copy of a given routes but without routers not marked as hidden.
+ *
+ * @param Route[] $routes
+ *
+ * @return Route[]
+ */
+ public static function filterHidden(array $routes)
+ {
+ $result = [];
+ foreach ($routes as $name => $route) {
+ if (!$route->getOption('hidden')) {
+ $result[$name] = $route;
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/src/Oro/Component/Routing/Tests/Unit/RouteCollectionUtilTest.php b/src/Oro/Component/Routing/Tests/Unit/RouteCollectionUtilTest.php
new file mode 100644
index 00000000000..e18d8bb8e83
--- /dev/null
+++ b/src/Oro/Component/Routing/Tests/Unit/RouteCollectionUtilTest.php
@@ -0,0 +1,66 @@
+add('route1', new Route('/route1'));
+ $src->add('route2', new Route('/route2', [], [], ['hidden' => true]));
+ $src->add('route3', new Route('/route3', [], [], ['hidden' => false]));
+ $src->addResource(new DirectoryResource('resource1'));
+ $src->addResource(new DirectoryResource('resource2'));
+
+ $result = RouteCollectionUtil::cloneWithoutHidden($src);
+
+ $this->assertCount(2, $result);
+ $this->assertNotNull($result->get('route1'));
+ $this->assertNotNull($result->get('route3'));
+
+ $this->assertCount(2, $result->getResources());
+ }
+
+ public function testCloneWithoutHiddenWithExistingDestination()
+ {
+ $src = new RouteCollection();
+ $src->add('route1', new Route('/route1'));
+ $src->add('route2', new Route('/route2', [], [], ['hidden' => true]));
+ $src->add('route3', new Route('/route3', [], [], ['hidden' => false]));
+ $src->addResource(new DirectoryResource('resource1'));
+ $src->addResource(new DirectoryResource('resource2'));
+
+ $dest = new RouteCollection();
+
+ $result = RouteCollectionUtil::cloneWithoutHidden($src, $dest);
+
+ $this->assertSame($dest, $result);
+
+ $this->assertCount(2, $result);
+ $this->assertNotNull($result->get('route1'));
+ $this->assertNotNull($result->get('route3'));
+
+ $this->assertCount(2, $result->getResources());
+ }
+
+ public function testFilterHidden()
+ {
+ $src = [
+ 'route1' => new Route('/route1'),
+ 'route2' => new Route('/route2', [], [], ['hidden' => true]),
+ 'route3' => new Route('/route3', [], [], ['hidden' => false])
+ ];
+
+ $result = RouteCollectionUtil::filterHidden($src);
+
+ $this->assertCount(2, $result);
+ $this->assertArrayHasKey('route1', $result);
+ $this->assertArrayHasKey('route3', $result);
+ }
+}
diff --git a/src/Oro/Component/Routing/composer.json b/src/Oro/Component/Routing/composer.json
index 542c62fbef7..5a790e3b83e 100644
--- a/src/Oro/Component/Routing/composer.json
+++ b/src/Oro/Component/Routing/composer.json
@@ -11,6 +11,9 @@
"symfony/routing": "~2.3",
"oro/php-utils": "dev-master"
},
+ "require-dev": {
+ "nelmio/api-doc-bundle": "~2.8"
+ },
"autoload": {
"psr-0": { "Oro\\Component\\Routing": "" }
},