Skip to content

About

Fix for a PrestaShop core bug (1.6 to 9.x): custom feature values shared between products are deleted for all of them when saving any one product

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PrestaShop — Shared custom feature values get deleted for all products

TL;DR — In PrestaShop, saving one product can silently wipe a feature from other products nobody touched. This repo contains a drop-in override that stops it, plus patches for the core.

Affected versions: 1.6 · 1.7 · 8.x · 9.x — verified identical in 1.6.1.24, 1.7.8.11, 8.2.7 and 9.1.1.


The bug

Two behaviours in classes/Product.php combine into silent, permanent data loss:

1. deleteFeatures() deletes custom values that other products still use

Called on every product save:

foreach ($features as $tab) {
    // Delete product custom features
    if ($tab['custom']) {
        Db::getInstance()->execute('DELETE FROM `ps_feature_value` WHERE `id_feature_value` = ' . (int) $tab['id_feature_value']);
        Db::getInstance()->execute('DELETE FROM `ps_feature_value_lang` WHERE `id_feature_value` = ' . (int) $tab['id_feature_value']);
    }
}

The DELETE is global. It does not check whether other products reference that value. Those products keep their ps_feature_product row pointing at an id_feature_value that no longer exists — the feature renders blank on the front office.

2. The webservice lets values be shared, and hides the flag that would prevent it

setWsProductFeatures() inserts the received id_feature_value as-is, with no validation:

foreach ($product_features as $product_feature) {
    $this->addFeaturesToDB($product_feature['id'], $product_feature['id_feature_value']);
}

And getWsProductFeatures() strips the custom flag from the API response:

unset(
    $rows[$keyrow]['id_product'],
    $rows[$keyrow]['custom']      // ← the "is this custom?" indicator is removed
);

An ERP/PIM connector reading one product's features and writing them to another has no way of knowing it is reusing someone else's custom value. It does the reasonable thing — resend the id PrestaShop just gave it — and causes the damage anyway.

This is a core API defect, not an integration bug.


Why it goes unnoticed for years

ps_feature_product has no ObjectModel, so PrestaShop fires no hook on these writes. No module can observe them without database triggers. The edited product looks perfect; the damage lands elsewhere and surfaces only when a customer complains.


Is my shop affected?

-- 1. Damage already done: associations pointing to a deleted value
SELECT fp.id_product, fp.id_feature, fp.id_feature_value
FROM ps_feature_product fp
LEFT JOIN ps_feature_value fv ON fv.id_feature_value = fp.id_feature_value
WHERE fv.id_feature_value IS NULL;

-- 2. Ticking bombs: custom values shared by more than one product
SELECT fv.id_feature_value, COUNT(DISTINCT fp.id_product) AS products
FROM ps_feature_value fv
INNER JOIN ps_feature_product fp ON fp.id_feature_value = fv.id_feature_value
WHERE fv.custom = 1
GROUP BY fv.id_feature_value
HAVING products > 1
ORDER BY products DESC;

-- 3. Quick health ratio (> 1.5 means values pile up as orphans)
SELECT
  (SELECT COUNT(*) FROM ps_feature_value)   AS values_count,
  (SELECT COUNT(*) FROM ps_feature_product) AS associations,
  ROUND((SELECT COUNT(*) FROM ps_feature_value) /
        (SELECT COUNT(*) FROM ps_feature_product), 2) AS ratio;

Query 1 returning rows means products are showing blank features right now.


Install the fix

Option A — Override (recommended, survives core updates)

  1. Copy override/classes/Product.php to your shop's override/classes/Product.php
  2. Delete var/cache/prod/class_index.php (and the dev one if present)
  3. Clear the cache in Advanced Parameters → Performance

If you already have an override/classes/Product.php, do not replace it — copy the four methods into your existing class.

Option B — Patch the core

Only if you are preparing a PR. Lost on update.

php patches/apply-patches.php /path/to/PrestaShop/classes/Product.php --dry-run
php patches/apply-patches.php /path/to/PrestaShop/classes/Product.php
php -l /path/to/PrestaShop/classes/Product.php

Matches by code pattern, not line number, so it works across 1.7.x / 8.x / 9.x. Creates a timestamped backup and is idempotent.


What the fix does

# Method Change
1 deleteFeatures() A custom value is deleted only if no other product uses it
2 setWsProductFeatures() A custom value already owned by another product is cloned, not shared
3 getWsProductFeatures() Stops stripping the custom flag
4 $webserviceParameters Declares custom as an exposed field

Catalogue values (custom = 0) are untouched — sharing those between products is correct and intended.

Change #1 alone stops the data loss. It is backwards compatible: when the product is the sole owner, the value is still deleted as before.


Limitations

  • Fixes new damage only. It does not repair what is already broken.
  • The text of already-deleted values is unrecoverable — deleteFeatures() removed it from ps_feature_value_lang too. It must be re-entered from your ERP or restored from a backup.
  • Does not cover the Symfony layer. ProductFeatureValueUpdater is a service, not a legacy class, so the classic override system cannot replace it — it would need decoration via services.yml. The legacy path, which is what shops hit in practice, is covered.

Validation performed

Check Result
php -l on the override (7.4 / 8.1 / 8.3) No syntax errors
Patches applied to real PS 9.1.1 Product.php 4/4 applied
Syntax of the patched file (7.4 / 8.1 / 8.3) No syntax errors
Re-running the patcher (idempotency) 4/4 skipped, nothing duplicated

Not tested against a live database. Back up and test on staging before production.


Real-world impact

Measured on a production shop (PrestaShop 8.2.1) synchronised from an external ERP via the webservice:

Associations sharing a custom value 1,704
Associations already pointing to a deleted value 824
Feature values vs. real associations 16,978 / 6,364 (ratio 2.67)

While the ERP account had write permission, product updates arrived in bursts — up to 32 products sharing the exact same date_upd down to the second, 93 within a single minute. After the permission was revoked, the bursts stopped and so did new damage.


Upstream

Reported to the PrestaShop project: PrestaShop/PrestaShop#42347

Full issue text with reproduction steps and proposed patches: docs/GITHUB_ISSUE.md.

If you hit this bug, please comment on issue #42347 — the more affected shops are documented, the sooner it gets fixed in core.


En español

Si en tu PrestaShop desaparecen características personalizadas de productos que nadie ha editado, este repositorio contiene la corrección.

La causa: varios productos comparten el mismo valor de característica personalizado. Al guardar uno, Product::deleteFeatures() borra ese valor de la base de datos sin comprobar si otros lo usan. Los demás quedan apuntando a un valor inexistente y su característica sale vacía.

Cómo llegan a compartirse: por el webservice. El API acepta que varios productos usen el mismo valor personalizado y, además, oculta el campo custom en sus respuestas, así que la integración externa no puede saber que está reutilizando un valor ajeno.

La solución: copia override/classes/Product.php a tu tienda, borra var/cache/prod/class_index.php y vacía la caché.

Ojo: esto evita daños nuevos, no repara los existentes. Y el texto de los valores ya borrados es irrecuperable.


License

AFL-3.0 — same license as PrestaShop modules.

Ecom Experts · ecomyseo@gmail.com

About

Fix for a PrestaShop core bug (1.6 to 9.x): custom feature values shared between products are deleted for all of them when saving any one product

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages