From cfa6ec40e64b92250174bf1cf714a37fc010bbfd Mon Sep 17 00:00:00 2001 From: atm-corentin <165782689+atm-corentin@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:50:29 +0200 Subject: [PATCH] FIX : preserve supplier price permissions on upgrade to 23.0 (#39066) 23.0 introduced dedicated advanced permissions to read/write supplier prices (product: read_supplier_prices / write_supplier_prices ; service: read_supplier_prices). In Advanced permissions mode (MAIN_USE_ADVANCED_PERMS), supplier price access is now gated by these new rights instead of the general product/service read and create rights, but nothing grants them, so every user and group silently loses access to supplier prices when upgrading. Grant the new permissions to the users and groups that already hold the matching product/service rights, from a migrate_supplier_prices_permissions() function in upgrade2.php run right after the module reload, so permission ids are resolved by name from rights_def instead of being hardcoded. Idempotent. --- htdocs/install/upgrade2.php | 117 ++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/htdocs/install/upgrade2.php b/htdocs/install/upgrade2.php index 8fc20e47425..b62f859ce51 100644 --- a/htdocs/install/upgrade2.php +++ b/htdocs/install/upgrade2.php @@ -717,6 +717,14 @@ if (!GETPOST('action', 'aZ09') || preg_match('/upgrade/i', GETPOST('action', 'aZ $error++; } + // Grant the supplier prices permissions added in 23.0 to users/groups that already have + // the matching product/service rights (ids resolved by name from the rights_def reloaded + // just above), so nobody loses access to supplier prices on upgrade. Idempotent. + $result = migrate_supplier_prices_permissions($db, $langs, $conf); + if ($result < 0) { + $error++; + } + // Reload menus (this must be always done, and only into last targeted version) // This reload the auguria menu.To reload a dynamic menu defined into module descriptor, see previours step $result = migrate_reload_menu($db, $langs, $conf); @@ -4056,6 +4064,115 @@ function migrate_remise_except_entity($db, $langs, $conf) print ''; } +/** + * Grant the "supplier prices" advanced permissions introduced in 23.0 (product/service + * read_supplier_prices and product write_supplier_prices) to the users and groups that already + * hold the matching product/service "read"/"create" rights. + * + * Before 23.0, access to supplier (purchase) prices followed the general product/service read and + * create rights. In "Advanced permissions" mode these are now gated by dedicated permissions that + * are not granted by default, so without this migration every user/group silently loses access to + * supplier prices on upgrade. Permission ids are resolved by name from llx_rights_def (populated by + * the module reload done just before this call) to avoid hardcoded permission ids. Idempotent. + * + * @param DoliDB $db Database handler + * @param Translate $langs Object langs + * @param Conf $conf Object conf + * @return int Return integer <0 if KO, >=0 if OK + */ +function migrate_supplier_prices_permissions($db, $langs, $conf) +{ + dolibarr_install_syslog("upgrade2::migrate_supplier_prices_permissions"); + + // Grant each target permission to holders of any of its source permissions. + // Format: array('to' => array(module, perms, subperms), 'from' => array(array(module, perms, subperms), ...)) + $rules = array( + array('to' => array('produit', 'product_advance', 'read_supplier_prices'), 'from' => array(array('produit', 'lire', ''), array('produit', 'creer', ''))), + array('to' => array('produit', 'product_advance', 'write_supplier_prices'), 'from' => array(array('produit', 'creer', ''))), + array('to' => array('service', 'service_advance', 'read_supplier_prices'), 'from' => array(array('service', 'lire', ''))), + ); + + $error = 0; + $db->begin(); + + foreach ($rules as $rule) { + // Resolve the target permission id by name (skip if the module/permission is not present) + $targetid = migrate_get_rights_def_id($db, $rule['to'][0], $rule['to'][1], $rule['to'][2]); + if ($targetid <= 0) { + continue; + } + + // Resolve the source permission ids by name + $sourceids = array(); + foreach ($rule['from'] as $from) { + $id = migrate_get_rights_def_id($db, $from[0], $from[1], $from[2]); + if ($id > 0) { + $sourceids[] = $id; + } + } + if (empty($sourceids)) { + continue; + } + $sourcelist = implode(',', $sourceids); + + // Grant to groups that hold a source permission but not yet the target + $sql = "INSERT INTO ".MAIN_DB_PREFIX."usergroup_rights (entity, fk_usergroup, fk_id)"; + $sql .= " SELECT DISTINCT gr.entity, gr.fk_usergroup, ".((int) $targetid); + $sql .= " FROM ".MAIN_DB_PREFIX."usergroup_rights as gr"; + $sql .= " WHERE gr.fk_id IN (".$db->sanitize($sourcelist).")"; + $sql .= " AND NOT EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."usergroup_rights as ex WHERE ex.fk_usergroup = gr.fk_usergroup AND ex.entity = gr.entity AND ex.fk_id = ".((int) $targetid).")"; + if (!$db->query($sql)) { + $error++; + break; + } + + // Grant to users that hold a source permission but not yet the target + $sql = "INSERT INTO ".MAIN_DB_PREFIX."user_rights (entity, fk_user, fk_id)"; + $sql .= " SELECT DISTINCT ur.entity, ur.fk_user, ".((int) $targetid); + $sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur"; + $sql .= " WHERE ur.fk_id IN (".$db->sanitize($sourcelist).")"; + $sql .= " AND NOT EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."user_rights as ex WHERE ex.fk_user = ur.fk_user AND ex.entity = ur.entity AND ex.fk_id = ".((int) $targetid).")"; + if (!$db->query($sql)) { + $error++; + break; + } + } + + if ($error) { + $db->rollback(); + return -1; + } + $db->commit(); + return 0; +} + +/** + * Return the permission id (llx_rights_def.id) matching a module/perms/subperms triplet, or 0 if none. + * + * @param DoliDB $db Database handler + * @param string $module Module (rights_def.module) + * @param string $perms First level permission (rights_def.perms) + * @param string $subperms Second level permission (rights_def.subperms), '' for none + * @return int Permission id, or 0 if not found + */ +function migrate_get_rights_def_id($db, $module, $perms, $subperms) +{ + $sql = "SELECT id FROM ".MAIN_DB_PREFIX."rights_def"; + $sql .= " WHERE module = '".$db->escape($module)."'"; + $sql .= " AND perms = '".$db->escape($perms)."'"; + if ($subperms === '') { + $sql .= " AND (subperms IS NULL OR subperms = '')"; + } else { + $sql .= " AND subperms = '".$db->escape($subperms)."'"; + } + $resql = $db->query($sql); + if (!$resql) { + return 0; + } + $obj = $db->fetch_object($resql); + return $obj ? (int) $obj->id : 0; +} + /** * Migrate to add entity value into llx_user_rights *