Simao Henriques Posted February 12 Share Posted February 12 (edited) Hi there, Does anyone know what is the way to change a product carrier programmatically ? Here is what I tried, with no avail: - tried using $product->setCarriers(), but it didn't seem to work, I am unsure it even exists on PS8. (maybe someone can confirm) - tried directly doing a sql querry to ps_product_carrier, but not only did I have some trouble making it work, there was also the following problem: even if it is successful (ex: inserting manualy trough database manager or sql), when I oped that same product in edit mode, the entry I just added gets deleted (so apparently that table is being managed somewhere). And I couldn't find the solution through searching online. Any help would be apreciated, thanks. Simão Henriques[SOLVED] So the problem I was having is that I was using the hook actionProductSave but that was triggering before the save and so it would get overwritten by prestashop saving process. I had to change to actionAdminProductsControllerSaveAfter so that it would trigger after the prestashop modifications. Here is how to change carriers programatically if anyone is looking for it: (just an example bellow, not a functioning module/code) <?php class changeCarriersModule extends Module { // Make sure to install the correct hook public function install() { // ... return parent::install() && // ... $this->registerHook('actionAdminProductsControllerSaveAfter'); } public function hookActionAdminProductsControllerSaveAfter($params) { $idProduct = $params['return']->id; if (!$idProduct) { return; } $product = new Product($idProduct); if (!Validate::isLoadedObject($product)) { return; } $carrierIdsArray = [2, 3, 5]; //(just an example) You can either initiate this array hardcoded or get it // from a configuration field that you may add // Build the array with references instead of Ids $carrierReferenceIdsArray = []; foreach ($carrierIdsArray as $carrierId) { $carrier = new Carrier((int)$carrierId); if (Validate::isLoadedObject($carrier)) { $carrierReferenceIdsArray[] = (int)$carrier->id_reference; } } // Finally, use setCarriers() (from product.php) to set carriers // Make sure to pass the REFERENCE array and not the Id one $product->setCarriers($carrierReferenceIdsArray); } Edited February 19 by Simao Henriques solved (see edit history) Link to comment Share on other sites More sharing options...
ps8modules Posted February 16 Share Posted February 16 (edited) Hi. setCarriers is an array field and all necessary parameters need to be passed. $carriersId = [1, 3, 5]; // id reference carriers $idShop = $this->context->shop->id; // id shop $idLang = $this->context->language->id; // id language $idProduct = 1; // id product $data = []; foreach ( $carriersId as $c ) { $data[] = (int) $c; } if ($data) { $product = new Product ((int) $idProduct, false, $idLang, $idShop, $this->context); $product->setCarriers($data); } Edited February 19 by ps8modules (see edit history) Link to comment Share on other sites More sharing options...
Simao Henriques Posted February 17 Author Share Posted February 17 Thanks for the input ! I tried that and it didn't work, so I went looking for that method (I don't know why I didn't before, it was obvious that if anything, it would be on product class), by the implementation it seems that it receives only the carrier id list (has to be references), but maybe there is a newer version somewhere (?) I tried using that implementation with just the carrier array passed as paranerer but I am now facing this problem: I am using the hook hookActionProductSave but it creates a loop (it calls itself). So when I run it, the website just timeouts for a while. So I tried adding a control variable to make sure an instance of the module doesn't call more hooks of that type. Here is the weird part, without the control variable (aka, in the infinite loop that eventually crashes the website), the carriers actually do work (as in they change to what I wanted), while if I make it run only once it doesn't really work. My theory is that that hook is getting overwritten normaly by prestashop's save procedure, and that same procedure is not completing correctly when the flow enters a loop, so it never gets to overwrite. I can't make other hooks work, I tried hookActionAdminProductsControllerSaveAfter but prestashop didn't even recognize it, tried hookActionObjectUpdateAfter but not only was the loop control harder to manage, it was also calling itself in other types of object updates. Here is the shortened code I am using: (its just the important lines relating to setting the carriers), I omited the logic behind it and the null/error checks). public function hookActionProductSave($params) { $idProduct = $params['id_product']; $product = new Product($idProduct); (...) $product->setCarriers($carrierReferenceArray); $product->update(); // also tried with ->save(), didn't find much difference } And here is what how it looks like with the loop control: public function hookActionProductSave($params) { static $isRunning = false; if ($isRunning == true) { return; } $isRunning = true; $idProduct = $params['id_product']; $product = new Product($idProduct); ... $product->setCarriers($carrierReferenceArray); $product->update(); // also tried with ->save(), didn't find much difference $isRunning = false; } So, with the second it only runs once, everything according to logs goes fine, but it doesn't change anything. With the first it enters a loop, and prestashop starts returning timeouts for a while, when it goes back to normal, the outcome is what I wanted (carriers change correctly on the product). Is there any hook I can use that would work (maybe activate after saving but having a way to not enter a loop) ? Thanks in advance. Link to comment Share on other sites More sharing options...
ps8modules Posted February 17 Share Posted February 17 Hi. If you look in ./classes/Product.php and find the setCarriers function. You no longer need to call $product->update() or $product->save() in the mentioned hooks. Link to comment Share on other sites More sharing options...
Simao Henriques Posted February 18 Author Share Posted February 18 Oh thanks, don't know how I missed that. You are right, it does prevent the infinite loop, so there is no need for the whole control block. On the other hand, it is still not changing the carriers, still getting overwritten by prestashop somehow 😔 Link to comment Share on other sites More sharing options...
ps8modules Posted February 18 Share Posted February 18 (edited) And how do you control it? Do you have a product edit open and then run some script and then check in the open product? Describe everything you do so that your chosen carrier does not change. Or better yet, put the whole code here. You are making a mistake somewhere and we are unable to guess where. Edited February 18 by ps8modules (see edit history) Link to comment Share on other sites More sharing options...
Simao Henriques Posted February 18 Author Share Posted February 18 The code I posted previously was pretty much all I do, I just omited the code related to setting categories automatically but that is working fine and doesn't set any extra hook (or at least shouldn't). On the bright side, I have made some progress while trying to fix this, and after some debugging it is now working (the carrier part) but I would like some recomendations on how to reach the root of the problem.What was happening: The setCarriers() method was being called not once, but actually twice. The second time having null parameters and such the carriers were set correctly but were being overwritten in a following call. Here were the debug statements on the prestashop logs: setcarriers, product.php: carrierlist: Array ( [0] => 30 [1] => 31 [2] => 33 ) Product modification setcarriers, product.php: carrierlist: Array ( ) The fix (def not ideal, please help) Added the following to start of setCarriers(): if (empty($carrier_list)) { Logger::addLog("empty, returning", 2); return; } Now, that solves the problem, but I have no clue why it was being called twice, specially with the second call being empty. And more importantly, the fix was changing the core file, which I would like to prevent, it might even be prejudicial for the correct function of the method.So now my question is, is there a way to even debug what is calling that method ? The hook productSave I am using is not being used by any other module. Here is the full code nonetheless, of both the install and the hook. The other hooks are not being used except one that only displays information with the received parameters (has no effect on the product or any other object). <?php class Autocategorytree extends Module { public function install() { Configuration::updateValue('AUTOCATEGORYTREE_LIVE_MODE', false); include(dirname(__FILE__).'/sql/install.php'); return parent::install() && $this->registerHook('header') && $this->registerHook('displayBackOfficeHeader') && $this->registerHook('actionProductSave') && $this->registerHook('actionObjectUpdateAfter') && $this->registerHook('actionObjectProductUpdateBefore') && $this->registerHook('actionProductUpdate'); $this->registerHook('actionAdminProductsControllerSaveAfter'); } protected function getConfigFormValues() { return array( // (...) Omited 3 fields that are not being used for now 'AUTOCATEGORYTREE_BIG_TVS_CARRIER_IDS' => Configuration::get('AUTOCATEGORYTREE_BIG_TVS_CARRIER_IDS', ''), 'AUTOCATEGORYTREE_SMALL_TVS_CARRIER_IDS' => Configuration::get('AUTOCATEGORYTREE_SMALL_TVS_CARRIER_IDS', ''), 'AUTOCATEGORYTREE_MIN_BIG_TVS_SIZE' => Configuration::get('AUTOCATEGORYTREE_MIN_BIG_TVS_SIZE', '55'), 'AUTOCATEGORYTREE_TV_CATEGORY_IDS' => Configuration::get('AUTOCATEGORYTREE_TV_CATEGORY_IDS', ''), ); } public function hookActionProductSave($params) { Logger::addLog('---------- HOOK PRODUCT SAVE ----------',1); $idProduct = $params['id_product']; // ID if (!$idProduct) { Logger::addLog('Id Invalid: ',2); return; } $product = new Product($idProduct); // PRODUTO if (!Validate::isLoadedObject($product)) { Logger::addLog('Product Invalid: ',2); return; } $currentCategories = $product->getCategories(); // CATEGORIAS if (empty($currentCategories)) { Logger::addLog('No current categories: ',2); return; } $deepestCategoryId = max($currentCategories); Logger::addLog('Deepest category: '.$deepestCategoryId,1); $allCategories = []; foreach ($currentCategories as $categoryId) { // SET CATEGORY PARENTS Logger::addLog('Iteration: category id: '.$categoryId,1); $currentCategory = new Category($categoryId); while ($currentCategory && $currentCategory->id_parent != 0) { Logger::addLog('Parent: '.$currentCategory->id_parent,1); $allCategories[] = $currentCategory->id; Logger::addLog('All Categories array: '. print_r($allCategories, true),1); $currentCategory = new Category($currentCategory->id_parent); } } $allCategories = array_unique($allCategories); Logger::addLog('allCategories: '.print_r($allCategories, true),1); sort($allCategories); $product->deleteCategories(); $product->addToCategories($allCategories); // QUERRIES INTO DB $query = 'UPDATE ' . _DB_PREFIX_ . 'product SET id_category_default = ' . (string)$deepestCategoryId . ' WHERE id_product = ' . (string)$idProduct; $query2 = 'UPDATE ' . _DB_PREFIX_ . 'product_shop SET id_category_default = ' . (string)$deepestCategoryId . ' WHERE id_product = ' . (string)$idProduct; Db::getInstance()->execute($query); Db::getInstance()->execute($query2); // ------------------- AUTO CARRIERS ------------------- // $bigTvCarriers = Configuration::get('AUTOCATEGORYTREE_BIG_TVS_CARRIER_IDS', ''); $smallTvCarriers = Configuration::get('AUTOCATEGORYTREE_SMALL_TVS_CARRIER_IDS', ''); $minBigTvSize = (float)Configuration::get('AUTOCATEGORYTREE_MIN_BIG_TVS_SIZE', 55); $tvCategoryIds = Configuration::get('AUTOCATEGORYTREE_TV_CATEGORY_IDS', ''); PrestaShopLogger::addLog( 'AutoCategoryTree:' . PHP_EOL . 'BIG TV Carriers: ' . print_r($bigTvCarriers, true) . PHP_EOL . 'SMALL TV Carriers: ' . print_r($smallTvCarriers, true) . PHP_EOL . 'Min Big TV Size: ' . $minBigTvSize . PHP_EOL . 'TV Category IDs: ' . print_r($tvCategoryIds, true), 1 ); $tvCategoryIdsArray = array_filter(array_map('intval', explode(';', $tvCategoryIds))); $bigTvCarriersArray = array_filter(array_map('intval', explode(';', $bigTvCarriers))); $smallTvCarriersArray = array_filter(array_map('intval', explode(';', $smallTvCarriers))); $commonTvCats = array_intersect($tvCategoryIdsArray, $allCategories); PrestaShopLogger::addLog( 'AutoCategoryTree:' . PHP_EOL . 'TV Carriers Array: ' . print_r($tvCategoryIdsArray, true) . PHP_EOL . 'SMALL TV Carriers Array: ' . print_r($smallTvCarriersArray, true) . PHP_EOL . 'Big TV Carriers Array: ' . print_r($bigTvCarriersArray, true) . PHP_EOL . 'Common TV Categories: ' . print_r($commonTvCats, true), 1 ); $convertedBigTvCarriers = []; foreach ($bigTvCarriersArray as $carrierId) { $carrier = new Carrier((int)$carrierId); if (Validate::isLoadedObject($carrier)) { Logger::addLog('AutoCategoryTree: big tv array before convert: '.$carrierId,1); $convertedBigTvCarriers[] = (int)$carrier->id_reference; Logger::addLog('AutoCategoryTree: big tv array after convert: '. print_r($convertedBigTvCarriers, true),1); } } $convertedSmallTvCarriers = []; foreach ($smallTvCarriersArray as $carrierId) { $carrier = new Carrier((int)$carrierId); if (Validate::isLoadedObject($carrier)) { Logger::addLog('AutoCategoryTree: small tv array before convert: '.$carrierId,1); $convertedSmallTvCarriers[] = (int)$carrier->id_reference; Logger::addLog('AutoCategoryTree: small tv array after convert: '. print_r($convertedSmallTvCarriers, true),1); } } if (!empty($commonTvCats)) { $langId = (int)$this->context->language->id; $productName = isset($product->name[$langId]) ? $product->name[$langId] : $product->name[(int)Configuration::get('PS_LANG_DEFAULT')]; $size = 0; if (preg_match('/(\d+)\s*(["\'])/', $productName, $matches)) { $size = (int)$matches[1]; Logger::addLog('AutoCategoryTree: parsed TV size from product name (ID: '.$idProduct.'): '.$size,1); } else { Logger::addLog('AutoCategoryTree: could not parse TV size from product name (ID: '.$idProduct.').',2); return; } if ($size >= $minBigTvSize && !empty($convertedBigTvCarriers)) { // ---------- BIG ---------- // Logger::addLog('AutoCategoryTree: Assigning BIG TV carriers ('.implode(', ', $convertedBigTvCarriers).') for product: '.$idProduct,1); $product->setCarriers($convertedBigTvCarriers); } elseif (!empty($convertedSmallTvCarriers)) { // --------- SMALL --------- // Logger::addLog('AutoCategoryTree: Assigning SMALL TV carriers ('.implode(', ', $convertedSmallTvCarriers).') for product: '.$idProduct,1); $product->setCarriers($convertedSmallTvCarriers); } else { Logger::addLog('AutoCategoryTree: No matching carriers array found for product: '.$idProduct,2); } } else { Logger::addLog('AutoCategoryTree: product '.$idProduct.' is NOT in any configured TV category. No carrier change.',1); } } } Link to comment Share on other sites More sharing options...
ps8modules Posted February 19 Share Posted February 19 (edited) /** * Sets carriers assigned to the product. * * @param int[] $carrier_list */ public function setCarriers($carrier_list) { $data = []; foreach ($carrier_list as $carrier) { $data[] = [ 'id_product' => (int) $this->id, 'id_carrier_reference' => (int) $carrier, 'id_shop' => (int) $this->id_shop, ]; } Db::getInstance()->execute( 'DELETE FROM `' . _DB_PREFIX_ . 'product_carrier` WHERE id_product = ' . (int) $this->id . ' AND id_shop = ' . (int) $this->id_shop ); $unique_array = []; foreach ($data as $sub_array) { if (!in_array($sub_array, $unique_array)) { $unique_array[] = $sub_array; } } if (count($unique_array)) { Db::getInstance()->insert('product_carrier', $unique_array, false, true, Db::INSERT_IGNORE); } } This is a function from Product.php Db::INSERT_IGNORE does not write a duplicate record to the database on an index collision. You wrote that you were trying to somehow add records to the database. Incorrect recording may have occurred and this causes a problem with saving new records. Edited February 19 by ps8modules (see edit history) Link to comment Share on other sites More sharing options...
Simao Henriques Posted February 19 Author Share Posted February 19 Yes that was the method I had to change (by adding the $params != null check), directly on product.php. I don't do anything else on the database apart from the 2 querries already on the code I sent (two UPDATEs on ps_product and ps_product_shop). So I am lost on why setCarriers is being called twice. I can maybe hope that check that fixes the problem doesn't impact normal behaviour. Link to comment Share on other sites More sharing options...
Simao Henriques Posted February 19 Author Share Posted February 19 Ok, while testing with other products and some edge cases, I finally reached the conclusion that the hook was the problem, hookActionProductSave actually is triggered before updating any info of the product. So what was happening was: the hook would trigger and change carriers (and even categories) and right after the saving process would go through and overwrite with the parameters that were selected upon saving the product. I fixed it by finally being able to make the hook hookActionAdminProductsControllerSaveAfter work, and everything is fine now, now instead of prestashop overwriting my save, it is the other way around. Will update the main post to include the answer, thanks for the patiente and the inputs @ps8modules 1 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now