Jump to content

[Tutorial][PS 1.7+] Adding prestashop products programmatically


 Share

Recommended Posts

Compatible with version: Prestashop 1.7+
Tutorial last update: 22/07/2021

Special thanks to following contributor(s): @mfdenis, @Inter Svetainė, @seblyon, @Oelita

A while ago i received the question on how to add products programmatically using a PHP file. After looking around in the Prestashop files and searching the worldwide-web  i have come up with a working script. I decided to share this code with the Prestashop community. If you have any questions our improvements don't hesitate to ask or say them.

The file we are creating can be easily run with a cron-job and is able to fetch data from a page using JSON trough an Ajax call.

Lets get started!

  1. The first step is creating a new PHP file in your admin folder.
    Create a new PHP file in the folder yourstore/back-office/FILENAME.php. In our small tutorial we use the filename importmyproduct.php
     
  2. Setting up connection with the config file
    First we start to check if the global variable _PS_ADMIN_DIR_ is defined. After we check that _PS_ADMIN_DIR_ is defined we will make connection with the prestashop config.inc.php file.
    So on top of our page we will add following code.
    <?php
        // Check if _PS_ADMIN_DIR_ is defined
        if (!defined('_PS_ADMIN_DIR_')) {
            // if _PS_ADMIN_DIR_ is not defined, define.
            define('_PS_ADMIN_DIR_', getcwd());
        }
        // Setup connection with config.inc.php (required for database connection, ...)
        include(_PS_ADMIN_DIR_.'/../config/config.inc.php');

     

  3. Adding access security and check if page is setup correctly.
    Since we do not want that our importmyproduct.php can be easily accessed by strangers we will add a security feature. We will also display our first echo to check if everything is working correctly. After we have setup our secure-key we can visit our PHP page by going to yourstore.com/yourbackoffice/importmyproduct.php?secure_key=yoursecuretoken
        $secure_key = 'ed3fa1ce558e1c2528cfbaa3f99403';
    
        // Check if the client use the correct secure_key, url to use: www.yourstore.com/yourbackoffice/importmyproduct.php?secure_key=ed3fa1ce558e1c2528cfbaa3f99403
        if(!Tools::getValue('secure_key') || Tools::getValue('secure_key') != $secure_key) {
            // If the secure_key is not set our not equal the php page will stop running.
            die('UNAUTHORIZED: We dont want you on this page!');
        }
        echo 'Welcome, the secure_key you have used is correct. Now we can start adding product programmatically ... <br>';
  4. Our product import function
    In this next step we will add our product import function which can be called later on to import a new product.
        function addProduct($ean13, $ref, $name, $qty, $text, $features, $price, $imgUrl, $catDef, $catAll) {
            $product = new Product();              // Create new product in prestashop
            $product->ean13 = $ean13;
            $product->reference = $ref;
            $product->name = createMultiLangField(utf8_encode($name));
            $product->description = htmlspecialchars($text);
            $product->id_category_default = $catDef;
            $product->redirect_type = '301';
            $product->price = number_format($price, 6, '.', '');
            $product->minimal_quantity = 1;
            $product->show_price = 1;
            $product->on_sale = 0;
            $product->online_only = 0;
            $product->meta_description = '';
            $product->link_rewrite = createMultiLangField(Tools::str2url($name)); // Contribution credits: mfdenis
            $product->add();                        // Submit new product
            StockAvailable::setQuantity($product->id, null, $qty); // id_product, id_product_attribute, quantity
            $product->addToCategories($catAll);     // After product is submitted insert all categories
    
            // Insert "feature name" and "feature value"
            if (is_array($features)) {
                foreach ($features as $feature) {
                    $attributeName = $feature['name'];
                    $attributeValue = $feature['value'];
    
                    // 1. Check if 'feature name' exist already in database
                    $FeatureNameId = Db::getInstance()->getValue('SELECT id_feature FROM ' . _DB_PREFIX_ . 'feature_lang WHERE name = "' . pSQL($attributeName) . '"');
                    // If 'feature name' does not exist, insert new.
                    if (empty($FeatureNameId)) {
                        Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'feature` (`id_feature`,`position`) VALUES (0, 0)');
                        $FeatureNameId = Db::getInstance()->Insert_ID(); // Get id of "feature name" for insert in product
                        Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'feature_shop` (`id_feature`,`id_shop`) VALUES (' . $FeatureNameId . ', 1)');
                        Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'feature_lang` (`id_feature`,`id_lang`, `name`) VALUES (' . $FeatureNameId . ', ' . Context::getContext()->language->id . ', "' . pSQL($attributeName) . '")');
                    }
    
                    // 1. Check if 'feature value name' exist already in database
                    $FeatureValueId = Db::getInstance()->getValue('SELECT id_feature_value FROM ' . _DB_PREFIX_ . 'feature_value WHERE id_feature_value IN (SELECT id_feature_value FROM `' . _DB_PREFIX_ . 'feature_value_lang` WHERE value = "' . pSQL($attributeValue) . '") AND id_feature = ' . $FeatureNameId);
                    // If 'feature value name' does not exist, insert new.
                    if (empty($FeatureValueId)) {
                        Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'feature_value` (`id_feature_value`,`id_feature`,`custom`) VALUES (0, ' . $FeatureNameId . ', 0)');
                        $FeatureValueId = Db::getInstance()->Insert_ID();
                        Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'feature_value_lang` (`id_feature_value`,`id_lang`,`value`) VALUES (' . $FeatureValueId . ', ' . Context::getContext()->language->id . ', "' . pSQL($attributeValue) . '")');
                    }
                    Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'feature_product` (`id_feature`, `id_product`, `id_feature_value`) VALUES (' . $FeatureNameId . ', ' . $product->id . ', ' . $FeatureValueId . ')');
                }
            }
    
            // add product image.
            $shops = Shop::getShops(true, null, true);
            $image = new Image();
            $image->id_product = $product->id;
            $image->position = Image::getHighestPosition($product->id) + 1;
            $image->cover = true;
            if (($image->validateFields(false, true)) === true && ($image->validateFieldsLang(false, true)) === true && $image->add()) {
                $image->associateTo($shops);
                if (!uploadImage($product->id, $image->id, $imgUrl)) {
                    $image->delete();
                }
            }
            echo 'Product added successfully (ID: ' . $product->id . ')';
        }

     

  5. We also will need a function to handle our image upload, add below following code:
    This function will insert the image into your store and assign it to your created product.
        function uploadImage($id_entity, $id_image = null, $imgUrl) {
            $tmpfile = tempnam(_PS_TMP_IMG_DIR_, 'ps_import');
            $watermark_types = explode(',', Configuration::get('WATERMARK_TYPES'));
            $image_obj = new Image((int)$id_image);
            $path = $image_obj->getPathForCreation();
            $imgUrl = str_replace(' ', '%20', trim($imgUrl));
            // Evaluate the memory required to resize the image: if it's too big we can't resize it.
            if (!ImageManager::checkImageMemoryLimit($imgUrl)) {
                return false;
            }
            if (@copy($imgUrl, $tmpfile)) {
                ImageManager::resize($tmpfile, $path . '.jpg');
                $images_types = ImageType::getImagesTypes('products');
                foreach ($images_types as $image_type) {
                    ImageManager::resize($tmpfile, $path . '-' . stripslashes($image_type['name']) . '.jpg', $image_type['width'], $image_type['height']);
                    if (in_array($image_type['id_image_type'], $watermark_types)) {
                    Hook::exec('actionWatermark', array('id_image' => $id_image, 'id_product' => $id_entity));
                    }
                }
            } else {
                unlink($tmpfile);
                return false;
            }
            unlink($tmpfile);
            return true;
        }

     

  6. Multi language support.
    As you can see in the above code we call the function createMultiLangField() This function make sure we add our product to all available languages on your store. Add the following function to your script.
        function createMultiLangField($field) {
            $res = array();
            foreach (Language::getIDs(false) as $id_lang) {
                $res[$id_lang] = $field;
            }
            return $res;
        }

     

  7. The last step
    And after this all we come at our last step, adding our first programmatically created product.  This can be done by calling our function addProduct and pass the necessary values.
        addProduct(
            '1234567891234',                         // Product EAN13
            'Tutorial by Crezzur',                         // Product reference
            'Crezzur',                               // Product name
            5,                                       // Product quantity
            'Code by Crezzur (https://crezzur.com)', // Product description
            array(                                  // Product features (array)
                array("name" => "Color", "value" => "Red"),
                array("name" => "Height", "value" => "200cm"),
           ),
            '999.95',                                // Product price
            'https://crezzur.com/img/crezzur-logo-1544601440.jpg',       // Product image
            1,                                       // Product default category
            array(1, 5)                              // All categorys for product (array)
       );

 

