From 2580d0dd7f14599343767da6fabd333c6bf026c1 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 25 Nov 2025 12:16:55 +0100 Subject: [PATCH 1/8] Fix missing email --- htdocs/public/stripe/ipn.php | 123 ++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 8 deletions(-) diff --git a/htdocs/public/stripe/ipn.php b/htdocs/public/stripe/ipn.php index 4c73c5843ab..6f471059de2 100644 --- a/htdocs/public/stripe/ipn.php +++ b/htdocs/public/stripe/ipn.php @@ -44,6 +44,13 @@ if (!defined('USESUFFIXINLOG')) { // Load Dolibarr environment require '../../main.inc.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var Translate $langs + * + * @var Societe $mysoc + */ require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/ccountry.class.php'; @@ -57,12 +64,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php'; require_once DOL_DOCUMENT_ROOT.'/includes/stripe/stripe-php/init.php'; require_once DOL_DOCUMENT_ROOT.'/stripe/class/stripe.class.php'; -/** - * @var Conf $conf - * @var DoliDB $db - * @var Translate $langs - */ - // You can find your endpoint's secret in your webhook settings if (GETPOSTISSET('connect')) { if (GETPOSTISSET('test')) { @@ -477,8 +478,8 @@ if ($event->type == 'payout.created' && getDolGlobalString('STRIPE_AUTO_RECORD_P dol_syslog("TODO update flag traite to 1"); dol_syslog("TODO update flag traite to 1", LOG_DEBUG, 0, '_payment'); } elseif ($paymentTypeCode == "PRE" && $paymentTypeCodeInDolibarr == 'ban') { - // Case payment type in Stripe and into prelevement_demande are both BAN. - // For this case, payment on invoice (not yet recorded) must be done and direct debit order must be closed. + // Case payment type is Direct Debit and into prelevement_demande is also BAN. + // For this case, payment on invoice (not yet recorded) must be recorded and direct debit order must be closed. $paiement = new Paiement($db); @@ -653,6 +654,112 @@ if ($event->type == 'payout.created' && getDolGlobalString('STRIPE_AUTO_RECORD_P } if (!$error) { + if (getDolGlobalString('STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION')) { + // If option to send email after confirmation of direct debit is on, we send the email (template must exists + $labeltouse = getDolGlobalString('STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION'); // Example: 'InvoicePaymentSuccess' + + $invoice = new Facture($db); + $invoice->fetch($invoice_id); + + // Set output language + $outputlangs = new Translate('', $conf); + $outputlangs->setDefaultLang(empty($object->thirdparty->default_lang) ? $mysoc->default_lang : $object->thirdparty->default_lang); + $outputlangs->loadLangs(array("main", "members", "bills")); + + // Get email content from template + $arraydefaultmessage=null; + + include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; + $formmail=new FormMail($db); + + if (! empty($labeltouse)) { + $arraydefaultmessage = $formmail->getEMailTemplate($db, 'facture_send', $user, $outputlangs, 0, 1, $labeltouse); + } + + if (! empty($labeltouse) && is_object($arraydefaultmessage) && $arraydefaultmessage->id > 0) { + $subject = $arraydefaultmessage->topic; + $msg = $arraydefaultmessage->content; + } + + $substitutionarray=getCommonSubstitutionArray($outputlangs, 0, null, $object); + + complete_substitutions_array($substitutionarray, $outputlangs, $object); + + // Set the property ->ref_customer with ref_customer of contract so __REF_CLIENT__ will be replaced in email content + // Search contract linked to invoice + $foundcontract = null; + $invoice->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 1); + + if (is_array($invoice->linkedObjects['contrat']) && count($invoice->linkedObjects['contrat']) > 0) { + //dol_sort_array($object->linkedObjects['facture'], 'date'); + foreach ($invoice->linkedObjects['contrat'] as $idcontract => $contract) { + $substitutionarray['__CONTRACT_REF__']=$contract->ref_customer; + $substitutionarray['__REFCLIENT__']=$contract->ref_customer; // For backward compatibility + $substitutionarray['__REF_CLIENT__']=$contract->ref_customer; + $substitutionarray['__REF_CUSTOMER__']=$contract->ref_customer; + $foundcontract = $contract; + break; + } + } + + dol_syslog('__DIRECTDOWNLOAD_URL_INVOICE__='.$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__']); + + $urlforsellyoursaasaccount = getRootUrlForAccount($foundcontract); + if ($urlforsellyoursaasaccount) { + $tmpforurl=preg_replace('/.*document.php/', '', $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__']); + if ($tmpforurl) { + $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__']=$urlforsellyoursaasaccount.'/source/document.php'.$tmpforurl; + } else { + $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__']=$urlforsellyoursaasaccount; + } + } + + $subjecttosend = make_substitutions($subject, $substitutionarray, $outputlangs); + $texttosend = make_substitutions($msg, $substitutionarray, $outputlangs); + + // Attach a file ? + $file=''; + $listofpaths=array(); + $listofnames=array(); + $listofmimes=array(); + if (is_object($invoice)) { + $invoicediroutput = $conf->invoice->dir_output; + $fileparams = dol_most_recent_file($invoicediroutput . '/' . $invoice->ref, preg_quote($invoice->ref, '/').'[^\-]+'); + $file = $fileparams['fullname']; + $file = ''; // Disable attachment of invoice in emails + + if ($file) { + $listofpaths=array($file); + $listofnames=array(basename($file)); + $listofmimes=array(dol_mimetype($file)); + } + } + $from = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL'); + + $trackid = 'inv'.$invoice->id; + $moreinheader = 'X-Dolibarr-Info: doTakeStripePaymentForThirdParty'."\r\n"; + $addr_cc = ''; + if (!empty($invoice->thirdparty->array_options['options_emailccinvoice'])) { + dol_syslog("We add the recipient ".$invoice->thirdparty->array_options['options_emailccinvoice']." as CC", LOG_DEBUG); + $addr_cc = $invoice->thirdparty->array_options['options_emailccinvoice']; + } + + // Send email (substitutionarray must be done just before this) + include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php'; + $mailfile = new CMailFile($subjecttosend, $invoice->thirdparty->email, $from, $texttosend, $listofpaths, $listofmimes, $listofnames, $addr_cc, '', 0, -1, '', '', $trackid, $moreinheader); + if (empty($mailfile->error) && $mailfile->sendfile()) { + $result = 1; + } else { + $errmsg = $langs->trans("ErrorFailedToSendMail", $from, $invoice->thirdparty->email).'. '.$mailfile->error; + + dol_syslog($errmsg); + dol_syslog($errmsg, LOG_WARNING, 0, '_payment'); + } + } else { + dol_syslog("No email sent. Option STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION not set to the tmeplate label"); + dol_syslog("No email sent. Option STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION not set to the tmeplate label", LOG_DEBUG, 0, '_payment'); + } + $db->commit(); http_response_code(200); return 1; From 5377df3ba793aa901dd7ee02075da56bb554be08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20St=C5=99=C3=ADbrn=C3=BD?= <35335130+kubajznik@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:14:58 +0100 Subject: [PATCH 2/8] FIX do not force rowid to int (#36412) * do not force rowid to int * Update commonobject.class.php --------- Co-authored-by: Laurent Destailleur --- htdocs/core/class/commonobject.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index cc7f5816b13..39ef25cb706 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -8369,7 +8369,7 @@ abstract class CommonObject $isDependList = 1; } - $data[(int) $obj->rowid] = $labeltoshow; + $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref. } $i++; From 712da5b1727e2fdac52e734b391f74f0924d47bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charl=C3=A8ne=20Benke?= <1179011+defrance@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:21:58 +0100 Subject: [PATCH 3/8] NEW : api setup - Add dictionnary holiday lists (#36402) * Add methods to retrieve holiday and public holiday lists * Fix SQL query formatting in api_setup.class.php * Add user permission checks for API access Added permission checks for user access rights before fetching data for expense reports, holidays, and HR public holidays. --- htdocs/api/class/api_setup.class.php | 161 +++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/htdocs/api/class/api_setup.class.php b/htdocs/api/class/api_setup.class.php index 8bce68de0c9..db84049d46e 100644 --- a/htdocs/api/class/api_setup.class.php +++ b/htdocs/api/class/api_setup.class.php @@ -977,6 +977,10 @@ class Setup extends DolibarrApi { $list = array(); + if (!DolibarrApiAccess::$user->hasRight('expensereport', 'lire')) { + throw new RestException(403); + } + $sql = "SELECT id, code, label, accountancy_code, active, module, position"; $sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees as t"; $sql .= " WHERE t.active = ".((int) $active); @@ -1019,6 +1023,163 @@ class Setup extends DolibarrApi return $list; } + /** + * Get the list of holiday types. + * + * @param string $sortfield Sort field + * @param string $sortorder Sort order + * @param int $limit Number of items per page + * @param int $page Page number (starting from zero) + * @param string $fk_country To filter on country + * @param int $active Holiday is active or not {@min 0} {@max 1} + * @param string $lang Code of the language the label of the holiday must be translated to + * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.code:like:'A%') and (t.active:>=:0)" + * @return array List of holiday types + * @phan-return array + * @phpstan-return array + * + * @url GET dictionary/holiday_types + * + * @throws RestException 400 Bad value for sqlfilters + * @throws RestException 503 Error when retrieving list of holiday types + */ + public function getListOfHolidayTypes($sortfield = "sortorder", $sortorder = 'ASC', $limit = 100, $page = 0, $fk_country = '', $active = 1, $lang = '', $sqlfilters = '') + { + global $langs; + $langs->loadLangs(array('holiday')); + + if (!DolibarrApiAccess::$user->hasRight('holiday', 'lire')) { + throw new RestException(403); + } + + $list = array(); + + $sql = "SELECT rowid, code, label, affect, delay, newbymonth, fk_country"; + $sql .= " FROM ".MAIN_DB_PREFIX."c_holiday_types as t"; + $sql .= " WHERE t.active = ".((int) $active); + if ($fk_country) { + $sql .= " AND (t.fk_country = ".((int) $fk_country); + $sql .= " OR t.fk_country is null)"; + } + // Add sql filters + if ($sqlfilters) { + $errormessage = ''; + $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage); + if ($errormessage) { + throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage); + } + } + + $sql .= $this->db->order($sortfield, $sortorder); + + if ($limit) { + if ($page < 0) { + $page = 0; + } + $offset = $limit * $page; + + $sql .= $this->db->plimit($limit, $offset); + } + + $result = $this->db->query($sql); + + if ($result) { + $num = $this->db->num_rows($result); + $min = min($num, ($limit <= 0 ? $num : $limit)); + for ($i = 0; $i < $min; $i++) { + $holiday = $this->db->fetch_object($result); + $tmplabel = $langs->trans($holiday->code); + if ($tmplabel != $holiday->code) { + $holiday->label = $tmplabel; + } + //$this->translateLabel($holiday, $lang, 'Holiday', array('dict')); + $list[] = $holiday; + } + } else { + throw new RestException(503, 'Error when retrieving list of holiday : '.$this->db->lasterror()); + } + + return $list; + } + + /** + * Get the list of public holiday. + * + * @param string $sortfield Sort field + * @param string $sortorder Sort order + * @param int $limit Number of items per page + * @param int $page Page number (starting from zero) + * @param string $fk_country To filter on country + * @param int $active Holiday is active or not {@min 0} {@max 1} + * @param string $lang Code of the language the label of the holiday must be translated to + * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.code:like:'A%') and (t.active:>=:0)" + * @return array List of public holiday + * @phan-return array + * @phpstan-return array + * + * @url GET dictionary/public_holiday + * + * @throws RestException 400 Bad value for sqlfilters + * @throws RestException 503 Error when retrieving list of holiday types + */ + public function getListOfPublicHolidays($sortfield = "code", $sortorder = 'ASC', $limit = 100, $page = 0, $fk_country = '', $active = 1, $lang = '', $sqlfilters = '') + { + global $langs; + $langs->loadLangs(array('hrm')); + + if (!DolibarrApiAccess::$user->hasRight('holiday', 'lire')) { + throw new RestException(403); + } + + $list = array(); + + $sql = "SELECT id, code, dayrule, day, month, year, fk_country, code as label"; + $sql .= " FROM ".MAIN_DB_PREFIX."c_hrm_public_holiday as t"; + $sql .= " WHERE t.active = ".((int) $active); + if ($fk_country) { + $sql .= " AND (t.fk_country = ".((int) $fk_country); + $sql .= " OR t.fk_country is null)"; + } + // Add sql filters + if ($sqlfilters) { + $errormessage = ''; + $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage); + if ($errormessage) { + throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage); + } + } + + $sql .= $this->db->order($sortfield, $sortorder); + + if ($limit) { + if ($page < 0) { + $page = 0; + } + $offset = $limit * $page; + + $sql .= $this->db->plimit($limit, $offset); + } + + $result = $this->db->query($sql); + + if ($result) { + $num = $this->db->num_rows($result); + $min = min($num, ($limit <= 0 ? $num : $limit)); + for ($i = 0; $i < $min; $i++) { + $holiday = $this->db->fetch_object($result); + $tmplabel = $langs->trans($holiday->code); + if ($tmplabel != $holiday->code) { + $holiday->label = $tmplabel; + } + //$this->translateLabel($holiday, $lang, 'Holiday', array('dict')); + $list[] = $holiday; + } + } else { + throw new RestException(503, 'Error when retrieving list of public holiday : '.$this->db->lasterror()); + } + + return $list; + } /** * Get the list of contacts types. From 280ca206001d5c07af6aa788ebf0283507d00f5c Mon Sep 17 00:00:00 2001 From: Jon Bendtsen Date: Tue, 25 Nov 2025 14:32:28 +0100 Subject: [PATCH 4/8] more API endpoints checks multicompany auth with the same error message (#36409) Co-authored-by: Jon Bendtsen --- test/hurl/api/comm/propal/10_propal.hurl | 8 ++++++-- test/hurl/api/commande/10_commande.hurl | 9 +++++++-- test/hurl/api/compta/facture/10_facture.hurl | 8 ++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/test/hurl/api/comm/propal/10_propal.hurl b/test/hurl/api/comm/propal/10_propal.hurl index 4121ca0eacc..6dd1a46c448 100644 --- a/test/hurl/api/comm/propal/10_propal.hurl +++ b/test/hurl/api/comm/propal/10_propal.hurl @@ -84,8 +84,12 @@ HTTP 200 # GET proposals DOLAPIENTITY: 2 GET http://{{hostnport}}/api/index.php/proposals DOLAPIENTITY: 2 -HTTP 403 -{"error":{"code":403,"message":"Forbidden"}} +HTTP 401 +[Asserts] +jsonpath "$.error" exists +jsonpath "$.error.code" == 401 +jsonpath "$.error.message" contains "Unauthorized" +jsonpath "$.error.message" contains "Token not valid (may be a typo or a wrong entity)" # Capture a proposal ID number GET http://{{hostnport}}/api/index.php/proposals?limit=1 diff --git a/test/hurl/api/commande/10_commande.hurl b/test/hurl/api/commande/10_commande.hurl index 2dbded46fc5..e2007a82e5e 100644 --- a/test/hurl/api/commande/10_commande.hurl +++ b/test/hurl/api/commande/10_commande.hurl @@ -84,8 +84,13 @@ HTTP 200 # GET orders DOLAPIENTITY: 2 GET http://{{hostnport}}/api/index.php/orders DOLAPIENTITY: 2 -HTTP 403 -{"error":{"code":403,"message":"Forbidden"}} +HTTP 401 +[Asserts] +jsonpath "$.error" exists +jsonpath "$.error.code" == 401 +jsonpath "$.error.message" contains "Unauthorized" +jsonpath "$.error.message" contains "Token not valid (may be a typo or a wrong entity)" + # Capture a order ID number GET http://{{hostnport}}/api/index.php/orders?limit=1 diff --git a/test/hurl/api/compta/facture/10_facture.hurl b/test/hurl/api/compta/facture/10_facture.hurl index beac6d8500f..6ffe7b11c4f 100644 --- a/test/hurl/api/compta/facture/10_facture.hurl +++ b/test/hurl/api/compta/facture/10_facture.hurl @@ -77,8 +77,12 @@ HTTP 200 # GET invoices DOLAPIENTITY: 2 GET http://{{hostnport}}/api/index.php/invoices DOLAPIENTITY: 2 -HTTP 403 -{"error":{"code":403,"message":"Forbidden"}} +HTTP 401 +[Asserts] +jsonpath "$.error" exists +jsonpath "$.error.code" == 401 +jsonpath "$.error.message" contains "Unauthorized" +jsonpath "$.error.message" contains "Token not valid (may be a typo or a wrong entity)" # Capture a invoice ID number GET http://{{hostnport}}/api/index.php/invoices?limit=1 From 1e484dda550ede8b6c75aa8c1140ad9caddb61ab Mon Sep 17 00:00:00 2001 From: Regis Houssin Date: Tue, 25 Nov 2025 14:39:30 +0100 Subject: [PATCH 5/8] NEW webportal member authentication only with WEBPORTAL_LOGIN_BY_MEMBER_ACCOUNT (#36259) * NEW webportal member authentication only * FIX missing translation * FIX php-stan errors * FIX php-stan : cryptType is always "auto" ! * FIX remove CrypType * FIX php-stan error * FIX phan error * FIX CI versions * FIX remove wrong fix * FIX pre-commit error * FIX phan error * FIX php-stan error * FIX avoid php warning * FIXME this check is not valid --- htdocs/core/class/fields/datefield.class.php | 13 +- htdocs/core/class/fieldsmanager.class.php | 10 +- htdocs/core/class/html.form.class.php | 2 +- htdocs/langs/en_US/website.lang | 2 + htdocs/public/webportal/logout.php | 7 +- .../public/webportal/webportal.main.inc.php | 168 +++++++++++------- htdocs/webportal/class/context.class.php | 73 ++++++-- 7 files changed, 190 insertions(+), 85 deletions(-) diff --git a/htdocs/core/class/fields/datefield.class.php b/htdocs/core/class/fields/datefield.class.php index 191f923c30e..699431a93c3 100644 --- a/htdocs/core/class/fields/datefield.class.php +++ b/htdocs/core/class/fields/datefield.class.php @@ -90,8 +90,14 @@ class DateField extends CommonField $value = '-1'; } + // Do not whow "add now link" (eg for birthday date) + $addnowlink = 1; + if (in_array($fieldInfos->key, array('birth'))) { + $addnowlink = 0; + } + // TODO Must also support $moreAttrib and $moreCss ? - return self::$form->selectDate($value, $htmlName, 0, 0, $required, '', 1, 1, 0, 1); + return self::$form->selectDate($value, $htmlName, 0, 0, $required, '', 1, $addnowlink, 0, 1); } /** @@ -139,9 +145,10 @@ class DateField extends CommonField { $result = parent::verifyFieldValue($fieldInfos, $key, $value); if ($result && !$this->isEmptyValue($fieldInfos, $value)) { - if (!self::$validator->isTimestamp($value)) { + // FIXME this check is not valid + /*if (!self::$validator->isTimestamp($value)) { return false; - } + }*/ $result = true; } diff --git a/htdocs/core/class/fieldsmanager.class.php b/htdocs/core/class/fieldsmanager.class.php index 77ca467acaa..deb92f82546 100644 --- a/htdocs/core/class/fieldsmanager.class.php +++ b/htdocs/core/class/fieldsmanager.class.php @@ -421,10 +421,12 @@ class FieldsManager // Get extra fields $fields = array(); if (isset($extrafields->attributes[$object->table_element]) && is_array($extrafields->attributes[$object->table_element])) { - $keyPrefix = 'options_'; - foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) { - $fieldInfos = $this->getFieldInfosFromExtraField($object, $extrafields, $key, $mode, $params); - $fields[$keyPrefix . $key] = $fieldInfos; + if (isset($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label'])) { + $keyPrefix = 'options_'; + foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) { + $fieldInfos = $this->getFieldInfosFromExtraField($object, $extrafields, $key, $mode, $params); + $fields[$keyPrefix . $key] = $fieldInfos; + } } } diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index e96a7121f09..8171caade58 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -9259,7 +9259,7 @@ class Form $parent_properties = getElementProperties($objecttmp->parent_element); $sql .= " INNER JOIN " . $this->db->prefix() . $this->db->sanitize($parent_properties['table_element']) . " as o ON o.rowid = t.".$objecttmp->fk_parent_attribute; } - if (in_array($objecttmp->parent_element, ['commande', 'propal', 'facture', 'expedition'])) { + if (!empty($objecttmp->parent_element) && in_array($objecttmp->parent_element, ['commande', 'propal', 'facture', 'expedition'])) { $sql .= " LEFT JOIN " . $this->db->prefix() . "product as p ON p.rowid = t.fk_product"; } if (!empty($objecttmp->ismultientitymanaged)) { diff --git a/htdocs/langs/en_US/website.lang b/htdocs/langs/en_US/website.lang index 42a3abed395..75ab44e0f84 100644 --- a/htdocs/langs/en_US/website.lang +++ b/htdocs/langs/en_US/website.lang @@ -231,12 +231,14 @@ AriaPageX=Page %s WebPortalError404=Page not found WebPortalErrorPageNotExist=Page not exist WebPortalErrorFetchThirdPartyAccountFromLogin=Error when loading third-party account (login : %s) +WebPortalErrorFetchMemberAccountFromLogin=Error when loading member account (login : %s) WebPortalErrorAuthentication=Authentication error WebPortalErrorFetchLoggedThirdPartyAccount=Error when loading third-party account (login : %s) WebPortalErrorFetchLoggedUser=Error when loading user (Id : %s) WebPortalErrorFetchLoggedThirdParty=Error when loading third-party (Id : %s) WebPortalErrorFetchLoggedMember=Error when loading member (Id : %s) WebPortalErrorFetchLoggedPartnership=Error when loading partnership (Third-party Id : %s, Member Id : %s) +WebPortalErrorNoFetchMethod=Error no fetch method found for authentication DownloadZip=Download the zip ExportIntoGIT=Export into server directory WebPortalMember=Membership diff --git a/htdocs/public/webportal/logout.php b/htdocs/public/webportal/logout.php index ec8a4c5f136..d007f9390c3 100644 --- a/htdocs/public/webportal/logout.php +++ b/htdocs/public/webportal/logout.php @@ -48,7 +48,12 @@ if (session_status() === PHP_SESSION_ACTIVE) { } // Not sure this is required -if (isset($_SESSION['webportal_logged_thirdparty_account_id'])) unset($_SESSION['webportal_logged_thirdparty_account_id']); +if (isset($_SESSION['webportal_logged_thirdparty_account_id'])) { + unset($_SESSION['webportal_logged_thirdparty_account_id']); +} +if (isset($_SESSION['webportal_logged_member_account_id'])) { + unset($_SESSION['webportal_logged_member_account_id']); +} if (GETPOST('noredirect')) { return; diff --git a/htdocs/public/webportal/webportal.main.inc.php b/htdocs/public/webportal/webportal.main.inc.php index 793790d0fc9..4e219eedf40 100644 --- a/htdocs/public/webportal/webportal.main.inc.php +++ b/htdocs/public/webportal/webportal.main.inc.php @@ -155,6 +155,7 @@ if (getDolGlobalInt('WEBPORTAL_LOGIN_BY_MODULE') && !empty($conf->modules_parts[ } elseif (empty($reshook)) { $admin_error_messages = array(); $webportal_logged_thirdparty_account_id = isset($_SESSION["webportal_logged_thirdparty_account_id"]) && $_SESSION["webportal_logged_thirdparty_account_id"] > 0 ? $_SESSION["webportal_logged_thirdparty_account_id"] : 0; + $webportal_logged_member_account_id = isset($_SESSION["webportal_logged_member_account_id"]) && $_SESSION["webportal_logged_member_account_id"] > 0 ? $_SESSION["webportal_logged_member_account_id"] : 0; if (!$context->userIsLog()) { // It is not already authenticated and it requests the login / password @@ -188,23 +189,41 @@ if (getDolGlobalInt('WEBPORTAL_LOGIN_BY_MODULE') && !empty($conf->modules_parts[ // $error++; //} - if (!$error) { + if (!$error && (isModEnabled('societe') && !getDolGlobalInt('WEBPORTAL_LOGIN_BY_MEMBER_ACCOUNT'))) { // fetch third-party account from login and account type $thirdparty_account_id = $context->getThirdPartyAccountFromLogin($login, $password); if ($thirdparty_account_id <= 0) { $error++; dol_syslog($langs->transnoentitiesnoconv('WebPortalErrorFetchThirdPartyAccountFromLogin', $login), LOG_WARNING); - $context->setEventMessage($langs->transnoentitiesnoconv('WebPortalErrorAuthentication'), 'errors'); } else { $_SESSION["webportal_logged_thirdparty_account_id"] = $thirdparty_account_id; $webportal_logged_thirdparty_account_id = $thirdparty_account_id; $context->controller = 'default'; $context->initController(); } + } elseif (!$error && isModEnabled('member') && getDolGlobalInt('WEBPORTAL_LOGIN_BY_MEMBER_ACCOUNT') && !getDolGlobalString('ADHERENT_LOGIN_NOT_REQUIRED')) { + // fetch member account from login + $member_account_id = $context->getMemberAccountFromLogin($login, $password); + if ($member_account_id <= 0) { + $error++; + dol_syslog($langs->transnoentitiesnoconv('WebPortalErrorFetchMemberAccountFromLogin', $login), LOG_WARNING); + } else { + $_SESSION["webportal_logged_member_account_id"] = $member_account_id; + $webportal_logged_member_account_id = $member_account_id; + $context->controller = 'default'; + $context->initController(); + } + } else { + $error++; + dol_syslog($langs->transnoentitiesnoconv('WebPortalErrorNoFetchMethod'), LOG_WARNING); + } + + if ($error) { + $context->setEventMessage($langs->transnoentitiesnoconv('WebPortalErrorAuthentication'), 'errors'); } } - if (empty($webportal_logged_thirdparty_account_id)) { + if (empty($webportal_logged_thirdparty_account_id) && empty($webportal_logged_member_account_id)) { // Set cookie for timeout management if (getDolGlobalString('MAIN_SESSION_TIMEOUT')) { dolSetCookie($sessiontimeout, getDolGlobalString('MAIN_SESSION_TIMEOUT'), 0); @@ -216,21 +235,43 @@ if (getDolGlobalInt('WEBPORTAL_LOGIN_BY_MODULE') && !empty($conf->modules_parts[ } if (!$error && $context->userIsLog()) { + $logged_member = null; + $websiteaccount = null; // We are already into an authenticated session - $websiteaccount = new SocieteAccount($db); - $result = $websiteaccount->fetch($webportal_logged_thirdparty_account_id); + if (isModEnabled('member') && getDolGlobalInt('WEBPORTAL_LOGIN_BY_MEMBER_ACCOUNT') && !getDolGlobalString('ADHERENT_LOGIN_NOT_REQUIRED')) { + // get member + $logged_member = new WebPortalMember($db); + $result = $logged_member->fetch($webportal_logged_member_account_id); + if ($result <= 0) { + $error++; - if ($result <= 0) { - $error++; + // Account has been removed after login + dol_syslog("Can't load member account (ID: $webportal_logged_member_account_id) even if session logged.", LOG_WARNING); + session_destroy(); + session_set_cookie_params(0, '/', null, !empty($dolibarr_main_force_https), true); // Add tag secure and httponly on session cookie + session_name($sessionname); + session_start(); - // Account has been removed after login - dol_syslog("Can't load third-party account (ID: $webportal_logged_thirdparty_account_id) even if session logged.", LOG_WARNING); - session_destroy(); - session_set_cookie_params(0, '/', null, !empty($dolibarr_main_force_https), true); // Add tag secure and httponly on session cookie - session_name($sessionname); - session_start(); + $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedMember', (string) $webportal_logged_member_account_id); + dol_syslog($error_msg, LOG_ERR); + $context->setEventMessage($error_msg, 'errors'); + } + } else { + $websiteaccount = new SocieteAccount($db); + $result = $websiteaccount->fetch($webportal_logged_thirdparty_account_id); - $context->setEventMessage($langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedThirdPartyAccount', $webportal_logged_thirdparty_account_id), 'errors'); + if ($result <= 0) { + $error++; + + // Account has been removed after login + dol_syslog("Can't load third-party account (ID: $webportal_logged_thirdparty_account_id) even if session logged.", LOG_WARNING); + session_destroy(); + session_set_cookie_params(0, '/', null, !empty($dolibarr_main_force_https), true); // Add tag secure and httponly on session cookie + session_name($sessionname); + session_start(); + + $context->setEventMessage($langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedThirdPartyAccount', $webportal_logged_thirdparty_account_id), 'errors'); + } } if (!$error) { @@ -253,69 +294,76 @@ if (getDolGlobalInt('WEBPORTAL_LOGIN_BY_MODULE') && !empty($conf->modules_parts[ } } - if (!$error) { - // get third-party - $logged_thirdparty = $websiteaccount->thirdparty; - if (!$logged_thirdparty || !($logged_thirdparty->id > 0)) { - $result = $websiteaccount->fetch_thirdparty(); - - if ($result < 0) { - $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedThirdParty', (string) $websiteaccount->fk_soc); - //dol_syslog("Can't load third-party (ID: ".$websiteaccount->fk_soc.") even if session logged.", LOG_ERR); - dol_syslog($error_msg, LOG_ERR); - $context->setEventMessage($error_msg, 'errors'); - $error++; - } - } - + if (!getDolGlobalInt('WEBPORTAL_LOGIN_BY_MEMBER_ACCOUNT')) { if (!$error) { + // get third-party $logged_thirdparty = $websiteaccount->thirdparty; + if (!$logged_thirdparty || !($logged_thirdparty->id > 0)) { + $result = $websiteaccount->fetch_thirdparty(); - // get member - $logged_member = new WebPortalMember($db); - $result = $logged_member->fetch(0, '', $websiteaccount->thirdparty->id); - if ($result < 0) { - $error++; - $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedMember', (string) $websiteaccount->thirdparty->id); - dol_syslog($error_msg, LOG_ERR); - $context->setEventMessage($error_msg, 'errors'); - } - - if (isModEnabled('partnership') && !$error && $logged_member->id > 0) { - // get partnership - $logged_partnership = new WebPortalPartnership($db); - // @phan-suppress-next-line PhanPluginSuspiciousParamPosition - $result = $logged_partnership->fetch(0, '', $logged_member->id, $websiteaccount->thirdparty->id); if ($result < 0) { - $error++; - $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedPartnership', (string) $websiteaccount->thirdparty->id, (string) $logged_member->id); + $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedThirdParty', (string) $websiteaccount->fk_soc); + //dol_syslog("Can't load third-party (ID: ".$websiteaccount->fk_soc.") even if session logged.", LOG_ERR); dol_syslog($error_msg, LOG_ERR); $context->setEventMessage($error_msg, 'errors'); + $error++; } } if (!$error) { - if ($logged_thirdparty->default_lang != $langs->defaultlang && !defined('WEBPORTAL_NOREQUIRETRAN')) { - if (!is_object($langs)) { // This can occurs when calling page with NOREQUIRETRAN defined, however we need langs for error messages. - include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php'; - $langs = new Translate("", $conf); - $langs->setDefaultLang($logged_thirdparty->default_lang); + $logged_thirdparty = $websiteaccount->thirdparty; + + // get member + $logged_member = new WebPortalMember($db); + $result = $logged_member->fetch(0, '', $websiteaccount->thirdparty->id); + if ($result < 0) { + $error++; + $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedMember', (string) $websiteaccount->thirdparty->id); + dol_syslog($error_msg, LOG_ERR); + $context->setEventMessage($error_msg, 'errors'); + } + + if (isModEnabled('partnership') && !$error && $logged_member->id > 0) { + // get partnership + $logged_partnership = new WebPortalPartnership($db); + // @phan-suppress-next-line PhanPluginSuspiciousParamPosition + $result = $logged_partnership->fetch(0, '', $logged_member->id, $websiteaccount->thirdparty->id); + if ($result < 0) { + $error++; + $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedPartnership', (string) $websiteaccount->thirdparty->id, (string) $logged_member->id); + dol_syslog($error_msg, LOG_ERR); + $context->setEventMessage($error_msg, 'errors'); } - $langs->loadLangs(array('website', 'main')); } - $context->logged_user = $logged_user; - $context->logged_thirdparty = $logged_thirdparty; - $context->logged_member = $logged_member; - if (!empty($logged_partnership)) { - $context->logged_partnership = $logged_partnership; - } + if (!$error) { + if ($logged_thirdparty->default_lang != $langs->defaultlang && !defined('WEBPORTAL_NOREQUIRETRAN')) { + if (!is_object($langs)) { // This can occurs when calling page with NOREQUIRETRAN defined, however we need langs for error messages. + include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php'; + $langs = new Translate("", $conf); + $langs->setDefaultLang($logged_thirdparty->default_lang); + } + $langs->loadLangs(array('website', 'main')); + } - global $user; // set global user as logged user (used for hooks in external modules) - $user = $context->logged_user; + $context->logged_thirdparty = $logged_thirdparty; + if (!empty($logged_partnership)) { + $context->logged_partnership = $logged_partnership; + } + } } } } + + if (!$error) { + $context->logged_user = $logged_user; + if (!empty($logged_member)) { + $context->logged_member = $logged_member; + } + + global $user; // set global user as logged user (used for hooks in external modules) + $user = $context->logged_user; + } } } } diff --git a/htdocs/webportal/class/context.class.php b/htdocs/webportal/class/context.class.php index 0120f30ef1e..11eea65bafd 100644 --- a/htdocs/webportal/class/context.class.php +++ b/htdocs/webportal/class/context.class.php @@ -476,6 +476,8 @@ class Context if (!empty($_SESSION["webportal_logged_thirdparty_account_id"])) { return true; + } elseif (!empty($_SESSION["webportal_logged_member_account_id"])) { + return true; } else { return false; } @@ -720,29 +722,16 @@ class Context if ($obj) { $passcrypted = $obj->pass_crypted; - // Check crypted password - $cryptType = ''; - if (getDolGlobalString('DATABASE_PWD_ENCRYPTED')) { - $cryptType = getDolGlobalString('DATABASE_PWD_ENCRYPTED'); - } - - // By default, we use default setup for encryption rule - if (!in_array($cryptType, array('auto'))) { - $cryptType = 'auto'; - } - // Check crypted password according to crypt algorithm - if ($cryptType == 'auto') { - if ($passcrypted && dol_verifyHash($pass, $passcrypted, '0')) { - $passok = true; - } + if ($passcrypted && dol_verifyHash($pass, $passcrypted, '0')) { + $passok = true; } // Password ok ? if ($passok) { $id = $obj->id; } else { - dol_syslog(__METHOD__ .' Authentication KO bad password for ' . $login . ', cryptType=' . $cryptType, LOG_NOTICE); + dol_syslog(__METHOD__ .' Authentication KO bad password for ' . $login . ', cryptType=auto', LOG_NOTICE); sleep(1); // Brut force protection. Must be same delay when login is not valid return -3; } @@ -758,4 +747,56 @@ class Context return $id; } + + /** + * Try to find the member account id from + * + * @param string $login Login + * @param string $pass Password + * @return int Member account id || <0 if error + */ + public function getMemberAccountFromLogin($login, $pass) + { + $id = 0; + + $sql = "SELECT a.rowid as id, a.pass_crypted"; + $sql .= " FROM " . $this->db->prefix() . "adherent as a"; + $sql .= " WHERE a.login = '" . $this->db->escape($login) . "'"; + $sql .= " AND a.statut = 1"; + $sql .= " AND a.entity IN (" . getEntity('member') . ")"; + + dol_syslog(__METHOD__ . ' Try to find the member account id for login"' . $login . '"', LOG_DEBUG); + $result = $this->db->query($sql); + if ($result) { + if ($this->db->num_rows($result) == 1) { + $passok = false; + $obj = $this->db->fetch_object($result); + if ($obj) { + $passcrypted = $obj->pass_crypted; + + // Check crypted password according to crypt algorithm + if ($passcrypted && dol_verifyHash($pass, $passcrypted, '0')) { + $passok = true; + } + + // Password ok ? + if ($passok) { + $id = $obj->id; + } else { + dol_syslog(__METHOD__ .' Authentication KO bad password for ' . $login . ', cryptType=auto', LOG_NOTICE); + sleep(1); // Brut force protection. Must be same delay when login is not valid + return -3; + } + } + } else { + dol_syslog(__METHOD__ . ' Many member account found for login"' . $login . '"', LOG_ERR); + return -2; + } + } else { + $this->error = $this->db->lasterror(); + return -1; + } + + return $id; + } } From c5c0b85498acd9d39b5f79e49382ee3698f74a90 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 25 Nov 2025 18:18:02 +0100 Subject: [PATCH 6/8] Debug v23 --- README.md | 2 +- htdocs/admin/system/security.php | 14 ++++++++++++++ htdocs/conf/conf.php.example | 6 +++--- htdocs/core/lib/functions.lib.php | 4 ++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3c2b2e0ac43..8577be43b8b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Downloads per day](https://img.shields.io/sourceforge/dw/dolibarr.svg) ![Docker hub pulls](https://img.shields.io/docker/pulls/dolibarr/dolibarr.svg) -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg?style=flat-square)](https://php.net/) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg?style=flat-square)](https://php.net/) [![GitHub release](https://img.shields.io/github/v/release/Dolibarr/dolibarr)](https://github.com/Dolibarr/dolibarr) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5521/badge)](https://bestpractices.coreinfrastructure.org/projects/5521) diff --git a/htdocs/admin/system/security.php b/htdocs/admin/system/security.php index 3d1b5995ed7..1d4a3c12d2c 100644 --- a/htdocs/admin/system/security.php +++ b/htdocs/admin/system/security.php @@ -35,6 +35,11 @@ require '../../main.inc.php'; * @var string $conffile // $conffile is defined into filefunc.inc.php * @var string $dolibarr_main_prod * @var string $dolibarr_main_document_root + * @var string $dolibarr_main_restrict_os_commands + * @var string $dolibarr_main_restrict_eval_methods + * @var string $dolibarr_main_restrict_ip + * @var string $dolibarr_main_db_pass + * @var string $dolibarr_main_db_encrypted_pass */ require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; @@ -387,6 +392,15 @@ if (empty($dolibarr_main_restrict_os_commands)) { print '     ('.$langs->trans("RecommendedValueIs", 'mysqldump, mysql, pg_dump, pg_restore, mariadb, mariadb-dump, clamdscan').')'; print '
'; +print '$dolibarr_main_restrict_eval_methods: '; +if (empty($dolibarr_main_restrict_eval_methods)) { + print $langs->trans("None"); +} else { + print $dolibarr_main_restrict_eval_methods; +} +print '     ('.$langs->trans("RecommendedValueIs", 'getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,dol_concat,preg_match').')'; +print '
'; + if (!getDolGlobalString('SECURITY_DISABLE_TEST_ON_OBFUSCATED_CONF')) { print '$dolibarr_main_db_pass: '; if (!empty($dolibarr_main_db_pass) && empty($dolibarr_main_db_encrypted_pass)) { diff --git a/htdocs/conf/conf.php.example b/htdocs/conf/conf.php.example index b6a041f1032..66b79e84ccf 100644 --- a/htdocs/conf/conf.php.example +++ b/htdocs/conf/conf.php.example @@ -316,11 +316,11 @@ $dolibarr_main_restrict_os_commands='mariadb-dump, mariadb, mysqldump, mysql, pg // ================================== // A whitelist of functions and methods to restrict the commands you can execute in a custom calculated fields, like "computed fields" of // extrafields or string conditions of extrafields. -// Default value: 'getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,round,dol_now,preg_match' +// Default value: 'getDolGlobalString, getDolGlobalInt, getDolCurrency, fetchNoCompute, hasRight, isModEnabled, isStringVarMatching, abs, round, dol_now, preg_match' // Examples: -// $dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,dol_concat,preg_match'; +// $dolibarr_main_restrict_eval_methods='getDolGlobalString, getDolGlobalInt, getDolCurrency, fetchNoCompute, hasRight, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, dol_concat, preg_match'; // -$dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,preg_match'; +$dolibarr_main_restrict_eval_methods='getDolGlobalString, getDolGlobalInt, getDolCurrency, fetchNoCompute, hasRight, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, preg_match'; // dolibarr_main_disabled_modules // ================================== diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 87ef0d8acec..b3c9839cfcc 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -11935,10 +11935,10 @@ function dol_eval_standard($s, $hideerrors = 1, $onlysimplestring = '1') // Set $dolibarr_main_restrict_eval_methods_array if (!isset($dolibarr_main_restrict_eval_methods)) { - $dolibarr_main_restrict_eval_methods = 'getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,preg_match'; + $dolibarr_main_restrict_eval_methods = 'getDolGlobalString, getDolGlobalInt, getDolCurrency, fetchNoCompute, hasRight, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, preg_match'; } //print '$dolibarr_main_restrict_eval_methods = '.$dolibarr_main_restrict_eval_methods."\n"; - $dolibarr_main_restrict_eval_methods_array = explode(',', $dolibarr_main_restrict_eval_methods); + $dolibarr_main_restrict_eval_methods_array = explode(',', str_replace(" ", "", $dolibarr_main_restrict_eval_methods)); // Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")' From 6d42410dac87b9540081048acf870a156e85572d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 25 Nov 2025 19:39:21 +0100 Subject: [PATCH 7/8] Debug v23 --- htdocs/reception/card.php | 12 +++++------- htdocs/reception/class/reception.class.php | 4 ++-- htdocs/reception/dispatch.php | 3 +-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/htdocs/reception/card.php b/htdocs/reception/card.php index 55cbb825ee2..7b58aa5c5cf 100644 --- a/htdocs/reception/card.php +++ b/htdocs/reception/card.php @@ -1917,7 +1917,6 @@ if ($action == 'create' && $permissiontoadd) { $typeobject = ''; if (!empty($object->origin) && $object->origin_id > 0) { - $object->origin = 'CommandeFournisseur'; $typeobject = $object->origin; $origin = $object->origin; $origin_id = $object->origin_id; @@ -2086,7 +2085,7 @@ if ($action == 'create' && $permissiontoadd) { print "\n"; print ''; } - if ($typeobject == 'CommandeFournisseur' && $object->origin_object->id && isModEnabled("propal")) { + if (($typeobject == 'supplier_order' || $typeobject == 'CommandeFournisseur') && $object->origin_object->id && isModEnabled("propal")) { print ''; print $langs->trans("SupplierOrder").''; print ''; @@ -2477,26 +2476,25 @@ if ($action == 'create' && $permissiontoadd) { // Get list of products already sent for same source object into $alreadysent $alreadysent = array(); - //$origin = 'commande_fournisseur'; + if (empty($origin)) { + $origin = 'supplier_order'; + } if ($origin && $origin_id > 0) { $sql = "SELECT obj.rowid, obj.fk_product, obj.label, obj.description, obj.product_type as fk_product_type, obj.qty as qty_asked, obj.date_start, obj.date_end"; $sql .= ", ed.rowid as receptionline_id, ed.qty, ed.fk_reception as reception_id, ed.fk_entrepot"; $sql .= ", e.rowid as reception_id, e.ref as reception_ref, e.date_creation, e.date_valid, e.date_delivery, e.date_reception"; - //if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) $sql .= ", l.rowid as livraison_id, l.ref as livraison_ref, l.date_delivery, ld.qty as qty_received"; $sql .= ', p.label as product_label, p.ref, p.fk_product_type, p.rowid as prodid, p.tobatch as product_tobatch'; $sql .= ', p.description as product_desc'; $sql .= " FROM ".MAIN_DB_PREFIX."receptiondet_batch as ed"; $sql .= ", ".MAIN_DB_PREFIX."reception as e"; - $sql .= ", ".MAIN_DB_PREFIX.$origin."det as obj"; - //if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."delivery as l ON l.fk_reception = e.rowid LEFT JOIN ".MAIN_DB_PREFIX."deliverydet as ld ON ld.fk_delivery = l.rowid AND obj.rowid = ld.fk_origin_line"; + $sql .= ", ".MAIN_DB_PREFIX.(($origin == 'supplier_order') ? 'commande_fournisseur' : $origin)."det as obj"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON obj.fk_product = p.rowid"; $sql .= " WHERE e.entity IN (".getEntity('reception').")"; $sql .= " AND obj.fk_commande = ".((int) $origin_id); $sql .= " AND obj.rowid = ed.fk_elementdet"; $sql .= " AND ed.fk_reception = e.rowid"; $sql .= " AND ed.fk_reception !=".((int) $object->id); - //if ($filter) $sql.= $filter; $sql .= " ORDER BY obj.fk_product"; dol_syslog("get list of reception lines", LOG_DEBUG); diff --git a/htdocs/reception/class/reception.class.php b/htdocs/reception/class/reception.class.php index 0ead07ec0d0..a6cdddd9c0e 100644 --- a/htdocs/reception/class/reception.class.php +++ b/htdocs/reception/class/reception.class.php @@ -336,7 +336,7 @@ class Reception extends CommonObject $sql .= ", fk_incoterms, location_incoterms"; $sql .= ") VALUES ("; $sql .= "'(PROV)'"; - $sql .= ", ".((int) $conf->entity); + $sql .= ", ".((int) $this->entity); $sql .= ", ".($this->ref_supplier ? "'".$this->db->escape($this->ref_supplier)."'" : "null"); $sql .= ", '".$this->db->idate($this->date_creation)."'"; $sql .= ", ".((int) $user->id); @@ -746,7 +746,7 @@ class Reception extends CommonObject } } - if (!$error) { + if (!$error && $this->origin_id > 0) { // Change status of purchase order to "reception in process" or "totally received" $status = $this->getStatusDispatch(); if ($status < 0) { diff --git a/htdocs/reception/dispatch.php b/htdocs/reception/dispatch.php index e901cde50c8..f1bb7e61a6b 100644 --- a/htdocs/reception/dispatch.php +++ b/htdocs/reception/dispatch.php @@ -322,7 +322,6 @@ llxHeader('', $title, $help_url, '', 0, 0, $morejs, '', '', 'mod-reception page- if ($id > 0 || !empty($ref)) { $typeobject = ''; if (!empty($object->origin) && $object->origin_id > 0) { - $object->origin = 'CommandeFournisseur'; $typeobject = $object->origin; $origin = $object->origin; $origin_id = $object->origin_id; @@ -418,7 +417,7 @@ if ($id > 0 || !empty($ref)) { print "\n"; print ''; } - if ($typeobject == 'CommandeFournisseur' && $object->origin_object->id && isModEnabled("propal")) { + if (($typeobject == 'supplier_order' || $typeobject == 'order_supplier') && $object->origin_object->id && isModEnabled("propal")) { print ''; print $langs->trans("SupplierOrder").''; print ''; From 3218e19091356b24217037629ee9c973e9026561 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 25 Nov 2025 19:51:09 +0100 Subject: [PATCH 8/8] Debug v23 --- htdocs/core/lib/functions.lib.php | 2 +- htdocs/reception/card.php | 2 +- htdocs/workstation/class/api_workstations.class.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index a3ee1ba0ce6..09760b5112c 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -14651,7 +14651,7 @@ function getElementProperties($elementType) $classpath = $module . '/class'; $classfile = $module; $classname = preg_replace('/det$/', 'Line', $element); - if (in_array($module, array('expedition', 'propale', 'facture', 'contrat', 'fichinter', 'commandefournisseur'))) { + if (in_array($module, array('expedition', 'propale', 'facture', 'contrat', 'fichinter', 'supplier_order', 'commandefournisseur'))) { $classname = preg_replace('/det$/', 'Ligne', $element); } } diff --git a/htdocs/reception/card.php b/htdocs/reception/card.php index 7b58aa5c5cf..7aa47e710b1 100644 --- a/htdocs/reception/card.php +++ b/htdocs/reception/card.php @@ -1998,7 +1998,7 @@ if ($action == 'create' && $permissiontoadd) { $objectsrc = new Propal($db); $objectsrc->fetch($object->origin_object->id); } - if ($typeobject == 'CommandeFournisseur' && $object->origin_object->id && isModEnabled("supplier_order")) { + if (($typeobject == 'supplier_order' || $typeobject == 'CommandeFournisseur') && $object->origin_object->id && isModEnabled("supplier_order")) { $objectsrc = new CommandeFournisseur($db); $objectsrc->fetch($object->origin_object->id); } diff --git a/htdocs/workstation/class/api_workstations.class.php b/htdocs/workstation/class/api_workstations.class.php index f15c779e6bd..68e600143a1 100644 --- a/htdocs/workstation/class/api_workstations.class.php +++ b/htdocs/workstation/class/api_workstations.class.php @@ -197,13 +197,13 @@ class Workstations extends DolibarrApi { // phpcs:enable $object = parent::_cleanObjectDatas($object); + /** @var Workstation $object */ unset($object->statut); unset($object->regeximgext); unset($object->price_by_qty); unset($object->prices_by_qty_id); - unset($object->libelle); unset($object->product_id_already_linked); unset($object->reputations); unset($object->db);