Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 7 additions & 34 deletions core/components/minishop3/elements/snippets/ms3_products.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
use MiniShop3\Model\msProductOption;
use MiniShop3\Model\msVendor;
use MiniShop3\Services\Category\CategoryProductScopeService;
use MiniShop3\Utils\EventGate;
use MiniShop3\Utils\ProductThumbnailJoin;
use MODX\Revolution\modPlugin;
use MODX\Revolution\modPluginEvent;
use ModxPro\PdoTools\Fetch;

/** @var modX $modx */
/** @var \MODX\Revolution\modX $modx */
/** @var array $scriptProperties */
/** @var MiniShop3 $ms3 */

Expand Down Expand Up @@ -271,43 +272,17 @@
$usePackages = array_map('trim', explode(',', $scriptProperties['usePackages']));
}

$clearEventReturnedValues = static function () use ($modx): void {
if (isset($modx->event->returnedValues)) {
$modx->event->returnedValues = null;
}
};
$getEventReturnedValues = static function () use ($modx): array {
return isset($modx->event->returnedValues) && is_array($modx->event->returnedValues)
? $modx->event->returnedValues
: [];
};
$applyReturnedArray = static function (array $current, array $returnedValues, string $key): array {
if (!isset($returnedValues[$key]) || !is_array($returnedValues[$key])) {
return $current;
}

// Lists are complete replacements; associative arrays may patch existing keys.
return array_is_list($returnedValues[$key])
? $returnedValues[$key]
: array_replace($current, $returnedValues[$key]);
};