Using the code above will allow you to mass import products using a Ajax call (JSON) our using a CSV file. We do not add this code since the variables for each web service will be different.

Generate Products Packs In PHP


For those searching to import product packs with my code can find more information about this in the topic of @jmauclair

 

A like and/or a thank you for my work is much appreciated 

 

Edited by Crezzur (see edit history)
  • Like 6
  • Thanks 4

Share this post


Link to post
Share on other sites

  • 7 months later...
  • 3 weeks later...
Posted (edited)

if you use multiple languages, you'll encounter problems displaying images unless you add this line to addProduct() function:

$product->link_rewrite = createMultiLangField(Tools::str2url($name));

along with all other $product-> lines.

without it images are nicely uploaded and even visible from backend product list, but links to them are corrupted on frontend if seo-friendly urls setting is on. Cost me half of day to figure out. result is hiding in table "_product_lang" column "link_rewrite" and used in urls generation (both for product url and images url), so very unobvious problem.

Tools::str2url used here is prestashop built-in tool to convert any language texts to urls.

(current PS 1.7.7.4, not sure about earlier or later versions)

in all other means - perfect tutorial, very helpful, THANK YOU!

Edited by mfdenis (see edit history)
  • Thanks 1

Share this post


Link to post
Share on other sites

1 hour ago, mfdenis said:

