Skip to content
Snippets Groups Projects
Unverified Commit 3f1f439d authored by Ievgen Shakhsuvarov's avatar Ievgen Shakhsuvarov
Browse files

MAGETWO-85332: Merge branch '2.2-develop' of...

MAGETWO-85332: Merge branch '2.2-develop' of github.com:magento-engcom/magento2ce into MAGETWO-85332-magento-magento2-12606
parents 31363512 d865ef1e
No related merge requests found
Showing
with 473 additions and 237 deletions
......@@ -943,8 +943,11 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements
*/
public function getProductCount()
{
$count = $this->_getResource()->getProductCount($this);
$this->setData(self::KEY_PRODUCT_COUNT, $count);
if (!$this->hasData(self::KEY_PRODUCT_COUNT)) {
$count = $this->_getResource()->getProductCount($this);
$this->setData(self::KEY_PRODUCT_COUNT, $count);
}
return $this->getData(self::KEY_PRODUCT_COUNT);
}
......
......@@ -8,9 +8,14 @@
namespace Magento\Catalog\Model\Indexer\Category\Product;
use Magento\Framework\DB\Query\Generator as QueryGenerator;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Query\Generator as QueryGenerator;
use Magento\Framework\DB\Select;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Store\Model\Store;
/**
* Class AbstractAction
......@@ -45,21 +50,21 @@ abstract class AbstractAction
/**
* Cached non anchor categories select by store id
*
* @var \Magento\Framework\DB\Select[]
* @var Select[]
*/
protected $nonAnchorSelects = [];
/**
* Cached anchor categories select by store id
*
* @var \Magento\Framework\DB\Select[]
* @var Select[]
*/
protected $anchorSelects = [];
/**
* Cached all product select by store id
*
* @var \Magento\Framework\DB\Select[]
* @var Select[]
*/
protected $productsSelects = [];
......@@ -119,19 +124,21 @@ abstract class AbstractAction
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Catalog\Model\Config $config
* @param QueryGenerator $queryGenerator
* @param MetadataPool|null $metadataPool
*/
public function __construct(
\Magento\Framework\App\ResourceConnection $resource,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Model\Config $config,
QueryGenerator $queryGenerator = null
QueryGenerator $queryGenerator = null,
MetadataPool $metadataPool = null
) {
$this->resource = $resource;
$this->connection = $resource->getConnection();
$this->storeManager = $storeManager;
$this->config = $config;
$this->queryGenerator = $queryGenerator ?: \Magento\Framework\App\ObjectManager::getInstance()
->get(QueryGenerator::class);
$this->queryGenerator = $queryGenerator ?: ObjectManager::getInstance()->get(QueryGenerator::class);
$this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class);
}
/**
......@@ -188,9 +195,9 @@ abstract class AbstractAction
*/
protected function getMainTmpTable()
{
return $this->useTempTable ? $this->getTable(
self::MAIN_INDEX_TABLE . self::TEMPORARY_TABLE_SUFFIX
) : $this->getMainTable();
return $this->useTempTable
? $this->getTable(self::MAIN_INDEX_TABLE . self::TEMPORARY_TABLE_SUFFIX)
: $this->getMainTable();
}
/**
......@@ -218,24 +225,25 @@ abstract class AbstractAction
/**
* Retrieve select for reindex products of non anchor categories
*
* @param \Magento\Store\Model\Store $store
* @return \Magento\Framework\DB\Select
* @param Store $store
* @return Select
* @throws \Exception when metadata not found for ProductInterface
*/
protected function getNonAnchorCategoriesSelect(\Magento\Store\Model\Store $store)
protected function getNonAnchorCategoriesSelect(Store $store)
{
if (!isset($this->nonAnchorSelects[$store->getId()])) {
$statusAttributeId = $this->config->getAttribute(
\Magento\Catalog\Model\Product::ENTITY,
Product::ENTITY,
'status'
)->getId();
$visibilityAttributeId = $this->config->getAttribute(
\Magento\Catalog\Model\Product::ENTITY,
Product::ENTITY,
'visibility'
)->getId();
$rootPath = $this->getPathFromCategoryId($store->getRootCategoryId());
$metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
$linkField = $metadata->getLinkField();
$select = $this->connection->select()->from(
['cc' => $this->getTable('catalog_category_entity')],
......@@ -304,12 +312,65 @@ abstract class AbstractAction
]
);
$this->addFilteringByChildProductsToSelect($select, $store);
$this->nonAnchorSelects[$store->getId()] = $select;
}
return $this->nonAnchorSelects[$store->getId()];
}
/**
* Add filtering by child products to select
*
* It's used for correct handling of composite products.
* This method makes assumption that select already joins `catalog_product_entity` as `cpe`.
*
* @param Select $select
* @param Store $store
* @return void
* @throws \Exception when metadata not found for ProductInterface
*/
private function addFilteringByChildProductsToSelect(Select $select, Store $store)
{
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
$linkField = $metadata->getLinkField();
$statusAttributeId = $this->config->getAttribute(Product::ENTITY, 'status')->getId();
$select->joinLeft(
['relation' => $this->getTable('catalog_product_relation')],
'cpe.' . $linkField . ' = relation.parent_id',
[]
)->joinLeft(
['relation_product_entity' => $this->getTable('catalog_product_entity')],
'relation.child_id = relation_product_entity.entity_id',
[]
)->joinLeft(
['child_cpsd' => $this->getTable('catalog_product_entity_int')],
'child_cpsd.' . $linkField . ' = '. 'relation_product_entity.' . $linkField
. ' AND child_cpsd.store_id = 0'
. ' AND child_cpsd.attribute_id = ' . $statusAttributeId,
[]
)->joinLeft(
['child_cpss' => $this->getTable('catalog_product_entity_int')],
'child_cpss.' . $linkField . ' = '. 'relation_product_entity.' . $linkField . ''
. ' AND child_cpss.attribute_id = child_cpsd.attribute_id'
. ' AND child_cpss.store_id = ' . $store->getId(),
[]
)->where(
'relation.child_id IS NULL OR '
. $this->connection->getIfNullSql('child_cpss.value', 'child_cpsd.value') . ' = ?',
\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED
)->group(
[
'cc.entity_id',
'ccp.product_id',
'visibility'
]
);
}
/**
* Check whether select ranging is needed
*
......@@ -323,16 +384,13 @@ abstract class AbstractAction
/**
* Return selects cut by min and max
*
* @param \Magento\Framework\DB\Select $select
* @param Select $select
* @param string $field
* @param int $range
* @return \Magento\Framework\DB\Select[]
* @return Select[]
*/
protected function prepareSelectsByRange(
\Magento\Framework\DB\Select $select,
$field,
$range = self::RANGE_CATEGORY_STEP
) {
protected function prepareSelectsByRange(Select $select, $field, $range = self::RANGE_CATEGORY_STEP)
{
if ($this->isRangingNeeded()) {
$iterator = $this->queryGenerator->generate(
$field,
......@@ -353,10 +411,10 @@ abstract class AbstractAction
/**
* Reindex products of non anchor categories
*
* @param \Magento\Store\Model\Store $store
* @param Store $store
* @return void
*/
protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store)
protected function reindexNonAnchorCategories(Store $store)
{
$selects = $this->prepareSelectsByRange($this->getNonAnchorCategoriesSelect($store), 'entity_id');
foreach ($selects as $select) {
......@@ -374,10 +432,10 @@ abstract class AbstractAction
/**
* Check if anchor select isset
*
* @param \Magento\Store\Model\Store $store
* @param Store $store
* @return bool
*/
protected function hasAnchorSelect(\Magento\Store\Model\Store $store)
protected function hasAnchorSelect(Store $store)
{
return isset($this->anchorSelects[$store->getId()]);
}
......@@ -385,19 +443,20 @@ abstract class AbstractAction
/**
* Create anchor select
*
* @param \Magento\Store\Model\Store $store
* @return \Magento\Framework\DB\Select
* @param Store $store
* @return Select
* @throws \Exception when metadata not found for ProductInterface or CategoryInterface
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function createAnchorSelect(\Magento\Store\Model\Store $store)
protected function createAnchorSelect(Store $store)
{
$isAnchorAttributeId = $this->config->getAttribute(
\Magento\Catalog\Model\Category::ENTITY,
'is_anchor'
)->getId();
$statusAttributeId = $this->config->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'status')->getId();
$statusAttributeId = $this->config->getAttribute(Product::ENTITY, 'status')->getId();
$visibilityAttributeId = $this->config->getAttribute(
\Magento\Catalog\Model\Product::ENTITY,
Product::ENTITY,
'visibility'
)->getId();
$rootCatIds = explode('/', $this->getPathFromCategoryId($store->getRootCategoryId()));
......@@ -405,12 +464,12 @@ abstract class AbstractAction
$temporaryTreeTable = $this->makeTempCategoryTreeIndex();
$productMetadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
$categoryMetadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\CategoryInterface::class);
$productMetadata = $this->metadataPool->getMetadata(ProductInterface::class);
$categoryMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\CategoryInterface::class);
$productLinkField = $productMetadata->getLinkField();
$categoryLinkField = $categoryMetadata->getLinkField();
return $this->connection->select()->from(
$select = $this->connection->select()->from(
['cc' => $this->getTable('catalog_category_entity')],
[]
)->joinInner(
......@@ -492,6 +551,10 @@ abstract class AbstractAction
'visibility' => new \Zend_Db_Expr($this->connection->getIfNullSql('cpvs.value', 'cpvd.value')),
]
);
$this->addFilteringByChildProductsToSelect($select, $store);
return $select;
}
/**
......@@ -586,10 +649,10 @@ abstract class AbstractAction
/**
* Retrieve select for reindex products of non anchor categories
*
* @param \Magento\Store\Model\Store $store
* @return \Magento\Framework\DB\Select
* @param Store $store
* @return Select
*/
protected function getAnchorCategoriesSelect(\Magento\Store\Model\Store $store)
protected function getAnchorCategoriesSelect(Store $store)
{
if (!$this->hasAnchorSelect($store)) {
$this->anchorSelects[$store->getId()] = $this->createAnchorSelect($store);
......@@ -600,10 +663,10 @@ abstract class AbstractAction
/**
* Reindex products of anchor categories
*
* @param \Magento\Store\Model\Store $store
* @param Store $store
* @return void
*/
protected function reindexAnchorCategories(\Magento\Store\Model\Store $store)
protected function reindexAnchorCategories(Store $store)
{
$selects = $this->prepareSelectsByRange($this->getAnchorCategoriesSelect($store), 'entity_id');
......@@ -622,22 +685,23 @@ abstract class AbstractAction
/**
* Get select for all products
*
* @param \Magento\Store\Model\Store $store
* @return \Magento\Framework\DB\Select
* @param Store $store
* @return Select
* @throws \Exception when metadata not found for ProductInterface
*/
protected function getAllProducts(\Magento\Store\Model\Store $store)
protected function getAllProducts(Store $store)
{
if (!isset($this->productsSelects[$store->getId()])) {
$statusAttributeId = $this->config->getAttribute(
\Magento\Catalog\Model\Product::ENTITY,
Product::ENTITY,
'status'
)->getId();
$visibilityAttributeId = $this->config->getAttribute(
\Magento\Catalog\Model\Product::ENTITY,
Product::ENTITY,
'visibility'
)->getId();
$metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
$linkField = $metadata->getLinkField();
$select = $this->connection->select()->from(
......@@ -726,10 +790,10 @@ abstract class AbstractAction
/**
* Reindex all products to root category
*
* @param \Magento\Store\Model\Store $store
* @param Store $store
* @return void
*/
protected function reindexRootCategory(\Magento\Store\Model\Store $store)
protected function reindexRootCategory(Store $store)
{
if ($this->isIndexRootCategoryNeeded()) {
$selects = $this->prepareSelectsByRange(
......@@ -750,16 +814,4 @@ abstract class AbstractAction
}
}
}
/**
* @return \Magento\Framework\EntityManager\MetadataPool
*/
private function getMetadataPool()
{
if (null === $this->metadataPool) {
$this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance()
->get(\Magento\Framework\EntityManager\MetadataPool::class);
}
return $this->metadataPool;
}
}
......@@ -6,9 +6,19 @@
namespace Magento\Catalog\Model\Indexer\Product\Category\Action;
use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\Config;
use Magento\Catalog\Model\Product;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Query\Generator as QueryGenerator;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Event\ManagerInterface as EventManagerInterface;
use Magento\Framework\Indexer\CacheContext;
use Magento\Store\Model\StoreManagerInterface;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction
{
/**
......@@ -19,32 +29,102 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
protected $limitationByProducts;
/**
* @var \Magento\Framework\Indexer\CacheContext
* @var CacheContext
*/
private $cacheContext;
/**
* @var EventManagerInterface|null
*/
private $eventManager;
/**
* @param ResourceConnection $resource
* @param StoreManagerInterface $storeManager
* @param Config $config
* @param QueryGenerator|null $queryGenerator
* @param MetadataPool|null $metadataPool
* @param CacheContext|null $cacheContext
* @param EventManagerInterface|null $eventManager
*/
public function __construct(
ResourceConnection $resource,
StoreManagerInterface $storeManager,
Config $config,
QueryGenerator $queryGenerator = null,
MetadataPool $metadataPool = null,
CacheContext $cacheContext = null,
EventManagerInterface $eventManager = null
) {
parent::__construct($resource, $storeManager, $config, $queryGenerator, $metadataPool);
$this->cacheContext = $cacheContext ?: ObjectManager::getInstance()->get(CacheContext::class);
$this->eventManager = $eventManager ?: ObjectManager::getInstance()->get(EventManagerInterface::class);
}
/**
* Refresh entities index
*
* @param int[] $entityIds
* @param bool $useTempTable
* @return $this
* @throws \Exception if metadataPool doesn't contain metadata for ProductInterface
* @throws \DomainException
*/
public function execute(array $entityIds = [], $useTempTable = false)
{
$this->limitationByProducts = $entityIds;
$idsToBeReIndexed = $this->getProductIdsWithParents($entityIds);
$this->limitationByProducts = $idsToBeReIndexed;
$this->useTempTable = $useTempTable;
$affectedCategories = $this->getCategoryIdsFromIndex($idsToBeReIndexed);
$this->removeEntries();
$this->reindex();
$this->registerProducts($entityIds);
$this->registerCategories($entityIds);
$affectedCategories = array_merge($affectedCategories, $this->getCategoryIdsFromIndex($idsToBeReIndexed));
$this->registerProducts($idsToBeReIndexed);
$this->registerCategories($affectedCategories);
$this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]);
return $this;
}
/**
* Get IDs of parent products by their child IDs.
*
* Returns identifiers of parent product from the catalog_product_relation.
* Please note that returned ids don't contain ids of passed child products.
*
* @param int[] $childProductIds
* @return int[]
* @throws \Exception if metadataPool doesn't contain metadata for ProductInterface
* @throws \DomainException
*/
private function getProductIdsWithParents(array $childProductIds)
{
/** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */
$metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
$fieldForParent = $metadata->getLinkField();
$select = $this->connection
->select()
->from(['relation' => $this->getTable('catalog_product_relation')], [])
->distinct(true)
->where('child_id IN (?)', $childProductIds)
->join(
['cpe' => $this->getTable('catalog_product_entity')],
'relation.parent_id = cpe.' . $fieldForParent,
['cpe.entity_id']
);
$parentProductIds = $this->connection->fetchCol($select);
return array_unique(array_merge($childProductIds, $parentProductIds));
}
/**
* Register affected products
*
......@@ -53,26 +133,19 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
*/
private function registerProducts($entityIds)
{
$this->getCacheContext()->registerEntities(Product::CACHE_TAG, $entityIds);
$this->cacheContext->registerEntities(Product::CACHE_TAG, $entityIds);
}
/**
* Register categories assigned to products
*
* @param array $entityIds
* @param array $categoryIds
* @return void
*/
private function registerCategories($entityIds)
private function registerCategories(array $categoryIds)
{
$categories = $this->connection->fetchCol(
$this->connection->select()
->from($this->getMainTable(), ['category_id'])
->where('product_id IN (?)', $entityIds)
->distinct()
);
if ($categories) {
$this->getCacheContext()->registerEntities(Category::CACHE_TAG, $categories);
if ($categoryIds) {
$this->cacheContext->registerEntities(Category::CACHE_TAG, $categoryIds);
}
}
......@@ -98,7 +171,7 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
protected function getNonAnchorCategoriesSelect(\Magento\Store\Model\Store $store)
{
$select = parent::getNonAnchorCategoriesSelect($store);
return $select->where('ccp.product_id IN (?)', $this->limitationByProducts);
return $select->where('ccp.product_id IN (?) OR relation.child_id IN (?)', $this->limitationByProducts);
}
/**
......@@ -136,16 +209,25 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
}
/**
* Get cache context
* Returns a list of category ids which are assigned to product ids in the index
*
* @return \Magento\Framework\Indexer\CacheContext
* @deprecated 101.0.0
*/
private function getCacheContext()
private function getCategoryIdsFromIndex(array $productIds)
{
if ($this->cacheContext === null) {
$this->cacheContext = \Magento\Framework\App\ObjectManager::getInstance()->get(CacheContext::class);
$categoryIds = $this->connection->fetchCol(
$this->connection->select()
->from($this->getMainTable(), ['category_id'])
->where('product_id IN (?)', $productIds)
->distinct()
);
$parentCategories = $categoryIds;
foreach ($categoryIds as $categoryId) {
$parentIds = explode('/', $this->getPathFromCategoryId($categoryId));
$parentCategories = array_merge($parentCategories, $parentIds);
}
return $this->cacheContext;
$categoryIds = array_unique($parentCategories);
return $categoryIds;
}
}
......@@ -28,6 +28,7 @@
<dt><?= /* @escapeNotVerified */ __('Category') ?></dt>
<dd>
<ol class="items">
<?php /** @var \Magento\Catalog\Model\Category $_category */ ?>
<?php foreach ($_categories as $_category): ?>
<?php if ($_category->getIsActive()): ?>
<li class="item">
......
......@@ -156,7 +156,7 @@ class Stock
$resource = $this->getStockStatusResource();
$resource->addStockDataToCollection(
$collection,
!$isShowOutOfStock || $collection->getFlag('require_stock_items')
!$isShowOutOfStock
);
$collection->setFlag($stockFlag, true);
}
......
......@@ -123,11 +123,12 @@ class Fulltext implements \Magento\Framework\Indexer\ActionInterface, \Magento\F
$saveHandler = $this->indexerHandlerFactory->create([
'data' => $this->data
]);
foreach ($storeIds as $storeId) {
$dimension = $this->dimensionFactory->create(['name' => 'scope', 'value' => $storeId]);
$productIds = array_unique(array_merge($ids, $this->fulltextResource->getRelationsByChild($ids)));
$saveHandler->deleteIndex([$dimension], new \ArrayObject($productIds));
$saveHandler->saveIndex([$dimension], $this->fullAction->rebuildStoreIndex($storeId, $ids));
$saveHandler->saveIndex([$dimension], $this->fullAction->rebuildStoreIndex($storeId, $productIds));
}
}
......
......@@ -377,9 +377,9 @@ class DataProvider
public function getProductChildIds($productId, $typeId)
{
$typeInstance = $this->getProductTypeInstance($typeId);
$relation = $typeInstance->isComposite(
$this->getProductEmulator($typeId)
) ? $typeInstance->getRelationInfo() : false;
$relation = $typeInstance->isComposite($this->getProductEmulator($typeId))
? $typeInstance->getRelationInfo()
: false;
if ($relation && $relation->getTable() && $relation->getParentFieldName() && $relation->getChildFieldName()) {
$select = $this->connection->select()->from(
......
......@@ -5,6 +5,7 @@
*/
namespace Magento\CatalogSearch\Model\Indexer\Fulltext\Action;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\CatalogSearch\Model\Indexer\Fulltext;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ResourceConnection;
......@@ -297,27 +298,32 @@ class Full
/**
* Get parents IDs of product IDs to be re-indexed
*
* @deprecated as it not used in the class anymore and duplicates another API method
* @see \Magento\CatalogSearch\Model\ResourceModel\Fulltext::getRelationsByChild()
*
* @param int[] $entityIds
* @return int[]
* @throws \Exception
*/
protected function getProductIdsFromParents(array $entityIds)
{
/** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */
$metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
$fieldForParent = $metadata->getLinkField();
$connection = $this->connection;
$linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
$select = $this->connection
$select = $connection
->select()
->from(['relation' => $this->getTable('catalog_product_relation')], [])
->from(
['relation' => $this->getTable('catalog_product_relation')],
[]
)
->distinct(true)
->where('child_id IN (?)', $entityIds)
->where('parent_id NOT IN (?)', $entityIds)
->join(
['cpe' => $this->getTable('catalog_product_entity')],
'relation.parent_id = cpe.' . $fieldForParent,
'relation.parent_id = cpe.' . $linkField,
['cpe.entity_id']
);
return $this->connection->fetchCol($select);
return $connection->fetchCol($select);
}
/**
......@@ -335,7 +341,7 @@ class Full
public function rebuildStoreIndex($storeId, $productIds = null)
{
if ($productIds !== null) {
$productIds = array_unique(array_merge($productIds, $this->getProductIdsFromParents($productIds)));
$productIds = array_unique($productIds);
}
// prepare searchable attributes
......
......@@ -82,17 +82,20 @@ class Fulltext extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
$connection = $this->getConnection();
$linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
$select = $connection->select()->from(
['relation' => $this->getTable('catalog_product_relation')],
[]
)->join(
['cpe' => $this->getTable('catalog_product_entity')],
'cpe.' . $linkField . ' = relation.parent_id',
['cpe.entity_id']
)->where(
'relation.child_id IN (?)',
$childIds
)->distinct(true);
$select = $connection
->select()
->from(
['relation' => $this->getTable('catalog_product_relation')],
[]
)->distinct(true)
->join(
['cpe' => $this->getTable('catalog_product_entity')],
'cpe.' . $linkField . ' = relation.parent_id',
['cpe.entity_id']
)->where(
'relation.child_id IN (?)',
$childIds
);
return $connection->fetchCol($select);
}
......
#Copyright © Magento, Inc. All rights reserved.
#See COPYING.txt for license details.
#*** Start of example .env ***#
#
# MAGENTO_BASE_URL=http://127.0.0.1:32772/
#
# MAGENTO_BACKEND_NAME=admin
# MAGENTO_ADMIN_USERNAME=admin
# MAGENTO_ADMIN_PASSWORD=123123q
#
# SELENIUM_HOST=127.0.0.1
# SELENIUM_PORT=4444
# SELENIUM_PROTOCOL=http
# SELENIUM_PATH=/wd/hub
#
# MAGENTO_RESTAPI_SERVER_HOST=127.0.0.1
# MAGENTO_RESTAPI_SERVER_PORT=32769
#
# TESTS_BP=/Users/First_Last/GitHub/magento2ce/dev/tests/acceptance/tests/functional
# FW_BP=/Users/First_Last/GitHub/magento2-functional-testing-framework
# TESTS_MODULE_PATH=/Users/First_Last/GitHub/magento2ce/dev/tests/acceptance/tests/functional/Magento/FunctionalTest
# MODULE_WHITELIST=Magento_NewModule
#
#*** End of example .env ***#
#*** Start of .env ***#
#*** Set the base URL for your Magento instance ***#
MAGENTO_BASE_URL=
#*** Set the Admin Username and Password for your Magento instance ***#
MAGENTO_BACKEND_NAME=
MAGENTO_ADMIN_USERNAME=
MAGENTO_ADMIN_PASSWORD=
#*** Uncomment and set host & port if your dev environment needs different value other than MAGENTO_BASE_URL for Rest Api Requests ***#
#*** Selenium Server Protocol, Host, Port, and Path, with local defaults. Uncomment and change if not running Selenium locally.
#SELENIUM_HOST=127.0.0.1
#SELENIUM_PORT=4444
#SELENIUM_PROTOCOL=http
#SELENIUM_PATH=/wd/hub
#*** Uncomment and set host & port if your dev environment needs different value other than MAGENTO_BASE_URL for Rest API Requests ***#
#MAGENTO_RESTAPI_SERVER_HOST=
#MAGENTO_RESTAPI_SERVER_PORT=
DB_DSN=
DB_USERNAME=
DB_PASSWORD=
#*** uncomment these properties to set up a dev environment with symlinked projects***#
#*** Uncomment these properties to set up a dev environment with symlinked projects ***#
#TESTS_BP=
#FW_BP=
#TESTS_MODULE_PATH=
#MODULE_WHITELIST=
\ No newline at end of file
#MODULE_WHITELIST=
#MFTF_DEBUG=true
#*** End of .env ***#
\ No newline at end of file
# Magento Functional Testing Framework
----
## System Requirements
[Magento Functional Testing Framework system requirements](http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/getting-started.html#prepare-environment)
## Installation
To install the Magento Functional Testing Framework, see [Getting Started](http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/getting-started.html)
## Contributing
Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions.
To learn about how to make a contribution, click [here][1].
To open an issue, click [here][2].
To suggest documentation improvements, click [here][3].
[1]: <http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/contribution-guidelines.html>
[2]: <https://github.com/magento/magento2-functional-testing-framework/issues>
[3]: <http://devdocs.magento.com>
### Labels applied by the MFTF team
Refer to the tables with descriptions of each label below. These labels are applied by the MFTF development team to community contributed issues and pull requests, to communicate status, impact, or which team is working on it.
### Pull Request Status
Label| Description
---|---
**accept**| The pull request has been accepted and will be merged into mainline code.
**reject**| The pull request has been rejected and will not be merged into mainline code. Possible reasons can include but are not limited to: issue has already been fixed in another code contribution, or there is an issue with the code contribution.
**needsUpdate**| The Magento Team needs additional information from the reporter to properly prioritize and process the pull request.
### Issue Resolution Status
Label| Description
---|---
**acknowledged**| The Magento Team has validated the issue and an internal ticket has been created.
**needsUpdate**| The Magento Team needs additional information from the reporter to properly prioritize and process the issue or pull request.
**cannot reproduce**| The Magento Team has not confirmed that this issue contains the minimum required information to reproduce.
**non-issue**| The Magento Team has not recognised any issue according to provided information.
### Domains Impacted
Label| Description
---|---
**PROD**| Affects the Product team (mostly feature requests or business logic change).
**DOC**| Affects Documentation domain.
**TECH**| Affects Architect Group (mostly to make decisions around technology changes).
### Type
Label| Description
---|---
**bugfix**| The issue or pull request relates to bug fixing.
**enhancement**| The issue or pull request that raises the MFTF to a higher degree (for example new features, optimization, refactoring, etc).
## License
Each Magento source file included in this distribution is licensed under APL 3.0
Please see LICENSE_APL3.txt for the full text of the APL 3.0 license or contact license@magentocommerce.com for a copy.
......@@ -21,8 +21,8 @@ class RoboFile extends \Robo\Tasks
function cloneFiles()
{
$this->_exec('cp -vn .env.example .env');
$this->_exec('cp -vn codeception.dist.yml codeception.yml');
$this->_exec('cp -vn tests/functional.suite.dist.yml tests/functional.suite.yml');
$this->_exec('cp -vf codeception.dist.yml codeception.yml');
$this->_exec('cp -vf tests'. DIRECTORY_SEPARATOR .'functional.suite.dist.yml tests'. DIRECTORY_SEPARATOR .'functional.suite.yml');
}
/**
......@@ -34,7 +34,7 @@ class RoboFile extends \Robo\Tasks
function buildProject()
{
$this->cloneFiles();
$this->_exec('./vendor/bin/codecept build');
$this->_exec('vendor'. DIRECTORY_SEPARATOR .'bin'. DIRECTORY_SEPARATOR .'codecept build');
}
/**
......@@ -72,75 +72,45 @@ class RoboFile extends \Robo\Tasks
}
/**
* Run all Functional tests using the Chrome environment.
* Run all Functional tests.
*
* @return void
*/
function chrome()
function functional()
{
$this->_exec('./vendor/bin/codecept run functional --env chrome --skip-group skip');
$this->_exec('.' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'codecept run functional --skip-group skip');
}
/**
* Run all Functional tests using the FireFox environment.
*
* @return void
*/
function firefox()
{
$this->_exec('./vendor/bin/codecept run functional --env firefox --skip-group skip');
}
/**
* Run all Functional tests using the PhantomJS environment.
*
* @return void
*/
function phantomjs()
{
$this->_exec('./vendor/bin/codecept run functional --env phantomjs --skip-group skip');
}
/**
* Run all Functional tests using the Chrome Headless environment.
*
* @return void
*/
function headless()
{
$this->_exec('./vendor/bin/codecept run functional --env headless --skip-group skip');
}
/**
* Run all Tests with the specified @group tag, excluding @group 'skip', using the Chrome environment.
* Run all Tests with the specified @group tag, excluding @group 'skip'.
*
* @param string $args
* @return void
*/
function group($args = '')
{
$this->taskExec('./vendor/bin/codecept run functional --verbose --steps --env chrome --skip-group skip --group')->args($args)->run();
$this->taskExec('.' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'codecept run functional --verbose --steps --skip-group skip --group')->args($args)->run();
}
/**
* Run all Functional tests located under the Directory Path provided using the Chrome environment.
* Run all Functional tests located under the Directory Path provided.
*
* @param string $args
* @return void
*/
function folder($args = '')
{
$this->taskExec('./vendor/bin/codecept run functional --env chrome')->args($args)->run();
$this->taskExec('.' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'codecept run functional')->args($args)->run();
}
/**
* Run all Tests marked with the @group tag 'example', using the Chrome environment.
* Run all Tests marked with the @group tag 'example'.
*
* @return void
*/
function example()
{
$this->_exec('./vendor/bin/codecept run --env chrome --group example --skip-group skip');
$this->_exec('.' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'codecept run --group example --skip-group skip');
}
/**
......@@ -150,7 +120,7 @@ class RoboFile extends \Robo\Tasks
*/
function allure1Generate()
{
return $this->_exec('allure generate tests/_output/allure-results/ -o tests/_output/allure-report/');
return $this->_exec('allure generate tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-results'. DIRECTORY_SEPARATOR .' -o tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .'');
}
/**
......@@ -160,7 +130,7 @@ class RoboFile extends \Robo\Tasks
*/
function allure2Generate()
{
return $this->_exec('allure generate tests/_output/allure-results/ --output tests/_output/allure-report/ --clean');
return $this->_exec('allure generate tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-results'. DIRECTORY_SEPARATOR .' --output tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .' --clean');
}
/**
......@@ -170,7 +140,7 @@ class RoboFile extends \Robo\Tasks
*/
function allure1Open()
{
$this->_exec('allure report open --report-dir tests/_output/allure-report/');
$this->_exec('allure report open --report-dir tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .'');
}
/**
......@@ -180,7 +150,7 @@ class RoboFile extends \Robo\Tasks
*/
function allure2Open()
{
$this->_exec('allure open --port 0 tests/_output/allure-report/');
$this->_exec('allure open --port 0 tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .'');
}
/**
......
{
"name": "magento/magento2ce-functional-tests",
"description": "Magento 2 (Open Source) Functional Tests",
"type": "project",
"version": "1.0.0-dev",
"license": [
"OSL-3.0",
"AFL-3.0"
],
"config": {
"sort-packages": true
},
"repositories": [
{
"type": "git",
"url": "git@github.com:magento/magento2-functional-testing-framework.git"
}
],
"require": {
"allure-framework/allure-codeception": "dev-master#af40af5ae2b717618a42fe3e137d75878508c75d",
"codeception/codeception": "~2.3.4",
"consolidation/robo": "^1.0.0",
"symfony/process": ">=2.7 <3.4",
"henrikbjorn/lurker": "^1.2",
"magento/magento2-functional-testing-framework": "1.0.0",
"php": "7.0.2|7.0.4|~7.0.6|~7.1.0",
"vlucas/phpdotenv": "~2.4"
},
"autoload": {
"psr-4": {
"Magento\\": "tests/functional/Magento"
}
},
"prefer-stable": true
}
......@@ -22,3 +22,9 @@ if (file_exists(PROJECT_ROOT . '/.env')) {
}
}
defined('FW_BP') || define('FW_BP', PROJECT_ROOT . $RELATIVE_FW_PATH);
// add the debug flag here
$debug_mode = $_ENV['MFTF_DEBUG'] ?? false;
if (!(bool)$debug_mode && extension_loaded('xdebug')) {
xdebug_disable();
}
......@@ -9,14 +9,14 @@
<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd">
<suite name="mySuite">
<before>
<createData entity="_defaultCategory" mergeKey="createCategory"/>
<createData entity="_defaultProduct" mergeKey="createProduct">
<createData entity="_defaultCategory" stepKey="createCategory"/>
<createData entity="_defaultProduct" stepKey="createProduct">
<required-entity createDataKey="createCategory"/>
</createData>
</before>
<after>
<deleteData mergeKey="deleteMyProduct" createDataKey="createProduct"/>
<deleteData mergeKey="deleteMyCategory" createDataKey="createCategory"/>
<deleteData stepKey="deleteMyProduct" createDataKey="createProduct"/>
<deleteData stepKey="deleteMyCategory" createDataKey="createCategory"/>
</after>
<include>
<group name="example"/>
......
......@@ -31,3 +31,11 @@ modules:
username: "%MAGENTO_ADMIN_USERNAME%"
password: "%MAGENTO_ADMIN_PASSWORD%"
pageload_timeout: 30
host: %SELENIUM_HOST%
port: %SELENIUM_PORT%
protocol: %SELENIUM_PROTOCOL%
path: %SELENIUM_PATH%
capabilities:
chromeOptions:
args: ["--start-maximized", "--disable-extensions", "--enable-automation"]
# Magento 2 Acceptance Tests
# Magento 2 Functional Tests
The Acceptance Tests Module for **Magento_AdminNotification** Module.
The Functional Tests Module for **Magento_AdminNotification** Module.
{
"name": "magento/magento2-functional-test-admin-notification",
"description": "Magento 2 Acceptance Test Module Admin Notification",
"repositories": [
{
"type" : "composer",
"url" : "https://repo.magento.com/"
}
],
"require": {
"php": "~7.0",
"codeception/codeception": "2.2|2.3",
"allure-framework/allure-codeception": "dev-master",
"consolidation/robo": "^1.0.0",
"henrikbjorn/lurker": "^1.2",
"vlucas/phpdotenv": "~2.4",
"magento/magento2-functional-testing-framework": "dev-develop"
},
"suggest": {
"magento/magento2-functional-test-module-store": "dev-master",
"magento/magento2-functional-test-module-backend": "dev-master",
"magento/magento2-functional-test-media-storage": "dev-master",
"magento/magento2-functional-test-module-ui": "dev-master"
},
"name": "magento/magento2-functional-test-module-admin-notification",
"description": "Magento 2 Functional Test Module Admin Notification",
"type": "magento2-test-module",
"version": "dev-master",
"version": "100.0.0-dev",
"license": [
"OSL-3.0",
"AFL-3.0"
],
"config": {
"sort-packages": true
},
"require": {
"magento/magento2-functional-testing-framework": "1.0.0",
"php": "7.0.2|7.0.4|~7.0.6|~7.1.0"
},
"suggest": {
"magento/magento2-functional-test-module-backend": "100.0.0-dev",
"magento/magento2-functional-test-module-media-storage": "100.0.0-dev",
"magento/magento2-functional-test-module-store": "100.0.0-dev",
"magento/magento2-functional-test-module-ui": "100.0.0-dev"
},
"autoload": {
"psr-4": {
"Magento\\FunctionalTest\\": [
"tests/functional/Magento/FunctionalTest",
"generated/Magento/FunctionalTest"
]
"Magento\\FunctionalTest\\AdminNotification\\": ""
}
},
"extra": {
"map": [
[
"*",
"tests/functional/Magento/FunctionalTest/AdminNotification"
"dev/tests/acceptance/tests/functional/Magento/FunctionalTest/AdminNotification"
]
]
}
......
# Magento 2 Acceptance Tests
# Magento 2 Functional Tests
The Acceptance Tests Module for **Magento_AdvancedPricingImportExport** Module.
The Functional Tests Module for **Magento_AdvancedPricingImportExport** Module.
{
"name": "magento/magento2-functional-test-module-advanced-pricing-import-export",
"description": "Magento 2 Acceptance Test Module Advanced Pricing Import Export",
"repositories": [
{
"type" : "composer",
"url" : "https://repo.magento.com/"
}
],
"require": {
"php": "~7.0",
"codeception/codeception": "2.2|2.3",
"allure-framework/allure-codeception": "dev-master",
"consolidation/robo": "^1.0.0",
"henrikbjorn/lurker": "^1.2",
"vlucas/phpdotenv": "~2.4",
"magento/magento2-functional-testing-framework": "dev-develop"
},
"suggest": {
"magento/magento2-functional-test-module-store": "dev-master",
"magento/magento2-functional-test-module-catalog": "dev-master",
"magento/magento2-functional-test-module-catalog-inventory": "dev-master",
"magento/magento2-functional-test-module-eav": "dev-master",
"magento/magento2-functional-test-module-import-export": "dev-master",
"magento/magento2-functional-test-module-catalog-import-export": "dev-master",
"magento/magento2-functional-test-module-customer": "dev-master"
},
"description": "Magento 2 Functional Test Module Advanced Pricing Import Export",
"type": "magento2-test-module",
"version": "dev-master",
"version": "100.0.0-dev",
"license": [
"OSL-3.0",
"AFL-3.0"
],
"config": {
"sort-packages": true
},
"require": {
"magento/magento2-functional-testing-framework": "1.0.0",
"php": "7.0.2|7.0.4|~7.0.6|~7.1.0"
},
"suggest": {
"magento/magento2-functional-test-module-catalog": "100.0.0-dev",
"magento/magento2-functional-test-module-catalog-import-export": "100.0.0-dev",
"magento/magento2-functional-test-module-catalog-inventory": "100.0.0-dev",
"magento/magento2-functional-test-module-customer": "100.0.0-dev",
"magento/magento2-functional-test-module-eav": "100.0.0-dev",
"magento/magento2-functional-test-module-import-export": "100.0.0-dev",
"magento/magento2-functional-test-module-store": "100.0.0-dev"
},
"autoload": {
"psr-0": {
"Yandex": "vendor/allure-framework/allure-codeception/src/"
},
"psr-4": {
"Magento\\FunctionalTestingFramework\\": [
"vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework"
],
"Magento\\FunctionalTest\\": [
"tests/functional/Magento/FunctionalTest",
"generated/Magento/FunctionalTest"
]
"Magento\\FunctionalTest\\AdvancedPricingImportExport\\": ""
}
},
"extra": {
"map": [
[
"*",
"tests/functional/Magento/FunctionalTest/AdvancedPricingImportExport"
"dev/tests/acceptance/tests/functional/Magento/FunctionalTest/AdvancedPricingImportExport"
]
]
}
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment