Prestashop has a great importer that allows you to import products directly from a CSV file. The problem with the default importer is that it always overwrites the product names. That means that if you import your products and then customize the product names you will lose that customization if you try to re-import the products again (i.e. to only update product prices).

New parameter added

What we want to achieve with this modification:

  • Initially import all the store products from a CSV file.
  • Be able to frequently update product prices through the same CSV importer without losing changes done to the product name.
  • Detects new products in the CSV and add them to the database.
  • Optionally allow the user to rewrite all products names if desired.

1Required changes

Required changes summary

  • We are going to add a new parameter called «keep_names» to the importer. This will allow the user to choose if wants to overwrite or keep product names. The default behaviour will be the same that by default uses Prestashop (overwir.
  • Modify the import process to inherit the product name when «keep_names» is selected or the product name is empty.

The rest of the article explains the changes done to the files. If you don’t want to go on reading you can:

download the full code

 

2Add a parameter to the Prestashop import form

Basically we will override the default admin files:

admin/themes/default/template/controllers/import/helpers/form/form.tpl
admin/themes/default/template/controllers/import/helpers/view/view.tpl

Replace «admin» with the name of your admin folder.

To override them we have copied them into:

override/controllers/admin/templates/import/helpers/form/form.tpl
override/controllers/admin/templates/import/helpers/view/view.tpl

To keep this article as clean as possible while I have also uploaded the full code to a gist. This way you can fastly see the code.

In the form.tpl file we have added the new parameter with:

1
2
3
4
<label for="keep_names" class="clear" style="display: none;">{l s='Keep product names?'}</label>
<div class="margin-form">
    <input name="keep_names" id="keep_names" style="margin-top: 6px; display: none;" type="checkbox">
</div>

We also added the JS logic to hide the field when not importing products:

1
2
3
4
5
6
7
8
9
10
if ($("#entity > option:selected").val() == 1)
{
    $("label[for=match_ref],#match_ref").show();
    $("label[for=keep_names],#keep_names").show();
}
else
{
    $("label[for=match_ref],#match_ref").hide();
    $("label[for=keep_names],#keep_names").hide();
}

In the view.tpl we have added the code to receive and propagate the keep_names as a hidden field:

1
2
3
{if $fields_value.keep_names}
    <input name="keep_names" value="1" type="hidden">
{/if}

3Override import controller

This is the file when the import occurres. What we are going to do is to copy the methods that we need to override from:

controllers/admin/AdminImportController.php

to the override class at:

override/controllers/admin/AdminImportController.php

As we only need to override two methods of the original file renderView() and productImport() we are not going to copy the full file. This way we can use the core methods if some bugs affect them.

You can see the final AdminImportController.php file at the gist.

In the renderView() method we have received the keep_names receive value to propagate it to the view:

1
'keep_names' => Tools::getValue('keep_names'),

We also hade to modify the call to the parent method as we are going to call now the Grandparent (AdminController) renderView method:

1
return AdminController::renderView();

In the productImport() method we will replace this code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$field_error = $product->validateFields(UNFRIENDLY_ERROR, true);
$lang_field_error = $product->validateFieldsLang(UNFRIENDLY_ERROR, true);
if ($field_error === true && $lang_field_error === true)
{
    // check quantity
    if ($product->quantity == null)
        $product->quantity = 0;
    // If match ref is specified && ref product && ref product already in base, trying to update
    if (Tools::getValue('match_ref') == 1 && $product->reference && $product->existsRefInDatabase($product->reference))
    {
        $datas = Db::getInstance()->getRow('
            SELECT product_shop.`date_add`, p.`id_product`
            FROM `'._DB_PREFIX_.'product` p
            '.Shop::addSqlAssociation('product', 'p').'
            WHERE p.`reference` = "'.$product->reference.'"
        ');
        $product->id = (int)$datas['id_product'];
        $product->date_add = pSQL($datas['date_add']);
        $res = $product->update();
    } // Else If id product && id product already in base, trying to update
    else if ($product->id && Product::existsInDatabase((int)$product->id, 'product'))
    {
        $datas = Db::getInstance()->getRow('
            SELECT product_shop.`date_add`
            FROM `'._DB_PREFIX_.'product` p
            '.Shop::addSqlAssociation('product', 'p').'
            WHERE p.`id_product` = '.(int)$product->id);
        $product->date_add = pSQL($datas['date_add']);
        $res = $product->update();
    }
    // If no id_product or update failed
    if (!$res)
    {
        if (isset($product->date_add) && $product->date_add != '')
            $res = $product->add(false);
        else
            $res = $product->add();
    }
}

with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
$preloadProduct     = false;
$preloadById        = ($product->id && Product::existsInDatabase((int)$product->id, 'product'));
$preloadByreference = (Tools::getValue('match_ref') == 1 && $product->reference && $product->existsRefInDatabase($product->reference));
if ($preloadByreference || $preloadById)
{
    $preloadProduct = true;
    $sql = 'SELECT
                product_shop.`date_add`, p.`id_product`, pl.`name`
            FROM
                `'._DB_PREFIX_.'product` p
            LEFT JOIN
                '._DB_PREFIX_.'product_lang pl ON (pl.id_product = p.id_product AND pl.`id_lang` = ' . (int) $default_language_id . ')
            ' . Shop::addSqlAssociation('product', 'p');
    if ($preloadByreference)
    {
        $sql .= 'WHERE p.`reference` = "' . $product->reference . '" ';
    }
    if ($preloadById)
    {
        $sql .= 'WHERE p.`id_product` = ' . (int) $product->id;
    }
    $datas = Db::getInstance()->getRow($sql);
    $product->id = (int)$datas['id_product'];
    $product->date_add = pSQL($datas['date_add']);
    // Check quantity
    if ($product->quantity == null)
    {
        $product->quantity = 0;
    }
    // Inherit product name if required or not set
    if (Tools::getValue('keep_names') || !isset($product->name) || empty($product->name))
    {
        $product->name = pSQL($datas['name']);
    }
}
$field_error = $product->validateFields(UNFRIENDLY_ERROR, true);
$lang_field_error = $product->validateFieldsLang(UNFRIENDLY_ERROR, true);
if ($field_error === true && $lang_field_error === true)
{
    if ($preloadProduct)
    {
        $res = $product->update();
    }
    // If no id_product or update failed
    if (!$res)
    {
        if (isset($product->date_add) && $product->date_add != '')
            $res = $product->add(false);
        else
            $res = $product->add();
    }
}

The code does a preload of the products when needed. This way products can inherit names when required.