if you use multiple languages, you'll encounter problems displaying images unless you add this line to addProduct() function:

$product->link_rewrite = createMultiLangField(Tools::str2url($name));

along with all other $product-> lines.

without it images are nicely uploaded and even visible from backend product list, but links to them are corrupted on frontend if seo-friendly urls setting is on. Cost me half of day to figure out. result is hiding in table "_product_lang" column "link_rewrite" and used in urls generation (both for product url and images url), so very unobvious problem.

Tools::str2url used here is prestashop built-in tool to convert any language texts to urls.

(current PS 1.7.7.4, not sure about earlier or later versions)

in all other means - perfect tutorial, very helpful, THANK YOU!

Hello @mfdenis

Thank you for your contribution!

I will look into you suggested update and add it to the code if it is working correctly.

I was not aware of this issue with SEO.

Kind regards,

Jaimy

Share this post


Link to post
Share on other sites

Another minor change I'd offer is 

$product->price = round($price, 6);

instead of simple $product->price = $price;

because this is price tax excluded and most likely it will be float, which doesn't pass prestashop's FieldValue check. (db value expects decimal with 6 figures after coma, while float in php has much longer 'tail')

(probably this depends on store's settings - how it calculates final price - if it stores price tax included or tax excluded in DB. In my case it caused script crash on non-integer values, so I've applied this fix.)

yet again - current PS 1.7.7.4, not sure about earlier or later versions

  • Thanks 1

Share this post


Link to post
Share on other sites

8 minutes ago, mfdenis said:

Another minor change I'd offer is 

$product->price = round($price, 6);

instead of simple $product->price = $price;

because this is price tax excluded and most likely it will be float, which doesn't pass prestashop's FieldValue check. (db value expects decimal with 6 figures after coma, while float in php has much longer 'tail')

(probably this depends on store's settings - how it calculates final price - if it stores price tax included or tax excluded in DB. In my case it caused script crash on non-integer values, so I've applied this fix.)

yet again - current PS 1.7.7.4, not sure about earlier or later versions

Hey @mfdenis

thank you for your contributions!

For the $product-price part i prefer using the following:

$product->price = number_format($price_incl, 6, '.', '');

When using my auto-importer i have noticed that some company's uses a comma instead of a point in there prices, this will then result in a import failure.

I did not update this code in this tutorial, so i added this today, thank you for pointing this out!

Share this post


Link to post
Share on other sites

3 hours ago, Crezzur said:

$product->price = number_format($price_incl, 6, '.', '');

yeah! that's even more fool-proof! Thank you!

Share this post


Link to post
Share on other sites

  • 3 weeks later...
3 minutes ago, Inter Svetainė said:

Line

$product->price = number_format($price_incl, 6, '.', '');

uses $price_incl, but in arguments is $price.

You are correct! Its a copy paste mistake from my side.

Thank for points this typo out!

  • Thanks 1

Share this post


Link to post
Share on other sites

one more copy-paste mistake is in this part:

// 1. Check if 'feature value name' exist already in database
                $FeatureValueId = Db::getInstance()->getValue('SELECT id_feature_value FROM webshop_feature_value WHERE id_feature_value IN (SELECT id_feature_value FROM webshop_feature_value_lang WHERE value = "' . pSQL($attributeValue) . '") AND id_feature = ' . $FeatureNameId);
           

where all "webshop_" parts should be replaced with ' . _DB_PREFIX_ . ' I suppose.

  • Thanks 1

Share this post


Link to post
Share on other sites

12 hours ago, mfdenis said:

one more copy-paste mistake is in this part:

// 1. Check if 'feature value name' exist already in database
                $FeatureValueId = Db::getInstance()->getValue('SELECT id_feature_value FROM webshop_feature_value WHERE id_feature_value IN (SELECT id_feature_value FROM webshop_feature_value_lang WHERE value = "' . pSQL($attributeValue) . '") AND id_feature = ' . $FeatureNameId);
           

where all "webshop_" parts should be replaced with ' . _DB_PREFIX_ . ' I suppose.

You are correct! Thank for points this out 😉

Share this post


