Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(admin): only save product settings once per request #279

Merged
merged 1 commit into from
Oct 24, 2024
Merged
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
100 changes: 74 additions & 26 deletions src/Hooks/HasPdkProductHooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,33 @@
use MyParcelNL\Pdk\Base\Support\Arr;
use MyParcelNL\Pdk\Facade\Actions;
use MyParcelNL\Pdk\Facade\Frontend;
use MyParcelNL\Pdk\Facade\Logger;
use MyParcelNL\Pdk\Facade\Pdk;
use MyParcelNL\PrestaShop\Facade\EntityManager;
use MyParcelNL\Sdk\src\Support\Str;
use Symfony\Component\HttpFoundation\Request;
use Throwable;
use Tools;

trait HasPdkProductHooks
{
/**
* Saves the MyParcel product settings when a product is updated.
*
* @param array $params
*
* @return void
*/
public function hookActionProductUpdate(array $params): void
{
$this->saveProductSettings((int) $params['id_product']);
try {
$this->saveProductSettings((int) $params['id_product']);
} catch (Throwable $e) {
Logger::error(sprintf('Failed to save product settings for product %d', (int) $params['id_product']), [
'error' => $e->getMessage(),
'trace' => $e->getTrace(),
]);
}
}

/**
Expand All @@ -39,6 +50,60 @@
return $this->renderProductSettings((int) $params['id_product']);
}

/**
* @param int $productId
* @param array $productSettings
*
* @return \Symfony\Component\HttpFoundation\Request
* @throws \JsonException
*/
private function createRequest(int $productId, array $productSettings): Request
{
$parameters = [
'action' => PdkBackendActions::UPDATE_PRODUCT_SETTINGS,
'productId' => $productId,
];

$requestBody = [];

foreach ($productSettings as $key => $value) {
$trimmedKey = Arr::last(explode('-', $key));
$requestBody[$trimmedKey] = $value;
}

$content = json_encode(['data' => ['product_settings' => $requestBody]], JSON_THROW_ON_ERROR);

return new Request($parameters, [], [], [], [], [], $content);
}

/**
* The product update hook is called multiple (about 8) times by PrestaShop. This method checks if the product
* settings are already saved by comparing a checksum of the settings with the checksum we add to $_POST.
*
* @see https://www.prestashop.com/forums/topic/591295-ps17-hookactionproductupdate-gets-multiple-called-and-no-image-uploaded/
*
* @param int $idProduct
* @param array $productSettings
*
* @return bool
* @throws \JsonException
*/
private function isAlreadySaved(int $idProduct, array $productSettings): bool

Check notice on line 91 in src/Hooks/HasPdkProductHooks.php

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Hooks/HasPdkProductHooks.php#L91

isAlreadySaved accesses the super-global variable $_POST.
{
$appInfo = Pdk::getAppInfo();
$checksumKey = "_$appInfo->name-product-save-checksum-$idProduct";
$existingChecksum = $_POST[$checksumKey] ?? null;

Check failure on line 95 in src/Hooks/HasPdkProductHooks.php

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Hooks/HasPdkProductHooks.php#L95

$_POST[$checksumKey] not unslashed before sanitization. Use wp_unslash() or similar

Check failure on line 95 in src/Hooks/HasPdkProductHooks.php

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Hooks/HasPdkProductHooks.php#L95

Direct use of $_POST Superglobal detected.

Check notice on line 95 in src/Hooks/HasPdkProductHooks.php

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Hooks/HasPdkProductHooks.php#L95

Processing form data without nonce verification.
$checksum = md5(json_encode($productSettings, JSON_THROW_ON_ERROR));

if ($existingChecksum === $checksum) {
return true;
}

$_POST[$checksumKey] = $checksum;

Check failure on line 102 in src/Hooks/HasPdkProductHooks.php

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Hooks/HasPdkProductHooks.php#L102

Direct use of $_POST Superglobal detected.

return false;
}

/**
* @param int $idProduct
*
Expand All @@ -54,42 +119,25 @@
}

/**
* @param int $idProduct
* @param int $productId
*
* @return void
* @throws \JsonException
*/
private function saveProductSettings(int $idProduct): void
private function saveProductSettings(int $productId): void
{
$postValues = Tools::getAllValues();
$name = Pdk::getAppInfo()->name;

$productSettings = array_filter($postValues, static function ($key) use ($name) {
$name = Pdk::getAppInfo()->name;
$productSettings = array_filter(Tools::getAllValues(), static function ($key) use ($name) {
return Str::startsWith((string) $key, $name);
}, ARRAY_FILTER_USE_KEY);

if (empty($productSettings)) {
if (empty($productSettings) || $this->isAlreadySaved($productId, $productSettings)) {
return;
}

$requestBody = [];

foreach ($productSettings as $key => $value) {
$trimmedKey = Arr::last(explode('-', $key));
$requestBody[$trimmedKey] = $value;
}
$request = $this->createRequest($productId, $productSettings);

$request = new Request(
[
'action' => PdkBackendActions::UPDATE_PRODUCT_SETTINGS,
'productId' => $idProduct,
],
[],
[],
[],
[],
[],
json_encode(['data' => ['product_settings' => $requestBody]])
);
Logger::debug(sprintf('Saving product settings for product %d', $productId), ['settings' => $productSettings]);

Actions::execute($request);

Expand Down