diff --git a/CHANGELOG.md b/CHANGELOG.md index c10a8d8c518..2c02d77927f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ CHANGELOG for 1.10.0 =================== This changelog references the relevant changes (new features, changes and bugs) done in 1.10.0 versions. - * The application has been upgraded to Symfony 2.8 + * The application has been upgraded to Symfony 2.8 (Symfony 2.8.10 doesn't supported because of [Symfony issue](https://github.com/symfony/symfony/issues/19840)) * Added support php 7 * Changed minimum required php version to 5.5.9 diff --git a/UPGRADE-1.10.1.md b/UPGRADE-1.10.1.md new file mode 100644 index 00000000000..a8970d57d3a --- /dev/null +++ b/UPGRADE-1.10.1.md @@ -0,0 +1,19 @@ +UPGRADE FROM 1.10.0 to 1.10.1 +============================= + +####EntityExtendBundle +- `Oro\Bundle\EntityExtendBundle\Migration\EntityMetadataHelper` + - `getEntityClassByTableName` deprecated, use `getEntityClassesByTableName` instead + - removed property `tableToClassMap` in favour of `tableToClassesMap` +- `Oro\Bundle\EntityExtendBundle\Migration\ExtendOptionsBuilder + - construction signature was changed now it takes next arguments: + `EntityMetadataHelper` $entityMetadataHelper, + `FieldTypeHelper` $fieldTypeHelper, + `ConfigManager` $configManager + - removed property `tableToEntityMap` in favour of `tableToEntitiesMap` + - renamed method `getEntityClassName` in favour of `getEntityClassNames` +- `Oro\Bundle\EntityExtendBundle\Migration\ExtendOptionsParser` + - construction signature was changed now it takes next arguments: + `EntityMetadataHelper` $entityMetadataHelper, + `FieldTypeHelper` $fieldTypeHelper, + `ConfigManager` $configManager diff --git a/UPGRADE-1.9.9.md b/UPGRADE-1.9.9.md new file mode 100644 index 00000000000..b5c43a7093e --- /dev/null +++ b/UPGRADE-1.9.9.md @@ -0,0 +1,19 @@ +UPGRADE FROM 1.9.8 to 1.9.9 +=========================== + +####EntityExtendBundle +- `Oro\Bundle\EntityExtendBundle\Migration\EntityMetadataHelper` + - `getEntityClassByTableName` deprecated, use `getEntityClassesByTableName` instead + - removed property `tableToClassMap` in favour of `tableToClassesMap` +- `Oro\Bundle\EntityExtendBundle\Migration\ExtendOptionsBuilder + - construction signature was changed now it takes next arguments: + `EntityMetadataHelper` $entityMetadataHelper, + `FieldTypeHelper` $fieldTypeHelper, + `ConfigManager` $configManager + - removed property `tableToEntityMap` in favour of `tableToEntitiesMap` + - renamed method `getEntityClassName` in favour of `getEntityClassNames` +- `Oro\Bundle\EntityExtendBundle\Migration\ExtendOptionsParser` + - construction signature was changed now it takes next arguments: + `EntityMetadataHelper` $entityMetadataHelper, + `FieldTypeHelper` $fieldTypeHelper, + `ConfigManager` $configManager diff --git a/composer.json b/composer.json index 54f6a149044..d5c7cef31fc 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "ext-mbstring": "*", "ext-gd": "*", "ext-xml": "*", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "twig/twig": "1.24.*", "doctrine/orm": "2.5.1", "doctrine/doctrine-bundle": "1.6.3", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 65bf38cd4d5..ae2661b8956 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -37,5 +37,4 @@ - diff --git a/src/Oro/Bundle/ActionBundle/Datagrid/Extension/DeleteMassActionExtension.php b/src/Oro/Bundle/ActionBundle/Datagrid/Extension/DeleteMassActionExtension.php index 75b648ddf35..3d391168fab 100644 --- a/src/Oro/Bundle/ActionBundle/Datagrid/Extension/DeleteMassActionExtension.php +++ b/src/Oro/Bundle/ActionBundle/Datagrid/Extension/DeleteMassActionExtension.php @@ -96,7 +96,7 @@ protected function getDatagridContext(DatagridConfiguration $config) { return [ ContextHelper::ENTITY_CLASS_PARAM => $this->gridConfigurationHelper->getEntity($config), - ContextHelper::DATAGRID_PARAM => $config->offsetGetByPath('[name]'), + ContextHelper::DATAGRID_PARAM => $config->getName(), ContextHelper::GROUP_PARAM => $this->groups, ]; } diff --git a/src/Oro/Bundle/ActionBundle/Resources/config/services.yml b/src/Oro/Bundle/ActionBundle/Resources/config/services.yml index 2ccadd6ef25..ff4e1f096fe 100644 --- a/src/Oro/Bundle/ActionBundle/Resources/config/services.yml +++ b/src/Oro/Bundle/ActionBundle/Resources/config/services.yml @@ -162,7 +162,6 @@ services: - [setGroups, [['', 'datagridRowAction']]] tags: - { name: oro_datagrid.extension } - lazy: true oro_action.datagrid.action.action_widget_action: class: %oro_action.datagrid.action.action_widget_action.class% diff --git a/src/Oro/Bundle/ActivityBundle/composer.json b/src/Oro/Bundle/ActivityBundle/composer.json index 956744dee36..de6d0386911 100644 --- a/src/Oro/Bundle/ActivityBundle/composer.json +++ b/src/Oro/Bundle/ActivityBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/ui-bundle": "dev-master", "oro/entity-config-bundle": "dev-master", "oro/entity-extend-bundle": "dev-master", diff --git a/src/Oro/Bundle/ActivityListBundle/Entity/Manager/ActivityListManager.php b/src/Oro/Bundle/ActivityListBundle/Entity/Manager/ActivityListManager.php index d68f1951707..02a14c2aba3 100644 --- a/src/Oro/Bundle/ActivityListBundle/Entity/Manager/ActivityListManager.php +++ b/src/Oro/Bundle/ActivityListBundle/Entity/Manager/ActivityListManager.php @@ -255,7 +255,8 @@ protected function getListDataIdsForInheritances(QueryBuilder $qb, $entityClass, $inheritanceQb, $inheritanceTarget, $key, - ':entityId' + ':entityId', + $this->config->get('oro_activity_list.grouping') ); $this->activityListFilterHelper->addFiltersToQuery($inheritanceQb, $filter); diff --git a/src/Oro/Bundle/ActivityListBundle/Helper/ActivityInheritanceTargetsHelper.php b/src/Oro/Bundle/ActivityListBundle/Helper/ActivityInheritanceTargetsHelper.php index 49196f19d63..2997984ba38 100644 --- a/src/Oro/Bundle/ActivityListBundle/Helper/ActivityInheritanceTargetsHelper.php +++ b/src/Oro/Bundle/ActivityListBundle/Helper/ActivityInheritanceTargetsHelper.php @@ -54,8 +54,9 @@ public function hasInheritances($entityClass) * @param array $inheritanceTarget * @param string $aliasSuffix * @param string $entityIdExpr + * @param bool $head Head activity only */ - public function applyInheritanceActivity(QueryBuilder $qb, $inheritanceTarget, $aliasSuffix, $entityIdExpr) + public function applyInheritanceActivity(QueryBuilder $qb, $inheritanceTarget, $aliasSuffix, $entityIdExpr, $head) { $alias = 'ta_' . $aliasSuffix; $qb->leftJoin('activity.' . $inheritanceTarget['targetClassAlias'], $alias); @@ -68,6 +69,9 @@ public function applyInheritanceActivity(QueryBuilder $qb, $inheritanceTarget, $ $aliasSuffix )->getDQL() )); + if ($head) { + $qb->andWhere($qb->expr()->andX('activity.head = true')); + } } /** diff --git a/src/Oro/Bundle/ActivityListBundle/Model/ActivityListProviderInterface.php b/src/Oro/Bundle/ActivityListBundle/Model/ActivityListProviderInterface.php index 0cba3721920..63aa95ddea7 100644 --- a/src/Oro/Bundle/ActivityListBundle/Model/ActivityListProviderInterface.php +++ b/src/Oro/Bundle/ActivityListBundle/Model/ActivityListProviderInterface.php @@ -30,6 +30,8 @@ public function isApplicableTarget($entityClass, $accessible = true); public function getSubject($entity); /** + * Return text representation. Should be a plain text. + * * @param object $entity * * @return string|null diff --git a/src/Oro/Bundle/ActivityListBundle/Provider/ActivityListChainProvider.php b/src/Oro/Bundle/ActivityListBundle/Provider/ActivityListChainProvider.php index 661c1d9d9b6..178697a1dee 100644 --- a/src/Oro/Bundle/ActivityListBundle/Provider/ActivityListChainProvider.php +++ b/src/Oro/Bundle/ActivityListBundle/Provider/ActivityListChainProvider.php @@ -417,8 +417,7 @@ protected function getActivityListEntityForEntity( } $list->setSubject($provider->getSubject($entity)); - //do not use htmlTagHelper->purify - it leads to a huge performance degradation - $list->setDescription(trim(strip_tags($provider->getDescription($entity)))); + $list->setDescription($provider->getDescription($entity)); $this->setDate($entity, $provider, $list); $list->setOwner($provider->getOwner($entity)); if ($provider instanceof ActivityListUpdatedByProviderInterface) { diff --git a/src/Oro/Bundle/ActivityListBundle/Tests/Unit/Helper/ActivityInheritanceTargetsHelperTest.php b/src/Oro/Bundle/ActivityListBundle/Tests/Unit/Helper/ActivityInheritanceTargetsHelperTest.php index c2da6c4106f..719db4db9cb 100644 --- a/src/Oro/Bundle/ActivityListBundle/Tests/Unit/Helper/ActivityInheritanceTargetsHelperTest.php +++ b/src/Oro/Bundle/ActivityListBundle/Tests/Unit/Helper/ActivityInheritanceTargetsHelperTest.php @@ -113,25 +113,35 @@ public function testHasInheritancesConfigured() public function testApplyInheritanceActivity() { - $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') - ->disableOriginalConstructor() - ->getMock(); - $expr = new Expr(); - $em->expects($this->any())->method('getExpressionBuilder')->willReturn($expr); + $mainQb = $this->prepareMock(); + $this->activityInheritanceTargetsHelper->applyInheritanceActivity( + $mainQb, + [ + 'targetClass' => 'Acme\Bundle\AcmeBundle\Entity\Contact', + 'targetClassAlias' => 'contact_e8d5b2ba', + 'path' => [ + 'accounts' + ] + ], + 0, + ':entityId', + false + ); - $mainQb = new QueryBuilder($em); - $inheritedQb = clone $mainQb; + $expectedDQL = 'SELECT activity.id, activity.updatedAt ' + . 'FROM ActivityList activity ' + . 'LEFT JOIN activity.contact_e8d5b2ba ta_0 ' + . 'WHERE ta_0.id IN(SELECT inherit_0.id' + . ' FROM Acme\Bundle\AcmeBundle\Entity\Contact inherit_0' + . ' INNER JOIN inherit_0.accounts t_0_0 WHERE t_0_0.id = :entityId)'; - $mainQb->select('activity.id, activity.updatedAt'); - $mainQb->from('ActivityList', 'activity'); + $this->assertSame($expectedDQL, $mainQb->getDQL()); + } - $em->expects($this->any()) - ->method('createQueryBuilder') - ->willReturn($inheritedQb); - $this->registry->expects($this->any()) - ->method('getManagerForClass') - ->willReturn($em); + public function testApplyInheritanceActivityHeadOnly() + { + $mainQb = $this->prepareMock(); $this->activityInheritanceTargetsHelper->applyInheritanceActivity( $mainQb, @@ -143,7 +153,8 @@ public function testApplyInheritanceActivity() ] ], 0, - ':entityId' + ':entityId', + true ); $expectedDQL = 'SELECT activity.id, activity.updatedAt ' @@ -151,8 +162,35 @@ public function testApplyInheritanceActivity() . 'LEFT JOIN activity.contact_e8d5b2ba ta_0 ' . 'WHERE ta_0.id IN(SELECT inherit_0.id' . ' FROM Acme\Bundle\AcmeBundle\Entity\Contact inherit_0' - . ' INNER JOIN inherit_0.accounts t_0_0 WHERE t_0_0.id = :entityId)'; + . ' INNER JOIN inherit_0.accounts t_0_0 WHERE t_0_0.id = :entityId) AND activity.head = true'; $this->assertSame($expectedDQL, $mainQb->getDQL()); } + + /** + * @return QueryBuilder + */ + protected function prepareMock() + { + $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + $expr = new Expr(); + $em->expects($this->any())->method('getExpressionBuilder')->willReturn($expr); + + $mainQb = new QueryBuilder($em); + $inheritedQb = clone $mainQb; + + $mainQb->select('activity.id, activity.updatedAt'); + $mainQb->from('ActivityList', 'activity'); + + $em->expects($this->any()) + ->method('createQueryBuilder') + ->willReturn($inheritedQb); + $this->registry->expects($this->any()) + ->method('getManagerForClass') + ->willReturn($em); + + return $mainQb; + } } diff --git a/src/Oro/Bundle/ActivityListBundle/composer.json b/src/Oro/Bundle/ActivityListBundle/composer.json index 37bc6faa7be..53093d1a258 100644 --- a/src/Oro/Bundle/ActivityListBundle/composer.json +++ b/src/Oro/Bundle/ActivityListBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/ui-bundle": "dev-master", "oro/organization-bundle": "dev-master", "oro/entity-config-bundle": "dev-master", diff --git a/src/Oro/Bundle/AddressBundle/composer.json b/src/Oro/Bundle/AddressBundle/composer.json index 0df57c1b249..618653b7298 100644 --- a/src/Oro/Bundle/AddressBundle/composer.json +++ b/src/Oro/Bundle/AddressBundle/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "doctrine/orm": ">=2.2.3,<2.5-dev", "doctrine/doctrine-bundle": "1.1.*", "friendsofsymfony/rest-bundle": "1.5.0-RC2", diff --git a/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConfigurationCompilerPass.php b/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConfigurationCompilerPass.php index bd5a8b1de67..39c3fef91ab 100644 --- a/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConfigurationCompilerPass.php +++ b/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConfigurationCompilerPass.php @@ -297,7 +297,7 @@ protected function getApiFormTypeGuessers(ContainerBuilder $container) public function getTagKeyForExtension() { return method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix') - ? 'extended-type' + ? 'extended_type' : 'alias'; } } diff --git a/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConstraintTextExtractorConfigurationCompilerPass.php b/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConstraintTextExtractorConfigurationCompilerPass.php new file mode 100644 index 00000000000..b5d505dc0e2 --- /dev/null +++ b/src/Oro/Bundle/ApiBundle/DependencyInjection/Compiler/ConstraintTextExtractorConfigurationCompilerPass.php @@ -0,0 +1,27 @@ +setStatusCode(Response::HTTP_BAD_REQUEST); + return self::create($title, $detail)->setStatusCode($statusCode); } /** diff --git a/src/Oro/Bundle/ApiBundle/OroApiBundle.php b/src/Oro/Bundle/ApiBundle/OroApiBundle.php index 332ed75c4f8..7f4303d00dd 100644 --- a/src/Oro/Bundle/ApiBundle/OroApiBundle.php +++ b/src/Oro/Bundle/ApiBundle/OroApiBundle.php @@ -10,6 +10,7 @@ use Oro\Component\ChainProcessor\DependencyInjection\LoadProcessorsCompilerPass; use Oro\Bundle\ApiBundle\DependencyInjection\Compiler\ApiDocConfigurationCompilerPass; use Oro\Bundle\ApiBundle\DependencyInjection\Compiler\ConfigurationCompilerPass; +use Oro\Bundle\ApiBundle\DependencyInjection\Compiler\ConstraintTextExtractorConfigurationCompilerPass; use Oro\Bundle\ApiBundle\DependencyInjection\Compiler\DataTransformerConfigurationCompilerPass; use Oro\Bundle\ApiBundle\DependencyInjection\Compiler\EntityAliasesConfigurationCompilerPass; use Oro\Bundle\ApiBundle\DependencyInjection\Compiler\ExceptionTextExtractorConfigurationCompilerPass; @@ -29,6 +30,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new EntityAliasesConfigurationCompilerPass()); $container->addCompilerPass(new ExclusionProviderConfigurationCompilerPass()); $container->addCompilerPass(new ExceptionTextExtractorConfigurationCompilerPass()); + $container->addCompilerPass(new ConstraintTextExtractorConfigurationCompilerPass()); $container->addCompilerPass( new LoadProcessorsCompilerPass( 'oro_api.processor_bag', diff --git a/src/Oro/Bundle/ApiBundle/Processor/Shared/CollectFormErrors.php b/src/Oro/Bundle/ApiBundle/Processor/Shared/CollectFormErrors.php index b7afe8410c4..d32abfe6e61 100644 --- a/src/Oro/Bundle/ApiBundle/Processor/Shared/CollectFormErrors.php +++ b/src/Oro/Bundle/ApiBundle/Processor/Shared/CollectFormErrors.php @@ -10,17 +10,27 @@ use Oro\Component\ChainProcessor\ContextInterface; use Oro\Component\ChainProcessor\ProcessorInterface; use Oro\Bundle\ApiBundle\Processor\FormContext; +use Oro\Bundle\ApiBundle\Request\ConstraintTextExtractorInterface; use Oro\Bundle\ApiBundle\Model\Error; use Oro\Bundle\ApiBundle\Model\ErrorSource; use Oro\Bundle\ApiBundle\Request\Constraint; -use Oro\Bundle\ApiBundle\Validator\Constraints\ConstraintWithStatusCodeInterface; -use Oro\Bundle\ApiBundle\Util\ValueNormalizerUtil; /** * Collects errors occurred during the the form submit and adds them into the Context. */ class CollectFormErrors implements ProcessorInterface { + /** @var ConstraintTextExtractorInterface */ + protected $constraintTextExtractor; + + /** + * @param ConstraintTextExtractorInterface $constraintTextExtractor + */ + public function __construct(ConstraintTextExtractorInterface $constraintTextExtractor) + { + $this->constraintTextExtractor = $constraintTextExtractor; + } + /** * {@inheritdoc} */ @@ -176,7 +186,7 @@ protected function getFormErrorTitle(FormError $formError) return Constraint::EXTRA_FIELDS; } - return ValueNormalizerUtil::humanizeClassName(get_class($cause->getConstraint()), 'Constraint'); + return $this->constraintTextExtractor->getConstraintType($cause->getConstraint()); } // undefined constraint type @@ -193,9 +203,7 @@ protected function getFormErrorStatusCode(FormError $formError) $cause = $formError->getCause(); if ($cause instanceof ConstraintViolation) { $constraint = $cause->getConstraint(); - if ($constraint instanceof ConstraintWithStatusCodeInterface) { - return $constraint->getStatusCode(); - } + return $this->constraintTextExtractor->getConstraintStatusCode($constraint); } return null; diff --git a/src/Oro/Bundle/ApiBundle/Processor/Shared/JsonApi/ValidateRequestData.php b/src/Oro/Bundle/ApiBundle/Processor/Shared/JsonApi/ValidateRequestData.php index ef16a039571..14214c25454 100644 --- a/src/Oro/Bundle/ApiBundle/Processor/Shared/JsonApi/ValidateRequestData.php +++ b/src/Oro/Bundle/ApiBundle/Processor/Shared/JsonApi/ValidateRequestData.php @@ -2,6 +2,8 @@ namespace Oro\Bundle\ApiBundle\Processor\Shared\JsonApi; +use Symfony\Component\HttpFoundation\Response; + use Oro\Component\ChainProcessor\ContextInterface; use Oro\Component\ChainProcessor\ProcessorInterface; use Oro\Component\PhpUtils\ArrayUtil; @@ -288,10 +290,11 @@ protected function buildPointer($parentPath, $property) /** * @param string $pointer * @param string $message + * @param integer|null $statusCode */ - protected function addError($pointer, $message) + protected function addError($pointer, $message, $statusCode = Response::HTTP_BAD_REQUEST) { - $error = Error::createValidationError(Constraint::REQUEST_DATA, $message) + $error = Error::createValidationError(Constraint::REQUEST_DATA, $message, $statusCode) ->setSource(ErrorSource::createByPointer($pointer)); $this->context->addError($error); diff --git a/src/Oro/Bundle/ApiBundle/Processor/Update/JsonApi/ValidateRequestData.php b/src/Oro/Bundle/ApiBundle/Processor/Update/JsonApi/ValidateRequestData.php index 7065522497a..86679a943d8 100644 --- a/src/Oro/Bundle/ApiBundle/Processor/Update/JsonApi/ValidateRequestData.php +++ b/src/Oro/Bundle/ApiBundle/Processor/Update/JsonApi/ValidateRequestData.php @@ -4,6 +4,7 @@ use Oro\Bundle\ApiBundle\Processor\Shared\JsonApi\ValidateRequestData as BaseProcessor; use Oro\Bundle\ApiBundle\Request\JsonApi\JsonApiDocumentBuilder as JsonApiDoc; +use Symfony\Component\HttpFoundation\Response; /** * Validates that the request data contains valid JSON.API object. diff --git a/src/Oro/Bundle/ApiBundle/Request/ChainConstraintTextExtractor.php b/src/Oro/Bundle/ApiBundle/Request/ChainConstraintTextExtractor.php new file mode 100644 index 00000000000..c4268a95f92 --- /dev/null +++ b/src/Oro/Bundle/ApiBundle/Request/ChainConstraintTextExtractor.php @@ -0,0 +1,71 @@ +extractors[] = $extractor; + } + + /** + * {@inheritdoc} + */ + public function getConstraintStatusCode(Validator\Constraint $constraint) + { + $result = null; + foreach ($this->extractors as $extractor) { + $result = $extractor->getConstraintStatusCode($constraint); + if (null !== $result) { + break; + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getConstraintCode(Validator\Constraint $constraint) + { + $result = null; + foreach ($this->extractors as $extractor) { + $result = $extractor->getConstraintCode($constraint); + if (null !== $result) { + break; + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getConstraintType(Validator\Constraint $constraint) + { + $result = null; + foreach ($this->extractors as $extractor) { + $result = $extractor->getConstraintType($constraint); + if (null !== $result) { + break; + } + } + + return $result; + } +} diff --git a/src/Oro/Bundle/ApiBundle/Request/ConstraintTextExtractor.php b/src/Oro/Bundle/ApiBundle/Request/ConstraintTextExtractor.php new file mode 100644 index 00000000000..b5a18922f5e --- /dev/null +++ b/src/Oro/Bundle/ApiBundle/Request/ConstraintTextExtractor.php @@ -0,0 +1,40 @@ +getStatusCode(); + } + + return Response::HTTP_BAD_REQUEST; + } + + /** + * {@inheritdoc} + */ + public function getConstraintCode(Validator\Constraint $constraint) + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getConstraintType(Validator\Constraint $constraint) + { + return ValueNormalizerUtil::humanizeClassName(get_class($constraint), 'Constraint'); + } +} diff --git a/src/Oro/Bundle/ApiBundle/Request/ConstraintTextExtractorInterface.php b/src/Oro/Bundle/ApiBundle/Request/ConstraintTextExtractorInterface.php new file mode 100644 index 00000000000..9b6d8d3bedd --- /dev/null +++ b/src/Oro/Bundle/ApiBundle/Request/ConstraintTextExtractorInterface.php @@ -0,0 +1,35 @@ +context->setClassName('Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Product'); $this->context->setRequestData($requestData); @@ -77,7 +77,7 @@ public function testProcessWithInvalidRequestData($requestData, $expectedErrorSt $errors = $this->context->getErrors(); $this->assertCount(1, $errors); $error = $errors[0]; - $this->assertEquals(400, $error->getStatusCode()); + $this->assertEquals($expectedCode, $error->getStatusCode()); $this->assertEquals('request data constraint', $error->getTitle()); $this->assertEquals($expectedErrorString, $error->getDetail()); $this->assertEquals($pointer, $error->getSource()->getPointer()); diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/Shared/CollectFormErrorsTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/Shared/CollectFormErrorsTest.php index f6563fb032d..27a50a56893 100644 --- a/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/Shared/CollectFormErrorsTest.php +++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/Shared/CollectFormErrorsTest.php @@ -8,6 +8,7 @@ use Oro\Bundle\ApiBundle\Model\Error; use Oro\Bundle\ApiBundle\Model\ErrorSource; use Oro\Bundle\ApiBundle\Processor\Shared\CollectFormErrors; +use Oro\Bundle\ApiBundle\Request\ConstraintTextExtractor; use Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\FormType\NameValuePairType; use Oro\Bundle\ApiBundle\Tests\Unit\Processor\FormProcessorTestCase; @@ -23,7 +24,7 @@ public function setUp() { parent::setUp(); - $this->processor = new CollectFormErrors(); + $this->processor = new CollectFormErrors(new ConstraintTextExtractor()); } public function testProcessWithoutForm() diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/Subresource/Shared/CollectFormErrorsTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/Subresource/Shared/CollectFormErrorsTest.php index f1c33cf3090..048f9bc9e61 100644 --- a/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/Subresource/Shared/CollectFormErrorsTest.php +++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Processor/Subresource/Shared/CollectFormErrorsTest.php @@ -2,6 +2,7 @@ namespace Oro\Bundle\ApiBundle\Tests\Unit\Processor\Subresource\Shared; +use Oro\Bundle\ApiBundle\Request\ConstraintTextExtractor; use Symfony\Component\Validator\Constraints; use Oro\Bundle\ApiBundle\Model\Error; @@ -21,7 +22,7 @@ public function setUp() { parent::setUp(); - $this->processor = new CollectFormErrors(); + $this->processor = new CollectFormErrors(new ConstraintTextExtractor()); } public function testErrorPropertyPathShouldBeEmptyStringForToOneAssociationRelatedError() diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/ChainConstraintTextExtractorTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/ChainConstraintTextExtractorTest.php new file mode 100644 index 00000000000..39a336bea66 --- /dev/null +++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/ChainConstraintTextExtractorTest.php @@ -0,0 +1,166 @@ +extractor = new ChainConstraintTextExtractor(); + + $firstExtractor = $this->getMock('Oro\Bundle\ApiBundle\Request\ConstraintTextExtractorInterface'); + $secondExtractor = $this->getMock('Oro\Bundle\ApiBundle\Request\ConstraintTextExtractorInterface'); + + $this->extractor->addExtractor($firstExtractor); + $this->extractor->addExtractor($secondExtractor); + + $this->extractors = [$firstExtractor, $secondExtractor]; + } + + public function testGetConstraintStatusCodeByFirstExtractor() + { + $constraint = new AccessGranted(); + + $this->extractors[0]->expects($this->once()) + ->method('getConstraintStatusCode') + ->with($this->identicalTo($constraint)) + ->willReturn(400); + $this->extractors[1]->expects($this->never()) + ->method('getConstraintStatusCode'); + + $this->assertEquals(400, $this->extractor->getConstraintStatusCode($constraint)); + } + + public function testGetConstraintStatusCodeBySecondExtractor() + { + $constraint = new AccessGranted(); + + $this->extractors[0]->expects($this->once()) + ->method('getConstraintStatusCode') + ->with($this->identicalTo($constraint)) + ->willReturn(null); + $this->extractors[1]->expects($this->once()) + ->method('getConstraintStatusCode') + ->with($this->identicalTo($constraint)) + ->willReturn(401); + + $this->assertEquals(401, $this->extractor->getConstraintStatusCode($constraint)); + } + + public function testGetConstraintStatusCodeWithNullResult() + { + $constraint = new AccessGranted(); + + $this->extractors[0]->expects($this->once()) + ->method('getConstraintStatusCode') + ->with($this->identicalTo($constraint)) + ->willReturn(null); + $this->extractors[1]->expects($this->once()) + ->method('getConstraintStatusCode') + ->with($this->identicalTo($constraint)) + ->willReturn(null); + + $this->assertNull($this->extractor->getConstraintStatusCode($constraint)); + } + + public function testGetConstraintCodeByFirstExtractor() + { + $constraint = new AccessGranted(); + + $this->extractors[0]->expects($this->once()) + ->method('getConstraintCode') + ->with($this->identicalTo($constraint)) + ->willReturn(645); + $this->extractors[1]->expects($this->never()) + ->method('getConstraintCode'); + + $this->assertEquals(645, $this->extractor->getConstraintCode($constraint)); + } + + public function testGetConstraintCodeBySecondExtractor() + { + $constraint = new AccessGranted(); + + $this->extractors[0]->expects($this->once()) + ->method('getConstraintCode') + ->with($this->identicalTo($constraint)) + ->willReturn(null); + $this->extractors[1]->expects($this->once()) + ->method('getConstraintCode') + ->with($this->identicalTo($constraint)) + ->willReturn(8456); + + $this->assertEquals(8456, $this->extractor->getConstraintCode($constraint)); + } + + public function testGetConstraintCodeWithNullResult() + { + $constraint = new AccessGranted(); + + $this->extractors[0]->expects($this->once()) + ->method('getConstraintCode') + ->with($this->identicalTo($constraint)) + ->willReturn(null); + $this->extractors[1]->expects($this->once()) + ->method('getConstraintCode') + ->with($this->identicalTo($constraint)) + ->willReturn(null); + + $this->assertNull($this->extractor->getConstraintCode($constraint)); + } + + public function testGetConstraintTypeByFirstExtractor() + { + $constraint = new AccessGranted(); + + $this->extractors[0]->expects($this->once()) + ->method('getConstraintType') + ->with($this->identicalTo($constraint)) + ->willReturn('first extractor type'); + $this->extractors[1]->expects($this->never()) + ->method('getConstraintType'); + + $this->assertEquals('first extractor type', $this->extractor->getConstraintType($constraint)); + } + + public function testGetConstraintTypeBySecondExtractor() + { + $constraint = new AccessGranted(); + + $this->extractors[0]->expects($this->once()) + ->method('getConstraintType') + ->with($this->identicalTo($constraint)) + ->willReturn(null); + $this->extractors[1]->expects($this->once()) + ->method('getConstraintType') + ->with($this->identicalTo($constraint)) + ->willReturn('second extractor type'); + + $this->assertEquals('second extractor type', $this->extractor->getConstraintType($constraint)); + } + + public function testGetConstraintTypWithNullResult() + { + $constraint = new AccessGranted(); + + $this->extractors[0]->expects($this->once()) + ->method('getConstraintType') + ->with($this->identicalTo($constraint)) + ->willReturn(null); + $this->extractors[1]->expects($this->once()) + ->method('getConstraintType') + ->with($this->identicalTo($constraint)) + ->willReturn(null); + + $this->assertNull($this->extractor->getConstraintType($constraint)); + } +} diff --git a/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/ConstraintTextExtractorTest.php b/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/ConstraintTextExtractorTest.php new file mode 100644 index 00000000000..9b3185a2410 --- /dev/null +++ b/src/Oro/Bundle/ApiBundle/Tests/Unit/Request/ConstraintTextExtractorTest.php @@ -0,0 +1,66 @@ +constraintTextExtractor = new ConstraintTextExtractor(); + } + + /** + * @dataProvider getConstraintStatusCodeDataProvider() + */ + public function testGetConstraintStatusCode(Constraint $constraint, $expectedStatusCode) + { + $this->assertEquals( + $expectedStatusCode, + $this->constraintTextExtractor->getConstraintStatusCode($constraint) + ); + } + + public function getConstraintStatusCodeDataProvider() + { + return [ + [new Blank(), 400], + [new HasAdderAndRemover(['class' => 'Test\Class', 'property' => 'test']), 501], + ]; + } + + public function testGetConstraintCode() + { + $this->assertNull($this->constraintTextExtractor->getConstraintCode(new Blank())); + } + + /** + * @dataProvider getConstraintTypeDataProvider + */ + public function testConstraintType(Constraint $constraint, $expectedType) + { + $this->assertEquals( + $expectedType, + $this->constraintTextExtractor->getConstraintType($constraint) + ); + } + + public function getConstraintTypeDataProvider() + { + return [ + [new Blank(), 'blank constraint'], + [ + new HasAdderAndRemover(['class' => 'Test\Class', 'property' => 'test']), + 'has adder and remover constraint' + ], + ]; + } +} diff --git a/src/Oro/Bundle/AsseticBundle/composer.json b/src/Oro/Bundle/AsseticBundle/composer.json index ccecf4c0dc3..5487c91d84f 100644 --- a/src/Oro/Bundle/AsseticBundle/composer.json +++ b/src/Oro/Bundle/AsseticBundle/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "kriswallsmith/assetic": "1.1.*@dev", "oro/config": "dev-master" }, diff --git a/src/Oro/Bundle/AttachmentBundle/composer.json b/src/Oro/Bundle/AttachmentBundle/composer.json index 81bd39d39b3..bbd68a4dac0 100644 --- a/src/Oro/Bundle/AttachmentBundle/composer.json +++ b/src/Oro/Bundle/AttachmentBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "liip/imagine-bundle": "dev-master", "knplabs/knp-gaufrette-bundle": "dev-master" }, diff --git a/src/Oro/Bundle/BatchBundle/ORM/QueryBuilder/CountQueryBuilderOptimizer.php b/src/Oro/Bundle/BatchBundle/ORM/QueryBuilder/CountQueryBuilderOptimizer.php index 7a7866f76bb..56cb89ed8eb 100644 --- a/src/Oro/Bundle/BatchBundle/ORM/QueryBuilder/CountQueryBuilderOptimizer.php +++ b/src/Oro/Bundle/BatchBundle/ORM/QueryBuilder/CountQueryBuilderOptimizer.php @@ -110,12 +110,12 @@ protected function buildCountQueryBuilder() ); } - if ($originalQueryParts['join']) { - $this->addJoins($optimizedQueryBuilder, $originalQueryParts); - } if (!$originalQueryParts['groupBy']) { $fieldsToSelect = $this->getFieldsToSelect($originalQueryParts); } + if ($originalQueryParts['join']) { + $this->addJoins($optimizedQueryBuilder, $originalQueryParts, $this->useNonSymmetricJoins($fieldsToSelect)); + } if ($originalQueryParts['where']) { $optimizedQueryBuilder->where( @@ -129,14 +129,30 @@ protected function buildCountQueryBuilder() return $optimizedQueryBuilder; } + /** + * Method to check if using of non symmetric joins is required (if they will affect number of rows or not). + * + * @param array $fieldsToSelect + * + * @return bool + */ + protected function useNonSymmetricJoins(array $fieldsToSelect) + { + return count($fieldsToSelect) !== 1 || stripos(reset($fieldsToSelect), 'DISTINCT(') !== 0; + } + /** * Add required JOINs to resulting Query Builder. * * @param QueryBuilder $optimizedQueryBuilder * @param array $originalQueryParts + * @param bool $useNonSymmetricJoins */ - protected function addJoins(QueryBuilder $optimizedQueryBuilder, array $originalQueryParts) - { + protected function addJoins( + QueryBuilder $optimizedQueryBuilder, + array $originalQueryParts, + $useNonSymmetricJoins = true + ) { // Collect list of tables which should be added to new query $whereAliases = $this->qbTools->getUsedTableAliases($originalQueryParts['where']); $groupByAliases = $this->qbTools->getUsedTableAliases($originalQueryParts['groupBy']); @@ -147,14 +163,16 @@ protected function addJoins(QueryBuilder $optimizedQueryBuilder, array $original // this joins cannot be removed outside of this class $requiredJoinAliases = $joinAliases; - $joinAliases = array_merge( - $joinAliases, - $this->getNonSymmetricJoinAliases( - $originalQueryParts['from'], - $originalQueryParts['join'], - $groupByAliases - ) - ); + if ($useNonSymmetricJoins) { + $joinAliases = array_merge( + $joinAliases, + $this->getNonSymmetricJoinAliases( + $originalQueryParts['from'], + $originalQueryParts['join'], + $groupByAliases + ) + ); + } $rootAliases = []; /** @var Expr\From $from */ diff --git a/src/Oro/Bundle/BatchBundle/composer.json b/src/Oro/Bundle/BatchBundle/composer.json index d4dbed4dfc2..c70a30d904e 100644 --- a/src/Oro/Bundle/BatchBundle/composer.json +++ b/src/Oro/Bundle/BatchBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/config": "dev-master", "oro/form-bundle": "dev-master" }, diff --git a/src/Oro/Bundle/BusinessEntitiesBundle/composer.json b/src/Oro/Bundle/BusinessEntitiesBundle/composer.json index b0cd8485db1..8dc9aec8b9a 100644 --- a/src/Oro/Bundle/BusinessEntitiesBundle/composer.json +++ b/src/Oro/Bundle/BusinessEntitiesBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "doctrine/orm": ">=2.3,<2.4-dev" }, "autoload": { diff --git a/src/Oro/Bundle/CacheBundle/composer.json b/src/Oro/Bundle/CacheBundle/composer.json index 239cd72b4d3..c32b23ba077 100644 --- a/src/Oro/Bundle/CacheBundle/composer.json +++ b/src/Oro/Bundle/CacheBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*" + "symfony/symfony": "2.8.*, !=2.8.10" }, "autoload": { "psr-0": { "Oro\\Bundle\\CacheBundle": "" } diff --git a/src/Oro/Bundle/CalendarBundle/Model/Recurrence/YearNthStrategy.php b/src/Oro/Bundle/CalendarBundle/Model/Recurrence/YearNthStrategy.php index 043563c164b..7b546599bda 100644 --- a/src/Oro/Bundle/CalendarBundle/Model/Recurrence/YearNthStrategy.php +++ b/src/Oro/Bundle/CalendarBundle/Model/Recurrence/YearNthStrategy.php @@ -117,7 +117,8 @@ protected function getNextOccurrence($interval, $dayOfWeek, $monthOfYear, $insta $occurrenceDate = new \DateTime("+{$interval} month {$date->format('c')}"); $instanceRelativeValue = $this->getInstanceRelativeValue($instance); - $month = date('M', mktime(0, 0, 0, $monthOfYear)); + //the 1st day is used to avoid situations like '31-06-2016' == '01-07-2016' (31 == current day by default) + $month = date('M', mktime(0, 0, 0, $monthOfYear, 1)); $year = $occurrenceDate->format('Y'); $time = $occurrenceDate->format('H:i:s.u'); $nextDays = []; diff --git a/src/Oro/Bundle/CalendarBundle/Provider/CalendarEventActivityListProvider.php b/src/Oro/Bundle/CalendarBundle/Provider/CalendarEventActivityListProvider.php index 5d604142c64..4229c923425 100644 --- a/src/Oro/Bundle/CalendarBundle/Provider/CalendarEventActivityListProvider.php +++ b/src/Oro/Bundle/CalendarBundle/Provider/CalendarEventActivityListProvider.php @@ -100,7 +100,7 @@ public function getSubject($entity) public function getDescription($entity) { /** @var $entity CalendarEvent */ - return $entity->getDescription(); + return trim(strip_tags($entity->getDescription())); } /** diff --git a/src/Oro/Bundle/CalendarBundle/Resources/config/requirejs.yml b/src/Oro/Bundle/CalendarBundle/Resources/config/requirejs.yml index f2a96e87936..b155d7194a4 100644 --- a/src/Oro/Bundle/CalendarBundle/Resources/config/requirejs.yml +++ b/src/Oro/Bundle/CalendarBundle/Resources/config/requirejs.yml @@ -1,11 +1,6 @@ config: - shim: - 'jquery.fullcalendar': - deps: - - 'jquery' - - 'moment' paths: - 'jquery.fullcalendar': 'bundles/orocalendar/lib/fullcalendar/fullcalendar.js' + 'fullcalendar': 'bundles/orocalendar/lib/fullcalendar/fullcalendar.js' 'orocalendar/js/calendar-view': 'bundles/orocalendar/js/calendar-view.js' 'orocalendar/js/calendar/connection/collection': 'bundles/orocalendar/js/calendar/connection/collection.js' 'orocalendar/js/calendar/connection/model': 'bundles/orocalendar/js/calendar/connection/model.js' diff --git a/src/Oro/Bundle/CalendarBundle/Resources/public/js/calendar-view.js b/src/Oro/Bundle/CalendarBundle/Resources/public/js/calendar-view.js index f5836b8ee52..a307b1cd779 100644 --- a/src/Oro/Bundle/CalendarBundle/Resources/public/js/calendar-view.js +++ b/src/Oro/Bundle/CalendarBundle/Resources/public/js/calendar-view.js @@ -23,7 +23,7 @@ define(function(require) { var PluginManager = require('oroui/js/app/plugins/plugin-manager'); var GuestsPlugin = require('orocalendar/js/app/plugins/calendar/guests-plugin'); var persistentStorage = require('oroui/js/persistent-storage'); - require('jquery.fullcalendar'); + require('fullcalendar'); CalendarView = BaseView.extend({ MOMENT_BACKEND_FORMAT: dateTimeFormatter.getBackendDateTimeFormat(), diff --git a/src/Oro/Bundle/CalendarBundle/composer.json b/src/Oro/Bundle/CalendarBundle/composer.json index d425023e2e7..30f474f2750 100644 --- a/src/Oro/Bundle/CalendarBundle/composer.json +++ b/src/Oro/Bundle/CalendarBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/ui-bundle": "dev-master", "oro/windows-bundle": "dev-master", "oro/form-bundle": "dev-master", diff --git a/src/Oro/Bundle/ChartBundle/composer.json b/src/Oro/Bundle/ChartBundle/composer.json index 6d0846e1fa7..7a7fb49674b 100644 --- a/src/Oro/Bundle/ChartBundle/composer.json +++ b/src/Oro/Bundle/ChartBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/ui-bundle": "dev-master", "oro/locale-bundle": "dev-master", "oro/requirejs-bundle": "dev-master", diff --git a/src/Oro/Bundle/CommentBundle/composer.json b/src/Oro/Bundle/CommentBundle/composer.json index c8c6b7e4266..2ced4c211ac 100644 --- a/src/Oro/Bundle/CommentBundle/composer.json +++ b/src/Oro/Bundle/CommentBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/ui-bundle": "dev-master" }, "autoload": { diff --git a/src/Oro/Bundle/ConfigBundle/composer.json b/src/Oro/Bundle/ConfigBundle/composer.json index dc52962409d..7915da79893 100644 --- a/src/Oro/Bundle/ConfigBundle/composer.json +++ b/src/Oro/Bundle/ConfigBundle/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/config": "dev-master", "oro/ui-bundle": "dev-master", "oro/user-bundle": "dev-master", diff --git a/src/Oro/Bundle/CronBundle/Entity/Repository/JobRepository.php b/src/Oro/Bundle/CronBundle/Entity/Repository/JobRepository.php new file mode 100644 index 00000000000..0598cb26f0b --- /dev/null +++ b/src/Oro/Bundle/CronBundle/Entity/Repository/JobRepository.php @@ -0,0 +1,67 @@ +getJobIdsOfIncomingDependencies($job); + if (empty($jobIds)) { + return []; + } + $query = "SELECT j, d FROM JMSJobQueueBundle:Job j LEFT JOIN j.dependencies d WHERE j.id IN (:ids)"; + return $this->_em->createQuery($query) + ->setParameter('ids', $jobIds) + ->getResult(); + } + + /** + * @param Job $job + * + * @return Job[] + */ + public function getIncomingDependencies(Job $job) + { + $jobIds = $this->getJobIdsOfIncomingDependencies($job); + if (empty($jobIds)) { + return []; + } + return $this->_em->createQuery("SELECT j FROM JMSJobQueueBundle:Job j WHERE j.id IN (:ids)") + ->setParameter('ids', $jobIds) + ->getResult(); + } + + /** + * @param Job $job + * + * @return array + */ + protected function getJobIdsOfIncomingDependencies(Job $job) + { + $query = "SELECT source_job_id FROM jms_job_dependencies WHERE dest_job_id = :id"; + $jobIds = $this->_em->getConnection() + ->executeQuery($query, ['id' => $job->getId()]) + ->fetchAll(\PDO::FETCH_COLUMN); + return $jobIds; + } +} diff --git a/src/Oro/Bundle/CronBundle/EventListener/ClassMetadataListener.php b/src/Oro/Bundle/CronBundle/EventListener/ClassMetadataListener.php new file mode 100644 index 00000000000..f07340ef8cd --- /dev/null +++ b/src/Oro/Bundle/CronBundle/EventListener/ClassMetadataListener.php @@ -0,0 +1,31 @@ +getClassMetadata(); + if ('JMS\JobQueueBundle\Entity\Job' === $classMetadata->name) { + $classMetadata->customRepositoryClassName = 'Oro\Bundle\CronBundle\Entity\Repository\JobRepository'; + } + } +} diff --git a/src/Oro/Bundle/CronBundle/Resources/config/services.yml b/src/Oro/Bundle/CronBundle/Resources/config/services.yml index a81e02d8779..f131bf34539 100644 --- a/src/Oro/Bundle/CronBundle/Resources/config/services.yml +++ b/src/Oro/Bundle/CronBundle/Resources/config/services.yml @@ -60,3 +60,16 @@ services: oro_cron.helper.cron: class: 'Oro\Bundle\CronBundle\Helper\CronHelper' + + # @deprecated Since 1.11, will be removed after 1.13. + # @TODO + # Remove this service after BAP-10703 implementation or + # after migration from jms/job-queue-bundle 1.2.* to jms/job-queue-bundle 1.3.* + # + # This fix brings performance optimization of JobRepository which was introduced in + # jms/job-queue-bundle 1.3.0. As of there are other stories to upgrade jms/job-queue-bundle version + # or replace it, this solution is temporary. + oro_cron.event_listener.class_metadata_listener: + class: 'Oro\Bundle\CronBundle\EventListener\ClassMetadataListener' + tags: + - { name: doctrine.event_listener, event: loadClassMetadata } diff --git a/src/Oro/Bundle/CronBundle/composer.json b/src/Oro/Bundle/CronBundle/composer.json index 76ed23e708a..dd9070a54ce 100644 --- a/src/Oro/Bundle/CronBundle/composer.json +++ b/src/Oro/Bundle/CronBundle/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "jms/job-queue-bundle": "dev-master", "mtdowling/cron-expression": "1.0.*", "oro/log": "dev-master", diff --git a/src/Oro/Bundle/DashboardBundle/composer.json b/src/Oro/Bundle/DashboardBundle/composer.json index 83413305050..8d1e5d00a76 100644 --- a/src/Oro/Bundle/DashboardBundle/composer.json +++ b/src/Oro/Bundle/DashboardBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/config": "dev-master", "oro/ui-bundle": "dev-master", "oro/requirejs-bundle": "dev-master", diff --git a/src/Oro/Bundle/DataAuditBundle/composer.json b/src/Oro/Bundle/DataAuditBundle/composer.json index 7d5aa67d56b..944df5fd74c 100644 --- a/src/Oro/Bundle/DataAuditBundle/composer.json +++ b/src/Oro/Bundle/DataAuditBundle/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "stof/doctrine-extensions-bundle": "dev-master", "friendsofsymfony/rest-bundle": "1.5.0-RC2", "nelmio/api-doc-bundle": "dev-master", diff --git a/src/Oro/Bundle/DataGridBundle/Datagrid/Builder.php b/src/Oro/Bundle/DataGridBundle/Datagrid/Builder.php index 5f207f5bfa7..3e4a5deb4f7 100644 --- a/src/Oro/Bundle/DataGridBundle/Datagrid/Builder.php +++ b/src/Oro/Bundle/DataGridBundle/Datagrid/Builder.php @@ -182,6 +182,7 @@ protected function createAcceptor(DatagridConfiguration $config, ParameterBag $p $acceptor->addExtension($extension); } } + $acceptor->sortExtensionsByPriority(); return $acceptor; } diff --git a/src/Oro/Bundle/DataGridBundle/Datagrid/ParameterBag.php b/src/Oro/Bundle/DataGridBundle/Datagrid/ParameterBag.php index bc43182770c..6509489914a 100644 --- a/src/Oro/Bundle/DataGridBundle/Datagrid/ParameterBag.php +++ b/src/Oro/Bundle/DataGridBundle/Datagrid/ParameterBag.php @@ -73,7 +73,7 @@ public function add(array $parameters = array()) */ public function get($key, $default = null) { - return $this->has($key) ? $this->parameters[$key] : $default; + return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; } /** diff --git a/src/Oro/Bundle/DataGridBundle/Event/OrmResultAfter.php b/src/Oro/Bundle/DataGridBundle/Event/OrmResultAfter.php index 4d9c423a433..63b30c1aa17 100644 --- a/src/Oro/Bundle/DataGridBundle/Event/OrmResultAfter.php +++ b/src/Oro/Bundle/DataGridBundle/Event/OrmResultAfter.php @@ -62,6 +62,14 @@ public function getRecords() return $this->records; } + /** + * @param array $records + */ + public function setRecords(array $records) + { + $this->records = $records; + } + /** * @return AbstractQuery */ diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Acceptor.php b/src/Oro/Bundle/DataGridBundle/Extension/Acceptor.php index 29e1f5f21a8..bcab26d94fb 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/Acceptor.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/Acceptor.php @@ -60,7 +60,7 @@ public function acceptMetadata(MetadataObject $data) } /** - * Add extension that applicable to datagrid and resort all added extensions + * Adds an extension that applicable to datagrid * * @param ExtensionVisitorInterface $extension * @@ -70,6 +70,14 @@ public function addExtension(ExtensionVisitorInterface $extension) { $this->extensions[] = $extension; + return $this; + } + + /** + * Sorts extensions by priority + */ + public function sortExtensionsByPriority() + { $comparisonClosure = function (ExtensionVisitorInterface $a, ExtensionVisitorInterface $b) { if ($a->getPriority() === $b->getPriority()) { return 0; diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Appearance/AppearanceExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/Appearance/AppearanceExtension.php index 7cd55f749d0..dd244819aa6 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/Appearance/AppearanceExtension.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/Appearance/AppearanceExtension.php @@ -40,14 +40,13 @@ public function __construct( } /** - * {@inheritDoc} + * {@inheritdoc} */ public function isApplicable(DatagridConfiguration $config) { $options = $config->offsetGetOr(static::APPEARANCE_CONFIG_PATH, []); - $hasOptions = count($options) > 0; - return $hasOptions; + return count($options) > 0; } /** diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Appearance/Configuration.php b/src/Oro/Bundle/DataGridBundle/Extension/Appearance/Configuration.php index adb1110f903..bdec162980e 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/Appearance/Configuration.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/Appearance/Configuration.php @@ -2,12 +2,9 @@ namespace Oro\Bundle\DataGridBundle\Extension\Appearance; -use Symfony\Component\Config\Definition\Builder\NodeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; -use Oro\Bundle\DataGridBundle\Entity\Manager\AppearanceTypeManager; - class Configuration implements ConfigurationInterface { const GRID_APPEARANCE_TYPE = 'grid'; diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Export/ExportExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/Export/ExportExtension.php index 1f6eac22bdb..fbb234edc48 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/Export/ExportExtension.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/Export/ExportExtension.php @@ -5,9 +5,7 @@ use Symfony\Component\Translation\TranslatorInterface; use Oro\Bundle\DataGridBundle\Extension\AbstractExtension; -use Oro\Bundle\DataGridBundle\Datagrid\Common\MetadataObject; use Oro\Bundle\DataGridBundle\Datagrid\Common\DatagridConfiguration; -use Oro\Bundle\DataGridBundle\Extension\Toolbar\ToolbarExtension; class ExportExtension extends AbstractExtension { @@ -27,7 +25,7 @@ public function __construct(TranslatorInterface $translator) } /** - * {@inheritDoc} + * {@inheritdoc} */ public function isApplicable(DatagridConfiguration $config) { diff --git a/src/Oro/Bundle/DataGridBundle/Extension/GridParams/GridParamsExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/GridParams/GridParamsExtension.php index 69ff37d3994..fb66bc49a07 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/GridParams/GridParamsExtension.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/GridParams/GridParamsExtension.php @@ -17,7 +17,7 @@ class GridParamsExtension extends AbstractExtension */ public function isApplicable(DatagridConfiguration $config) { - return $config->getDatasourceType() == OrmDatasource::TYPE; + return $config->getDatasourceType() === OrmDatasource::TYPE; } /** diff --git a/src/Oro/Bundle/DataGridBundle/Extension/InlineEditing/InlineEditColumnOptionsGuesser.php b/src/Oro/Bundle/DataGridBundle/Extension/InlineEditing/InlineEditColumnOptionsGuesser.php index 2b4d8fbf676..2061955e9aa 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/InlineEditing/InlineEditColumnOptionsGuesser.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/InlineEditing/InlineEditColumnOptionsGuesser.php @@ -4,24 +4,17 @@ use Symfony\Component\Validator\Mapping\ClassMetadataInterface; use Symfony\Component\Validator\Mapping\Loader\AbstractLoader; +use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; use Oro\Bundle\DataGridBundle\Extension\InlineEditing\InlineEditColumnOptions\GuesserInterface; -/** - * Class InlineEditColumnOptionsGuesser - * @package Oro\Bundle\DataGridBundle\Extension\InlineEditing - */ class InlineEditColumnOptionsGuesser { - /** - * @var ValidatorInterface - */ + /** @var ValidatorInterface */ protected $validator; - /** - * @var GuesserInterface[] - */ + /** @var GuesserInterface[] */ protected $guessers; /** @@ -51,7 +44,7 @@ public function addGuesser(GuesserInterface $guesser) */ public function getColumnOptions($columnName, $entityName, $column, $behaviour) { - /** @var ValidatorInterface $validatorMetadata */ + /** @var ClassMetadataInterface $validatorMetadata */ $validatorMetadata = $this->validator->getMetadataFor($entityName); $isEnabledInline = isset($column[Configuration::BASE_CONFIG_KEY][Configuration::CONFIG_ENABLE_KEY]) && @@ -93,6 +86,7 @@ public function getColumnOptions($columnName, $entityName, $column, $behaviour) */ protected function getValidationRules($validatorMetadata, $columnName) { + /** @var PropertyMetadataInterface $metadata */ $metadata = $validatorMetadata->getPropertyMetadata($columnName); $metadata = is_array($metadata) && isset($metadata[0]) ? $metadata[0] : $metadata; diff --git a/src/Oro/Bundle/DataGridBundle/Extension/InlineEditing/InlineEditingExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/InlineEditing/InlineEditingExtension.php index d7fffd1e793..ba3733d10ca 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/InlineEditing/InlineEditingExtension.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/InlineEditing/InlineEditingExtension.php @@ -32,6 +32,7 @@ class InlineEditingExtension extends AbstractExtension * @param InlineEditColumnOptionsGuesser $inlineEditColumnOptionsGuesser * @param SecurityFacade $securityFacade * @param EntityClassNameHelper $entityClassNameHelper + * @param AuthorizationCheckerInterface $authorizationChecker */ public function __construct( InlineEditColumnOptionsGuesser $inlineEditColumnOptionsGuesser, @@ -93,6 +94,7 @@ public function processConfigs(DatagridConfiguration $config) $columns = $config->offsetGetOr(FormatterConfiguration::COLUMNS_KEY, []); $blackList = $configuration->getBlackList(); $behaviour = $config->offsetGetByPath(Configuration::BEHAVIOUR_CONFIG_PATH); + $objectIdentity = new ObjectIdentity('entity', $configItems['entity_name']); foreach ($columns as $columnName => &$column) { if (!in_array($columnName, $blackList, true)) { @@ -101,7 +103,7 @@ public function processConfigs(DatagridConfiguration $config) $dadaFieldName = $this->getColummFieldName($columnName, $column); if (!$this->authChecker->isGranted( 'EDIT', - new FieldVote(new ObjectIdentity('entity', $configItems['entity_name']), $dadaFieldName) + new FieldVote($objectIdentity, $dadaFieldName) ) ) { if (array_key_exists(Configuration::BASE_CONFIG_KEY, $column)) { diff --git a/src/Oro/Bundle/DataGridBundle/Extension/MassAction/DeleteMassActionExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/MassAction/DeleteMassActionExtension.php index e76eb465b3e..dc78b00a4ab 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/MassAction/DeleteMassActionExtension.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/MassAction/DeleteMassActionExtension.php @@ -43,19 +43,22 @@ public function __construct(DoctrineHelper $doctrineHelper, GridConfigurationHel */ public function isApplicable(DatagridConfiguration $config) { - // validate configuration and fill default values - $options = $this->validateConfiguration( - new DeleteMassActionConfiguration(), - ['delete' => $config->offsetGetByPath(self::MASS_ACTION_OPTION_PATH, true)] - ); + if (!$this->isDeleteActionExists($config, static::MASS_ACTION_KEY) // is 'mass delete action' do not exists + && $this->isDeleteActionExists($config, static::ACTION_KEY) // is 'delete action' exists + && $this->isApplicableForEntity($config) + ) { + // validate configuration and fill default values + $options = $this->validateConfiguration( + new DeleteMassActionConfiguration(), + ['delete' => $config->offsetGetByPath(self::MASS_ACTION_OPTION_PATH, true)] + ); + + if ($options['enabled']) { + return true; + } + } - return - // Checks if mass delete action does not exists - !$this->isDeleteActionExists($config, static::MASS_ACTION_KEY) && - // Checks if delete action exists - $this->isDeleteActionExists($config, static::ACTION_KEY) && - $this->isApplicableForEntity($config) && - $options['enabled']; + return false; } /** @@ -88,7 +91,7 @@ protected function isDeleteActionExists(DatagridConfiguration $config, $key) { $actions = $config->offsetGetOr($key, []); foreach ($actions as $action) { - if ($action[static::ACTION_TYPE_KEY] == static::ACTION_TYPE_DELETE) { + if ($action[static::ACTION_TYPE_KEY] === static::ACTION_TYPE_DELETE) { return true; } } diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Pager/OrmPagerExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/Pager/OrmPagerExtension.php index 9820c64963b..8567105883a 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/Pager/OrmPagerExtension.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/Pager/OrmPagerExtension.php @@ -14,9 +14,6 @@ use Oro\Bundle\DataGridBundle\Extension\Toolbar\ToolbarExtension; /** - * Class OrmPagerExtension - * @package Oro\Bundle\DataGridBundle\Extension\Pager - * * Responsibility of this extension is to apply pagination on query for ORM datasource */ class OrmPagerExtension extends AbstractExtension @@ -45,11 +42,10 @@ public function __clone() */ public function isApplicable(DatagridConfiguration $config) { - // enabled by default for ORM datasource - $disabled = $this->getOr(PagerInterface::DISABLED_PARAM, false) - || $config->offsetGetByPath(ToolbarExtension::TOOLBAR_PAGINATION_HIDE_OPTION_PATH, false); - - return !$disabled && $config->getDatasourceType() == OrmDatasource::TYPE; + return + $config->getDatasourceType() === OrmDatasource::TYPE + && !$this->getOr(PagerInterface::DISABLED_PARAM, false) + && !$config->offsetGetByPath(ToolbarExtension::TOOLBAR_PAGINATION_HIDE_OPTION_PATH, false); } /** diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Sorter/OrmSorterExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/Sorter/OrmSorterExtension.php index 69eca1b3713..6685764a894 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/Sorter/OrmSorterExtension.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/Sorter/OrmSorterExtension.php @@ -35,11 +35,9 @@ class OrmSorterExtension extends AbstractExtension */ public function isApplicable(DatagridConfiguration $config) { - $columns = $config->offsetGetByPath(Configuration::COLUMNS_PATH); - $isApplicable = $config->getDatasourceType() === OrmDatasource::TYPE - && is_array($columns); - - return $isApplicable; + return + $config->getDatasourceType() === OrmDatasource::TYPE + && is_array($config->offsetGetByPath(Configuration::COLUMNS_PATH)); } /** @@ -252,6 +250,12 @@ protected function normalizeDirection($direction) switch (true) { case in_array($direction, [self::DIRECTION_ASC, self::DIRECTION_DESC], true): break; + case ($direction === 1): + $direction = self::DIRECTION_DESC; + break; + case ($direction === -1): + $direction = self::DIRECTION_ASC; + break; case ($direction === false): $direction = self::DIRECTION_DESC; break; diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Totals/OrmTotalsExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/Totals/OrmTotalsExtension.php index b062f403f2c..abe238125c1 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/Totals/OrmTotalsExtension.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/Totals/OrmTotalsExtension.php @@ -2,13 +2,12 @@ namespace Oro\Bundle\DataGridBundle\Extension\Totals; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; -use Oro\Component\DoctrineUtils\ORM\QueryUtils; use Symfony\Component\Translation\TranslatorInterface; +use Oro\Component\DoctrineUtils\ORM\QueryUtils; use Oro\Component\PhpUtils\ArrayUtil; use Oro\Bundle\DataGridBundle\Datagrid\Common\DatagridConfiguration; diff --git a/src/Oro/Bundle/DataGridBundle/Resources/config/extensions.yml b/src/Oro/Bundle/DataGridBundle/Resources/config/extensions.yml index a2d5280dd5a..0b0ff807bbd 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/config/extensions.yml +++ b/src/Oro/Bundle/DataGridBundle/Resources/config/extensions.yml @@ -24,7 +24,6 @@ services: - '@translator' tags: - { name: oro_datagrid.extension } - lazy: true oro_datagrid.extension.orm_pager: class: %oro_datagrid.extension.orm_pager.class% diff --git a/src/Oro/Bundle/DataGridBundle/Resources/public/css/less/oro.grid.less b/src/Oro/Bundle/DataGridBundle/Resources/public/css/less/oro.grid.less index 83028c9794f..94873f08cd5 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/public/css/less/oro.grid.less +++ b/src/Oro/Bundle/DataGridBundle/Resources/public/css/less/oro.grid.less @@ -191,7 +191,6 @@ table.grid { .integer-cell { text-align: right; } - .datetime-cell, .date-cell { white-space: nowrap; } @@ -362,3 +361,44 @@ td > .nowrap-ellipsis { .grid .action-column >.dropdown { margin-left: 0; } +.with-floating-header { + .grid-header-cell{ + padding-right: 0; + } + .grid-header-cell-link { + width: 100%; + display: flex; + } + .grid-header-cell-label { + text-overflow: ellipsis; + display: block; + overflow: hidden; + float: left; + } + &.floatThead .grid-header.thead-sizing .grid-header-cell:not(.abbreviated) .grid-header-cell-label { + width: 0 !important; + } + &:not(.floatThead) .grid-header .grid-header-cell:not(.abbreviated) .grid-header-cell-label, + .grid-header.thead-sizing .grid-header-cell:not(.abbreviated) .grid-header-cell-label { + width: 40px !important; + } +} +.grid-header-cell-hint { + position: absolute; + display: block; + background: #fff; + padding: 0 7px; + border-radius: 3px; + box-shadow: 1px 1px 3px rgba(0,0,0,0.2); + z-index: 10000; + &:after{ + content: ''; + display: inline-block; + border-left: 6px solid rgba(0, 0, 0, 0); + border-right: 6px solid rgba(0, 0, 0, 0); + border-bottom: 6px solid #fff; + position: absolute; + top: -4px; + left: 6px; + } +} diff --git a/src/Oro/Bundle/DataGridBundle/Resources/public/js/app/plugins/grid/floating-header-plugin.js b/src/Oro/Bundle/DataGridBundle/Resources/public/js/app/plugins/grid/floating-header-plugin.js index b43c98f5a01..b65d0c9f01a 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/public/js/app/plugins/grid/floating-header-plugin.js +++ b/src/Oro/Bundle/DataGridBundle/Resources/public/js/app/plugins/grid/floating-header-plugin.js @@ -39,6 +39,8 @@ define(function(require) { this.setupCache(); + this.$el.addClass('with-floating-header'); + this.isHeaderCellWidthFixed = false; this.rescrollCb = this.enableOtherScroll(); if (!this.isHeaderCellWidthFixed) { @@ -69,6 +71,9 @@ define(function(require) { this.domCache.headerCells.attr('style', ''); this.domCache.firstRowCells.attr('style', ''); } + + this.$el.removeClass('with-floating-header'); + FloatingHeaderPlugin.__super__.disable.call(this); }, @@ -164,10 +169,12 @@ define(function(require) { headerCells.each(function(i, headerCell) { var cellWidth = widths[i] - widthDecrement; headerCell.style.width = cellWidth + 'px'; + headerCell.style.maxWidth = cellWidth + 'px'; headerCell.style.minWidth = cellWidth + 'px'; headerCell.style.boxSizing = 'border-box'; if (firstRowCells[i]) { firstRowCells[i].style.width = cellWidth + 'px'; + firstRowCells[i].style.maxWidth = cellWidth + 'px'; firstRowCells[i].style.minWidth = cellWidth + 'px'; firstRowCells[i].style.boxSizing = 'border-box'; } diff --git a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/cell/html-cell.js b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/cell/html-cell.js index 94663dea117..6ded23855f8 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/cell/html-cell.js +++ b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/cell/html-cell.js @@ -18,7 +18,7 @@ define([ * model's raw value for this cell's column. */ render: function() { - this.$el.empty().html(this.formatter.fromRaw(this.model.get(this.column.get('name')))); + this.$el.empty().html(this.model.get(this.column.get('name'))); return this; } }); diff --git a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/cell/select-cell.js b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/cell/select-cell.js index 504901598d8..0538d1dc5f7 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/cell/select-cell.js +++ b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/cell/select-cell.js @@ -1,8 +1,9 @@ define([ 'underscore', 'backgrid', - 'orodatagrid/js/datagrid/editor/select-cell-radio-editor' -], function(_, Backgrid, SelectCellRadioEditor) { + 'orodatagrid/js/datagrid/editor/select-cell-radio-editor', + 'oroui/js/tools/text-util' +], function(_, Backgrid, SelectCellRadioEditor, textUtil) { 'use strict'; var SelectCell; @@ -26,7 +27,7 @@ define([ if (options.column.get('metadata').choices) { this.optionValues = []; _.each(options.column.get('metadata').choices, function(value, key) { - this.optionValues.push([value, key]); + this.optionValues.push([textUtil.prepareText(value), key]); }, this); } else { throw new Error('Column metadata must have choices specified'); diff --git a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/cell-formatter.js b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/cell-formatter.js index 88f50d6d542..1cc31bedf21 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/cell-formatter.js +++ b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/cell-formatter.js @@ -1,5 +1,5 @@ -define(['underscore', 'backgrid' - ], function(_, Backgrid) { +define(['underscore', 'backgrid', 'oroui/js/tools/text-util' + ], function(_, Backgrid, textUtil) { 'use strict'; /** @@ -21,7 +21,8 @@ define(['underscore', 'backgrid' if (rawData === null) { return ''; } - return Backgrid.CellFormatter.prototype.fromRaw.apply(this, arguments); + var result = Backgrid.CellFormatter.prototype.fromRaw.apply(this, arguments); + return textUtil.prepareText(result); } }); diff --git a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/datetime-formatter.js b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/datetime-formatter.js index d0bad088596..20a535aec50 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/datetime-formatter.js +++ b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/datetime-formatter.js @@ -30,7 +30,8 @@ define(['underscore', 'backgrid', 'orolocale/js/formatter/datetime' return ''; } // Call one of formatDate formatTime formatDateTime - return this._getFormatterFunction('format').call(DateTimeFormatter, rawData); + return this._getFormatterFunction('format', this.type === 'dateTime' ? 'NBSP' : undefined) + .call(DateTimeFormatter, rawData); }, /** diff --git a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/phone-formatter.js b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/phone-formatter.js index 9f97c133805..e9301758146 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/phone-formatter.js +++ b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/formatter/phone-formatter.js @@ -33,7 +33,8 @@ define(['underscore', 'backgrid' * @return {string} */ generateLinkHTML: function(phoneNumber) { - return '' + _.escape(phoneNumber) + ''; + var number = phoneNumber.trim(); + return '' + _.escape(number) + ''; } }); diff --git a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/grid-views/view.js b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/grid-views/view.js index a805c2e468f..d4d4756e5b6 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/grid-views/view.js +++ b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/grid-views/view.js @@ -214,6 +214,7 @@ define(function(require) { var self = this; model.save({ + icon: void 0, label: model.get('label'), filters: this.collection.state.filters, sorters: this.collection.state.sorters, diff --git a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/header-cell/header-cell.js b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/header-cell/header-cell.js index 6bea9e8685b..9efa7aa75ca 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/header-cell/header-cell.js +++ b/src/Oro/Bundle/DataGridBundle/Resources/public/js/datagrid/header-cell/header-cell.js @@ -1,7 +1,9 @@ define([ 'underscore', - 'backgrid' -], function(_, Backgrid) { + 'jquery', + 'backgrid', + 'oroui/js/tools/text-util' +], function(_, $, Backgrid, textUtil) { 'use strict'; var HeaderCell; @@ -18,20 +20,31 @@ define([ /** @property */ template: _.template( '<% if (sortable) { %>' + - '' + - '<%- label %> ' + + '' + + '<%- label %>' + '' + '' + '<% } else { %>' + - '<%- label %>' + // wrap label into span otherwise underscore will not render it + '' + + '<%- label %>' + + '' + '<% } %>' ), /** @property {Boolean} */ allowNoSorting: true, + /** @property {Number} */ + minWordsToAbbreviate: 4, + keepElement: false, + events: { + mouseenter: 'onMouseEnter', + mouseleave: 'onMouseLeave', + click: 'onClick' + }, + /** * Initialize. * @@ -95,8 +108,15 @@ define([ render: function() { this.$el.empty(); + var label = this.column.get('label'); + var abbreviation = textUtil.abbreviate(label, this.minWordsToAbbreviate); + + this.isLabelAbbreviated = abbreviation !== label; + + this.$el.toggleClass('abbreviated', this.isLabelAbbreviated); + this.$el.append(this.template({ - label: this.column.get('label'), + label: abbreviation, sortable: this.column.get('sortable') })); @@ -155,6 +175,49 @@ define([ cycleSort(this, column); } } + }, + + onMouseEnter: function(e) { + var _this = this; + var $label = this.$('.grid-header-cell-label'); + + // measure text content + var realWidth = $label[0].clientWidth; + $label.css({overflow: 'visible'}); + var fullWidth = $label[0].clientWidth; + $label.css({overflow: ''}); + + if (!this.isLabelAbbreviated && fullWidth === realWidth) { + // hint is not required all text is visible + return; + } + + this.popoverAdded = true; + + $label.popover({ + content: _this.column.get('label'), + trigger: 'manual', + placement: 'bottom', + animation: 'false', + container: 'body', + template: '
Lorem ipsum
+dolor sit amet, consectetur adipiscing elit. +Integer | +sagittis | +
---|---|
ornare | +do | +
The body text
+ + +HTMLTEXT; + + return [ + 'plain text' => ['test text', 'test text'], + 'text with css' => [ + ' some text', + 'some text' + ], + 'text with javascript' => [ + ' another text', + 'another text' + ], + 'text with body tag' => [$htmlTest, 'The body text'], + 'text with non printed symbols' => ["some\ntext with\tsymbols", 'some text with symbols'] + ]; + } +} diff --git a/src/Oro/Bundle/EmailBundle/Tools/EmailBodyHelper.php b/src/Oro/Bundle/EmailBundle/Tools/EmailBodyHelper.php new file mode 100644 index 00000000000..f14bea08120 --- /dev/null +++ b/src/Oro/Bundle/EmailBundle/Tools/EmailBodyHelper.php @@ -0,0 +1,55 @@ +htmlTagHelper = $htmlTagHelper; + } + + /** + * Returns the plain text representation of email body + * + * @param string $bodyContent + * + * @return string + */ + public function getClearBody($bodyContent) + { + /** + * @todo: Should be refactored or deleted in scope of BAP-11622 + */ + if (extension_loaded('tidy')) { + $config = [ + 'show-body-only' => true, + 'clean' => true, + 'hide-comments' => true + ]; + $tidy = new \tidy(); + $body = $tidy->repairString($bodyContent, $config, 'UTF8'); + } else { + $body = $bodyContent; + // get `body` content in case of html text + if (preg_match('~]*>(.*?)~si', $bodyContent, $bodyText)) { + $body = $bodyText[1]; + } + } + + // clear `script` and `style` tags from content + $body = preg_replace('/<(style|script).*?>.*?<\/\1>/s', '', $body); + + return preg_replace('/(\s\s+|\n+|[^[:print:]])/', ' ', $this->htmlTagHelper->stripTags($body)); + } +} diff --git a/src/Oro/Bundle/EmailBundle/Twig/EmailExtension.php b/src/Oro/Bundle/EmailBundle/Twig/EmailExtension.php index b77c57d55b8..1974095575d 100644 --- a/src/Oro/Bundle/EmailBundle/Twig/EmailExtension.php +++ b/src/Oro/Bundle/EmailBundle/Twig/EmailExtension.php @@ -17,6 +17,7 @@ use Oro\Bundle\EmailBundle\Model\WebSocket\WebSocketSendProcessor; use Oro\Bundle\EmailBundle\Provider\RelatedEmailsProvider; use Oro\Bundle\EmailBundle\Tools\EmailAddressHelper; +use Oro\Bundle\EmailBundle\Tools\EmailBodyHelper; use Oro\Bundle\EmailBundle\Tools\EmailHolderHelper; use Oro\Bundle\SecurityBundle\SecurityFacade; @@ -45,6 +46,9 @@ class EmailExtension extends Twig_Extension /** @var RelatedEmailsProvider */ protected $relatedEmailsProvider; + /** @var EmailBodyHelper */ + protected $emailBodyHelper; + /** * @param EmailHolderHelper $emailHolderHelper * @param EmailAddressHelper $emailAddressHelper @@ -53,6 +57,7 @@ class EmailExtension extends Twig_Extension * @param MailboxProcessStorage $mailboxProcessStorage * @param SecurityFacade $securityFacade * @param RelatedEmailsProvider $relatedEmailsProvider + * @param EmailBodyHelper $emailBodyHelper */ public function __construct( EmailHolderHelper $emailHolderHelper, @@ -61,7 +66,8 @@ public function __construct( EntityManager $em, MailboxProcessStorage $mailboxProcessStorage, SecurityFacade $securityFacade, - RelatedEmailsProvider $relatedEmailsProvider + RelatedEmailsProvider $relatedEmailsProvider, + EmailBodyHelper $emailBodyHelper ) { $this->emailHolderHelper = $emailHolderHelper; $this->emailAddressHelper = $emailAddressHelper; @@ -70,6 +76,7 @@ public function __construct( $this->mailboxProcessStorage = $mailboxProcessStorage; $this->securityFacade = $securityFacade; $this->relatedEmailsProvider = $relatedEmailsProvider; + $this->emailBodyHelper = $emailBodyHelper; } /** @@ -90,6 +97,28 @@ public function getFunctions() ]; } + /** + * {@inheritDoc} + */ + public function getFilters() + { + return [ + new \Twig_SimpleFilter('oro_cleanup_email_body', [$this, 'getCleanEmailBody']) + ]; + } + + /** + * Returns clean text representation without tags + * + * @param string $emailBodyText + * + * @return string + */ + public function getCleanEmailBody($emailBodyText) + { + return $this->emailBodyHelper->getClearBody($emailBodyText); + } + /** * Gets the email address of the given object * diff --git a/src/Oro/Bundle/EmailBundle/composer.json b/src/Oro/Bundle/EmailBundle/composer.json index c70246d9f0e..e84b5f135e7 100644 --- a/src/Oro/Bundle/EmailBundle/composer.json +++ b/src/Oro/Bundle/EmailBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "doctrine/orm": ">=2.3,<2.4-dev", "a2lix/translation-form-bundle" : "1.x-dev", "oro/attachment-bundle": "dev-master", diff --git a/src/Oro/Bundle/EntityBundle/Tests/Unit/Tools/EntityRoutingHelperTest.php b/src/Oro/Bundle/EntityBundle/Tests/Unit/Tools/EntityRoutingHelperTest.php index 760a1f453f5..5dd7ff2394e 100644 --- a/src/Oro/Bundle/EntityBundle/Tests/Unit/Tools/EntityRoutingHelperTest.php +++ b/src/Oro/Bundle/EntityBundle/Tests/Unit/Tools/EntityRoutingHelperTest.php @@ -273,7 +273,7 @@ public function testGetEntityForNotManageableEntity() /** * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - * @expectedExceptionMessage Record doesn't found. + * @expectedExceptionMessage Record doesn't exist */ public function testGetEntityForNotExistingEntity() { diff --git a/src/Oro/Bundle/EntityBundle/Tools/EntityRoutingHelper.php b/src/Oro/Bundle/EntityBundle/Tools/EntityRoutingHelper.php index 7b2b536aa13..911fb72d0d4 100644 --- a/src/Oro/Bundle/EntityBundle/Tools/EntityRoutingHelper.php +++ b/src/Oro/Bundle/EntityBundle/Tools/EntityRoutingHelper.php @@ -223,7 +223,7 @@ public function getEntity($entityClass, $entityId) } if (!$entity) { throw new RecordNotFoundException( - sprintf("Record doesn't found.") + sprintf("Record doesn't exist") ); } diff --git a/src/Oro/Bundle/EntityBundle/composer.json b/src/Oro/Bundle/EntityBundle/composer.json index 033f0b153a9..afb1903aba4 100644 --- a/src/Oro/Bundle/EntityBundle/composer.json +++ b/src/Oro/Bundle/EntityBundle/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/entity-config-bundle": "dev-master", "oro/ui-bundle": "dev-master", "oro/grid-bundle": "dev-master", diff --git a/src/Oro/Bundle/EntityConfigBundle/composer.json b/src/Oro/Bundle/EntityConfigBundle/composer.json index 420fd8c5362..73a1e487a76 100644 --- a/src/Oro/Bundle/EntityConfigBundle/composer.json +++ b/src/Oro/Bundle/EntityConfigBundle/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/config": "dev-master", "oro/log": "dev-master", "oro/cache-bundle": "dev-master", diff --git a/src/Oro/Bundle/EntityExtendBundle/Grid/AbstractFieldsExtension.php b/src/Oro/Bundle/EntityExtendBundle/Grid/AbstractFieldsExtension.php index 830071c3c4d..c4301dbc50d 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Grid/AbstractFieldsExtension.php +++ b/src/Oro/Bundle/EntityExtendBundle/Grid/AbstractFieldsExtension.php @@ -52,7 +52,7 @@ public function __construct( */ public function isApplicable(DatagridConfiguration $config) { - return $config->getDatasourceType() == OrmDatasource::TYPE; + return $config->getDatasourceType() === OrmDatasource::TYPE; } /** diff --git a/src/Oro/Bundle/EntityExtendBundle/Grid/DynamicFieldsExtension.php b/src/Oro/Bundle/EntityExtendBundle/Grid/DynamicFieldsExtension.php index 6a64fe7f40a..70c978dde1f 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Grid/DynamicFieldsExtension.php +++ b/src/Oro/Bundle/EntityExtendBundle/Grid/DynamicFieldsExtension.php @@ -12,7 +12,7 @@ class DynamicFieldsExtension extends AbstractFieldsExtension { - const EXTEND_ENTITY_CONFIG_PATH = '[extended_entity_name]'; + const EXTEND_ENTITY_CONFIG_PATH = 'extended_entity_name'; /** * {@inheritdoc} @@ -21,7 +21,7 @@ public function isApplicable(DatagridConfiguration $config) { return parent::isApplicable($config) - && $config->offsetGetByPath(self::EXTEND_ENTITY_CONFIG_PATH, false) !== false; + && $config->offsetGetOr(self::EXTEND_ENTITY_CONFIG_PATH, false) !== false; } /** @@ -37,7 +37,7 @@ public function getPriority() */ protected function getEntityName(DatagridConfiguration $config) { - return $config->offsetGetByPath(self::EXTEND_ENTITY_CONFIG_PATH); + return $config->offsetGetOr(self::EXTEND_ENTITY_CONFIG_PATH); } /** diff --git a/src/Oro/Bundle/EntityExtendBundle/Migration/EntityMetadataHelper.php b/src/Oro/Bundle/EntityExtendBundle/Migration/EntityMetadataHelper.php index e93ee41f499..39be5cef177 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Migration/EntityMetadataHelper.php +++ b/src/Oro/Bundle/EntityExtendBundle/Migration/EntityMetadataHelper.php @@ -14,9 +14,9 @@ class EntityMetadataHelper protected $doctrine; /** - * @var string[] {table name} => {class name} + * @var array {table name} => [{class name}, ...] */ - protected $tableToClassMap; + protected $tableToClassesMap; /** * @var string[] {class name} => {table name} @@ -32,18 +32,38 @@ public function __construct(ManagerRegistry $doctrine) } /** + * @deprecated Use getEntityClassesByTableNames instead + * * Gets an entity full class name by entity table name * * @param string $tableName * @return string|null */ public function getEntityClassByTableName($tableName) + { + $classes = $this->getEntityClassesByTableName($tableName); + if (count($classes) > 1) { + throw new \RuntimeException(sprintf( + 'Table "%s" has more than 1 class. Use "getEntityClassesByTableNames" method instead.', + $tableName + )); + } + + return reset($classes) ?: null; + } + + /** + * @param string $tableName + * + * @return string[] + */ + public function getEntityClassesByTableName($tableName) { $this->ensureNameMapsLoaded(); - return isset($this->tableToClassMap[$tableName]) - ? $this->tableToClassMap[$tableName] - : null; + return isset($this->tableToClassesMap[$tableName]) + ? $this->tableToClassesMap[$tableName] + : []; } /** @@ -70,8 +90,8 @@ public function getTableNameByEntityClass($className) */ public function getFieldNameByColumnName($tableName, $columnName) { - $className = $this->getEntityClassByTableName($tableName); - if ($className) { + $classNames = $this->getEntityClassesByTableName($tableName); + foreach ($classNames as $className) { $manager = $this->doctrine->getManagerForClass($className); if ($manager instanceof EntityManager) { return $manager->getClassMetadata($className)->getFieldName($columnName); @@ -93,7 +113,7 @@ public function registerEntityClass($tableName, $className) { $this->ensureNameMapsLoaded(); - $this->tableToClassMap[$tableName] = $className; + $this->tableToClassesMap[$tableName][] = $className; $this->classToTableMap[$className] = $tableName; } @@ -102,7 +122,7 @@ public function registerEntityClass($tableName, $className) */ protected function ensureNameMapsLoaded() { - if (null === $this->tableToClassMap) { + if (null === $this->tableToClassesMap) { $this->loadNameMaps(); } } @@ -112,7 +132,7 @@ protected function ensureNameMapsLoaded() */ protected function loadNameMaps() { - $this->tableToClassMap = []; + $this->tableToClassesMap = []; $this->classToTableMap = []; $names = array_keys($this->doctrine->getManagers()); foreach ($names as $name) { @@ -123,7 +143,7 @@ protected function loadNameMaps() $tableName = $metadata->getTableName(); if (!empty($tableName)) { $className = $metadata->getName(); - $this->tableToClassMap[$tableName] = $className; + $this->tableToClassesMap[$tableName][] = $className; $this->classToTableMap[$className] = $tableName; } } diff --git a/src/Oro/Bundle/EntityExtendBundle/Migration/ExtendOptionsBuilder.php b/src/Oro/Bundle/EntityExtendBundle/Migration/ExtendOptionsBuilder.php index 78402cefbbd..9c8288ebd67 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Migration/ExtendOptionsBuilder.php +++ b/src/Oro/Bundle/EntityExtendBundle/Migration/ExtendOptionsBuilder.php @@ -2,6 +2,7 @@ namespace Oro\Bundle\EntityExtendBundle\Migration; +use Oro\Bundle\EntityConfigBundle\Config\ConfigManager; use Oro\Bundle\EntityExtendBundle\Extend\FieldTypeHelper; use Oro\Bundle\EntityExtendBundle\Extend\RelationType; use Oro\Bundle\EntityExtendBundle\Tools\ExtendHelper; @@ -14,8 +15,11 @@ class ExtendOptionsBuilder /** @var FieldTypeHelper */ protected $fieldTypeHelper; + /** @var ConfigManager */ + protected $configManager; + /** @var array */ - protected $tableToEntityMap = []; + protected $tableToEntitiesMap = []; /** @var array */ protected $result = []; @@ -23,13 +27,16 @@ class ExtendOptionsBuilder /** * @param EntityMetadataHelper $entityMetadataHelper * @param FieldTypeHelper $fieldTypeHelper + * @param ConfigManager $configManager */ public function __construct( EntityMetadataHelper $entityMetadataHelper, - FieldTypeHelper $fieldTypeHelper + FieldTypeHelper $fieldTypeHelper, + ConfigManager $configManager ) { $this->entityMetadataHelper = $entityMetadataHelper; $this->fieldTypeHelper = $fieldTypeHelper; + $this->configManager = $configManager; } /** @@ -47,18 +54,22 @@ public function getOptions() public function addTableOptions($tableName, array $options) { $customEntityClassName = $this->getAndRemoveOption($options, ExtendOptionsManager::ENTITY_CLASS_OPTION); - $entityClassName = $this->getEntityClassName($tableName, $customEntityClassName, false); - if (!$entityClassName) { + $entityClassNames = $this->getEntityClassNames($tableName, $customEntityClassName, false); + if (!$entityClassNames) { return; } $tableMode = $this->getAndRemoveOption($options, ExtendOptionsManager::MODE_OPTION); if (!empty($options)) { - $this->result[$entityClassName]['configs'] = $options; + foreach ($entityClassNames as $entityClassName) { + $this->result[$entityClassName]['configs'] = $options; + } } if ($tableMode) { - $this->result[$entityClassName]['mode'] = $tableMode; + foreach ($entityClassNames as $entityClassName) { + $this->result[$entityClassName]['mode'] = $tableMode; + } } } @@ -72,17 +83,19 @@ public function addTableOptions($tableName, array $options) */ public function addColumnOptions($tableName, $columnName, $options) { - $entityClassName = $this->getEntityClassName($tableName, null, false); - if (!$entityClassName) { + $entityClassNames = $this->getEntityClassNames($tableName, null, false); + if (!$entityClassNames) { return; } $newColumnName = $this->getAndRemoveOption($options, ExtendOptionsManager::NEW_NAME_OPTION); if ($newColumnName) { - $this->result[ExtendConfigProcessor::RENAME_CONFIGS][$entityClassName][$columnName] = $newColumnName; + foreach ($entityClassNames as $entityClassName) { + $this->result[ExtendConfigProcessor::RENAME_CONFIGS][$entityClassName][$columnName] = $newColumnName; + } if (empty($options)) { return; - }; + } } $fieldName = $this->getAndRemoveOption($options, ExtendOptionsManager::FIELD_NAME_OPTION); @@ -101,7 +114,15 @@ public function addColumnOptions($tableName, $columnName, $options) foreach ($target as $optionName => $optionValue) { switch ($optionName) { case 'table_name': - $options['extend']['target_entity'] = $this->getEntityClassName($optionValue); + $targetEntityNames = $this->getEntityClassNames($optionValue); + if (count($targetEntityNames) > 1) { + throw new \LogicException(sprintf( + 'Table "%s" is expected to be related with 1 entity, but %d entities found', + $optionValue, + count($entityClassNames) + )); + } + $options['extend']['target_entity'] = reset($targetEntityNames); break; case 'column': $options['extend']['target_field'] = $this->getFieldName($target['table_name'], $optionValue); @@ -122,8 +143,15 @@ public function addColumnOptions($tableName, $columnName, $options) } if (!isset($options['extend']['relation_key'])) { + if (count($entityClassNames) > 1) { + throw new \LogicException(sprintf( + 'Table "%s" is expected to be related with 1 entity, but %d entities found', + $tableName, + count($entityClassNames) + )); + } $options['extend']['relation_key'] = ExtendHelper::buildRelationKey( - $entityClassName, + reset($entityClassNames), $fieldName, $columnUnderlyingType, $options['extend']['target_entity'] @@ -131,15 +159,17 @@ public function addColumnOptions($tableName, $columnName, $options) } } - $this->result[$entityClassName]['fields'][$fieldName] = []; - if (!empty($options)) { - $this->result[$entityClassName]['fields'][$fieldName]['configs'] = $options; - } - if ($columnType) { - $this->result[$entityClassName]['fields'][$fieldName]['type'] = $columnType; - } - if ($columnMode) { - $this->result[$entityClassName]['fields'][$fieldName]['mode'] = $columnMode; + foreach ($entityClassNames as $entityClassName) { + $this->result[$entityClassName]['fields'][$fieldName] = []; + if (!empty($options)) { + $this->result[$entityClassName]['fields'][$fieldName]['configs'] = $options; + } + if ($columnType) { + $this->result[$entityClassName]['fields'][$fieldName]['type'] = $columnType; + } + if ($columnMode) { + $this->result[$entityClassName]['fields'][$fieldName]['mode'] = $columnMode; + } } } @@ -150,12 +180,14 @@ public function addColumnOptions($tableName, $columnName, $options) */ public function addTableAuxiliaryOptions($configType, $tableName, $options) { - $entityClassName = $this->getEntityClassName($tableName, null, false); - if (!$entityClassName) { + $entityClassNames = $this->getEntityClassNames($tableName, null, false); + if (!$entityClassNames) { return; } - $this->result[$configType][$entityClassName]['configs'] = $options; + foreach ($entityClassNames as $entityClassName) { + $this->result[$configType][$entityClassName]['configs'] = $options; + } } /** @@ -166,14 +198,16 @@ public function addTableAuxiliaryOptions($configType, $tableName, $options) */ public function addColumnAuxiliaryOptions($configType, $tableName, $columnName, $options) { - $entityClassName = $this->getEntityClassName($tableName, null, false); - if (!$entityClassName) { + $entityClassNames = $this->getEntityClassNames($tableName, null, false); + if (!$entityClassNames) { return; } $fieldName = $this->getFieldName($tableName, $columnName); - $this->result[$configType][$entityClassName]['fields'][$fieldName] = $options; + foreach ($entityClassNames as $entityClassName) { + $this->result[$configType][$entityClassName]['fields'][$fieldName] = $options; + } } /** @@ -200,23 +234,26 @@ public function getAuxiliaryConfigType($sectionName) * @param string $customEntityClassName The name of a custom entity * @param bool $throwExceptionIfNotFound * - * @return string|null + * @return string[] * * @throws \RuntimeException if an entity class name was not found and $throwExceptionIfNotFound = TRUE */ - protected function getEntityClassName($tableName, $customEntityClassName = null, $throwExceptionIfNotFound = true) + protected function getEntityClassNames($tableName, $customEntityClassName = null, $throwExceptionIfNotFound = true) { - if (!isset($this->tableToEntityMap[$tableName])) { - $entityClassName = !empty($customEntityClassName) - ? $customEntityClassName - : $this->entityMetadataHelper->getEntityClassByTableName($tableName); - if ($throwExceptionIfNotFound && empty($entityClassName)) { - throw new \RuntimeException(sprintf('Cannot find entity for "%s" table.', $tableName)); + if (!isset($this->tableToEntitiesMap[$tableName])) { + $entityClassNames = !empty($customEntityClassName) + ? [$customEntityClassName] + : array_filter( + $this->entityMetadataHelper->getEntityClassesByTableName($tableName), + [$this->configManager, 'hasConfig'] + ); + if ($throwExceptionIfNotFound && empty($entityClassNames)) { + throw new \RuntimeException(sprintf('Cannot find configurable entity for "%s" table.', $tableName)); } - $this->tableToEntityMap[$tableName] = $entityClassName; + $this->tableToEntitiesMap[$tableName] = $entityClassNames; } - $result = $this->tableToEntityMap[$tableName]; + $result = $this->tableToEntitiesMap[$tableName]; if ($throwExceptionIfNotFound && empty($result)) { throw new \RuntimeException(sprintf('Cannot find entity for "%s" table.', $tableName)); } diff --git a/src/Oro/Bundle/EntityExtendBundle/Migration/ExtendOptionsParser.php b/src/Oro/Bundle/EntityExtendBundle/Migration/ExtendOptionsParser.php index 93d12b6aedf..72e415cd1d6 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Migration/ExtendOptionsParser.php +++ b/src/Oro/Bundle/EntityExtendBundle/Migration/ExtendOptionsParser.php @@ -2,6 +2,7 @@ namespace Oro\Bundle\EntityExtendBundle\Migration; +use Oro\Bundle\EntityConfigBundle\Config\ConfigManager; use Oro\Bundle\EntityExtendBundle\Extend\FieldTypeHelper; class ExtendOptionsParser @@ -12,16 +13,22 @@ class ExtendOptionsParser /** @var FieldTypeHelper */ protected $fieldTypeHelper; + /** @var ConfigManager */ + protected $configManager; + /** * @param EntityMetadataHelper $entityMetadataHelper * @param FieldTypeHelper $fieldTypeHelper + * @param ConfigManager $configManager */ public function __construct( EntityMetadataHelper $entityMetadataHelper, - FieldTypeHelper $fieldTypeHelper + FieldTypeHelper $fieldTypeHelper, + ConfigManager $configManager ) { $this->entityMetadataHelper = $entityMetadataHelper; $this->fieldTypeHelper = $fieldTypeHelper; + $this->configManager = $configManager; } /** @@ -32,7 +39,7 @@ public function __construct( */ public function parseOptions(array $options) { - $builder = new ExtendOptionsBuilder($this->entityMetadataHelper, $this->fieldTypeHelper); + $builder = new ExtendOptionsBuilder($this->entityMetadataHelper, $this->fieldTypeHelper, $this->configManager); $objectKeys = array_filter( array_keys($options), diff --git a/src/Oro/Bundle/EntityExtendBundle/Migration/UpdateExtendIndicesMigration.php b/src/Oro/Bundle/EntityExtendBundle/Migration/UpdateExtendIndicesMigration.php index 927d898cfd7..3312e8d162a 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Migration/UpdateExtendIndicesMigration.php +++ b/src/Oro/Bundle/EntityExtendBundle/Migration/UpdateExtendIndicesMigration.php @@ -112,8 +112,8 @@ public function up(Schema $schema, QueryBag $queries) */ protected function processColumn(Schema $schema, QueryBag $queries, $tableName, $columnName, $options) { - $className = $this->entityMetadataHelper->getEntityClassByTableName($tableName); - if (null === $className) { + $classNames = $this->entityMetadataHelper->getEntityClassesByTableName($tableName); + if (!$classNames) { return; } @@ -124,11 +124,15 @@ protected function processColumn(Schema $schema, QueryBag $queries, $tableName, if (!isset($options[ExtendOptionsManager::NEW_NAME_OPTION])) { if (isset($options[ExtendOptionsManager::TYPE_OPTION])) { - $this->buildIndex($columnName, $options, $className, $table); + foreach ($classNames as $className) { + $this->buildIndex($columnName, $options, $className, $table); + } } } else { // in case of renaming column name we should rename existing index - $this->renameIndex($schema, $queries, $tableName, $columnName, $options, $className, $table); + foreach ($classNames as $className) { + $this->renameIndex($schema, $queries, $tableName, $columnName, $options, $className, $table); + } } } diff --git a/src/Oro/Bundle/EntityExtendBundle/Resources/config/services.yml b/src/Oro/Bundle/EntityExtendBundle/Resources/config/services.yml index 79296c8eb84..3405842431d 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Resources/config/services.yml +++ b/src/Oro/Bundle/EntityExtendBundle/Resources/config/services.yml @@ -268,6 +268,7 @@ services: arguments: - "@oro_entity_extend.migration.entity_metadata_helper" - "@oro_entity_extend.extend.field_type_helper" + - "@oro_entity_config.config_manager" oro_entity_extend.migration.extension.extend: class: %oro_entity_extend.migration.extension.extend.class% diff --git a/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/EntityMetadataHelperTest.php b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/EntityMetadataHelperTest.php index c4e34d791bb..3a4a2dd0b05 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/EntityMetadataHelperTest.php +++ b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/EntityMetadataHelperTest.php @@ -2,6 +2,8 @@ namespace Oro\Bundle\EntityExtendBundle\Tests\Unit\Migration; +use Doctrine\ORM\Mapping\ClassMetadataInfo; + use Oro\Bundle\EntityExtendBundle\Migration\EntityMetadataHelper; class EntityMetadataHelperTest extends \PHPUnit_Framework_TestCase @@ -26,15 +28,8 @@ protected function setUp() public function testGetEntityClassByTableName() { - $metadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata') - ->disableOriginalConstructor() - ->getMock(); - $metadata->expects($this->once()) - ->method('getTableName') - ->will($this->returnValue('acme_test')); - $metadata->expects($this->once()) - ->method('getName') - ->will($this->returnValue('Oro\Bundle\EntityBundle\Tests\Unit\ORM\Fixtures\TestEntity')); + $metadata = new ClassMetadataInfo('Oro\Bundle\EntityBundle\Tests\Unit\ORM\Fixtures\TestEntity'); + $metadata->table['name'] = 'acme_test'; $metadataFactory = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataFactory') ->disableOriginalConstructor() @@ -64,17 +59,48 @@ public function testGetEntityClassByTableName() ); } - public function testGetTableNameByEntityClass() + public function testGetEntityClassesByTableName() { - $metadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata') + $testEntityMetadata = new ClassMetadataInfo('Oro\Bundle\EntityBundle\Tests\Unit\ORM\Fixtures\TestEntity'); + $testEntityMetadata->table['name'] = 'acme_test'; + $testEntity2Metadata = new ClassMetadataInfo('Oro\Bundle\EntityBundle\Tests\Unit\ORM\Fixtures\TestEntity2'); + $testEntity2Metadata->table['name'] = 'acme_test'; + + $metadataFactory = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataFactory') + ->disableOriginalConstructor() + ->getMock(); + $metadataFactory->expects($this->once()) + ->method('getAllMetadata') + ->will($this->returnValue([$testEntityMetadata, $testEntity2Metadata])); + + $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') ->disableOriginalConstructor() ->getMock(); - $metadata->expects($this->once()) - ->method('getTableName') - ->will($this->returnValue('acme_test')); - $metadata->expects($this->once()) - ->method('getName') - ->will($this->returnValue('Oro\Bundle\EntityBundle\Tests\Unit\ORM\Fixtures\TestEntity')); + $em->expects($this->once()) + ->method('getMetadataFactory') + ->will($this->returnValue($metadataFactory)); + + $this->doctrine->expects($this->once()) + ->method('getManagers') + ->will($this->returnValue(array('default' => $em))); + $this->doctrine->expects($this->once()) + ->method('getManager') + ->with($this->equalTo('default')) + ->will($this->returnValue($em)); + + $this->assertEquals( + [ + 'Oro\Bundle\EntityBundle\Tests\Unit\ORM\Fixtures\TestEntity', + 'Oro\Bundle\EntityBundle\Tests\Unit\ORM\Fixtures\TestEntity2', + ], + $this->helper->getEntityClassesByTableName('acme_test') + ); + } + + public function testGetTableNameByEntityClass() + { + $metadata = new ClassMetadataInfo('Oro\Bundle\EntityBundle\Tests\Unit\ORM\Fixtures\TestEntity'); + $metadata->table['name'] = 'acme_test'; $metadataFactory = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataFactory') ->disableOriginalConstructor() @@ -106,19 +132,9 @@ public function testGetTableNameByEntityClass() public function testGetFieldNameByColumnName() { - $metadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata') - ->disableOriginalConstructor() - ->getMock(); - $metadata->expects($this->once()) - ->method('getTableName') - ->will($this->returnValue('acme_test')); - $metadata->expects($this->once()) - ->method('getFieldName') - ->with('name_column') - ->will($this->returnValue('name_field')); - $metadata->expects($this->once()) - ->method('getName') - ->will($this->returnValue('Oro\Bundle\EntityBundle\Tests\Unit\ORM\Fixtures\TestEntity')); + $metadata = new ClassMetadataInfo('Oro\Bundle\EntityBundle\Tests\Unit\ORM\Fixtures\TestEntity'); + $metadata->table['name'] = 'acme_test'; + $metadata->fieldNames['name_column'] = 'name_field'; $metadataFactory = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataFactory') ->disableOriginalConstructor() diff --git a/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/ExtendOptionsParserTest.php b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/ExtendOptionsParserTest.php index a10dc95eaf1..16b43ccae99 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/ExtendOptionsParserTest.php +++ b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/ExtendOptionsParserTest.php @@ -24,17 +24,24 @@ protected function setUp() ->getMock(); $this->entityMetadataHelper->expects($this->any()) - ->method('getEntityClassByTableName') + ->method('getEntityClassesByTableName') ->willReturnMap( [ - ['table1', 'Test\Entity1'], - ['table2', 'Test\Entity2'], + ['table1', ['Test\Entity1']], + ['table2', ['Test\Entity2']], ] ); + $configManager = $this->getMockBuilder('Oro\Bundle\EntityConfigBundle\Config\ConfigManager') + ->disableOriginalConstructor() + ->getMock(); + $configManager->expects($this->any()) + ->method('hasConfig') + ->will($this->returnValue(true)); $this->extendOptionsParser = new ExtendOptionsParser( $this->entityMetadataHelper, - new FieldTypeHelper(['enum' => 'manyToOne', 'multiEnum' => 'manyToMany']) + new FieldTypeHelper(['enum' => 'manyToOne', 'multiEnum' => 'manyToMany']), + $configManager ); } diff --git a/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/Extension/ExtendExtensionTest.php b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/Extension/ExtendExtensionTest.php index e1bdf23ef8f..7b74e3df936 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/Extension/ExtendExtensionTest.php +++ b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/Extension/ExtendExtensionTest.php @@ -49,13 +49,31 @@ protected function setUp() ] ) ); + $this->entityMetadataHelper->expects($this->any()) + ->method('getEntityClassesByTableName') + ->will( + $this->returnValueMap( + [ + ['table1', ['Acme\AcmeBundle\Entity\Entity1']], + ['table2', ['Acme\AcmeBundle\Entity\Entity2']], + ['oro_enum_test_enum', [ExtendHelper::ENTITY_NAMESPACE . 'EV_Test_Enum']], + ] + ) + ); $this->entityMetadataHelper->expects($this->any()) ->method('getFieldNameByColumnName') ->will($this->returnArgument(1)); $this->extendOptionsManager = new ExtendOptionsManager(); + $configManager = $this->getMockBuilder('Oro\Bundle\EntityConfigBundle\Config\ConfigManager') + ->disableOriginalConstructor() + ->getMock(); + $configManager->expects($this->any()) + ->method('hasConfig') + ->will($this->returnValue(true)); $this->extendOptionsParser = new ExtendOptionsParser( $this->entityMetadataHelper, - new FieldTypeHelper(['enum' => 'manyToOne', 'multiEnum' => 'manyToMany']) + new FieldTypeHelper(['enum' => 'manyToOne', 'multiEnum' => 'manyToMany']), + $configManager ); } diff --git a/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/Schema/ExtendSchemaTest.php b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/Schema/ExtendSchemaTest.php index e8634a66957..cfc41e2f6f7 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/Schema/ExtendSchemaTest.php +++ b/src/Oro/Bundle/EntityExtendBundle/Tests/Unit/Migration/Schema/ExtendSchemaTest.php @@ -38,18 +38,25 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->entityMetadataHelper->expects($this->any()) - ->method('getEntityClassByTableName') + ->method('getEntityClassesByTableName') ->will( $this->returnValueMap( [ - ['table1', 'Acme\AcmeBundle\Entity\Entity1'] + ['table1', ['Acme\AcmeBundle\Entity\Entity1']], ] ) ); $this->extendOptionsManager = new ExtendOptionsManager(); + $configManager = $this->getMockBuilder('Oro\Bundle\EntityConfigBundle\Config\ConfigManager') + ->disableOriginalConstructor() + ->getMock(); + $configManager->expects($this->any()) + ->method('hasConfig') + ->will($this->returnValue(true)); $this->extendOptionsParser = new ExtendOptionsParser( $this->entityMetadataHelper, - new FieldTypeHelper(['enum' => 'manyToOne', 'multiEnum' => 'manyToMany']) + new FieldTypeHelper(['enum' => 'manyToOne', 'multiEnum' => 'manyToMany']), + $configManager ); $this->nameGenerator = new ExtendDbIdentifierNameGenerator(); } diff --git a/src/Oro/Bundle/EntityExtendBundle/composer.json b/src/Oro/Bundle/EntityExtendBundle/composer.json index ae88cde4989..fb8ec907898 100644 --- a/src/Oro/Bundle/EntityExtendBundle/composer.json +++ b/src/Oro/Bundle/EntityExtendBundle/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/log": "dev-master", "oro/ui-bundle": "dev-master", "oro/grid-bundle": "dev-master", diff --git a/src/Oro/Bundle/EntityMergeBundle/DataGrid/Extension/MassAction/MergeMassAction.php b/src/Oro/Bundle/EntityMergeBundle/DataGrid/Extension/MassAction/MergeMassAction.php index 52ff9506265..17e9064a917 100644 --- a/src/Oro/Bundle/EntityMergeBundle/DataGrid/Extension/MassAction/MergeMassAction.php +++ b/src/Oro/Bundle/EntityMergeBundle/DataGrid/Extension/MassAction/MergeMassAction.php @@ -6,43 +6,44 @@ use Oro\Bundle\DataGridBundle\Extension\Action\ActionConfiguration; use Oro\Bundle\DataGridBundle\Extension\MassAction\Actions\AbstractMassAction; -use Oro\Bundle\EntityMergeBundle\Metadata\MetadataRegistry; +use Oro\Bundle\EntityConfigBundle\Provider\ConfigProvider; +use Oro\Bundle\EntityMergeBundle\Metadata\EntityMetadata; class MergeMassAction extends AbstractMassAction { - /** - * @var MetadataRegistry - */ - protected $metadataRegistry; + /** @var ConfigProvider */ + protected $entityConfigProvider; - /** - * @var TranslatorInterface - */ + /** @var TranslatorInterface */ protected $translator; /** - * @param MetadataRegistry $metadataRegistry + * @param ConfigProvider $entityConfigProvider * @param TranslatorInterface $translator */ - public function __construct(MetadataRegistry $metadataRegistry, TranslatorInterface $translator) - { - $this->metadataRegistry = $metadataRegistry; - $this->translator = $translator; + public function __construct( + ConfigProvider $entityConfigProvider, + TranslatorInterface $translator + ) { + parent::__construct(); + + $this->entityConfigProvider = $entityConfigProvider; + $this->translator = $translator; } /** @var array */ protected $requiredOptions = ['route', 'entity_name', 'data_identifier', 'max_element_count']; /** @var array */ - protected $defaultOptions = array( - 'frontend_handle' => 'redirect', - 'handler' => 'oro_entity_merge.mass_action.data_handler', - 'icon' => 'random', - 'frontend_type' => 'merge-mass', - 'route' => 'oro_entity_merge_massaction', - 'data_identifier' => 'id', - 'route_parameters' => array(), - ); + protected $defaultOptions = [ + 'frontend_handle' => 'redirect', + 'handler' => 'oro_entity_merge.mass_action.data_handler', + 'icon' => 'random', + 'frontend_type' => 'merge-mass', + 'route' => 'oro_entity_merge_massaction', + 'data_identifier' => 'id', + 'route_parameters' => [], + ]; /** * {@inheritdoc} @@ -52,17 +53,17 @@ public function setOptions(ActionConfiguration $options) $this->setDefaultOptions($options); if (isset($options['entity_name'])) { - $metadata = $this - ->metadataRegistry - ->getEntityMetadata($options['entity_name']); - - $options['max_element_count'] = $metadata->getMaxEntitiesCount(); + $entityConfig = $this->entityConfigProvider->getConfig($options['entity_name']); + $options['max_element_count'] = $entityConfig->get( + 'max_element_count', + false, + EntityMetadata::MAX_ENTITIES_COUNT + ); - $options['label'] = $this->translator - ->trans( - 'oro.entity_merge.action.merge', - ['{{ label }}' => $this->translator->trans($metadata->get('label'))] - ); + $options['label'] = $this->translator->trans( + 'oro.entity_merge.action.merge', + ['{{ label }}' => $this->translator->trans($entityConfig->get('label'))] + ); } return parent::setOptions($options); diff --git a/src/Oro/Bundle/EntityMergeBundle/EventListener/DataGrid/MergeMassActionListener.php b/src/Oro/Bundle/EntityMergeBundle/EventListener/DataGrid/MergeMassActionListener.php index a4d1ee242a8..0ff845c7c98 100644 --- a/src/Oro/Bundle/EntityMergeBundle/EventListener/DataGrid/MergeMassActionListener.php +++ b/src/Oro/Bundle/EntityMergeBundle/EventListener/DataGrid/MergeMassActionListener.php @@ -3,21 +3,19 @@ namespace Oro\Bundle\EntityMergeBundle\EventListener\DataGrid; use Oro\Bundle\DataGridBundle\Event\BuildBefore; -use Oro\Bundle\EntityMergeBundle\Metadata\MetadataRegistry; +use Oro\Bundle\EntityConfigBundle\Provider\ConfigProvider; class MergeMassActionListener { - /** - * @var MetadataRegistry - */ - protected $metadataRegistry; + /** @var ConfigProvider */ + protected $entityConfigProvider; /** - * @param MetadataRegistry $metadataRegistry + * @param ConfigProvider $entityConfigProvider */ - public function __construct(MetadataRegistry $metadataRegistry) + public function __construct(ConfigProvider $entityConfigProvider) { - $this->metadataRegistry = $metadataRegistry; + $this->entityConfigProvider = $entityConfigProvider; } /** @@ -28,18 +26,15 @@ public function __construct(MetadataRegistry $metadataRegistry) public function onBuildBefore(BuildBefore $event) { $config = $event->getConfig(); - - $massActions = isset($config['mass_actions']) ? $config['mass_actions'] : array(); - - if (empty($massActions['merge']['entity_name'])) { + if (!isset($config['mass_actions']) + || empty($config['mass_actions']['merge']['entity_name']) + ) { return; } - $entityName = $massActions['merge']['entity_name']; - - $entityMergeEnable = $this->metadataRegistry->getEntityMetadata($entityName)->is('enable'); - - if (!$entityMergeEnable) { + $entityClassName = $config['mass_actions']['merge']['entity_name']; + $entityMergeEnabled = $this->entityConfigProvider->getConfig($entityClassName)->is('enable'); + if (!$entityMergeEnabled) { $config->offsetUnsetByPath('[mass_actions][merge]'); } } diff --git a/src/Oro/Bundle/EntityMergeBundle/Resources/config/mass_action.yml b/src/Oro/Bundle/EntityMergeBundle/Resources/config/mass_action.yml index 9f8bcd4ea0f..faefe9f0d11 100644 --- a/src/Oro/Bundle/EntityMergeBundle/Resources/config/mass_action.yml +++ b/src/Oro/Bundle/EntityMergeBundle/Resources/config/mass_action.yml @@ -6,7 +6,7 @@ services: oro_entity_merge.mass_action.merge: class: %oro_entity_merge.mass_action.merge.class% arguments: - - '@oro_entity_merge.metadata.registry' + - '@oro_entity_config.provider.merge' - '@translator' scope: prototype tags: @@ -20,6 +20,6 @@ services: oro_entity_merge.mass_action.merge_mass_action_listener: class: %oro_entity_merge.mass_action.merge_mass_action_listener.class% arguments: - - '@oro_entity_merge.metadata.registry' + - '@oro_entity_config.provider.merge' tags: - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.before, method: onBuildBefore } diff --git a/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/DataGrid/Extension/MassAction/MergeMassActionTest.php b/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/DataGrid/Extension/MassAction/MergeMassActionTest.php index 5d5cec96c2a..0a680ac8397 100644 --- a/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/DataGrid/Extension/MassAction/MergeMassActionTest.php +++ b/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/DataGrid/Extension/MassAction/MergeMassActionTest.php @@ -3,6 +3,7 @@ namespace Oro\Bundle\EntityMergeBundle\Tests\Unit\DataGrid\Extension\MassAction; use Oro\Bundle\DataGridBundle\Extension\Action\ActionConfiguration; +use Oro\Bundle\EntityConfigBundle\Config\Config as EntityConfig; use Oro\Bundle\EntityMergeBundle\DataGrid\Extension\MassAction\MergeMassAction; class MergeMassActionTest extends \PHPUnit_Framework_TestCase @@ -16,29 +17,21 @@ class MergeMassActionTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $metadata = $this - ->getMockBuilder('Oro\Bundle\EntityMergeBundle\Metadata\EntityMetadata') + $entityConfigProvider = $this->getMockBuilder('Oro\Bundle\EntityConfigBundle\Provider\ConfigProvider') ->disableOriginalConstructor() ->getMock(); - - $metadata - ->expects($this->any()) - ->method('getMaxEntitiesCount') - ->will($this->returnValue(self::MAX_ENTITIES_COUNT)); - - $metadataRegistry = $this - ->getMockBuilder('Oro\Bundle\EntityMergeBundle\Metadata\MetadataRegistry') - ->disableOriginalConstructor() - ->getMock(); - - $metadataRegistry - ->expects($this->any()) - ->method('getEntityMetadata') - ->will($this->returnValue($metadata)); + $entityConfig = new EntityConfig( + $this->getMock('Oro\Bundle\EntityConfigBundle\Config\Id\ConfigIdInterface'), + ['max_element_count' => self::MAX_ENTITIES_COUNT] + ); + $entityConfigProvider->expects($this->any()) + ->method('getConfig') + ->with('SomeEntityClass') + ->willReturn($entityConfig); $translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface'); - $this->target = new MergeMassAction($metadataRegistry, $translator); + $this->target = new MergeMassAction($entityConfigProvider, $translator); } /** diff --git a/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/DependencyInjection/OroEntityMergeExtensionTest.php b/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/DependencyInjection/OroEntityMergeExtensionTest.php index 36915f9e49e..8f57a27bfb9 100644 --- a/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/DependencyInjection/OroEntityMergeExtensionTest.php +++ b/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/DependencyInjection/OroEntityMergeExtensionTest.php @@ -81,7 +81,7 @@ public function loadServiceDataProvider() 'service' => 'oro_entity_merge.mass_action.merge', 'class' => '%oro_entity_merge.mass_action.merge.class%', 'arguments' => array( - new Reference('oro_entity_merge.metadata.registry'), + new Reference('oro_entity_config.provider.merge'), new Reference('translator') ), 'tags' => array( diff --git a/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/EventListener/DataGrid/MergeMassActionListenerTest.php b/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/EventListener/DataGrid/MergeMassActionListenerTest.php index c830bfaedc4..42004581750 100644 --- a/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/EventListener/DataGrid/MergeMassActionListenerTest.php +++ b/src/Oro/Bundle/EntityMergeBundle/Tests/Unit/EventListener/DataGrid/MergeMassActionListenerTest.php @@ -2,6 +2,9 @@ namespace Oro\Bundle\EntityMergeBundle\Tests\Unit\EventListener\DataGrid; +use Oro\Bundle\DataGridBundle\Datagrid\Common\DatagridConfiguration; +use Oro\Bundle\DataGridBundle\Event\BuildBefore; +use Oro\Bundle\EntityConfigBundle\Config\Config as EntityConfig; use Oro\Bundle\EntityMergeBundle\EventListener\DataGrid\MergeMassActionListener; class MergeMassActionListenerTest extends \PHPUnit_Framework_TestCase @@ -9,158 +12,115 @@ class MergeMassActionListenerTest extends \PHPUnit_Framework_TestCase /** * @var MergeMassActionListener */ - private $target; + private $mergeMassActionListener; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $entityMetadata; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $buildBefore; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $datagridConfig; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $metadataRegistry; - - /** - * @var string - */ - private $entityName; - - /** - * @var array - */ - private $config; + private $entityConfigProvider; protected function setUp() { - $this->metadataRegistry = $this->getMockBuilder('Oro\Bundle\EntityMergeBundle\Metadata\MetadataRegistry') - ->disableOriginalConstructor() - ->getMock(); - - $this->entityMetadata = $this->getMockBuilder('Oro\Bundle\EntityMergeBundle\Metadata\EntityMetadata') - ->disableOriginalConstructor() - ->getMock(); - - $this->buildBefore = $this->getMockBuilder('Oro\Bundle\DataGridBundle\Event\BuildBefore') - ->disableOriginalConstructor() - ->getMock(); - - $this->entityName = 'testEntityName'; - $this->config = array('mass_actions' => array('merge' => array('entity_name' => $this->entityName))); - - $this->datagridConfig = $this->getMockBuilder('Oro\Bundle\DataGridBundle\Datagrid\Common\DatagridConfiguration') + $this->entityConfigProvider = $this->getMockBuilder('Oro\Bundle\EntityConfigBundle\Provider\ConfigProvider') ->disableOriginalConstructor() ->getMock(); - $this->buildBefore->expects($this->any()) - ->method('getConfig') - ->will($this->returnValue($this->datagridConfig)); - - $this->target = new MergeMassActionListener($this->metadataRegistry); + $this->mergeMassActionListener = new MergeMassActionListener($this->entityConfigProvider); } public function testOnBuildUnsetMergeMassAction() { - $this->init(); + $entityName = 'testEntityName'; + $datagridConfig = DatagridConfiguration::create( + ['mass_actions' => ['merge' => ['entity_name' => $entityName]]] + ); + $entityConfig = $this->getEntityConfig(); - $this->entityMetadata->expects($this->once()) - ->method('is') - ->with('enable', true) - ->will($this->returnValue(false)); + $this->entityConfigProvider->expects($this->once()) + ->method('getConfig') + ->with($entityName) + ->willReturn($entityConfig); - $this->datagridConfig->expects($this->once()) - ->method('offsetUnsetByPath') - ->with('[mass_actions][merge]'); + $event = $this->getBuildBeforeEvent($datagridConfig); + $this->mergeMassActionListener->onBuildBefore($event); - $this->target->onBuildBefore($this->buildBefore); + $this->assertEquals( + ['mass_actions' => []], + $datagridConfig->toArray() + ); } public function testOnBuildNotUnsetMergeMass() { - $this->init(); - - $this->entityMetadata->expects($this->once()) - ->method('is') - ->with('enable', true) - ->will($this->returnValue(true)); + $entityName = 'testEntityName'; + $datagridConfig = DatagridConfiguration::create( + ['mass_actions' => ['merge' => ['entity_name' => $entityName]]] + ); + $entityConfig = $this->getEntityConfig(['enable' => true]); - $this->datagridConfig->expects($this->never()) - ->method('offsetUnsetByPath') - ->withAnyParameters(); + $this->entityConfigProvider->expects($this->once()) + ->method('getConfig') + ->with($entityName) + ->willReturn($entityConfig); + $event = $this->getBuildBeforeEvent($datagridConfig); + $this->mergeMassActionListener->onBuildBefore($event); - $this->target->onBuildBefore($this->buildBefore); + $this->assertEquals( + ['mass_actions' => ['merge' => ['entity_name' => $entityName]]], + $datagridConfig->toArray() + ); } public function testOnBuildBeforeSkipsForEmptyMassActions() { - $this->initDatagridConfig(array('mass_actions' => array())); + $datagridConfig = DatagridConfiguration::create( + ['mass_actions' => []] + ); - $this->metadataRegistry->expects($this->never()) - ->method('getEntityMetadata') - ->withAnyParameters(); + $this->entityConfigProvider->expects($this->never()) + ->method('getConfig'); - $this->target->onBuildBefore($this->buildBefore); + $event = $this->getBuildBeforeEvent($datagridConfig); + $this->mergeMassActionListener->onBuildBefore($event); } public function testOnBuildBeforeForEmptyEntityName() { - $this->initDatagridConfig(array('mass_actions' => array('merge' => array('entity_name' => '')))); + $datagridConfig = DatagridConfiguration::create( + ['mass_actions' => ['merge' => ['entity_name' => '']]] + ); - $this->metadataRegistry->expects($this->never()) - ->method('getEntityMetadata') - ->withAnyParameters(); - - $this->target->onBuildBefore($this->buildBefore); - } + $this->entityConfigProvider->expects($this->never()) + ->method('getConfig'); - protected function initMetadataRegistry() - { - $this->metadataRegistry->expects($this->any()) - ->method('getEntityMetadata') - ->will($this->returnValue($this->entityMetadata)); + $event = $this->getBuildBeforeEvent($datagridConfig); + $this->mergeMassActionListener->onBuildBefore($event); } - protected function initDatagridConfig($offsetResult = null) + /** + * @param DatagridConfiguration $datagridConfig + * + * @return BuildBefore + */ + protected function getBuildBeforeEvent(DatagridConfiguration $datagridConfig) { - $rawConfig = $this->config; - $offsetResult = $offsetResult === null ? $this->config['mass_actions'] : $offsetResult; - - $this->datagridConfig->expects($this->any()) - ->method('offsetExists') - ->with('mass_actions') - ->will( - $this->returnCallback( - function ($offset) use ($rawConfig) { - return isset($rawConfig[$offset]); - } - ) - ); - - $this->datagridConfig->expects($this->any()) - ->method('offsetGet') - ->with('mass_actions') - ->will($this->returnValue($offsetResult)); - - $this->datagridConfig->expects($this->any()) - ->method('offsetGet') - ->with('mass_actions') - ->will($this->returnValue($offsetResult)); + return new BuildBefore( + $this->getMock('Oro\Bundle\DataGridBundle\Datagrid\DatagridInterface'), + $datagridConfig + ); } - protected function init() + /** + * @param array $values + * + * @return EntityConfig + */ + protected function getEntityConfig(array $values = []) { - $this->initMetadataRegistry(); - $this->initDatagridConfig(); + return new EntityConfig( + $this->getMock('Oro\Bundle\EntityConfigBundle\Config\Id\ConfigIdInterface'), + $values + ); } } diff --git a/src/Oro/Bundle/EntityMergeBundle/composer.json b/src/Oro/Bundle/EntityMergeBundle/composer.json index 83e3c01ca45..b46d6a72ea6 100644 --- a/src/Oro/Bundle/EntityMergeBundle/composer.json +++ b/src/Oro/Bundle/EntityMergeBundle/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/entity-config-bundle": "dev-master", "oro/ui-bundle": "dev-master", "oro/grid-bundle": "dev-master", diff --git a/src/Oro/Bundle/EntityPaginationBundle/Datagrid/EntityPaginationExtension.php b/src/Oro/Bundle/EntityPaginationBundle/Datagrid/EntityPaginationExtension.php index 34c66e03246..1bc047eeb16 100644 --- a/src/Oro/Bundle/EntityPaginationBundle/Datagrid/EntityPaginationExtension.php +++ b/src/Oro/Bundle/EntityPaginationBundle/Datagrid/EntityPaginationExtension.php @@ -22,7 +22,7 @@ public function isApplicable(DatagridConfiguration $config) return false; } - return $config->getDatasourceType() == OrmDatasource::TYPE; + return $config->getDatasourceType() === OrmDatasource::TYPE; } /** diff --git a/src/Oro/Bundle/FilterBundle/Filter/AbstractFilter.php b/src/Oro/Bundle/FilterBundle/Filter/AbstractFilter.php index a3de85c794f..eb74de8e53d 100644 --- a/src/Oro/Bundle/FilterBundle/Filter/AbstractFilter.php +++ b/src/Oro/Bundle/FilterBundle/Filter/AbstractFilter.php @@ -13,6 +13,10 @@ use Oro\Component\DoctrineUtils\ORM\QueryUtils; use Oro\Component\PhpUtils\ArrayUtil; +/** + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @todo refactor in BAP-11688 + */ abstract class AbstractFilter implements FilterInterface { /** @var FormFactoryInterface */ @@ -102,10 +106,25 @@ public function apply(FilterDatasourceAdapterInterface $ds, $data) $subQb ->resetDqlPart('orderBy') ->select($fieldExpr) - ->andWhere($comparisonExpr); - $dql = $this->createDQLWithReplacedAliases($ds, $subQb); - - $subExprs[] = $joinOperator ? $qb->expr()->notIn($fieldExpr, $dql) : $qb->expr()->in($fieldExpr, $dql); + ->andWhere($comparisonExpr) + ->andWhere(sprintf('%1$s = %1$s', $fieldExpr)); + $groupBy = implode(', ', $this->getSelectFieldFromGroupBy($qb)); + if ($groupBy) { + // replace aliases from SELECT by expressions, since SELECT clause is changed + $subQb->groupBy($groupBy); + } + list($dql, $replacements) = $this->createDQLWithReplacedAliases($ds, $subQb); + list($fieldAlias, $field) = explode('.', $fieldExpr); + $replacedFieldExpr = sprintf('%s.%s', $replacements[$fieldAlias], $field); + $oldExpr = sprintf('%1$s = %1$s', $replacedFieldExpr); + $newExpr = sprintf('%s = %s', $replacedFieldExpr, $fieldExpr); + $dql = strtr($dql, [$oldExpr => $newExpr]); + + $subExpr = $qb->expr()->exists($dql); + if ($joinOperator) { + $subExpr = $qb->expr()->not($subExpr); + } + $subExprs[] = $subExpr; } $this->applyFilterToClause($ds, call_user_func_array([$qb->expr(), 'andX'], $subExprs)); } else { @@ -348,7 +367,7 @@ protected function getJoinOperator($operator) * @param FilterDatasourceAdapterInterface $ds * @param QueryBuilder $qb * - * @return string + * @return [$dql, $replacedAliases] */ protected function createDQLWithReplacedAliases(FilterDatasourceAdapterInterface $ds, QueryBuilder $qb) { @@ -359,20 +378,13 @@ function ($alias) use ($ds) { $ds->generateParameterName($this->getName()), ]; }, - $qb->getAllAliases() + QueryUtils::getDqlAliases($qb->getDQL()) ); - return array_reduce( - $replacements, - function ($carry, array $replacement) { - /* - * Replaces old parameter names by newly generated parameter names, so that we don't have - * conflicts in the query. - */ - return preg_replace(sprintf('/(?<=[^\w\.\:])%s(?=\b)/', $replacement[0]), $replacement[1], $carry); - }, - $qb->getDql() - ); + return [ + QueryUtils::replaceDqlAliases($qb->getDQL(), $replacements), + array_combine(array_column($replacements, 0), array_column($replacements, 1)) + ]; } /** @@ -398,7 +410,7 @@ protected function findRelatedJoin(FilterDatasourceAdapterInterface $ds) */ protected function createConditionFieldExprs(QueryBuilder $qb) { - $groupByFields = $this->getSelectFieldFromGroupBy($qb->getDqlPart('groupBy')); + $groupByFields = $this->getSelectFieldFromGroupBy($qb); if ($groupByFields) { return $groupByFields; } @@ -415,37 +427,47 @@ protected function createConditionFieldExprs(QueryBuilder $qb) } /** - * @param Expr\GroupBy[] $groupBy + * @param QueryBuilder $qb * * @return array */ - protected function getSelectFieldFromGroupBy(array $groupBy) + protected function getSelectFieldFromGroupBy(QueryBuilder $qb) { + $groupBy = $qb->getDQLPart('groupBy'); + $expressions = []; foreach ($groupBy as $groupByPart) { foreach ($groupByPart->getParts() as $part) { - $expressions = array_merge($expressions, $this->getSelectFieldFromGroupByPart($part)); + $expressions = array_merge($expressions, $this->getSelectFieldFromGroupByPart($qb, $part)); } } - return $expressions; + $fields = []; + foreach ($expressions as $expression) { + $fields[] = QueryUtils::getSelectExprByAlias($qb, $expression) ?: $expression; + } + + return $fields; } /** - * @param string $groupByPart + * @param QueryBuilder $qb + * @param string $groupByPart * * @return array */ - protected function getSelectFieldFromGroupByPart($groupByPart) + protected function getSelectFieldFromGroupByPart(QueryBuilder $qb, $groupByPart) { $expressions = []; if (strpos($groupByPart, ',') !== false) { $groupByParts = explode(',', $groupByPart); foreach ($groupByParts as $part) { - $expressions = array_merge($expressions, $this->getSelectFieldFromGroupByPart($part)); + $expressions = array_merge($expressions, $this->getSelectFieldFromGroupByPart($qb, $part)); } } else { - $expressions[] = trim($groupByPart); + $trimmedGroupByPart = trim($groupByPart); + $expr = QueryUtils::getSelectExprByAlias($qb, $groupByPart); + $expressions[] = $expr ?: $trimmedGroupByPart; } return $expressions; diff --git a/src/Oro/Bundle/FilterBundle/Grid/Extension/OrmFilterExtension.php b/src/Oro/Bundle/FilterBundle/Grid/Extension/OrmFilterExtension.php index 35ffa932bf0..7add3ad93b2 100644 --- a/src/Oro/Bundle/FilterBundle/Grid/Extension/OrmFilterExtension.php +++ b/src/Oro/Bundle/FilterBundle/Grid/Extension/OrmFilterExtension.php @@ -2,7 +2,6 @@ namespace Oro\Bundle\FilterBundle\Grid\Extension; -use Oro\Bundle\DataGridBundle\Extension\GridViews\GridViewsExtension; use Symfony\Component\Translation\TranslatorInterface; use Oro\Bundle\DataGridBundle\Datagrid\Common\MetadataObject; @@ -13,8 +12,6 @@ use Oro\Bundle\DataGridBundle\Extension\Formatter\Configuration as FormatterConfiguration; use Oro\Bundle\DataGridBundle\Extension\Formatter\Property\PropertyInterface; use Oro\Bundle\DataGridBundle\Datagrid\ParameterBag; -use Oro\Bundle\DataGridBundle\Extension\Pager\PagerInterface; -use Oro\Bundle\DataGridBundle\Extension\Sorter\OrmSorterExtension; use Oro\Bundle\DataGridBundle\Provider\ConfigurationProvider; use Oro\Bundle\FilterBundle\Filter\FilterUtility; use Oro\Bundle\FilterBundle\Filter\FilterInterface; @@ -59,7 +56,7 @@ public function isApplicable(DatagridConfiguration $config) return false; } - return $config->getDatasourceType() == OrmDatasource::TYPE; + return $config->getDatasourceType() === OrmDatasource::TYPE; } /** diff --git a/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/dictionary-filter.js b/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/dictionary-filter.js index 6fea153d13c..093424cc7de 100644 --- a/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/dictionary-filter.js +++ b/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/dictionary-filter.js @@ -339,6 +339,8 @@ define(function(require) { * @return {*} */ setValue: function(value) { + this.preloadSelectedData(value); + var oldValue = this.value; this.value = tools.deepClone(value); this.$(this.elementSelector).inputWidget('data', this.getDataForSelect2()); @@ -353,6 +355,33 @@ define(function(require) { return this; }, + /** + * Preloads selectedData with available data from select2 so that we don't have to + * make additional requests + */ + preloadSelectedData: function(value) { + if (!this.isInitSelect2 || !value.value) { + return; + } + + var data = this.$(this.elementSelector).inputWidget('data'); + _.each(value.value, function(id) { + if (this.selectedData[id]) { + return; + } + + var item = _.find(data, function(item) { + return item.id === id; + }); + + if (!item) { + return; + } + + this.selectedData[item.id] = item; + }, this); + }, + /** * @inheritDoc */ diff --git a/src/Oro/Bundle/FilterBundle/composer.json b/src/Oro/Bundle/FilterBundle/composer.json index 3726056e2d1..f43ed4a903d 100644 --- a/src/Oro/Bundle/FilterBundle/composer.json +++ b/src/Oro/Bundle/FilterBundle/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/ui-bundle": "dev-master", "oro/requirejs-bundle": "dev-master", "oro/locale-bundle": "dev-master", diff --git a/src/Oro/Bundle/FormBundle/Form/DataTransformer/DurationToStringTransformer.php b/src/Oro/Bundle/FormBundle/Form/DataTransformer/DurationToStringTransformer.php index d48904b17c2..1738a833b6c 100644 --- a/src/Oro/Bundle/FormBundle/Form/DataTransformer/DurationToStringTransformer.php +++ b/src/Oro/Bundle/FormBundle/Form/DataTransformer/DurationToStringTransformer.php @@ -21,9 +21,16 @@ * Parts are rounded up (to int). All PHP string-to-int conversion rules apply. * Invalid numbers result to 0's (1a:b2:3.5m => 1:0:4). Missing leading zeros are also valid (1: => 0:1:0). * In both styles time parts are cumulative, so '1m 119.5s' (or '1:119.5') becomes 3 min. + * + * Supports "," and "." as decimal delimiter */ class DurationToStringTransformer implements DataTransformerInterface { + const DURATION_JIRA_REGEX = '/^ + (?:(?:(\d+(?:[\.,]\d{0,2})?)?)h + (?:[\s]*|$))?(?:(?:(\d+(?:[\.,]\d{0,2})?)?)m + (?:[\s]*|$))?(?:(?:(\d+(?:[\.,]\d{0,2})?)?)s)? + $/ix'; /** * {@inheritdoc} */ @@ -80,17 +87,11 @@ private function getTimeParts($time) $time = trim((string)$time); // matches JIRA style string - $regex = '/^' . - '(?:(?:(\d+(?:\.\d)?)?)h(?:[\s]*|$))?' . - '(?:(?:(\d+(?:\.\d)?)?)m(?:[\s]*|$))?' . - '(?:(?:(\d+(?:\.\d)?)?)s)?' . - '$/i'; - - if (preg_match_all($regex, $time, $matches)) { + if (preg_match_all(self::DURATION_JIRA_REGEX, $time, $matches)) { return [ - 'h' => $matches[1][0], - 'm' => $matches[2][0], - 's' => $matches[3][0], + 'h' => $this->getFloat($matches[1][0]), + 'm' => $this->getFloat($matches[2][0]), + 's' => $this->getFloat($matches[3][0]), ]; } @@ -99,12 +100,24 @@ private function getTimeParts($time) $parts = array_pad(explode(':', $time), -3, 0); return [ - 'h' => (float)$parts[0], - 'm' => (float)$parts[1], - 's' => round($parts[2]), + 'h' => $this->getFloat($parts[0]), + 'm' => $this->getFloat($parts[1]), + 's' => round($this->getFloat($parts[2])), ]; } + /** + * Returns float from a string. Supports either ',' or '.' as decimal delimiter. + * + * @param string $string + * + * @return float + */ + private function getFloat($string) + { + return (float) str_replace(',', '.', $string); + } + /** * Convert \DateInterval to JIRA style encoded time string (1h 2m 3s). Zero values are omitted. * diff --git a/src/Oro/Bundle/FormBundle/Form/DataTransformer/SanitizeHTMLTransformer.php b/src/Oro/Bundle/FormBundle/Form/DataTransformer/SanitizeHTMLTransformer.php index d300b4c6453..d30e2ad4cdf 100644 --- a/src/Oro/Bundle/FormBundle/Form/DataTransformer/SanitizeHTMLTransformer.php +++ b/src/Oro/Bundle/FormBundle/Form/DataTransformer/SanitizeHTMLTransformer.php @@ -12,6 +12,11 @@ class SanitizeHTMLTransformer implements DataTransformerInterface const SUB_DIR = 'ezyang'; const MODE = 0775; + /** + * @var \HtmlPurifier|null + */ + protected $htmlPurifier; + /** * @var string|null */ @@ -55,18 +60,24 @@ public function reverseTransform($value) */ protected function sanitize($value) { - $config = \HTMLPurifier_Config::createDefault(); - $this->fillAllowedElementsConfig($config); - $this->fillCacheConfig($config); - // add inline data support - $config->set( - 'URI.AllowedSchemes', - ['http' => true, 'https' => true, 'mailto' => true, 'ftp' => true, 'data' => true, 'tel' => true] - ); - $config->set('Attr.AllowedFrameTargets', ['_blank']); - $purifier = new \HTMLPurifier($config); - - return $purifier->purify($value); + if (!$value) { + return $value; + } + + if (!$this->htmlPurifier) { + $config = \HTMLPurifier_Config::createDefault(); + $this->fillAllowedElementsConfig($config); + $this->fillCacheConfig($config); + // add inline data support + $config->set( + 'URI.AllowedSchemes', + ['http' => true, 'https' => true, 'mailto' => true, 'ftp' => true, 'data' => true, 'tel' => true] + ); + $config->set('Attr.AllowedFrameTargets', ['_blank']); + $this->htmlPurifier = new \HTMLPurifier($config); + } + + return $this->htmlPurifier->purify($value); } /** diff --git a/src/Oro/Bundle/FormBundle/Form/Type/OroDurationType.php b/src/Oro/Bundle/FormBundle/Form/Type/OroDurationType.php index eef8847087d..0483553fb9e 100644 --- a/src/Oro/Bundle/FormBundle/Form/Type/OroDurationType.php +++ b/src/Oro/Bundle/FormBundle/Form/Type/OroDurationType.php @@ -21,6 +21,15 @@ class OroDurationType extends AbstractType { const NAME = 'oro_duration'; + const VALIDATION_REGEX_JIRA = '/^ + (?:(?:(\d+(?:[\.,]\d{0,2})?)?)h + (?:[\s]*|$))?(?:(?:(\d+(?:[\.,]\d{0,2})?)?)m + (?:[\s]*|$))?(?:(?:(\d+(?:[\.,]\d{0,2})?)?)s?)? + $/ix'; + + const VALIDATION_REGEX_COLUMN = '/^ + ((\d{1,3}:)?\d{1,3}:)?\d{1,3} + $/ix'; /** * {@inheritdoc} @@ -53,18 +62,8 @@ public function preSubmit(FormEvent $event) */ protected function isValidDuration($value) { - $regexJIRAFormat = - '/^' . - '(?:(?:(\d+(?:\.\d)?)?)h(?:[\s]*|$))?' . - '(?:(?:(\d+(?:\.\d)?)?)m(?:[\s]*|$))?' . - '(?:(?:(\d+(?:\.\d)?)?)s?)?' . - '$/i'; - $regexColumnFormat = - '/^' . - '((\d{1,3}:)?\d{1,3}:)?\d{1,3}' . - '$/i'; - - return preg_match($regexJIRAFormat, $value) || preg_match($regexColumnFormat, $value); + return preg_match(self::VALIDATION_REGEX_JIRA, $value) || + preg_match(self::VALIDATION_REGEX_COLUMN, $value); } /** diff --git a/src/Oro/Bundle/FormBundle/Resources/config/requirejs.yml b/src/Oro/Bundle/FormBundle/Resources/config/requirejs.yml index 91fddcf3f03..c4aa3368b79 100644 --- a/src/Oro/Bundle/FormBundle/Resources/config/requirejs.yml +++ b/src/Oro/Bundle/FormBundle/Resources/config/requirejs.yml @@ -49,6 +49,7 @@ config: 'oroform/js/validator/time': 'bundles/oroform/js/validator/time.js' 'oroform/js/validator/type': 'bundles/oroform/js/validator/type.js' 'oroform/js/validator/url': 'bundles/oroform/js/validator/url.js' + 'oroform/js/optional-validation-handler': 'bundles/oroform/js/optional-validation-handler.js' #inline editing 'oroform/js/tools/frontend-type-map': 'bundles/oroform/js/tools/frontend-type-map.js' diff --git a/src/Oro/Bundle/FormBundle/Resources/public/css/less/inline-editing.less b/src/Oro/Bundle/FormBundle/Resources/public/css/less/inline-editing.less index 07164d30ca5..3a6008e45b0 100644 --- a/src/Oro/Bundle/FormBundle/Resources/public/css/less/inline-editing.less +++ b/src/Oro/Bundle/FormBundle/Resources/public/css/less/inline-editing.less @@ -189,6 +189,11 @@ .timepicker-input { width: 121px; } + .fields-row { + display: -webkit-flex; + display: -ms-flexbox; + display: flex + } } &.select-editor { min-width: 180px; diff --git a/src/Oro/Bundle/FormBundle/Resources/public/js/extend/validate.js b/src/Oro/Bundle/FormBundle/Resources/public/js/extend/validate.js index 5ac040851cb..606426ad001 100644 --- a/src/Oro/Bundle/FormBundle/Resources/public/js/extend/validate.js +++ b/src/Oro/Bundle/FormBundle/Resources/public/js/extend/validate.js @@ -4,7 +4,7 @@ define([ 'orotranslation/js/translator', 'oroui/js/tools', 'oroui/js/tools/logger', - './../optional-validation-handler', + 'oroform/js/optional-validation-handler', 'jquery.validate' ], function($, _, __, tools, logger, validationHandler) { 'use strict'; diff --git a/src/Oro/Bundle/FormBundle/Resources/public/js/optional-validation-handler.js b/src/Oro/Bundle/FormBundle/Resources/public/js/optional-validation-handler.js index eed9ecd521d..5a819551dd6 100644 --- a/src/Oro/Bundle/FormBundle/Resources/public/js/optional-validation-handler.js +++ b/src/Oro/Bundle/FormBundle/Resources/public/js/optional-validation-handler.js @@ -82,8 +82,9 @@ define(['jquery'], function($) { * @constructor */ initialize: function(formElement) { - var groups = formElement.find('[data-validation-optional-group]'); var self = this; + + var groups = formElement.find('[data-validation-optional-group]'); var labels = groups.find('label[data-required]'); labels.find('em').hide().html('*'); diff --git a/src/Oro/Bundle/FormBundle/Tests/Unit/Form/DataTransformer/DurationToStringTransformerTest.php b/src/Oro/Bundle/FormBundle/Tests/Unit/Form/DataTransformer/DurationToStringTransformerTest.php index 1c777c2fcaa..1aad7699904 100644 --- a/src/Oro/Bundle/FormBundle/Tests/Unit/Form/DataTransformer/DurationToStringTransformerTest.php +++ b/src/Oro/Bundle/FormBundle/Tests/Unit/Form/DataTransformer/DurationToStringTransformerTest.php @@ -121,7 +121,11 @@ public function reverseTransformDataProvider() 3723, // '01:02:03' ], 'JIRA style all parts with fractions' => [ - '1.5h 2.5m 3.5s', + '1.5h 2.25m 3.5s', + 5539, // '01:32:19' rounded + ], + 'JIRA style all parts with comma fractions' => [ + '1,5h 2.5m 3,5s', 5554, // '01:32:34' rounded ], 'JIRA style no spaces fractions' => [ diff --git a/src/Oro/Bundle/FormBundle/composer.json b/src/Oro/Bundle/FormBundle/composer.json index 2115729ecff..58cda5a4cff 100644 --- a/src/Oro/Bundle/FormBundle/composer.json +++ b/src/Oro/Bundle/FormBundle/composer.json @@ -9,7 +9,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "genemu/form-bundle": "2.1.*", "oro/security-bundle": "dev-master", "oro/search-bundle": "dev-master", diff --git a/src/Oro/Bundle/GoogleIntegrationBundle/composer.json b/src/Oro/Bundle/GoogleIntegrationBundle/composer.json index b105557b3f8..18cf29ef880 100644 --- a/src/Oro/Bundle/GoogleIntegrationBundle/composer.json +++ b/src/Oro/Bundle/GoogleIntegrationBundle/composer.json @@ -6,7 +6,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/config-bundle": "dev-master", "oro/migration-bundle": "dev-master", "hwi/oauth-bundle": "~0.3", diff --git a/src/Oro/Bundle/HelpBundle/composer.json b/src/Oro/Bundle/HelpBundle/composer.json index 46f5034dfc3..fd87fb1d98f 100644 --- a/src/Oro/Bundle/HelpBundle/composer.json +++ b/src/Oro/Bundle/HelpBundle/composer.json @@ -7,8 +7,7 @@ "license": "MIT", "require": { "php": ">=5.5.9", - "symfony/symfony": "2.8.*", - "symfony/symfony": "2.8.*", + "symfony/symfony": "2.8.*, !=2.8.10", "oro/config": "dev-master", "oro/platform-bundle": "dev-master" }, diff --git a/src/Oro/Bundle/ImapBundle/Command/Cron/EmailSyncCommand.php b/src/Oro/Bundle/ImapBundle/Command/Cron/EmailSyncCommand.php index 7dfb38603c9..4e64e45d6e4 100644 --- a/src/Oro/Bundle/ImapBundle/Command/Cron/EmailSyncCommand.php +++ b/src/Oro/Bundle/ImapBundle/Command/Cron/EmailSyncCommand.php @@ -11,6 +11,7 @@ use Oro\Bundle\CronBundle\Command\CronCommandInterface; use Oro\Bundle\CronBundle\Command\CronCommandConcurrentJobsInterface; +use Oro\Bundle\EmailBundle\Sync\Model\SynchronizationProcessorSettings; use Oro\Bundle\ImapBundle\Sync\ImapEmailSynchronizer; class EmailSyncCommand extends ContainerAwareCommand implements CronCommandInterface, CronCommandConcurrentJobsInterface @@ -89,6 +90,19 @@ protected function configure() null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'The identifier of email origin to be synchronized.' + ) + ->addOption( + 'force', + null, + InputOption::VALUE_NONE, + 'Allows set the force mode. In this mode all emails will be re-synced again for checked folders. + Option "--force" can be used only with option "--id".' + ) + ->addOption( + 'vvv', + null, + InputOption::VALUE_NONE, + 'This option allows show the log messages during resync email' ); } @@ -101,16 +115,24 @@ protected function execute(InputInterface $input, OutputInterface $output) $synchronizer = $this->getContainer()->get('oro_imap.email_synchronizer'); $synchronizer->setLogger(new OutputLogger($output)); + $force = $input->getOption('force'); + $showMessage = $input->getOption('vvv'); $originIds = $input->getOption('id'); - if (!empty($originIds)) { - $synchronizer->syncOrigins($originIds); + + if ($force && empty($originIds)) { + $this->writeAttentionMessageForOptionForce($output); } else { - $synchronizer->sync( - (int)$input->getOption('max-concurrent-tasks'), - (int)$input->getOption('min-exec-interval'), - (int)$input->getOption('max-exec-time'), - (int)$input->getOption('max-tasks') - ); + if (!empty($originIds)) { + $settings = new SynchronizationProcessorSettings($force, $showMessage); + $synchronizer->syncOrigins($originIds, $settings); + } else { + $synchronizer->sync( + (int)$input->getOption('max-concurrent-tasks'), + (int)$input->getOption('min-exec-interval'), + (int)$input->getOption('max-exec-time'), + (int)$input->getOption('max-tasks') + ); + } } } @@ -121,4 +143,17 @@ public function getMaxJobsCount() { return self::MAX_JOBS_COUNT; } + + /** + * @param OutputInterface $output + */ + protected function writeAttentionMessageForOptionForce(OutputInterface $output) + { + $output->writeln( + '