Link to post
Share on other sites

There is no limit to upgrade Your already perfect code :)
for example add (int)
into  this line

$image_obj = new Image($id_image);

and it will look like

$image_obj = new Image((int)$id_image);

to be sure that id of image will be for sure an integer ;) 

  • Thanks 1

Share this post


Link to post
Share on other sites

4 hours ago, Inter Svetainė said:

There is no limit to upgrade Your already perfect code :)
for example add (int)
into  this line

$image_obj = new Image($id_image);

and it will look like

$image_obj = new Image((int)$id_image);

to be sure that id of image will be for sure an integer ;) 

Thank you for your contribution, i have added it to the code.

Have a nice day!

  • Thanks 1

Share this post


Link to post
Share on other sites

8 hours ago, Crezzur said:

You are correct! Thank for points this out 😉

Hey @Crezzur, there are TWO mentions of db tables in that long SQL query, I noticed you've fixed only one in code, second is still there 😅

(nonetheless one of the most useful tutorials over whole forum, just can't stop praise it!)

Share this post


Link to post
Share on other sites

Posted (edited)
16 minutes ago, mfdenis said:

Hey @Crezzur, there are TWO mentions of db tables in that long SQL query, I noticed you've fixed only one in code, second is still there 😅

(nonetheless one of the most useful tutorials over whole forum, just can't stop praise it!)

My mistake! changed now.

When I started creating a script for programmatically adding products, I quickly found that little has been written on this subject. You'll find little bits of code all over the prestashop forums that often also contained errors. After a month of searching and testing I finally had a working script. It therefore seemed fairest to me to share it with the community where I was able to find parts of the script.

Then I wrote a module (https://crezzur.com/en/prestashop-modules/16-crezzur-dst) with this script that works quickly and without errors. The script has already been tested with an import of more than 10,000 products and has more than proven its effectiveness.

I am glad to hear that you enjoy it as much as i do 😉

Edited by Crezzur (see edit history)

Share this post


Link to post
Share on other sites

Update 14/06/2021: Added utf8_encode() to prevent characters like ö, à, ï to be replaced by question marks.

OLD CODE:

$product->name = createMultiLangField($name);

NEW CODE:

$product->name = createMultiLangField(utf8_encode($name));

 

Share this post


Link to post
Share on other sites

Hi guys, 

 

First, thank you for your really cool scrypt !

1 ) here is a solution for a problem , i encounter on stock. (after a lot of research)

this line is not working on my prestashop (1.7.7), stock is always 0 :

$product->quantity = $qty; 

you need to set stock like this after adding product

StockAvailable::setQuantity($product->id, null, $qty);

 

2) i have a problem with image

upload works fine, in Admin the image is set cover no problem.

But on front , image is not displaying (empty/broken)

when i go on my product admin file and validate it .... front product file is working.

 

Have you any idear what is missing ? 

 

thank you !! 

 

  • Thanks 1

Share this post


Link to post
Share on other sites

45 minutes ago, seblyon said:

Hi guys, 

 i have a problem with image : <
upload works fine, in Admin the image is set cover no problem.
But on front , image is not displaying (empty/broken)
when i go on my product admin file and validate it .... front product file is working.

Have you any idear what is missing ?

 

First of all, thank you for your contribution! I have checked and confirmed that this was an issue. We have updated our tutorial with your suggestions which is working fine.

For you image problems lets try to trouble shout the issue. Normally there are no issues with the importer (checked and still working)

Can you add this code:

    $img = $product->getCover($product->id);
    echo 'Product IMAGE: ' . Context::getContext()->link->getImageLink($product->link_rewrite[1], $img['id_image']);

under the line:

echo 'Product added successfully (ID: ' . $product->id . ')';

 

Now you will receive the image url, does this url work correctly?

Share this post


Link to post
Share on other sites

  • 2 weeks later...

Hi,
I met what I think is an error, in the  features code :

// 1. Check if 'feature name' exist already in database
$FeatureNameId = Db::getInstance()->getValue('SELECT id_feature FROM ' . _DB_PREFIX_ . 'feature_lang WHERE name = "' . pSQL($attributeName) . '"');
// If 'feature name' does not exist, insert new.
if (empty($getFeatureName)) {

As it is, it duplicates the existing features.

That should be  (change on last line) :
 

// 1. Check if 'feature name' exist already in database
$FeatureNameId = Db::getInstance()->getValue('SELECT id_feature FROM ' . _DB_PREFIX_ . 'feature_lang WHERE name = "' . pSQL($attributeName) . '"');
// If 'feature name' does not exist, insert new.
if (empty($FeatureNameId )) {

Thanks a lot for your code !

  • Thanks 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...

Important Information

Cookies ensure the smooth running of our services. Using these, you accept the use of cookies. Learn More