diff --git a/src/Oro/Bundle/ActivityListBundle/Resources/public/js/app/views/activity-view.js b/src/Oro/Bundle/ActivityListBundle/Resources/public/js/app/views/activity-view.js index def1cba7e7c..97dc151c4c4 100644 --- a/src/Oro/Bundle/ActivityListBundle/Resources/public/js/app/views/activity-view.js +++ b/src/Oro/Bundle/ActivityListBundle/Resources/public/js/app/views/activity-view.js @@ -147,8 +147,9 @@ define(function(require) { }, _onContentChange: function() { - this.disposePageComponents(); - this.$(this.options.infoBlock).html(this.model.get('contentHTML')); + this.$(this.options.infoBlock) + .trigger('content:remove') // to dispose only components related to infoBlock + .html(this.model.get('contentHTML')); this.initLayout().done(_.bind(function() { // if the activity has an EmailTreadView -- handle comment count change in own way var emailTreadView = this.getEmailThreadView(); diff --git a/src/Oro/Bundle/DashboardBundle/Filter/DateFilterProcessor.php b/src/Oro/Bundle/DashboardBundle/Filter/DateFilterProcessor.php index d8742e7fb89..a012a787c60 100644 --- a/src/Oro/Bundle/DashboardBundle/Filter/DateFilterProcessor.php +++ b/src/Oro/Bundle/DashboardBundle/Filter/DateFilterProcessor.php @@ -4,9 +4,12 @@ use Doctrine\ORM\QueryBuilder; +use Oro\Bundle\DashboardBundle\Exception\InvalidArgumentException; use Oro\Bundle\FilterBundle\Datasource\Orm\OrmFilterDatasourceAdapter; use Oro\Bundle\FilterBundle\Filter\FilterUtility; +use Oro\Bundle\FilterBundle\Form\Type\Filter\AbstractDateFilterType; use Oro\Bundle\FilterBundle\Utils\DateFilterModifier; +use Oro\Bundle\LocaleBundle\Model\LocaleSettings; class DateFilterProcessor { @@ -16,6 +19,9 @@ class DateFilterProcessor /** @var DateFilterModifier */ protected $modifier; + /** @var LocaleSettings */ + protected $localeSettings; + /** * @param DateRangeFilter $filter * @param DateFilterModifier $modifier @@ -54,4 +60,57 @@ public function getModifiedDateData(array $dateData) return $this->modifier->modify($dateData, ['start', 'end'], false); } + + /** + * Initialize locale settings to be used in the class methods + * + * @param LocaleSettings $localeSettings + */ + public function setLocaleSettings(LocaleSettings $localeSettings) + { + $this->localeSettings = $localeSettings; + } + + /** + * @param mixed $date + * + * @throws \Oro\Bundle\DashboardBundle\Exception\InvalidArgumentException in case localeSettings is not set + * @return \DateTime + */ + protected function prepareDate($date) + { + if (!$this->localeSettings) { + throw new InvalidArgumentException('Processor should be initialized with LocaleSettings'); + } + + return $date instanceof \DateTime + ? $date + : new \DateTime($date, new \DateTimeZone($this->localeSettings->getTimeZone())); + } + /** + * @param QueryBuilder $qb + * @param $dateRange + * @param $fieldAlias + */ + public function applyDateRangeFilterToQuery(QueryBuilder $qb, $dateRange, $fieldAlias) + { + $dateRange = $this->getModifiedDateData($dateRange); + switch ($dateRange['type']) { + case AbstractDateFilterType::TYPE_MORE_THAN: + $start = $this->prepareDate($dateRange['value']['start']); + $qb->andWhere(sprintf('%s >= :start', $fieldAlias))->setParameter('start', $start); + break; + case AbstractDateFilterType::TYPE_LESS_THAN: + $end = $this->prepareDate($dateRange['value']['end']); + $qb->andWhere(sprintf('%s <= :end', $fieldAlias))->setParameter('end', $end); + break; + case AbstractDateFilterType::TYPE_ALL_TIME: + return; + default: + $start = $this->prepareDate($dateRange['value']['start']); + $end = $this->prepareDate($dateRange['value']['end']); + $qb->andWhere(sprintf('%s >= :start', $fieldAlias))->setParameter('start', $start); + $qb->andWhere(sprintf('%s <= :end', $fieldAlias))->setParameter('end', $end); + } + } } diff --git a/src/Oro/Bundle/DashboardBundle/Resources/config/services.yml b/src/Oro/Bundle/DashboardBundle/Resources/config/services.yml index 451be0bf24d..d2d659f6524 100644 --- a/src/Oro/Bundle/DashboardBundle/Resources/config/services.yml +++ b/src/Oro/Bundle/DashboardBundle/Resources/config/services.yml @@ -300,6 +300,8 @@ services: arguments: - '@oro_dashboard.filter.date_range' - '@oro_filter.utils.date_filter_modifier' + calls: + - ['setLocaleSettings', ['@oro_locale.settings']] # Twig extension oro_dashboard.twig.extension: diff --git a/src/Oro/Bundle/DashboardBundle/Resources/public/js/widget/date-range.js b/src/Oro/Bundle/DashboardBundle/Resources/public/js/widget/date-range.js index 09c29e33697..4091ecf4092 100644 --- a/src/Oro/Bundle/DashboardBundle/Resources/public/js/widget/date-range.js +++ b/src/Oro/Bundle/DashboardBundle/Resources/public/js/widget/date-range.js @@ -16,13 +16,21 @@ define(function(require) { value: -5 }, + fieldsDataName: { + datePart: 'date_part', + customPart: 'custom_part' + }, + + domCache: null, + /** * @inheritDoc */ events: { + 'change select': 'skipOnChangeFilterTypeHandler', 'change .date-visual-element': '_onClickUpdateCriteria', 'change select[name=date_part], input[name$="[type]"]': 'onChangeFilterType', - 'change select[name=""]': 'onChangeFilterTypeView' + 'change select[data-name$="_part"]': 'onChangeFilterTypeView' }, /** @@ -35,22 +43,30 @@ define(function(require) { options.$form.on('submit' + this.eventNamespace(), _.bind(this.onSubmit, this)); }, - onSubmit: function(e) { + createDomCache: function() { + this.domCache = { + $datePart: this.$('select[data-name="' + this.fieldsDataName.datePart + '"]'), + $customPart: this.$('select[data-name="' + this.fieldsDataName.customPart + '"]'), + $dateTypeCriteriaValue: this.$(this.criteriaValueSelectors.date_type) + }; + }, + + onSubmit: function() { var value = _.extend({}, this.emptyValue, this.getValue()); if (_.values(this.typeValues).indexOf(parseInt(value.type)) !== -1 && !value.value.start && !value.value.end ) { - this.$('select[name=""]').eq(0).val(this.typeDefinedValues.all_time).change(); - this.applyValue(); + var defaultTypeValue = this.getDefaultTypeValue(); + this.domCache.$datePart.val(defaultTypeValue).change(); } }, onChangeFilterTypeView: function(e) { var val = parseInt($(e.target).val()); if (val === this.customChoice.value) { - val = this.$('select[name=""]').eq(1).val(); + val = this.domCache.$customPart.val(); } - this.$(this.criteriaValueSelectors.date_type).val(val).change(); + this.domCache.$dateTypeCriteriaValue.val(val).change(); this.applyValue(); }, @@ -71,6 +87,7 @@ define(function(require) { return; } + this.domCache = null; this.options.$form.off(this.eventNamespace()); WidgetConfigDateRangeFilter.__super__.dispose.apply(this, arguments); }, @@ -80,31 +97,32 @@ define(function(require) { var type = parseInt(value, 10); if (!isNaN(type)) { - var $select = this.$('.selector:has(select[name=""]):eq(1), select[name=""]:eq(1)').eq(0); - if (_.values(this.typeDefinedValues).indexOf(type) > -1) { - $select.hide(); + if (_.values(this.typeDefinedValues).indexOf(type) === -1) { + this.domCache.$customPart.show(); } else { - // set correct width of uniform widget in case ', $fieldType, $fieldName)); + $dynamicField = new InputFormField($doc->getElementsByTagName('input')->item(0)); + $form->set($dynamicField); + } + + $form[$fieldName] = $value; + } + + /** + * @param Widget $widget + * @param array $configFields + */ + protected function configureWidget(Widget $widget, array $configFields) + { + $this->client->request( + 'GET', + $this->getUrl( + 'oro_dashboard_configure', + ['id' => $widget->getId(), '_widgetContainer' => 'dialog'] + ) + ); + $response = $this->client->getResponse(); + $this->assertEquals($response->getStatusCode(), 200, 'Failed in getting configure widget dialog window !'); + + /** + * @var $crawler Crawler + * @var $form Form + */ + $crawler = $this->client->getCrawler(); + /** @var Form $form */ + $form = $crawler->selectButton('Save')->form(); + + foreach ($configFields as $fieldsName => $value) { + $this->setOrAdd($form, $fieldsName, $value); + } + + $this->client->submit($form); + + $response = $this->client->getResponse(); + $this->assertEquals($response->getStatusCode(), 200, "Failed in submit widget configuration options !"); + } + + /** + * Returns data for which will be used to show widget's chart + * + * @param $crawler + * @return array + */ + protected function getChartData($crawler) + { + $dataComponent = $crawler->filter('.column-chart'); + if ($dataComponent->extract(['data-page-component-options'])) { + $data = $dataComponent->extract(['data-page-component-options']); + $data = json_decode($data[0]); + return $data->chartOptions->dataSource->data; + } else { + $dataComponent = $crawler->filter('.dashboard-widget-content > [data-page-component-options]'); + $data = $dataComponent->extract(['data-page-component-options']); + $data = json_decode($data[0]); + return $data->data; + } + } +} diff --git a/src/Oro/Bundle/DataAuditBundle/Resources/views/Audit/widget/history.html.twig b/src/Oro/Bundle/DataAuditBundle/Resources/views/Audit/widget/history.html.twig index 272e7ffe45d..838fb924be1 100644 --- a/src/Oro/Bundle/DataAuditBundle/Resources/views/Audit/widget/history.html.twig +++ b/src/Oro/Bundle/DataAuditBundle/Resources/views/Audit/widget/history.html.twig @@ -2,11 +2,15 @@
{% set fieldName = (fieldName is defined) ? fieldName : '' %} + {% set scope = entityClass~entityId~date().timestamp %}
{% block content %} {# gridName set from corresponding controllers #} - {{ dataGrid.renderGrid(gridName, {object_class: entityClass, object_id: entityId, field_name: fieldName}) }} + {{ dataGrid.renderGrid( + oro_datagrid_build_fullname(gridName, scope), + {object_class: entityClass, object_id: entityId, field_name: fieldName} + ) }} {% endblock %}
diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Sorter/OrmSorterExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/Sorter/OrmSorterExtension.php index 6685764a894..69007f504f2 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/Sorter/OrmSorterExtension.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/Sorter/OrmSorterExtension.php @@ -2,6 +2,8 @@ namespace Oro\Bundle\DataGridBundle\Extension\Sorter; +use Doctrine\ORM\Query\Expr; + use Oro\Bundle\DataGridBundle\Datagrid\Common\DatagridConfiguration; use Oro\Bundle\DataGridBundle\Datagrid\Common\MetadataObject; use Oro\Bundle\DataGridBundle\Datagrid\ParameterBag; @@ -69,6 +71,36 @@ public function visitDatasource(DatagridConfiguration $config, DatasourceInterfa $datasource->getQueryBuilder()->addOrderBy($sortKey, $direction); } } + + // ensure that ORDER BY is specified explicitly, in case if sorting was not requested + // use sorting by + // - the primary key of the root table if the query does not have GROUP BY + // - the first column of GROUP BY if this clause exists + // if ORDER BY is not given, the order of rows is not predictable and they are returned + // in whatever order SQL server finds fastest to produce. + /** @var OrmDatasource $datasource */ + $qb = $datasource->getQueryBuilder(); + $orderBy = $qb->getDQLPart('orderBy'); + if (empty($orderBy)) { + $groupBy = $qb->getDQLPart('groupBy'); + if (empty($groupBy)) { + $rootEntities = $qb->getRootEntities(); + $rootEntity = reset($rootEntities); + if ($rootEntity) { + $rootAliases = $qb->getRootAliases(); + $rootAlias = reset($rootAliases); + $rootIdFieldNames = $qb->getEntityManager() + ->getClassMetadata($rootEntity) + ->getIdentifierFieldNames(); + $qb->addOrderBy($rootAlias . '.' . reset($rootIdFieldNames)); + } + } else { + /** @var Expr\GroupBy $firstExpr */ + $firstExpr = reset($groupBy); + $exprParts = $firstExpr->getParts(); + $qb->addOrderBy(reset($exprParts)); + } + } } /** @@ -219,13 +251,6 @@ protected function getSortersToApply(DatagridConfiguration $config, $readParamet $sortBy = $defaultSorters; } - // if default sorter was not specified, just take first sortable column - if (!$sortBy && $sorters) { - $names = array_keys($sorters); - $firstSorterName = reset($names); - $sortBy = [$firstSorterName => self::DIRECTION_ASC]; - } - foreach ($sortBy as $column => $direction) { $sorter = isset($sorters[$column]) ? $sorters[$column] : false; diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Sorter/PostgresqlGridModifier.php b/src/Oro/Bundle/DataGridBundle/Extension/Sorter/PostgresqlGridModifier.php deleted file mode 100644 index 1678ade9e50..00000000000 --- a/src/Oro/Bundle/DataGridBundle/Extension/Sorter/PostgresqlGridModifier.php +++ /dev/null @@ -1,193 +0,0 @@ -databaseDriver = $databaseDriver; - $this->entityClassResolver = $entityClassResolver; - } - - /** - * {@inheritdoc} - */ - public function isApplicable(DatagridConfiguration $config) - { - return $this->databaseDriver === DatabaseDriverInterface::DRIVER_POSTGRESQL; - } - - /** - * {@inheritDoc} - */ - public function getPriority() - { - return self::PRIORITY; - } - - /** - * Add sorting by identifier because postgresql return rows in different order on two the same sql, but - * different LIMIT number - * - * @param DatagridConfiguration $config - * @param DatasourceInterface $datasource - * @return mixed|void - */ - public function visitDatasource(DatagridConfiguration $config, DatasourceInterface $datasource) - { - //getQueryBuilder exists only in datagrid orm datasource - if (!$datasource instanceof OrmDatasource) { - return; - } - - $entityClassName = $this->getEntityClassName($config); - /** @var QueryBuilder $queryBuilder */ - $queryBuilder = $datasource->getQueryBuilder(); - - if (!$entityClassName) { - return; - } - - $fromParts = $queryBuilder->getDQLPart('from'); - $alias = false; - - $metadata = $queryBuilder->getEntityManager()->getClassMetadata($entityClassName); - $identifier = $metadata->getSingleIdentifierFieldName(); - - /** @var From $fromPart */ - foreach ($fromParts as $fromPart) { - if ($this->entityClassResolver->getEntityClass($fromPart->getFrom()) == $entityClassName) { - $alias = $fromPart->getAlias(); - break; - } - } - - if ($alias && $this->isAllowedAddingSorting($alias, $identifier, $queryBuilder)) { - $field = $alias . '.' . $identifier; - $orderBy = $queryBuilder->getDQLPart('orderBy'); - if (!isset($orderBy[$field])) { - if ($this->isDistinct($queryBuilder)) { - $this->ensureIdentifierSelected($queryBuilder, $field); - } - $queryBuilder->addOrderBy($field, 'ASC'); - } - } - } - - /** - * @param DatagridConfiguration $config - * - * @return null|string - */ - protected function getEntityClassName(DatagridConfiguration $config) - { - $entityClassName = $config->offsetGetByPath('[extended_entity_name]'); - if ($entityClassName) { - return $entityClassName; - } - - $from = $config->offsetGetByPath('[source][query][from]'); - if (count($from) !== 0) { - return $this->entityClassResolver->getEntityClass($from[0]['table']); - } - - return null; - } - - /** - * @param string $alias - * @param string $identifier - * @param QueryBuilder $queryBuilder - * @return bool - */ - protected function isAllowedAddingSorting($alias, $identifier, QueryBuilder $queryBuilder) - { - $groupByParts = $queryBuilder->getDQLPart('groupBy'); - - if (!count($groupByParts)) { - return true; - } - - foreach ($groupByParts as $groupBy) { - if (in_array($alias.'.'.$identifier, $groupBy->getParts(), true) !== false) { - return true; - } - } - - return false; - } - - /** - * @param QueryBuilder $queryBuilder - * @return bool - */ - protected function isDistinct(QueryBuilder $queryBuilder) - { - if ($queryBuilder->getDQLPart('distinct')) { - return true; - } - - foreach ($queryBuilder->getDQLPart('select') as $select) { - $selectString = ltrim(strtolower((string)$select)); - if (strpos($selectString, 'distinct ') === 0) { - return true; - } - } - - return false; - } - - /** - * @param QueryBuilder $queryBuilder - * @param string $field - */ - protected function ensureIdentifierSelected(QueryBuilder $queryBuilder, $field) - { - $isSelected = false; - /** @var Select $select */ - foreach ($queryBuilder->getDQLPart('select') as $select) { - $selectString = ltrim(strtolower((string)$select)); - if (strpos($selectString, 'distinct ') === 0) { - $selectString = substr($selectString, 9); - } - // if field itself or field with alias - if ($selectString === $field || - ( - strpos($selectString, $field) === 0 && - strpos(strtolower(ltrim(substr($selectString, strlen($field)))), 'as ') === 0 - ) - ) { - $isSelected = true; - break; - } - } - - if (!$isSelected) { - $queryBuilder->addSelect($field); - } - } -} diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Sorter/PreciseOrderByExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/Sorter/PreciseOrderByExtension.php new file mode 100644 index 00000000000..cbeadf7e37f --- /dev/null +++ b/src/Oro/Bundle/DataGridBundle/Extension/Sorter/PreciseOrderByExtension.php @@ -0,0 +1,88 @@ +queryHintResolver = $queryHintResolver; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + // should visit after all extensions and after SorterExtension + return -261; + } + + /** + * {@inheritdoc} + */ + public function isApplicable(DatagridConfiguration $config) + { + return OrmDatasource::TYPE === $config->getDatasourceType(); + } + + /** + * {@inheritdoc} + */ + public function processConfigs(DatagridConfiguration $config) + { + $addHint = true; + $resolvedHintName = $this->queryHintResolver->resolveHintName(self::HINT_PRECISE_ORDER_BY); + $hints = $config->offsetGetByPath('[source][hints]', []); + foreach ($hints as $hintKey => $hint) { + if (is_array($hint)) { + $hintName = $this->getHintAttribute($hint, 'name'); + if (self::HINT_PRECISE_ORDER_BY === $hintName || $resolvedHintName === $hintName) { + $addHint = false; + $hintValue = $this->getHintAttribute($hint, 'value'); + if (false === $hintValue) { + // remove the hint if it was disabled + unset($hints[$hintKey]); + $config->offsetSetByPath('[source][hints]', $hints); + } + break; + } + } elseif (self::HINT_PRECISE_ORDER_BY === $hint || $resolvedHintName === $hint) { + $addHint = false; + break; + } + } + if ($addHint) { + $config->offsetAddToArrayByPath('[source][hints]', [self::HINT_PRECISE_ORDER_BY]); + } + } + + /** + * @param array $hint + * @param string $attributeName + * + * @return mixed + */ + private function getHintAttribute(array $hint, $attributeName) + { + return array_key_exists($attributeName, $hint) + ? $hint[$attributeName] + : null; + } +} diff --git a/src/Oro/Bundle/DataGridBundle/Extension/Totals/OrmTotalsExtension.php b/src/Oro/Bundle/DataGridBundle/Extension/Totals/OrmTotalsExtension.php index d14c560ea81..b48cf7ce038 100644 --- a/src/Oro/Bundle/DataGridBundle/Extension/Totals/OrmTotalsExtension.php +++ b/src/Oro/Bundle/DataGridBundle/Extension/Totals/OrmTotalsExtension.php @@ -300,13 +300,6 @@ protected function getData(ResultsObject $pageData, $columnsConfig, $perPage = f ->select($totalQueries) ->resetDQLPart('groupBy'); - $parameters = $queryBuilder->getParameters(); - if ($parameters->count()) { - $queryBuilder->resetDQLPart('where') - ->resetDQLPart('having'); - QueryUtils::removeUnusedParameters($queryBuilder); - } - $this->addPageLimits($queryBuilder, $pageData, $perPage); $query = $queryBuilder->getQuery(); diff --git a/src/Oro/Bundle/DataGridBundle/Resources/config/extensions.yml b/src/Oro/Bundle/DataGridBundle/Resources/config/extensions.yml index eee29da9066..0929fdaf851 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/config/extensions.yml +++ b/src/Oro/Bundle/DataGridBundle/Resources/config/extensions.yml @@ -13,7 +13,6 @@ parameters: oro_datagrid.extension.totals.class: Oro\Bundle\DataGridBundle\Extension\Totals\OrmTotalsExtension oro_datagrid.extension.columns.class: Oro\Bundle\DataGridBundle\Extension\Columns\ColumnsExtension oro_datagrid.extension.mode.class: Oro\Bundle\DataGridBundle\Extension\Mode\ModeExtension - oro_datagrid.extension.postgresql_grid_modifier.class: Oro\Bundle\DataGridBundle\Extension\Sorter\PostgresqlGridModifier oro_datagrid.extension.board.class: Oro\Bundle\DataGridBundle\Extension\Board\BoardExtension oro_datagrid.extension.appearance.class: Oro\Bundle\DataGridBundle\Extension\Appearance\AppearanceExtension @@ -136,11 +135,10 @@ services: tags: - { name: oro_datagrid.extension } - oro_datagrid.extension.postgresql_grid_modifier: - class: %oro_datagrid.extension.postgresql_grid_modifier.class% + oro_datagrid.extension.precise_order_by: + class: Oro\Bundle\DataGridBundle\Extension\Sorter\PreciseOrderByExtension arguments: - - %database_driver% - - '@oro_entity.orm.entity_class_resolver' + - '@oro_entity.query_hint_resolver' tags: - { name: oro_datagrid.extension } diff --git a/src/Oro/Bundle/DataGridBundle/Resources/doc/backend/datasources/orm.md b/src/Oro/Bundle/DataGridBundle/Resources/doc/backend/datasources/orm.md index 34a313d89e1..22795c35ab6 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/doc/backend/datasources/orm.md +++ b/src/Oro/Bundle/DataGridBundle/Resources/doc/backend/datasources/orm.md @@ -22,6 +22,24 @@ datagrid: from: - { table: OroCRMContactBundle:Group, alias: g } ``` +Important notes +--------------- + +By default all datagrids that use ORM datasource is marked by [HINT_PRECISE_ORDER_BY](../../../../../../Component/DoctrineUtils/README.md#preciseorderbywalker-class) query hint. This guarantee that rows are sorted in the same way independent from a state of SQL server and from values of OFFSET and LIMIT clauses. More details you can find in [PostgreSQL documentation](https://www.postgresql.org/docs/8.1/static/queries-limit.html). + +If you need to disable this behaviour for your datagrid the following configuration can be used: + +```yaml + +datagrids: + DATAGRID_NAME_HERE: + source: + type: orm + query: + ... + hints: + - { name: HINT_PRECISE_ORDER_BY, value: false } +``` Query hints ----------- diff --git a/src/Oro/Bundle/DataGridBundle/Resources/translations/messages.en.yml b/src/Oro/Bundle/DataGridBundle/Resources/translations/messages.en.yml index d9407d5b2e6..fda88a5a8c4 100644 --- a/src/Oro/Bundle/DataGridBundle/Resources/translations/messages.en.yml +++ b/src/Oro/Bundle/DataGridBundle/Resources/translations/messages.en.yml @@ -23,7 +23,7 @@ oro: columns_data.label: Columns data appearance_type.label: Appearance type appearance_data.label: Appearance data - duplicate.label: Duplicate %entity% + duplicate.label: Duplicated %entity% appearance: grid: Grid board: Kanban Board diff --git a/src/Oro/Bundle/DataGridBundle/Tests/Functional/AbstractDatagridTestCase.php b/src/Oro/Bundle/DataGridBundle/Tests/Functional/AbstractDatagridTestCase.php index 3b95c9bdc88..e0858373530 100644 --- a/src/Oro/Bundle/DataGridBundle/Tests/Functional/AbstractDatagridTestCase.php +++ b/src/Oro/Bundle/DataGridBundle/Tests/Functional/AbstractDatagridTestCase.php @@ -45,11 +45,13 @@ public function testGrid($requestData) ); $result = $this->getJsonResponseContent($response, 200); - foreach ($result['data'] as $row) { - foreach ($requestData['assert'] as $fieldName => $value) { - $this->assertEquals($value, $row[$fieldName]); + if (!empty($requestData['assert'])) { + foreach ($result['data'] as $row) { + foreach ($requestData['assert'] as $fieldName => $value) { + $this->assertEquals($value, $row[$fieldName]); + } + break; } - break; } $this->assertCount((int) $requestData['expectedResultCount'], $result['data']); diff --git a/src/Oro/Bundle/DataGridBundle/Tests/Functional/Extension/PostgresqlGridModifierTest.php b/src/Oro/Bundle/DataGridBundle/Tests/Functional/Extension/PostgresqlGridModifierTest.php deleted file mode 100644 index a15f83369b0..00000000000 --- a/src/Oro/Bundle/DataGridBundle/Tests/Functional/Extension/PostgresqlGridModifierTest.php +++ /dev/null @@ -1,81 +0,0 @@ -initClient([], $this->generateBasicAuthHeader()); - $this->client->useHashNavigation(true); - $this->container = $this->client->getKernel()->getContainer(); - $this->loadFixtures([ - 'Oro\Bundle\DataGridBundle\Tests\Functional\DataFixtures\LoadUserData', - ]); - } - - public function testGridIsValidAndContainsEntityIdentifierInSorting() - { - if ($this->container->getParameter('database_driver') !== DatabaseDriverInterface::DRIVER_POSTGRESQL) { - $this->markTestSkipped('Test runs only on PostgreSQL environment'); - } - - $getIdFunction = function ($array) { - if (isset($array['id'])) { - return $array['id']; - } - return null; - }; - - // any route just to initialize security context - $this->client->request('GET', $this->getUrl('oro_user_index')); - - $isFoundIdentifier = false; - $usersGrid = $this->container->get('oro_datagrid.datagrid.manager')->getDatagrid( - $this->gridName, - $this->gridParameters - ); - //this is just running my extension - $usersGrid->getData(); - /** @var QueryBuilder $queryBuilder */ - $queryBuilder = $usersGrid->getDatasource()->getQueryBuilder(); - $queryBuilder->setFirstResult(0)->setMaxResults(10); - $idsWithLittleLimit = array_map($getIdFunction, $queryBuilder->getQuery()->getResult()); - $queryBuilder->setFirstResult(0)->setMaxResults(50000); - $idsWithLargeLimit = array_map($getIdFunction, $queryBuilder->getQuery()->getResult()); - $orderByParts = $usersGrid->getDatasource()->getQueryBuilder()->getDQLPart('orderBy'); - - foreach ($orderByParts as $part) { - $parts = $part->getParts(); - if (in_array($this->identifier . ' ASC', $parts, true) !== false) { - $isFoundIdentifier = true; - break; - } - } - - $this->assertTrue($isFoundIdentifier); - $this->assertEquals($idsWithLargeLimit, $idsWithLittleLimit); - - // make sure query builder valid - $queryBuilder->getQuery()->execute(); - } -} diff --git a/src/Oro/Bundle/DataGridBundle/Tests/Unit/Extension/Sorter/OrmSorterExtensionTest.php b/src/Oro/Bundle/DataGridBundle/Tests/Unit/Extension/Sorter/OrmSorterExtensionTest.php index 64c9a34dbea..690bc437f58 100644 --- a/src/Oro/Bundle/DataGridBundle/Tests/Unit/Extension/Sorter/OrmSorterExtensionTest.php +++ b/src/Oro/Bundle/DataGridBundle/Tests/Unit/Extension/Sorter/OrmSorterExtensionTest.php @@ -2,7 +2,11 @@ namespace Oro\Bundle\DataGridBundle\Tests\Unit\Extension\Sorter; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\QueryBuilder; use Oro\Bundle\DataGridBundle\Datagrid\ParameterBag; +use Oro\Bundle\DataGridBundle\Datasource\Orm\OrmDatasource; use Oro\Bundle\DataGridBundle\Extension\Sorter\OrmSorterExtension; use Oro\Bundle\DataGridBundle\Datagrid\Common\DatagridConfiguration; use Oro\Bundle\DataGridBundle\Datagrid\Common\MetadataObject; @@ -125,8 +129,8 @@ public function visitMetadataDataProvider() 'addSorting' => false, ], ], - 'initialState' => ['sorters' => ['name' => 'ASC',]], - 'state' => ['sorters' => ['name' => 'ASC',]], + 'initialState' => ['sorters' => []], + 'state' => ['sorters' => []], ] ], 'multiple' => [ @@ -154,8 +158,8 @@ public function visitMetadataDataProvider() 'addSorting' => false, ], ], - 'initialState' => ['sorters' => ['name' => 'ASC',]], - 'state' => ['sorters' => ['name' => 'ASC',]], + 'initialState' => ['sorters' => []], + 'state' => ['sorters' => []], ] ], 'toolbar' => [ @@ -190,8 +194,8 @@ public function visitMetadataDataProvider() 'addSorting' => true, ], ], - 'initialState' => ['sorters' => ['name' => 'ASC',]], - 'state' => ['sorters' => ['name' => 'ASC',]], + 'initialState' => ['sorters' => []], + 'state' => ['sorters' => []], ] ] ]; @@ -246,4 +250,161 @@ public function visitMetadataUnknownColumnDataProvider() ], ]; } + + public function testVisitDatasourceWithoutDefaultSorting() + { + $em = $this->getMockBuilder(EntityManager::class) + ->disableOriginalConstructor() + ->getMock(); + $metadata = $this->getMockBuilder(ClassMetadata::class) + ->disableOriginalConstructor() + ->getMock(); + $em->expects(self::once()) + ->method('getClassMetadata') + ->with('Test\Entity') + ->willReturn($metadata); + $metadata->expects(self::once()) + ->method('getIdentifierFieldNames') + ->willReturn(['id']); + + $qb = new QueryBuilder($em); + $qb->select('e.id')->from('Test\Entity', 'e'); + + $datasource = $this->getMockBuilder(OrmDatasource::class) + ->disableOriginalConstructor() + ->getMock(); + $datasource->expects(self::once()) + ->method('getQueryBuilder') + ->willReturn($qb); + + $this->extension->setParameters(new ParameterBag()); + $this->extension->visitDatasource( + DatagridConfiguration::create(['sorters' => ['columns' => []]]), + $datasource + ); + + self::assertEquals( + 'SELECT e.id FROM Test\Entity e ORDER BY e.id ASC', + $qb->getDQL() + ); + } + + public function testVisitDatasourceWhenQueryAlreadyHasOrderBy() + { + $em = $this->getMockBuilder(EntityManager::class) + ->disableOriginalConstructor() + ->getMock(); + $em->expects(self::never()) + ->method('getClassMetadata'); + + $qb = new QueryBuilder($em); + $qb->select('e.id')->from('Test\Entity', 'e')->addOrderBy('e.name'); + + $datasource = $this->getMockBuilder(OrmDatasource::class) + ->disableOriginalConstructor() + ->getMock(); + $datasource->expects(self::once()) + ->method('getQueryBuilder') + ->willReturn($qb); + + $this->extension->setParameters(new ParameterBag()); + $this->extension->visitDatasource( + DatagridConfiguration::create(['sorters' => ['columns' => []]]), + $datasource + ); + + self::assertEquals( + 'SELECT e.id FROM Test\Entity e ORDER BY e.name ASC', + $qb->getDQL() + ); + } + + public function testVisitDatasourceWithoutDefaultSortingForEmptyQuery() + { + $em = $this->getMockBuilder(EntityManager::class) + ->disableOriginalConstructor() + ->getMock(); + $em->expects(self::never()) + ->method('getClassMetadata'); + + $qb = new QueryBuilder($em); + + $datasource = $this->getMockBuilder(OrmDatasource::class) + ->disableOriginalConstructor() + ->getMock(); + $datasource->expects(self::once()) + ->method('getQueryBuilder') + ->willReturn($qb); + + $this->extension->setParameters(new ParameterBag()); + $this->extension->visitDatasource( + DatagridConfiguration::create(['sorters' => ['columns' => []]]), + $datasource + ); + + self::assertEquals( + [], + $qb->getDQLPart('orderBy') + ); + } + + public function testVisitDatasourceWithoutDefaultSortingAndGroupBy() + { + $em = $this->getMockBuilder(EntityManager::class) + ->disableOriginalConstructor() + ->getMock(); + $em->expects(self::never()) + ->method('getClassMetadata'); + + $qb = new QueryBuilder($em); + $qb->select('e.id')->from('Test\Entity', 'e')->groupBy('e.name'); + + $datasource = $this->getMockBuilder(OrmDatasource::class) + ->disableOriginalConstructor() + ->getMock(); + $datasource->expects(self::once()) + ->method('getQueryBuilder') + ->willReturn($qb); + + $this->extension->setParameters(new ParameterBag()); + $this->extension->visitDatasource( + DatagridConfiguration::create(['sorters' => ['columns' => []]]), + $datasource + ); + + self::assertEquals( + 'SELECT e.id FROM Test\Entity e GROUP BY e.name ORDER BY e.name ASC', + $qb->getDQL() + ); + } + + public function testVisitDatasourceWithoutDefaultSortingAndMultipleGroupBy() + { + $em = $this->getMockBuilder(EntityManager::class) + ->disableOriginalConstructor() + ->getMock(); + $em->expects(self::never()) + ->method('getClassMetadata'); + + $qb = new QueryBuilder($em); + $qb->select('e.id')->from('Test\Entity', 'e')->addGroupBy('e.id')->addGroupBy('e.name'); + + $datasource = $this->getMockBuilder(OrmDatasource::class) + ->disableOriginalConstructor() + ->getMock(); + $datasource->expects(self::once()) + ->method('getQueryBuilder') + ->willReturn($qb); + + $this->extension->setParameters(new ParameterBag()); + $this->extension->visitDatasource( + DatagridConfiguration::create(['sorters' => ['columns' => []]]), + $datasource + ); + + self::assertEquals( + 'SELECT e.id FROM Test\Entity e GROUP BY e.id, e.name ORDER BY e.id ASC', + $qb->getDQL() + ); + } } diff --git a/src/Oro/Bundle/DataGridBundle/Tests/Unit/Extension/Sorter/PreciseOrderByExtensionTest.php b/src/Oro/Bundle/DataGridBundle/Tests/Unit/Extension/Sorter/PreciseOrderByExtensionTest.php new file mode 100644 index 00000000000..c12029a4e32 --- /dev/null +++ b/src/Oro/Bundle/DataGridBundle/Tests/Unit/Extension/Sorter/PreciseOrderByExtensionTest.php @@ -0,0 +1,121 @@ +getMockBuilder(QueryHintResolver::class) + ->disableOriginalConstructor() + ->getMock(); + $queryHintResolver->expects(self::any()) + ->method('resolveHintName') + ->with(PreciseOrderByExtension::HINT_PRECISE_ORDER_BY) + ->willReturn('oro_entity.precise_order_by'); + + $this->extension = new PreciseOrderByExtension($queryHintResolver); + } + + public function testGetPriority() + { + self::assertSame(-261, $this->extension->getPriority()); + } + + public function testIsApplicableForNotOrmDatasource() + { + $config = DatagridConfiguration::create(['source' => ['type' => 'other']]); + self::assertFalse($this->extension->isApplicable($config)); + } + + public function testIsApplicableForOrmDatasource() + { + $config = DatagridConfiguration::create(['source' => ['type' => 'orm']]); + self::assertTrue($this->extension->isApplicable($config)); + } + + public function testProcessConfigsWhenNoPreciseOrderByHint() + { + $config = DatagridConfiguration::create([]); + $this->extension->processConfigs($config); + self::assertEquals( + ['HINT_PRECISE_ORDER_BY'], + $config->offsetGetByPath('[source][hints]') + ); + } + + public function testProcessConfigsWhenPreciseOrderByHintAlreadyExists() + { + $config = DatagridConfiguration::create(['source' => ['hints' => ['HINT_PRECISE_ORDER_BY']]]); + $this->extension->processConfigs($config); + self::assertEquals( + ['HINT_PRECISE_ORDER_BY'], + $config->offsetGetByPath('[source][hints]') + ); + } + + public function testProcessConfigsWhenPreciseOrderByHintAlreadyExistsAndWasAddedByItsName() + { + $config = DatagridConfiguration::create(['source' => ['hints' => ['oro_entity.precise_order_by']]]); + $this->extension->processConfigs($config); + self::assertEquals( + ['oro_entity.precise_order_by'], + $config->offsetGetByPath('[source][hints]') + ); + } + + public function testProcessConfigsWhenPreciseOrderByHintAlreadyExistsFullDeclaration() + { + $config = DatagridConfiguration::create( + ['source' => ['hints' => [['name' => 'HINT_PRECISE_ORDER_BY', 'value' => true]]]] + ); + $this->extension->processConfigs($config); + self::assertEquals( + [['name' => 'HINT_PRECISE_ORDER_BY', 'value' => true]], + $config->offsetGetByPath('[source][hints]') + ); + } + + public function testProcessConfigsWhenPreciseOrderByHintAlreadyExistsFullDeclarationAndWasAddedByItsName() + { + $config = DatagridConfiguration::create( + ['source' => ['hints' => [['name' => 'oro_entity.precise_order_by', 'value' => true]]]] + ); + $this->extension->processConfigs($config); + self::assertEquals( + [['name' => 'oro_entity.precise_order_by', 'value' => true]], + $config->offsetGetByPath('[source][hints]') + ); + } + + public function testProcessConfigsWhenPreciseOrderByHintDisabled() + { + $config = DatagridConfiguration::create( + ['source' => ['hints' => [['name' => 'HINT_PRECISE_ORDER_BY', 'value' => false]]]] + ); + $this->extension->processConfigs($config); + self::assertEquals( + [], + $config->offsetGetByPath('[source][hints]') + ); + } + + public function testProcessConfigsWhenPreciseOrderByHintDisabledByItsName() + { + $config = DatagridConfiguration::create( + ['source' => ['hints' => [['name' => 'oro_entity.precise_order_by', 'value' => false]]]] + ); + $this->extension->processConfigs($config); + self::assertEquals( + [], + $config->offsetGetByPath('[source][hints]') + ); + } +} diff --git a/src/Oro/Bundle/EmailBundle/Entity/Provider/EmailOwnerProvider.php b/src/Oro/Bundle/EmailBundle/Entity/Provider/EmailOwnerProvider.php index aeb807f5d0e..0f8f0150816 100644 --- a/src/Oro/Bundle/EmailBundle/Entity/Provider/EmailOwnerProvider.php +++ b/src/Oro/Bundle/EmailBundle/Entity/Provider/EmailOwnerProvider.php @@ -44,4 +44,25 @@ public function findEmailOwner(EntityManager $em, $email) return $emailOwner; } + + /** + * Find an entity objects which is an owner of the given email address + * + * @param \Doctrine\ORM\EntityManager $em + * @param string $email + * + * @return EmailOwnerInterface[] + */ + public function findEmailOwners(EntityManager $em, $email) + { + $emailOwners = []; + foreach ($this->emailOwnerProviderStorage->getProviders() as $provider) { + $emailOwner = $provider->findEmailOwner($em, $email); + if ($emailOwner !== null) { + $emailOwners[] = $emailOwner; + } + } + + return $emailOwners; + } } diff --git a/src/Oro/Bundle/EmailBundle/Mailer/DirectMailer.php b/src/Oro/Bundle/EmailBundle/Mailer/DirectMailer.php index fb9a987da4f..86884a7e1ba 100644 --- a/src/Oro/Bundle/EmailBundle/Mailer/DirectMailer.php +++ b/src/Oro/Bundle/EmailBundle/Mailer/DirectMailer.php @@ -48,6 +48,10 @@ public function __construct(\Swift_Mailer $baseMailer, ContainerInterface $conta $this->addXOAuth2Authenticator($transport); } + if ($transport instanceof \Swift_Transport_AbstractSmtpTransport) { + $this->configureTransportLocalDomain($transport); + } + parent::__construct($transport); } @@ -66,6 +70,10 @@ public function prepareSmtpTransport($emailOrigin) new SendEmailTransport($emailOrigin, $this->getTransport()) ); $this->smtpTransport = $event->getTransport(); + + if ($this->smtpTransport instanceof \Swift_Transport_AbstractSmtpTransport) { + $this->configureTransportLocalDomain($this->smtpTransport); + } } } @@ -195,4 +203,15 @@ protected function addXOAuth2Authenticator($transport) return $this; } + + /** + * @param \Swift_Transport_AbstractSmtpTransport $transport + */ + protected function configureTransportLocalDomain(\Swift_Transport_AbstractSmtpTransport $transport) + { + // fix local domain when wild-card vhost is used and auto-detection fails + if (0 === strpos($transport->getLocalDomain(), '*') && !empty($_SERVER['HTTP_HOST'])) { + $transport->setLocalDomain($_SERVER['HTTP_HOST']); + } + } } diff --git a/src/Oro/Bundle/EmailBundle/Mailer/Processor.php b/src/Oro/Bundle/EmailBundle/Mailer/Processor.php index c5115128a37..d4230f7f252 100644 --- a/src/Oro/Bundle/EmailBundle/Mailer/Processor.php +++ b/src/Oro/Bundle/EmailBundle/Mailer/Processor.php @@ -349,7 +349,13 @@ public function getEmailOrigin( $originName = InternalEmailOrigin::BAP, $enableUseUserEmailOrigin = true ) { - return $this->emailOriginHelper->getEmailOrigin($email, $organization, $originName, $enableUseUserEmailOrigin); + return $this->emailOriginHelper->getEmailOrigin( + $email, + $organization, + $originName, + $enableUseUserEmailOrigin, + true + ); } /** diff --git a/src/Oro/Bundle/EmailBundle/Provider/RelatedEmailsProvider.php b/src/Oro/Bundle/EmailBundle/Provider/RelatedEmailsProvider.php index 7649fc9559c..1525fc85cc8 100644 --- a/src/Oro/Bundle/EmailBundle/Provider/RelatedEmailsProvider.php +++ b/src/Oro/Bundle/EmailBundle/Provider/RelatedEmailsProvider.php @@ -11,8 +11,11 @@ use Oro\Bundle\EmailBundle\Entity\EmailInterface; use Oro\Bundle\EmailBundle\Model\EmailAttribute; +use Oro\Bundle\EmailBundle\Model\EmailHolderInterface; use Oro\Bundle\EmailBundle\Model\Recipient; +use Oro\Bundle\EntityBundle\Provider\EntityFieldProvider; use Oro\Bundle\EntityConfigBundle\Config\ConfigManager; +use Oro\Bundle\EntityExtendBundle\Tools\ExtendHelper; use Oro\Bundle\SecurityBundle\SecurityFacade; use Oro\Bundle\LocaleBundle\Formatter\NameFormatter; use Oro\Bundle\EmailBundle\Tools\EmailAddressHelper; @@ -41,6 +44,9 @@ class RelatedEmailsProvider /** @var EmailRecipientsHelper */ protected $emailRecipientsHelper; + /** @var EntityFieldProvider */ + protected $entityFieldProvider; + /** * @param Registry $registry * @param ConfigManager $configManager @@ -65,6 +71,14 @@ public function __construct( $this->emailRecipientsHelper = $emailRecipientsHelper; } + /** + * @param EntityFieldProvider $entityFieldProvider + */ + public function setEntityFieldProvider(EntityFieldProvider $entityFieldProvider) + { + $this->entityFieldProvider = $entityFieldProvider; + } + /** * @param object $object * @param int $depth @@ -91,16 +105,14 @@ public function getRecipients($object, $depth = 1, $ignoreAcl = false, Organizat $metadata = $this->getMetadata($className); $attributes = $this->initAttributes($className, $metadata); - foreach ($metadata->associationMappings as $name => $assoc) { - if (in_array( - 'Oro\Bundle\EmailBundle\Entity\EmailInterface', - class_implements($assoc['targetEntity']), - true - )) { - $attributes[] = new EmailAttribute($name, true); + $relations = $this->entityFieldProvider->getRelations($className); + + foreach ($relations as $relation) { + if (is_a($relation['related_entity_name'], EmailInterface::class, true)) { + $attributes[] = new EmailAttribute($relation['name'], true); } else { if ($depth > 1) { - $assocObject = $this->getPropertyAccessor()->getValue($object, $name); + $assocObject = $this->getPropertyAccessor()->getValue($object, $relation['name']); if (!$assocObject instanceof \Traversable && !is_array($assocObject)) { if ($assocObject) { $assocObject = [$assocObject]; @@ -278,7 +290,7 @@ protected function getMetadata($className) protected function initAttributes($className, $metadata) { $attributes = []; - if (in_array('Oro\Bundle\EmailBundle\Model\EmailHolderInterface', class_implements($className), true)) { + if (is_a($className, EmailHolderInterface::class, true)) { $attributes[] = new EmailAttribute('email'); } $attributes = array_merge($attributes, $this->getFieldAttributes($metadata)); diff --git a/src/Oro/Bundle/EmailBundle/Resources/config/services.yml b/src/Oro/Bundle/EmailBundle/Resources/config/services.yml index 1e6d4e3d3d1..2c6b8b88243 100644 --- a/src/Oro/Bundle/EmailBundle/Resources/config/services.yml +++ b/src/Oro/Bundle/EmailBundle/Resources/config/services.yml @@ -699,6 +699,8 @@ services: - '@oro_locale.formatter.name' - '@oro_email.email.address.helper' - '@oro_email.provider.email_recipients.helper' + calls: + - [setEntityFieldProvider, ['@oro_entity.entity_field_provider']] oro_email.email_recipients.provider: class: %oro_email.email_recipients.provider.class% diff --git a/src/Oro/Bundle/EmailBundle/Tests/Unit/Tools/EmailOriginHelperTest.php b/src/Oro/Bundle/EmailBundle/Tests/Unit/Tools/EmailOriginHelperTest.php index 64d5bb7db64..ce42b6b0e8b 100644 --- a/src/Oro/Bundle/EmailBundle/Tests/Unit/Tools/EmailOriginHelperTest.php +++ b/src/Oro/Bundle/EmailBundle/Tests/Unit/Tools/EmailOriginHelperTest.php @@ -100,7 +100,7 @@ public function testGetEmailOriginFromSecurity() $owner = new \stdClass(); $this->emailOwnerProvider->expects($this->once()) - ->method('findEmailOwner') + ->method('findEmailOwners') ->willReturn($owner); $entityRepository = $this->getMockBuilder('Doctrine\ORM\EntityRepository') ->disableOriginalConstructor() @@ -128,7 +128,7 @@ public function testGetEmailOriginCache() $owner = new \stdClass(); $this->emailOwnerProvider->expects($this->exactly(2)) - ->method('findEmailOwner') + ->method('findEmailOwners') ->willReturn($owner); $entityRepository = $this->getMockBuilder('Doctrine\ORM\EntityRepository') ->disableOriginalConstructor() diff --git a/src/Oro/Bundle/EmailBundle/Tools/EmailOriginHelper.php b/src/Oro/Bundle/EmailBundle/Tools/EmailOriginHelper.php index c53fef12f61..107c2db0620 100644 --- a/src/Oro/Bundle/EmailBundle/Tools/EmailOriginHelper.php +++ b/src/Oro/Bundle/EmailBundle/Tools/EmailOriginHelper.php @@ -8,6 +8,7 @@ use Oro\Bundle\EmailBundle\Entity\EmailOrigin; use Oro\Bundle\EmailBundle\Entity\EmailFolder; +use Oro\Bundle\EmailBundle\Entity\EmailOwnerInterface; use Oro\Bundle\EmailBundle\Entity\InternalEmailOrigin; use Oro\Bundle\EmailBundle\Entity\Mailbox; use Oro\Bundle\EmailBundle\Entity\Provider\EmailOwnerProvider; @@ -95,7 +96,7 @@ public function findEmailOrigin($emailOwner, $organization, $originName, $enable return $origin; } - + /** * Find existing email origin entity by email string or create and persist new one. * @@ -103,6 +104,7 @@ public function findEmailOrigin($emailOwner, $organization, $originName, $enable * @param OrganizationInterface $organization * @param string $originName * @param boolean $enableUseUserEmailOrigin + * @param boolean $secured Check origin can be used by current user * * @return EmailOrigin */ @@ -110,27 +112,93 @@ public function getEmailOrigin( $email, OrganizationInterface $organization = null, $originName = InternalEmailOrigin::BAP, - $enableUseUserEmailOrigin = true + $enableUseUserEmailOrigin = true, + $secured = false ) { $originKey = $originName . $email . $enableUseUserEmailOrigin; if (!$organization && $this->securityFacade !== null && $this->securityFacade->getOrganization()) { $organization = $this->securityFacade->getOrganization(); } if (!array_key_exists($originKey, $this->origins)) { - $emailOwner = $this->emailOwnerProvider->findEmailOwner( - $this->getEntityManager(), - $this->emailAddressHelper->extractPureEmailAddress($email) + $emailOwners = $this->emailOwnerProvider + ->findEmailOwners( + $this->getEntityManager(), + $this->emailAddressHelper->extractPureEmailAddress($email) + ); + $origin = $this->findEmailOrigin( + $this->chooseEmailOwner($emailOwners, $secured), + $organization, + $originName, + $enableUseUserEmailOrigin ); - $origin = $this - ->findEmailOrigin($emailOwner, $organization, $originName, $enableUseUserEmailOrigin); - $this->origins[$originKey] = $origin; } return $this->origins[$originKey]; } + /** + * Get first accessible email owner + * + * @param EmailOwnerInterface[] $emailOwners + * @param bool $secured + * + * @return null + */ + protected function chooseEmailOwner($emailOwners, $secured) + { + $selectedEmailOwner = null; + foreach ($emailOwners as $emailOwner) { + if ($secured && !$this->hasOriginAccess($emailOwner)) { + continue; + } + $selectedEmailOwner = $emailOwner; + break; + } + + return $selectedEmailOwner; + } + + /** + * Check on access to email owner data by logged in user + * + * @param EmailOwnerInterface $emailOwner + * + * @return bool + */ + protected function hasOriginAccess($emailOwner) + { + $access = false; + if ($emailOwner instanceof User + && $this->securityFacade->getLoggedUserId() === $emailOwner->getId()) { + $access = true; + } elseif ($emailOwner instanceof Mailbox) { + $ownerIds = []; + $authorizedUsers = $emailOwner->getAuthorizedUsers(); + foreach ($authorizedUsers as $user) { + $ownerIds[] = $user->getId(); + } + + $authorizedRoles = $emailOwner->getAuthorizedRoles(); + foreach ($authorizedRoles as $role) { + $users = $this->getEntityManager()->getRepository('OroUserBundle:Role') + ->getUserQueryBuilder($role) + ->getQuery()->getResult(); + + foreach ($users as $user) { + $ownerIds[] = $user->getId(); + } + } + + if (in_array($this->securityFacade->getLoggedUserId(), $ownerIds, true)) { + $access = true; + } + } + + return $access; + } + /** * @param mixed $origin * diff --git a/src/Oro/Bundle/EntityBundle/Resources/config/orm.yml b/src/Oro/Bundle/EntityBundle/Resources/config/orm.yml index 7159dd33eee..5f950a37b3c 100644 --- a/src/Oro/Bundle/EntityBundle/Resources/config/orm.yml +++ b/src/Oro/Bundle/EntityBundle/Resources/config/orm.yml @@ -49,3 +49,13 @@ services: class: %oro_entity.orm.insert_from_select_query_executor.class% arguments: - '@oro_entity.orm.native_query_executor_helper' + + oro_entity.query_hint.precise_order_by: + public: false + abstract: true + tags: + - + name: oro_entity.query_hint + hint: oro_entity.precise_order_by + alias: HINT_PRECISE_ORDER_BY + tree_walker: Oro\Component\DoctrineUtils\ORM\Walker\PreciseOrderByWalker diff --git a/src/Oro/Bundle/EntityExtendBundle/Migration/Extension/ExtendExtension.php b/src/Oro/Bundle/EntityExtendBundle/Migration/Extension/ExtendExtension.php index 9511b5c6053..5df4cdef653 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Migration/Extension/ExtendExtension.php +++ b/src/Oro/Bundle/EntityExtendBundle/Migration/Extension/ExtendExtension.php @@ -342,14 +342,13 @@ public function addEnumField( * Adds one-to-many relation * * @param Schema $schema - * @param Table|string $table A Table object or table name + * @param Table|string $table A Table object or table name of owning side entity * @param string $associationName The name of a relation field - * @param Table|string $targetTable A Table object or table name + * @param Table|string $targetTable A Table object or table name of inverse side entity * @param string[] $targetTitleColumnNames Column names are used to show a title of related entity * @param string[] $targetDetailedColumnNames Column names are used to show detailed info about related entity * @param string[] $targetGridColumnNames Column names are used to show related entity in a grid - * @param array $options Entity config values - * format is [CONFIG_SCOPE => [CONFIG_KEY => CONFIG_VALUE]] + * @param array $options Entity config options. [scope => [name => value, ...], ...] * @param string $fieldType The field type. By default the field type is oneToMany, * but you can specify another type if it is based on oneToMany. * In this case this type should be registered @@ -417,13 +416,12 @@ public function addOneToManyRelation( * Adds the inverse side of a one-to-many relation * * @param Schema $schema - * @param Table|string $table A Table object or table name - * @param string $associationName The name of a relation field - * @param Table|string $targetTable A Table object or table name - * @param string $targetAssociationName The name of a relation field on the inverse side - * @param string $targetColumnName A column name is used to show related entity - * @param array $options Entity config values - * format is [CONFIG_SCOPE => [CONFIG_KEY => CONFIG_VALUE]] + * @param Table|string $table A Table object or table name of owning side entity + * @param string $associationName The name of a relation field + * @param Table|string $targetTable A Table object or table name of inverse side entity + * @param string $targetAssociationName The name of a relation field on the inverse side + * @param string $titleColumnName A column name is used to show owning side entity + * @param array $options Entity config options. [scope => [name => value, ...], ...] */ public function addOneToManyInverseRelation( Schema $schema, @@ -431,12 +429,13 @@ public function addOneToManyInverseRelation( $associationName, $targetTable, $targetAssociationName, - $targetColumnName, + $titleColumnName, array $options = [] ) { $this->ensureExtendFieldSet($options); $selfTableName = $this->getTableName($table); + $selfTable = $this->getTable($selfTableName, $schema); $selfClassName = $this->getEntityClassByTableName($selfTableName); $targetTableName = $this->getTableName($targetTable); @@ -448,7 +447,7 @@ public function addOneToManyInverseRelation( $associationName ); - $this->checkColumnsExist($targetTable, [$targetColumnName]); + $this->checkColumnsExist($selfTable, [$titleColumnName]); $this->checkColumnsExist($targetTable, [$existingTargetColumnName]); $relationKey = ExtendHelper::buildRelationKey( @@ -480,7 +479,7 @@ public function addOneToManyInverseRelation( $options[ExtendOptionsManager::TARGET_OPTION] = [ 'table_name' => $selfTableName, 'relation_key' => $relationKey, - 'column' => $targetColumnName, + 'column' => $titleColumnName, ]; $options[ExtendOptionsManager::TYPE_OPTION] = RelationType::MANY_TO_ONE; $options['extend']['column_name'] = $existingTargetColumnName; @@ -495,13 +494,13 @@ public function addOneToManyInverseRelation( * Adds many-to-many relation * * @param Schema $schema - * @param Table|string $table A Table object or table name + * @param Table|string $table A Table object or table name of owning side entity * @param string $associationName The name of a relation field - * @param Table|string $targetTable A Table object or table name + * @param Table|string $targetTable A Table object or table name of inverse side entity * @param string[] $targetTitleColumnNames Column names are used to show a title of related entity * @param string[] $targetDetailedColumnNames Column names are used to show detailed info about related entity * @param string[] $targetGridColumnNames Column names are used to show related entity in a grid - * @param array $options + * @param array $options Entity config options. [scope => [name => value, ...], ...] * @param string $fieldType The field type. By default the field type is manyToMany, * but you can specify another type if it is based on manyToMany. * In this case this type should be registered @@ -587,15 +586,14 @@ public function addManyToManyRelation( * Adds the inverse side of a many-to-many relation * * @param Schema $schema - * @param Table|string $table A Table object or table name - * @param string $associationName The name of a relation field - * @param Table|string $targetTable A Table object or table name - * @param string $targetAssociationName The name of a relation field on the inverse side - * @param string[] $targetTitleColumnNames Column names are used to show a title of related entity - * @param string[] $targetDetailedColumnNames Column names are used to show detailed info about related entity - * @param string[] $targetGridColumnNames Column names are used to show related entity in a grid - * @param array $options Entity config values - * format is [CONFIG_SCOPE => [CONFIG_KEY => CONFIG_VALUE]] + * @param Table|string $table A Table object or table name of owning side entity + * @param string $associationName The name of a relation field + * @param Table|string $targetTable A Table object or table name of inverse side entity + * @param string $targetAssociationName The name of a relation field on the inverse side + * @param string[] $titleColumnNames Column names are used to show a title of owning side entity + * @param string[] $detailedColumnNames Column names are used to show detailed info about owning side entity + * @param string[] $gridColumnNames Column names are used to show owning side entity in a grid + * @param array $options Entity config options. [scope => [name => value, ...], ...] */ public function addManyToManyInverseRelation( Schema $schema, @@ -603,23 +601,23 @@ public function addManyToManyInverseRelation( $associationName, $targetTable, $targetAssociationName, - array $targetTitleColumnNames, - array $targetDetailedColumnNames, - array $targetGridColumnNames, + array $titleColumnNames, + array $detailedColumnNames, + array $gridColumnNames, array $options = [] ) { $this->ensureExtendFieldSet($options); $selfTableName = $this->getTableName($table); + $selfTable = $this->getTable($selfTableName, $schema); $selfClassName = $this->getEntityClassByTableName($selfTableName); $targetTableName = $this->getTableName($targetTable); - $targetTable = $this->getTable($targetTable, $schema); $targetClassName = $this->getEntityClassByTableName($targetTableName); - $this->checkColumnsExist($targetTable, $targetTitleColumnNames); - $this->checkColumnsExist($targetTable, $targetDetailedColumnNames); - $this->checkColumnsExist($targetTable, $targetGridColumnNames); + $this->checkColumnsExist($selfTable, $titleColumnNames); + $this->checkColumnsExist($selfTable, $detailedColumnNames); + $this->checkColumnsExist($selfTable, $gridColumnNames); $relationKey = ExtendHelper::buildRelationKey( $selfClassName, @@ -651,9 +649,9 @@ public function addManyToManyInverseRelation( 'table_name' => $selfTableName, 'relation_key' => $relationKey, 'columns' => [ - 'title' => $targetTitleColumnNames, - 'detailed' => $targetDetailedColumnNames, - 'grid' => $targetGridColumnNames, + 'title' => $titleColumnNames, + 'detailed' => $detailedColumnNames, + 'grid' => $gridColumnNames, ], ]; $options[ExtendOptionsManager::TYPE_OPTION] = RelationType::MANY_TO_MANY; @@ -668,22 +666,22 @@ public function addManyToManyInverseRelation( * Adds many-to-one relation * * @param Schema $schema - * @param Table|string $table A Table object or table name - * @param string $associationName The name of a relation field - * @param Table|string $targetTable A Table object or table name - * @param string $targetColumnName A column name is used to show related entity - * @param array $options - * @param string $fieldType The field type. By default the field type is manyToOne, - * but you can specify another type if it is based on manyToOne. - * In this case this type should be registered - * in entity_extend.yml under underlying_types section + * @param Table|string $table A Table object or table name of owning side entity + * @param string $associationName The name of a relation field + * @param Table|string $targetTable A Table object or table name of inverse side entity + * @param string $targetTitleColumnName A column name is used to show related entity + * @param array $options Entity config options. [scope => [name => value, ...], ...] + * @param string $fieldType The field type. By default the field type is manyToOne, + * but you can specify another type if it is based on manyToOne. + * In this case this type should be registered + * in entity_extend.yml under underlying_types section */ public function addManyToOneRelation( Schema $schema, $table, $associationName, $targetTable, - $targetColumnName, + $targetTitleColumnName, array $options = [], $fieldType = RelationType::MANY_TO_ONE ) { @@ -699,7 +697,7 @@ public function addManyToOneRelation( '_' . $primaryKeyColumnName ); - $this->checkColumnsExist($targetTable, [$targetColumnName]); + $this->checkColumnsExist($targetTable, [$targetTitleColumnName]); $this->addRelation( $selfTable, @@ -711,7 +709,7 @@ public function addManyToOneRelation( $options[ExtendOptionsManager::TARGET_OPTION] = [ 'table_name' => $targetTableName, - 'column' => $targetColumnName, + 'column' => $targetTitleColumnName, ]; $options[ExtendOptionsManager::TYPE_OPTION] = $fieldType; $this->extendOptionsManager->setColumnOptions( @@ -725,15 +723,14 @@ public function addManyToOneRelation( * Adds the inverse side of a many-to-one relation * * @param Schema $schema - * @param Table|string $table A Table object or table name - * @param string $associationName The name of a relation field - * @param Table|string $targetTable A Table object or table name - * @param string $targetAssociationName The name of a relation field on the inverse side - * @param string[] $targetTitleColumnNames Column names are used to show a title of related entity - * @param string[] $targetDetailedColumnNames Column names are used to show detailed info about related entity - * @param string[] $targetGridColumnNames Column names are used to show related entity in a grid - * @param array $options Entity config values - * format is [CONFIG_SCOPE => [CONFIG_KEY => CONFIG_VALUE]] + * @param Table|string $table A Table object or table name of owning side entity + * @param string $associationName The name of a relation field + * @param Table|string $targetTable A Table object or table name of inverse side entity + * @param string $targetAssociationName The name of a relation field on the inverse side + * @param string[] $titleColumnNames Column names are used to show a title of owning side entity + * @param string[] $detailedColumnNames Column names are used to show detailed info about owning side entity + * @param string[] $gridColumnNames Column names are used to show owning side entity in a grid + * @param array $options Entity config options. [scope => [name => value, ...], ...] */ public function addManyToOneInverseRelation( Schema $schema, @@ -741,23 +738,23 @@ public function addManyToOneInverseRelation( $associationName, $targetTable, $targetAssociationName, - array $targetTitleColumnNames, - array $targetDetailedColumnNames, - array $targetGridColumnNames, + array $titleColumnNames, + array $detailedColumnNames, + array $gridColumnNames, array $options = [] ) { $this->ensureExtendFieldSet($options); $selfTableName = $this->getTableName($table); + $selfTable = $this->getTable($selfTableName, $schema); $selfClassName = $this->getEntityClassByTableName($selfTableName); $targetTableName = $this->getTableName($targetTable); - $targetTable = $this->getTable($targetTable, $schema); $targetClassName = $this->getEntityClassByTableName($targetTableName); - $this->checkColumnsExist($targetTable, $targetTitleColumnNames); - $this->checkColumnsExist($targetTable, $targetDetailedColumnNames); - $this->checkColumnsExist($targetTable, $targetGridColumnNames); + $this->checkColumnsExist($selfTable, $titleColumnNames); + $this->checkColumnsExist($selfTable, $detailedColumnNames); + $this->checkColumnsExist($selfTable, $gridColumnNames); $relationKey = ExtendHelper::buildRelationKey( $selfClassName, @@ -789,9 +786,9 @@ public function addManyToOneInverseRelation( 'table_name' => $selfTableName, 'relation_key' => $relationKey, 'columns' => [ - 'title' => $targetTitleColumnNames, - 'detailed' => $targetDetailedColumnNames, - 'grid' => $targetGridColumnNames, + 'title' => $titleColumnNames, + 'detailed' => $detailedColumnNames, + 'grid' => $gridColumnNames, ], ]; $options[ExtendOptionsManager::TYPE_OPTION] = RelationType::ONE_TO_MANY; diff --git a/src/Oro/Bundle/EntityExtendBundle/Resources/doc/relations.md b/src/Oro/Bundle/EntityExtendBundle/Resources/doc/relations.md index f10e4c818aa..f9714a47cc2 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Resources/doc/relations.md +++ b/src/Oro/Bundle/EntityExtendBundle/Resources/doc/relations.md @@ -33,7 +33,7 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'room', // owning side field name - 'acme_user_room', // target side table + 'acme_user_room', // inverse side table 'name', // column name is used to show related entity ['extend' => ['owner' => ExtendScope::OWNER_CUSTOM]] ); @@ -71,7 +71,7 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'room', // owning side field name - 'acme_user_room', // target side table + 'acme_user_room', // inverse side table 'name', // column name is used to show related entity ['extend' => ['owner' => ExtendScope::OWNER_CUSTOM]] ); @@ -79,11 +79,11 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'room', // owning side field name - 'acme_user_room', // target side table - 'users', // target side field name - ['name'], // column names are used to show a title of related entity - ['name'], // column names are used to show detailed info about related entity - ['name'], // Column names are used to show related entity in a grid + 'acme_user_room', // inverse side table + 'users', // inverse side field name + ['name'], // column names are used to show a title of owning side entity + ['name'], // column names are used to show detailed info about owning side entity + ['name'], // Column names are used to show owning side entity in a grid ['extend' => ['owner' => ExtendScope::OWNER_CUSTOM]] ); } @@ -120,7 +120,7 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table + 'acme_user_room', // inverse side table ['name'], // column names are used to show a title of related entity ['name'], // column names are used to show detailed info about related entity ['name'], // Column names are used to show related entity in a grid @@ -160,7 +160,7 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table + 'acme_user_room', // inverse side table ['name'], // column names are used to show a title of related entity ['name'], // column names are used to show detailed info about related entity ['name'], // Column names are used to show related entity in a grid @@ -200,7 +200,7 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table + 'acme_user_room', // inverse side table ['name'], // column names are used to show a title of related entity ['name'], // column names are used to show detailed info about related entity ['name'], // Column names are used to show related entity in a grid @@ -210,11 +210,11 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table - 'users', // target side field name - ['name'], // column names are used to show a title of related entity - ['name'], // column names are used to show detailed info about related entity - ['name'], // Column names are used to show related entity in a grid + 'acme_user_room', // inverse side table + 'users', // inverse side field name + ['name'], // column names are used to show a title of owning side entity + ['name'], // column names are used to show detailed info about owning side entity + ['name'], // Column names are used to show owning side entity in a grid ['extend' => ['owner' => ExtendScope::OWNER_CUSTOM]] ); } @@ -251,7 +251,7 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table + 'acme_user_room', // inverse side table ['name'], // column names are used to show a title of related entity ['name'], // column names are used to show detailed info about related entity ['name'], // Column names are used to show related entity in a grid @@ -261,11 +261,11 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table - 'users', // target side field name - ['name'], // column names are used to show a title of related entity - ['name'], // column names are used to show detailed info about related entity - ['name'], // Column names are used to show related entity in a grid + 'acme_user_room', // inverse side table + 'users', // inverse side field name + ['name'], // column names are used to show a title of owning side entity + ['name'], // column names are used to show detailed info about owning side entity + ['name'], // Column names are used to show owning side entity in a grid ['extend' => ['owner' => ExtendScope::OWNER_CUSTOM]] ); } @@ -302,7 +302,7 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table + 'acme_user_room', // inverse side table ['name'], // column names are used to show a title of related entity ['name'], // column names are used to show detailed info about related entity ['name'], // Column names are used to show related entity in a grid @@ -342,7 +342,7 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table + 'acme_user_room', // inverse side table ['name'], // column names are used to show a title of related entity ['name'], // column names are used to show detailed info about related entity ['name'], // Column names are used to show related entity in a grid @@ -382,7 +382,7 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table + 'acme_user_room', // inverse side table ['name'], // column names are used to show a title of related entity ['name'], // column names are used to show detailed info about related entity ['name'], // Column names are used to show related entity in a grid @@ -392,9 +392,9 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table - 'user', // target side field name - 'name', // column name is used to show related entity + 'acme_user_room', // inverse side table + 'user', // inverse side field name + 'name', // column name is used to show owning side entity ['extend' => ['owner' => ExtendScope::OWNER_CUSTOM]] ); } @@ -431,7 +431,7 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table + 'acme_user_room', // inverse side table ['name'], // column names are used to show a title of related entity ['name'], // column names are used to show detailed info about related entity ['name'], // Column names are used to show related entity in a grid @@ -441,9 +441,9 @@ class OroCRMSalesBundle implements Migration, ExtendExtensionAwareInterface $schema, 'oro_user', // owning side table 'rooms', // owning side field name - 'acme_user_room', // target side table - 'user', // target side field name - 'name', // column name is used to show related entity + 'acme_user_room', // inverse side table + 'user', // inverse side field name + 'name', // column name is used to show owning side entity ['extend' => ['owner' => ExtendScope::OWNER_CUSTOM]] ); } diff --git a/src/Oro/Bundle/FilterBundle/Filter/StringFilter.php b/src/Oro/Bundle/FilterBundle/Filter/StringFilter.php index eb072214e43..4e84ae932f0 100644 --- a/src/Oro/Bundle/FilterBundle/Filter/StringFilter.php +++ b/src/Oro/Bundle/FilterBundle/Filter/StringFilter.php @@ -102,12 +102,9 @@ protected function buildComparisonExpr( $fieldName = $ds->expr()->trim($fieldName); } - return $ds->expr()->andX( - $ds->expr()->orX( - $ds->expr()->isNull($fieldName), - $ds->expr()->eq($fieldName, $emptyString) - ), - $ds->expr()->eq(true, true) + return $ds->expr()->orX( + $ds->expr()->isNull($fieldName), + $ds->expr()->eq($fieldName, $emptyString) ); case FilterUtility::TYPE_NOT_EMPTY: $emptyString = $ds->expr()->literal(''); diff --git a/src/Oro/Bundle/FilterBundle/Resources/public/css/less/oro.filter.less b/src/Oro/Bundle/FilterBundle/Resources/public/css/less/oro.filter.less index bc5344e77ee..b5223073d10 100644 --- a/src/Oro/Bundle/FilterBundle/Resources/public/css/less/oro.filter.less +++ b/src/Oro/Bundle/FilterBundle/Resources/public/css/less/oro.filter.less @@ -325,6 +325,7 @@ button.ui-multiselect.select-filter-widget.ui-state-hover{ } .select2-container { border: 0 none; + background: transparent; .select2-choices { border-left: 0; border-right: 0; diff --git a/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/abstract-filter.js b/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/abstract-filter.js index dc8e71dc0d9..d732b37b728 100644 --- a/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/abstract-filter.js +++ b/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/abstract-filter.js @@ -101,7 +101,7 @@ define([ * * @property {Array.} */ - dropdownFitContainers: ['.ui-dialog-content', '#container', 'body'], + dropdownFitContainers: ['.ui-dialog-content>*:first-child', '#container', 'body'], /** * Initialize. diff --git a/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/choice-filter.js b/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/choice-filter.js index ff4af68371e..31247ee44ad 100644 --- a/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/choice-filter.js +++ b/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/choice-filter.js @@ -134,12 +134,8 @@ define([ if (!this._criteriaRenderd) { this._renderCriteria(); } - ChoiceFilter.__super__._showCriteria.apply(this, arguments); - }, - - _onClickCriteriaSelector: function() { - ChoiceFilter.__super__._onClickCriteriaSelector.apply(this, arguments); this._updateValueField(); + ChoiceFilter.__super__._showCriteria.apply(this, arguments); }, _onClickChoiceValue: function() { diff --git a/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/text-filter.js b/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/text-filter.js index 75c01e606d4..ff10c1cd31f 100644 --- a/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/text-filter.js +++ b/src/Oro/Bundle/FilterBundle/Resources/public/js/filter/text-filter.js @@ -2,8 +2,9 @@ define([ 'jquery', 'underscore', 'orotranslation/js/translator', - './empty-filter' -], function($, _, __, EmptyFilter) { + './empty-filter', + 'oroui/js/tools' +], function($, _, __, EmptyFilter, tools) { 'use strict'; var TextFilter; @@ -74,6 +75,10 @@ define([ 'click .disable-filter': '_onClickDisableFilter' }, + listen: { + 'layout:reposition mediator': '_onLayoutReposition' + }, + /** * Initialize. * @@ -156,6 +161,12 @@ define([ } }, + _onLayoutReposition: function() { + if (this.popupCriteriaShowed) { + this._alignCriteria(); + } + }, + /** * @protected */ @@ -217,14 +228,19 @@ define([ * @private */ _alignCriteria: function() { - var $container = this._findDropdownFitContainer(); - if ($container === null) { + if (tools.isMobile()) { + // no need to align criteria on mobile version, it is aligned over CSS + return; + } + var $container = this.$el.closest('.filter-box'); + if (!$container.length) { return; } var $dropdown = this.$(this.criteriaSelector); + $dropdown.css('margin-left', 'auto'); var rect = $dropdown.get(0).getBoundingClientRect(); var containerRect = $container.get(0).getBoundingClientRect(); - var shift = rect.right - (containerRect.left + $container.prop('clientWidth')); + var shift = rect.right - containerRect.right; if (shift > 0) { /** * reduce shift to avoid overlaping left edge of container diff --git a/src/Oro/Bundle/FilterBundle/Resources/views/Js/default_templates.js.twig b/src/Oro/Bundle/FilterBundle/Resources/views/Js/default_templates.js.twig index d768f8ec793..18f7e074282 100644 --- a/src/Oro/Bundle/FilterBundle/Resources/views/Js/default_templates.js.twig +++ b/src/Oro/Bundle/FilterBundle/Resources/views/Js/default_templates.js.twig @@ -105,7 +105,7 @@