Skip to content

Commit

Permalink
Merge branch 'develop' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
paulpartington-cti committed Jan 14, 2021
2 parents 9afcd8c + 5158bbe commit 0a51fcd
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 28 deletions.
19 changes: 4 additions & 15 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
dist: trusty
language: php
php:
- 7.2
- 7.3
services:
- mysql
- elasticsearch
sudo: required
env:
matrix:
- TEST_SUITE=unit
- TEST_SUITE=phpcs
- TEST_SUITE=configurator
MAGE_VERSION=2.2.5
- TEST_SUITE=configurator
MAGE_VERSION=2.3.1
- TEST_SUITE=configurator
MAGE_VERSION=2.3.2
- TEST_SUITE=configurator
MAGE_VERSION=2.3.3

matrix:
allow_failures:
- php: 7.2
- env: TEST_SUITE=configurator MAGE_VERSION=2.2.5
- env: TEST_SUITE=configurator MAGE_VERSION=2.3.1
- env: TEST_SUITE=configurator MAGE_VERSION=2.3.2
MAGE_VERSION=2.4.1

before_install:
- phpenv config-rm xdebug.ini || true
- composer self-update --1
- echo "{\"http-basic\":{\"repo.magento.com\":{\"username\":\"${MAGENTO_USERNAME}\",\"password\":\"${MAGENTO_PASSWORD}\"}}}" > auth.json
- sh -c "if [ '$TEST_SUITE' = 'phpcs' ]; then composer require magento/framework:^103.0.1; fi"
- sh -c "if [ '$TEST_SUITE' = 'unit' ]; then composer require magento/framework:^103.0.1; fi"
Expand Down
230 changes: 230 additions & 0 deletions Component/Product/Validator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
<?php

namespace CtiDigital\Configurator\Component\Product;

use CtiDigital\Configurator\Component\Products;
use Firegento\FastSimpleImport\Model\Importer;
use FireGento\FastSimpleImport\Model\Adapters\ImportAdapterFactoryInterface;

class Validator
{
/**
* Product import attributes which can be nulled if the data is not in a valid format
*/
const ATTRIBUTES_NULLIFY_ALLOW_LIST = [
'image',
'small_image',
'thumbnail',
'additional_images',
];

/**
* Indicate that the row data should be removed entirely
*/
const IMPORT_DATA_ACTION_REMOVE = 'remove';

/**
* Indicate that the attribute that's failing to import should be set to 'null'
*/
const IMPORT_DATA_ACTION_NULLIFY = 'nullify';

/**
* @var ImportAdapterFactoryInterface
*/
private $importAdapterFactory;

/**
* @var array
*/
private $logs = [];

private $removedRows = [];

/**
* Validator constructor.
* @param ImportAdapterFactoryInterface $importAdapterFactory
*/
public function __construct(
ImportAdapterFactoryInterface $importAdapterFactory
) {
$this->importAdapterFactory = $importAdapterFactory;
}

/**
* @return array
*/
public function getLogs()
{
return $this->logs;
}

/**
* @return array
*/
public function getRemovedRows()
{
return $this->removedRows;
}

/**
* @param $rowData
* @param $row
* @param $attributeCode
* @param $errorMessage
* @param string $type
*/
private function writeLog($rowData, $row, $attributeCode, $errorMessage, $type = self::IMPORT_DATA_ACTION_REMOVE)
{
$sku = isset($rowData['sku']) ? $rowData['sku'] : null;
$identifierMessage = ($sku !== null) ? sprintf('SKU: %s', $sku) : sprintf('Row Number : %s', $row);
switch ($type) {
case self::IMPORT_DATA_ACTION_NULLIFY:
$message = sprintf(
'%s Error: %s Resolution: Unset the value for attribute code %s',
$identifierMessage,
$errorMessage,
$attributeCode
);
break;
default:
$message = sprintf(
'%s Error: %s Resolution: Removed the row due to error with attribute code %s',
$identifierMessage,
$errorMessage,
$attributeCode
);
$this->removedRows[$row] = $message;
break;
}
$this->logs[] = $message;
}

/**
* Runs the import data through the validation steps and returns the modified values
*
* @param Importer $import
* @param $importLines
* @return array
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function getValidatedImport(Importer $import, $importLines)
{
$this->logs = [];
$failedImportRows = $this->getImportRowFailures($import, $importLines);
$importLines = $this->omitItemsFromImport($importLines, $failedImportRows);
return $importLines;
}

/**
* @param Importer $import
* @param $importLines
* @return array
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function getImportRowFailures(Importer $import, $importLines)
{
$failedRows = [];
// Creates a validation model and runs the import data through so we can find which rows would fail
$validation = $import->createImportModel();
$validationSource = $this->importAdapterFactory->create([
'data' => $importLines,
'multipleValueSeparator' => Products::SEPARATOR
]);
$validation->validateSource($validationSource);
$errors = $validation->getErrorAggregator();
foreach ($errors->getRowsGroupedByErrorCode() as $error => $rows) {
if (is_array($rows)) {
$failedRow = $this->formatRowRemoveData($error, $rows);
$failedRows[] = $failedRow;
}
}
return $failedRows;
}

/**
* Either removes a row entirely or nulls specific attributes that we know are okay to ignore
*
* @param array $importLines
* @param array $failedRows
* @return array
*/
public function omitItemsFromImport(array $importLines, array $failedRows)
{
foreach ($failedRows as $failedRow) {
$attributeCode = $failedRow['attribute_code'];
foreach ($failedRow['rows'] as $row) {
switch ($failedRow['action']) {
case self::IMPORT_DATA_ACTION_NULLIFY:
if (isset($importLines[$row][$attributeCode])) {
$this->writeLog(
$importLines[$row],
$row,
$attributeCode,
$failedRow['message'],
self::IMPORT_DATA_ACTION_NULLIFY
);
$importLines[$row][$attributeCode] = null;
}
break;
default:
if (isset($importLines[$row])) {
$this->writeLog(
$importLines[$row],
$row,
$attributeCode,
$failedRow['message'],
self::IMPORT_DATA_ACTION_REMOVE
);
unset($importLines[$row]);
}
}
}
}
$importLines = array_values($importLines);
return $importLines;
}

/**
* Gets the attribute code from the error returned by the validator
*
* @param $error
* @return string|null
*/
public function getAttributeCodeFromError($error)
{
$matches = [];
$attributeCode = null;
preg_match('/attribute\s([^\s]*)/', $error, $matches);
if (isset($matches[1])) {
$attributeCode = $matches[1];
}
return $attributeCode;
}

/**
* Processes the error into a set format
*
* @param $error
* @param $rows
* @return array
*/
private function formatRowRemoveData($error, $rows)
{
// Magento increases the row number by 1 as it assumes you've uploaded a CSV file with a header
$rowsProcessed = array_map(function ($row) {
return $row - 1;
}, $rows);

$attributeCode = $this->getAttributeCodeFromError($error);
$action = (in_array($attributeCode, self::ATTRIBUTES_NULLIFY_ALLOW_LIST)) ?
self::IMPORT_DATA_ACTION_NULLIFY :
self::IMPORT_DATA_ACTION_REMOVE;

return [
'action' => $action,
'message' => $error,
'rows' => $rowsProcessed,
'attribute_code' => $attributeCode
];
}
}
36 changes: 29 additions & 7 deletions Component/Products.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use CtiDigital\Configurator\Component\Product\AttributeOption;
use FireGento\FastSimpleImport\Model\ImporterFactory;
use CtiDigital\Configurator\Exception\ComponentException;
use CtiDigital\Configurator\Component\Product\ValidatorFactory;
use CtiDigital\Configurator\Component\Product\Validator;