// Event: msOnProductsLoad - bulk loading of additional data from external packages
if (!empty($rows) && is_array($rows)) {
$productIds = array_column($rows, 'id');
$clearEventReturnedValues();
// Two propagation paths supported:
// 1) by-ref mutation of $rows in the plugin scope — preserved for plugins
// (ms3Variants and others) that mutate $scriptProperties['rows'] directly.
// 2) $modx->event->returnedValues['rows'] — the explicit channel introduced
// in #219/#245 for plugins that prefer the returned-values contract.
$modx->invokeEvent('msOnProductsLoad', [
// by-ref + returnedValues['rows'] — see EventGate contract (#219/#245).
$event = EventGate::invokeRaw($modx, 'msOnProductsLoad', [
'rows' => &$rows,
'productIds' => $productIds,
'usePackages' => $usePackages,
'scriptProperties' => $scriptProperties,
]);
$rows = $applyReturnedArray($rows, $getEventReturnedValues(), 'rows');
$rows = EventGate::applyReturnedArray($rows, $event['returnedValues'], 'rows');
$pdoFetch->addTime('Invoked msOnProductsLoad event');
}

Expand Down Expand Up @@ -362,14 +337,12 @@
$opt_time += microtime(true) - $opt_time_start;

// Event: msOnProductPrepare - enrich single product data from external packages
$clearEventReturnedValues();
// by-ref + returnedValues — see msOnProductsLoad comment above.
$modx->invokeEvent('msOnProductPrepare', [
$event = EventGate::invokeRaw($modx, 'msOnProductPrepare', [
'row' => &$rows[$k],
'productId' => $row['id'],
'idx' => $row['idx'],
]);
$rows[$k] = $applyReturnedArray($rows[$k], $getEventReturnedValues(), 'row');
$rows[$k] = EventGate::applyReturnedArray($rows[$k], $event['returnedValues'], 'row');
$row = $rows[$k];

$rawPrice = (float)($row['price'] ?? 0);
Expand Down
9 changes: 5 additions & 4 deletions core/components/minishop3/elements/tasks/csvImport.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<?php

use MiniShop3\Utils\ImportCSV;
use MiniShop3\Services\Product\Import\ProductImportService;

/** @var modX $modx */
/** @var \MODX\Revolution\modX $modx */
/** @var sFileTask $task */
/** @var sTaskRun $run */
/** @var array $scriptProperties */

$importCSV = new ImportCSV($modx);
$result = $importCSV->process($scriptProperties);
/** @var ProductImportService $importService */
$importService = $modx->services->get('ms3_product_import');
$result = $importService->process($scriptProperties);

if (!is_array($result) || empty($result['success'])) {
$errorMessage = is_array($result) && !empty($result['message'])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use MODX\Revolution\modX;
use MiniShop3\MiniShop3;
use MiniShop3\Model\msOrder;
use MiniShop3\Utils\EventGate;

/**
* Central notification service
Expand Down Expand Up @@ -77,29 +78,20 @@ public function send(Notification $notification, array $recipient, string $recip
// 2) $modx->event->returnedValues['recipient']/['channels'] —
// explicit channel introduced in #219/#245 for plugins that prefer
// the returned-values contract.
$this->clearEventReturnedValues();
$eventResult = $this->modx->invokeEvent('msOnBeforeSendNotification', [
$event = EventGate::invokeRaw($this->modx, 'msOnBeforeSendNotification', [
'notification' => $notification,
'recipient' => &$recipient,
'recipientType' => $recipientType,
'channels' => &$channels,
]);
$returnedValues = $this->getEventReturnedValues();
$recipient = $this->applyReturnedArray($recipient, $returnedValues, 'recipient');
$returnedValues = $event['returnedValues'];
$recipient = EventGate::applyReturnedArray($recipient, $returnedValues, 'recipient');
if (isset($returnedValues['channels']) && is_array($returnedValues['channels'])) {
$channels = $returnedValues['channels'];
}

// Check if notification was cancelled by plugin
$cancelled = false;
if (is_array($eventResult) && !empty($eventResult)) {
foreach ($eventResult as $pluginResult) {
if ($pluginResult === false || $pluginResult === 'cancel') {
$cancelled = true;
break;
}
}
}
$cancelled = $event['cancelled'];
if (!$cancelled && isset($this->modx->event->output) && $this->modx->event->output === false) {
$cancelled = true;
}
Expand Down Expand Up @@ -277,30 +269,4 @@ protected function isSchedulerAvailable(): bool
{
return $this->modx->services->has('scheduler');
}

protected function clearEventReturnedValues(): void
{
if (isset($this->modx->event->returnedValues)) {
$this->modx->event->returnedValues = null;
}
}

protected function getEventReturnedValues(): array
{
return isset($this->modx->event->returnedValues) && is_array($this->modx->event->returnedValues)
? $this->modx->event->returnedValues
: [];
}

protected function applyReturnedArray(array $current, array $returnedValues, string $key): array
{
if (!isset($returnedValues[$key]) || !is_array($returnedValues[$key])) {
return $current;
}

// Lists are complete replacements; associative arrays may patch existing keys.
return array_is_list($returnedValues[$key])
? $returnedValues[$key]
: array_replace($current, $returnedValues[$key]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace MiniShop3\Processors\Utilities\Import;

use MiniShop3\Utils\ImportCSV;
use MiniShop3\Services\Product\Import\ProductImportService;
use MODX\Revolution\Processors\Processor;
use MiniShop3\MiniShop3;
use MiniShop3\Model\msProduct;
Expand Down Expand Up @@ -93,7 +93,7 @@ public function process(): array
$realPath = realpath($filePath);

if ($realPath && !$useScheduler) {
$rowCount = ImportCSV::countRows($realPath, $importParams['delimiter']);
$rowCount = ProductImportService::countRows($realPath, $importParams['delimiter']);
if ($rowCount > $syncLimit) {
// Return warning but allow to proceed
// Frontend will show confirmation dialog
Expand All @@ -102,8 +102,9 @@ public function process(): array

if (empty($useScheduler)) {
// Synchronous import
$importCSV = new ImportCSV($this->modx);
$result = $importCSV->process($importParams);
/** @var ProductImportService $importService */
$importService = $this->modx->services->get('ms3_product_import');
$result = $importService->process($importParams);

// Convert utils response format to processor format
$data = $result['data'] ?? [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace MiniShop3\Processors\Utilities\Import;

use MiniShop3\Utils\ImportCSV;
use MiniShop3\Services\Product\Import\ProductImportService;
use MODX\Revolution\Processors\Processor;

/**
Expand Down Expand Up @@ -57,13 +57,13 @@ public function process(): array
}

// Get headers (first row)
$headers = ImportCSV::detectHeaders($realPath, $delimiter);
$headers = ProductImportService::detectHeaders($realPath, $delimiter);

// Count total rows
$totalRows = ImportCSV::countRows($realPath, $delimiter);
$totalRows = ProductImportService::countRows($realPath, $delimiter);

// Get preview data with encoding info
$previewData = ImportCSV::getPreview($realPath, $delimiter, $previewRows, false);
$previewData = ProductImportService::getPreview($realPath, $delimiter, $previewRows, false);
$preview = $previewData['rows'] ?? [];
$encoding = $previewData['encoding'] ?? 'UTF-8';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace MiniShop3\Processors\Utilities\Import;

use MiniShop3\Utils\ImportCSV;
use MiniShop3\Services\Product\Import\ProductImportService;
use MODX\Revolution\Processors\Processor;

/**
Expand Down Expand Up @@ -31,7 +31,7 @@ public function process(): array
return $this->failure('Import ID is required');
}

$progress = ImportCSV::getProgress($this->modx, $importId);
$progress = ProductImportService::getProgress($this->modx, $importId);

if ($progress === null) {
return $this->failure('Import not found or expired');
Expand Down
4 changes: 4 additions & 0 deletions core/components/minishop3/src/ServiceRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ class ServiceRegistry
'class' => \MiniShop3\Services\Product\ProductDataService::class,
'interface' => null,
],
'ms3_product_import' => [
'class' => \MiniShop3\Services\Product\Import\ProductImportService::class,
'interface' => null,
],
'ms3_repeater_field' => [
'class' => \MiniShop3\Services\ExtraFields\RepeaterFieldService::class,
'interface' => null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace MiniShop3\Services\Product\Import;

use MiniShop3\MiniShop3;
use MODX\Revolution\modX;

/**
* Mutable import run state shared between CSV import collaborators.
*/
final class ImportCsvContext
{
/** @var array<string, int> */
public array $tvCache = [];

public function __construct(
public modX $modx,
public MiniShop3 $ms3,
public array $params,
public string $importId,
public int $rows = 0,
public int $created = 0,
public int $updated = 0,
public int $errors = 0,
public int $skipped = 0,
public ?string $detectedEncoding = null,
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace MiniShop3\Services\Product\Import;

use MODX\Revolution\modX;

final class ImportCsvGalleryHandler
{
public function __construct(
private modX $modx,
) {
}

/**
* @param array<string, mixed> $resource
* @param list<string> $gallery
*/
public function importForResource(array $resource, array $gallery): void
{
if ($gallery === []) {
return;
}

$this->modx->log(modX::LOG_LEVEL_INFO, "Importing images: \n" . print_r($gallery, true));

$basePath = (string) $this->modx->getOption('base_path', null, '');
$processorsPath = (string) $this->modx->getOption('core_path', null, '')
. 'components/minishop3/src/Processors/';

foreach ($gallery as $v) {
if (!is_string($v) || $v === '') {
continue;
}

$image = ImportCsvPathGuard::resolveAssetUnderBase($v, $basePath);
if ($image === null) {
$this->modx->log(
modX::LOG_LEVEL_ERROR,
"[Import Security] Could not import gallery image \"$v\": path outside base or not found."
);
continue;
}

$response = $this->modx->runProcessor(
'MiniShop3\\Processors\\Gallery\\Upload',
['id' => $resource['id'], 'name' => $v, 'file' => $image],
['processors_path' => $processorsPath]
);

if ($response->isError()) {
$this->modx->log(
modX::LOG_LEVEL_ERROR,
"Error on upload \"$v\": \n" . print_r($response->getAllErrors(), true)
);
} else {
$this->modx->log(
modX::LOG_LEVEL_INFO,
"Successful upload \"$v\": \n" . print_r($response->getObject(), true)
);
}
}
}
}
Loading