diff '--color=auto' -r dolibarr/htdocs/product/card.php dolibarr-22.0.3/htdocs/product/card.php
220c220
< 	$permissiontoeditextra = dol_eval($extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')]);
---
> 	$permissiontoeditextra = dol_eval((string) $extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')]);
634,645d633
< //MF
< 			//if (empty($object->description) && !empty($object->label)) {
< 			//    $object->description = $object->label;
< 			//}
< 			if (!empty($object->label)) {
< 				if (empty($object->description)) {
< 					$object->description = $object->label;
< 				} else {
< 					$object->description = trim($object->label . ' - ' . $object->description);
< 				}
< 			}
< //
815,825d802
< //MF
< 				//if (empty($object->description) && !empty($object->label)) {
< 				//	$object->description = $object->label;
< 				//}
< 				if (!empty($object->label)) {
< 					if (stripos(trim($object->description), $object->label) !== 0) {
< 						$object->description = trim($object->label . ' ' . $object->description);
< 					}
< 				}
< //
< 
1527,1539d1503
< //MF
< 
< 			if ($picto=="product") {
< 			// Other attributes
< 			$parameters = array('colspan' => ' colspan="2"', 'cols' => 2);
< 			$reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
< 			print $hookmanager->resPrint;
< 			if (empty($reshook)) {
< 				print $object->showOptionals($extrafields, 'create', $parameters);
< 			}
< //			print $title;
< 			}
< //MF
1840a1805,1811
> 			// Other attributes
> 			$parameters = array('colspan' => ' colspan="2"', 'cols' => 2);
> 			$reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
> 			print $hookmanager->resPrint;
> 			if (empty($reshook)) {
> 				print $object->showOptionals($extrafields, 'create', $parameters);
> 			}
2099,2107d2069
< 				//MF
< 				// Other attributes
< 				$parameters = array('colspan' => ' colspan="2"', 'cols' => 2);
< 				$reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
< 				print $hookmanager->resPrint;
< 				if (empty($reshook)) {
< 					print $object->showOptionals($extrafields, 'edit', $parameters);
< 				}
< 
2381,2386d2342
< 
< 					if (isModEnabled('stock') && getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
< 						print '<tr><td>' . $langs->trans("StockableProduct") . '</td>';
< 						$checked = $object->stockable_product == 1 ? "checked" : "";
< 						print '<td><input type="checkbox" id="stockable_product" name="stockable_product" ' . $checked . ' /></td></tr>';
< 					}
2485a2442,2449
> 				}
> 
> 				// Other attributes
> 				$parameters = array('colspan' => ' colspan="2"', 'cols' => 2);
> 				$reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
> 				print $hookmanager->resPrint;
> 				if (empty($reshook)) {
> 					print $object->showOptionals($extrafields, 'edit', $parameters);
Only in dolibarr/htdocs/product/: card.php.bak
diff '--color=auto' -r dolibarr/htdocs/product/class/product.class.php dolibarr-22.0.3/htdocs/product/class/product.class.php
3077,3078c3077,3078
< 				preg_match('/(\d+)(\w+)/', $obj->duration, $matches);
< 				$this->duration_value = !empty($matches[1]) ? (int) $matches[1] : 0;
---
> 				preg_match('/([\d.]+)(\w+)/', $obj->duration, $matches);
> 				$this->duration_value = !empty($matches[1]) ? (float) $matches[1] : 0;
3276c3276,3277
< 						} elseif ($result = $this->db->fetch_array($resql)) {
---
> 						} else {
> 							$result = $this->db->fetch_array($resql);
3689,3692c3690,3694
< 					$sql = "SELECT sum(".$this->db->ifsql('f.type=2', '-1', '1')." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
< 					$sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
< 					$sql .= " JOIN ".MAIN_DB_PREFIX."element_element as el ON ((el.fk_target = f.rowid AND el.targettype = 'facture' AND sourcetype = 'commande') OR (el.fk_source = f.rowid AND el.targettype = 'commande' AND sourcetype = 'facture'))";
< 					$sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
---
> 					$sql = "SELECT sum(".$this->db->ifsql('f.type=2', '-1', '1')." * fd.qty) as count";
> 					$sql .= " FROM " . $this->db->prefix() . "facturedet as fd";
> 					$sql .= " JOIN " . $this->db->prefix() . "facture as f ON fd.fk_facture = f.rowid";
> 					$sql .= " JOIN " . $this->db->prefix() . "element_element as el ON el.fk_target = f.rowid AND el.targettype = 'facture' AND el.sourcetype = 'commande'";
> 					$sql .= " JOIN " . $this->db->prefix() . "commande as c ON el.fk_source = c.rowid";
3694c3696,3702
< 
---
> 					$sql .= " UNION ALL";
> 					$sql .= " SELECT sum(".$this->db->ifsql('f.type=2', '-1', '1')." * fd.qty) as count";
> 					$sql .= " FROM " . $this->db->prefix() . "facturedet as fd";
> 					$sql .= " JOIN " . $this->db->prefix() . "facture as f ON fd.fk_facture = f.rowid";
> 					$sql .= " JOIN " . $this->db->prefix() . "element_element as el ON el.fk_source = f.rowid AND el.targettype = 'commande' AND el.sourcetype = 'facture'";
> 					$sql .= " JOIN " . $this->db->prefix() . "commande as c ON el.fk_source = c.rowid";
> 					$sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product =".((int) $this->id);
5802c5810
< 				if (!empty($this->pmp) && $this->pmp) {
---
> 				if ($this->isStockManaged() && !empty($this->pmp) && $this->pmp) {
diff '--color=auto' -r dolibarr/htdocs/product/composition/card.php dolibarr-22.0.3/htdocs/product/composition/card.php
556a557
> 		print '<div class="div-table-responsive-no-min">';
795a797
> 		print '</div>';
diff '--color=auto' -r dolibarr/htdocs/product/dynamic_price/class/price_parser.class.php dolibarr-22.0.3/htdocs/product/dynamic_price/class/price_parser.class.php
120c120
< 			return $langs->trans("ErrorPriceExpression".$code, $info);
---
> 			return $langs->trans("ErrorPriceExpression".$code, (string) $info);
124c124
< 			return $langs->trans("ErrorPriceExpressionInternal", $code);
---
> 			return $langs->trans("ErrorPriceExpressionInternal", (string) $code);
126c126
< 			return $langs->trans("ErrorPriceExpressionUnknown", $code);
---
> 			return $langs->trans("ErrorPriceExpressionUnknown", (string) $code);
diff '--color=auto' -r dolibarr/htdocs/product/inventory/inventory.php dolibarr-22.0.3/htdocs/product/inventory/inventory.php
1197c1197
< 			$qty_tmp = price2num(GETPOST("id_".$obj->rowid."_input_tmp", 'MS')) >= 0 ? GETPOST("id_".$obj->rowid."_input_tmp") : $qty_view;
---
> 			$qty_tmp = price2num(GETPOST("id_".$obj->rowid."_input_tmp"), 'MS') >= 0 ? GETPOST("id_".$obj->rowid."_input_tmp") : $qty_view;
diff '--color=auto' -r dolibarr/htdocs/product/inventory/list.php dolibarr-22.0.3/htdocs/product/inventory/list.php
128c128
< 			'enabled' => (abs($visible) != 3 && (bool) dol_eval($val['enabled'], 1)),
---
> 			'enabled' => (abs($visible) != 3 && (bool) dol_eval((string) $val['enabled'], 1)),
diff '--color=auto' -r dolibarr/htdocs/product/list.php dolibarr-22.0.3/htdocs/product/list.php
312c312
< 		$visible = dol_eval($val['visible'], 1, 1, '1');
---
> 		$visible = dol_eval((string) $val['visible'], 1, 1, '1');
316c316
< 			'enabled'=>(abs($visible) != 3 && (bool) dol_eval($val['enabled'], 1)),
---
> 			'enabled'=>(abs($visible) != 3 && (bool) dol_eval((string) $val['enabled'], 1)),
diff '--color=auto' -r dolibarr/htdocs/product/price.php dolibarr-22.0.3/htdocs/product/price.php
522,567d521
< 					if ($res > 0) {
< 						$extralabels = $extrafields->fetch_name_optionals_label("product");
< 						if (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES') && !empty($extralabels)) {
< 							// Default price
< 							$extrafield_values = $extrafields->getOptionalsFromPost("product");
< 							foreach ($extrafield_values as $efkey => $value) {
< 								$object->array_options[$efkey] = $value;
< 							}
< 							$result = $object->insertExtraFields();
< 							if ($result < 0) {
< 								$error++;
< 							}
< 						} elseif ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && !empty($extralabels)) {
< 							$price_extralabels = $extrafields->fetch_name_optionals_label("product_price");
< 							$sql = "SELECT rowid";
< 							$sql .= " FROM ".$object->db->prefix()."product_price";
< 							$sql .= " WHERE entity IN (".getEntity('productprice').")";
< 							$sql .= " AND price_level=".((int) $key); // $i
< 							$sql .= " AND fk_product = ".((int) $object->id);
< 							$sql .= " ORDER BY date_price DESC, rowid DESC";
< 							$sql .= " LIMIT 1";
< 							$resql = $object->db->query($sql);
< 							if ($resql) {
< 								$lineid = $object->db->fetch_object($resql);
< 								$db->free($resql);
< 							}
< 							if (!empty($lineid->rowid)) {
< 								if (!empty($price_extralabels) && is_array($price_extralabels)) {
< 									foreach ($price_extralabels as $code => $label) {
< 										$code_array = GETPOST($code, 'array');
< 										$object->array_options['options_'.$code] = $code_array[$key];
< 									}
< 									// We need to force table to update product_price and not product extrafields
< 									$object->id = $lineid->rowid;
< 									$object->table_element = 'product_price';
< 									$result = $object->insertExtraFields();
< 								}
< 								// Back to product table
< 								$object->id = $id;
< 								$object->table_element = 'product';
< 								if ($result < 0) {
< 									$error++;
< 								}
< 							}
< 						}
< 					}
diff '--color=auto' -r dolibarr/htdocs/product/price_suppliers.php dolibarr-22.0.3/htdocs/product/price_suppliers.php
975,977c975
< 				'pfp.packaging' => array('label' => $langs->trans("PackagingForThisProduct"), 'enabled' => (string) getDolGlobalInt('PRODUCT_USE_SUPPLIER_PACKAGING'), 'checked' => '0', 'position' => 17),
< 				'pfp.status' => array('label' => $langs->trans("Status"), 'enabled' => '1', 'checked' => '0', 'position' => 40),
< 				'pfp.tms' => array('label' => $langs->trans("DateModification"), 'enabled' => '1', 'checked' => '1', 'position' => 50),
---
> 				'pfp.packaging' => array('label' => $langs->trans("PackagingForThisProduct"), 'enabled' => (string) getDolGlobalInt('PRODUCT_USE_SUPPLIER_PACKAGING'), 'checked' => (getDolGlobalInt('PRODUCT_USE_SUPPLIER_PACKAGING') ? '1' : '0'), 'position' => 17),
979a978,979
> 				'pfp.tms' => array('label' => $langs->trans("DateModification"), 'enabled' => '1', 'checked' => '-1', 'position' => 80),
> 				'pfp.status' => array('label' => $langs->trans("Status"), 'enabled' => '1', 'checked' => '0', 'position' => 200),
diff '--color=auto' -r dolibarr/htdocs/product/reassort.php dolibarr-22.0.3/htdocs/product/reassort.php
295,297c295,296
< 		//$natural_search_physique = natural_search('HAVING SUM(' . $db->ifsql('s.reel IS NULL', '0', 's.reel') . ')', $search_stock_physique, 1, 1);
< 		$natural_search_physique = natural_search('SUM(' . $db->ifsql('s.reel IS NULL', '0', 's.reel') . ')', $search_stock_physique, 1, 1);
< 		$natural_search_physique = " " . substr($natural_search_physique, 1, -1); // remove first "(" and last ")" characters
---
> 		$natural_search_physique = natural_search('__SUM_OF_REEL__', $search_stock_physique, 1, 1);
> 		$natural_search_physique = " " . substr(str_replace('__SUM_OF_REEL__', 'SUM(COALESCE(s.reel, 0))', $natural_search_physique), 1, -1); // remove first "(" and last ")" characters
diff '--color=auto' -r dolibarr/htdocs/product/reassortlot.php dolibarr-22.0.3/htdocs/product/reassortlot.php
397,398c397,398
< 	$natural_search_physique = natural_search('SUM(' . $db->ifsql('pb.qty IS NULL', $db->ifsql('ps.reel IS NULL', '0', 'ps.reel'), 'pb.qty') . ')', $search_stock_physique, 1, 1);
< 	$natural_search_physique = " " . substr($natural_search_physique, 1, -1); // remove first "(" and last ")" characters
---
> 	$natural_search_physique = natural_search('__SEARCH_PHYSIQUE__', $search_stock_physique, 1, 1);
> 	$natural_search_physique = " " . substr(str_replace('__SEARCH_PHYSIQUE__', 'SUM(COALESCE(pb.qty, ps.reel, 0))', $natural_search_physique), 1, -1); // remove first "(" and last ")" characters
diff '--color=auto' -r dolibarr/htdocs/product/stock/card.php dolibarr-22.0.3/htdocs/product/stock/card.php
112c112
< 	$permissiontoeditextra = dol_eval($extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')]);
---
> 	$permissiontoeditextra = dol_eval((string) $extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')]);
diff '--color=auto' -r dolibarr/htdocs/product/stock/class/mouvementstock.class.php dolibarr-22.0.3/htdocs/product/stock/class/mouvementstock.class.php
216,217c216,217
< 	 * 	@param		int				$id_product_batch	Id product_batch (when skip_batch is false and we already know which record of product_batch to use)
< 	 *  @param		int<0,1>		$disablestockchangeforsubproduct	Disable stock change for sub-products of kit (useful only if product is a subproduct)
---
> 	 * 	@param		int				$id_product_batch	Id product_batch (when skip_batch is false and we already know which record of product_batch table to use)
> 	 *  @param		int<0,1>		$disablestockchangeforsubproduct	Disable stock change for sub-products of kit (useful only if product is a kit)
969d968
< 		$fk_product_stock = 0;
1173c1172
< 		global $langs, $conf, $db;
---
> 		global $langs;
1266c1265
< 		global $conf, $user, $langs;
---
> 		global $langs;
1333c1332,1333
< 	 * reverse movement for object by updating infos
---
> 	 * Reverse movement for object by updating infos
> 	 *
1336c1336
< 	public function reverseMouvement()
---
> 	public function reverseMovement()
1338,1339c1338,1341
< 		$formattedDate = "REVERTMV" .dol_print_date($this->datem, '%Y%m%d%His');
< 		if ($this->label == 'Annulation movement ID'.$this->id) {
---
> 		global $user;
> 
> 		$formattedDate = "REVERT-" .($this->inventorycode ? $this->inventorycode : dol_print_date($this->datem, '%Y%m%d%His'));
> 		if ($this->inventorycode == $formattedDate) {
1342c1344,1356
< 		if ($this->inventorycode == $formattedDate) {
---
> 
> 		$newlabel = 'Revert '.$this->label;
> 		// type is 0=input (stock increase by a stock transfer), 1=output (stock decrease by a stock transfer), 2=output (stock decrease), 3=input (stock increase)
> 		// Note that qty should be > 0 with 0 or 3, < 0 with 1 or 2.
> 		if ($this->type == 0) {
> 			$newtype = 1;
> 		} elseif ($this->type == 1) {
> 			$newtype = 0;
> 		} elseif ($this->type == 2) {
> 			$newtype = 3;
> 		} elseif ($this->type == 3) {
> 			$newtype = 2;
> 		} else {
1344a1359
> 		$newqty = - $this->qty;
1346,1349c1361
< 		$sql = "UPDATE ".$this->db->prefix()."stock_mouvement SET";
< 		$sql .= " label = 'Annulation movement ID ".((int) $this->id)."',";
< 		$sql .= "inventorycode = '".($formattedDate)."'";
< 		$sql .= " WHERE rowid = ".((int) $this->id);
---
> 		$this->db->begin();
1351c1363
< 		$resql = $this->db->query($sql);
---
> 		$result = $this->_create($user, $this->product_id, $this->warehouse_id, $newqty, $newtype, 0, $newlabel, $formattedDate, '', 0, 0, $this->batch);
1353c1365
< 		if ($resql) {
---
> 		if ($result > 0) {
diff '--color=auto' -r dolibarr/htdocs/product/stock/list.php dolibarr-22.0.3/htdocs/product/stock/list.php
135c135
< 			'enabled' => (string) (int) (abs($visible) != 3 && (bool) dol_eval($val['enabled'], 1)),
---
> 			'enabled' => (string) (int) (abs($visible) != 3 && (bool) dol_eval((string) $val['enabled'], 1)),
Only in dolibarr/htdocs/product/stock: movement_card.php
diff '--color=auto' -r dolibarr/htdocs/product/stock/movement_list.php dolibarr-22.0.3/htdocs/product/stock/movement_list.php
193c193
< 	$permissiontoeditextra = dol_eval($extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')]);
---
> 	$permissiontoeditextra = dol_eval((string) $extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')]);
621a622,623
> 	$db->begin();
> 
624,629c626
< 	$sql .= " WHERE rowid IN (";
< 	foreach ($toselect as $id) {
< 		$sql .= ((int) $id).",";
< 	}
< 	$sql = rtrim($sql, ',');
< 	$sql .= ")";
---
> 	$sql .= " WHERE rowid IN (".$db->sanitize(implode(',', $toselect)).")";
635,636c632
< 		$hasSuccess = false;
< 		$hasError = false;
---
> 		$error =0;
639,640c635,642
< 			$object->fetch($obj->rowid);
< 			$reverse = $object->reverseMouvement();
---
> 
> 			$object->id = 0;
> 			$object->fetch($obj->rowid);		// $object is MouvementStock
> 
> 			// TODO Add a protection to disallow reversion if type of movement is not the same value for all selected lines
> 
> 			// Create the reverse movement
> 			$reverse = $object->reverseMovement();
642,644c644,646
< 				$hasError = true;
< 			} else {
< 				$hasSuccess = true;
---
> 				setEventMessages($object->error, $object->errors, 'errors');
> 				$error++;
> 				break;
648,655c650,659
< 		if ($hasError) {
< 			setEventMessages($langs->trans("WarningAlreadyReverse", $langs->transnoentities($idAlreadyReverse)), null, 'warnings');
< 		}
< 		if ($hasSuccess) {
< 			setEventMessages($langs->trans("ReverseConfirmed"), null);
< 		}
< 		header("Location: ".$_SERVER["PHP_SELF"]);
< 		exit;
---
> 	} else {
> 		setEventMessages($db->lasterror(), null, 'errors');
> 		$error++;
> 	}
> 
> 	if (!$error) {
> 		setEventMessages($langs->trans("ReverseConfirmed"), null);
> 		$db->commit();
> 	} else {
> 		$db->rollback();
656a661,663
> 
> 	header("Location: ".$_SERVER["PHP_SELF"]);
> 	exit;
diff '--color=auto' -r dolibarr/htdocs/product/stock/productlot_list.php dolibarr-22.0.3/htdocs/product/stock/productlot_list.php
128c128
< 			'enabled' => (abs($visible) != 3 && (bool) dol_eval($val['enabled'], 1)),
---
> 			'enabled' => (abs($visible) != 3 && (bool) dol_eval((string) $val['enabled'], 1)),
diff '--color=auto' -r dolibarr/htdocs/product/stock/stockatdate.php dolibarr-22.0.3/htdocs/product/stock/stockatdate.php
299c299
< $reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters); // Note that $action and $object may have been modified by hook
---
> $reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
308c308
< $reshook = $hookmanager->executeHooks('printFieldListJoin', $parameters); // Note that $action and $object may have been modified by hook
---
> $reshook = $hookmanager->executeHooks('printFieldListJoin', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
326,327c326,343
< $sql .= ' GROUP BY p.rowid, p.ref, p.label, p.description, p.price, p.pmp, p.price_ttc, p.price_base_type, p.fk_product_type, p.desiredstock, p.seuil_stock_alerte,';
< $sql .= ' p.tms, p.duration, p.tobuy, p.stock';
---
> 
> $sqlGroupBy = ' GROUP BY p.rowid, p.ref, p.label, p.description, p.price, p.pmp, p.price_ttc, p.price_base_type, p.fk_product_type, p.desiredstock, p.seuil_stock_alerte,';
> $sqlGroupBy .= ' p.tms, p.duration, p.tobuy, p.stock';
> 
> $parameters = array('sqlGroupBy' => $sqlGroupBy);
> $reshook = $hookmanager->executeHooks('printFieldListGroupBy', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
> 
> if ($reshook == 0) {
> 	// Allows the hook to add things (old behavior)
> 	$sql .= $hookmanager->resPrint;
> 	// Allows the hook to REPLACE the clause (new behavior)
> 	if (!empty($hookmanager->resArray['sqlGroupBy'])) {
> 		$sqlGroupBy = $hookmanager->resArray['sqlGroupBy'];
> 	}
> }
> 
> $sql .= $sqlGroupBy;
> 
diff '--color=auto' -r dolibarr/htdocs/product/stock/stocktransfer/stocktransfer_list.php dolibarr-22.0.3/htdocs/product/stock/stocktransfer/stocktransfer_list.php
124c124
< 			'enabled' => (abs($visible) != 3 && (bool) dol_eval($val['enabled'], 1)),
---
> 			'enabled' => (abs($visible) != 3 && (bool) dol_eval((string) $val['enabled'], 1)),