/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
Expand Down Expand Up @@ -69,6 +71,11 @@ class Products implements ComponentInterface
*/
protected $image;

/**
* @var ValidatorFactory
*/
protected $validatorFactory;

/**
* @var AttributeOption
*/
Expand Down Expand Up @@ -99,19 +106,22 @@ class Products implements ComponentInterface
* @param ImporterFactory $importerFactory
* @param ProductFactory $productFactory
* @param Image $image
* @param ValidatorFactory $validatorFactory
* @param AttributeOption $attributeOption
* @param LoggerInterface $log
*/
public function __construct(
ImporterFactory $importerFactory,
ProductFactory $productFactory,
Image $image,
ValidatorFactory $validatorFactory,
AttributeOption $attributeOption,
LoggerInterface $log
) {
$this->productFactory= $productFactory;
$this->importerFactory = $importerFactory;
$this->image = $image;
$this->validatorFactory = $validatorFactory;
$this->attributeOption = $attributeOption;
$this->log = $log;
}
Expand Down Expand Up @@ -155,9 +165,11 @@ public function execute($data = null)
}
if ($this->isConfigurable($productArray)) {
$variations = $this->constructConfigurableVariations($productArray);
if (strlen($variations) > 0) {
$productArray['configurable_variations'] = $variations;
if (strlen($variations) === 0) {
$this->skippedProducts[] = $product[$this->skuColumn];
continue;
}
$productArray['configurable_variations'] = $variations;
unset($productArray['associated_products']);
unset($productArray['configurable_attributes']);
}
Expand All @@ -176,11 +188,20 @@ public function execute($data = null)
);
}
$this->attributeOption->saveOptions();
$this->log->logInfo(sprintf('Attempting to import %s rows', count($this->successProducts)));
$this->log->logInfo('Validating import...');
$validatorImport = $this->importerFactory->create();
$validatorImport->setMultipleValueSeparator(self::SEPARATOR);
/**
* @var Validator $validatorModel
*/
$validatorModel = $this->validatorFactory->create();
$validatedProducts = $validatorModel->getValidatedImport($validatorImport, $productsArray);
$this->log->logInfo(sprintf('Removed %s products after validation.', count($validatorModel->getRemovedRows())));
$this->log->logInfo(sprintf('Attempting to import %s rows', count($validatedProducts)));
try {
$import = $this->importerFactory->create();
$import->setMultipleValueSeparator(self::SEPARATOR);
$import->processImport($productsArray);
$import->processImport($validatedProducts);
} catch (\Exception $e) {
$this->log->logError($e->getMessage());
}
Expand Down Expand Up @@ -239,6 +260,7 @@ public function isConfigurable($data = [])

/**
* Create the configurable product string
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*
* @param $data
* @return string
Expand All @@ -254,6 +276,9 @@ public function constructConfigurableVariations($data)
$productsCount = count($products);
$count = 0;
foreach ($products as $sku) {
if ($count > 0 && $count < $productsCount) {
$variations .= '|';
}
$productModel = $this->productFactory->create();
$id = $productModel->getIdBySku($sku);
$productModel->load($id);
Expand All @@ -264,9 +289,6 @@ public function constructConfigurableVariations($data)
$variations .= 'sku=' . $sku . self::SEPARATOR . $configSkuAttributes;
}
$count++;
if ($count < $productsCount) {
$variations .= '|';
}
}
}
}
Expand Down
Loading

0 comments on commit 0a51fcd

Please sign in to comment.