Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop

This commit is contained in:
Laurent Destailleur 2025-11-25 21:04:24 +01:00
commit cb221fa2b8
20 changed files with 516 additions and 119 deletions

View file

@ -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)

View file

@ -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 ' &nbsp; &nbsp; <span class="opacitymedium">('.$langs->trans("RecommendedValueIs", 'mysqldump, mysql, pg_dump, pg_restore, mariadb, mariadb-dump, clamdscan').')</span>';
print '<br>';
print '<strong>$dolibarr_main_restrict_eval_methods</strong>: ';
if (empty($dolibarr_main_restrict_eval_methods)) {
print $langs->trans("None");
} else {
print $dolibarr_main_restrict_eval_methods;
}
print ' &nbsp; &nbsp; <span class="opacitymedium">('.$langs->trans("RecommendedValueIs", 'getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,dol_concat,preg_match').')</span>';
print '<br>';
if (!getDolGlobalString('SECURITY_DISABLE_TEST_ON_OBFUSCATED_CONF')) {
print '<strong>$dolibarr_main_db_pass</strong>: ';
if (!empty($dolibarr_main_db_pass) && empty($dolibarr_main_db_encrypted_pass)) {

View file

@ -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<Object|false>
* @phpstan-return array<Object|false>
*
* @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<Object|false>
* @phpstan-return array<Object|false>
*
* @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.

View file

@ -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
// ==================================

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -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)) {

View file

@ -11938,10 +11938,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")'
@ -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);
}
}

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
}
}
}

View file

@ -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;
@ -1999,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);
}
@ -2086,7 +2085,7 @@ if ($action == 'create' && $permissiontoadd) {
print "</td>\n";
print '</tr>';
}
if ($typeobject == 'CommandeFournisseur' && $object->origin_object->id && isModEnabled("propal")) {
if (($typeobject == 'supplier_order' || $typeobject == 'CommandeFournisseur') && $object->origin_object->id && isModEnabled("propal")) {
print '<tr><td>';
print $langs->trans("SupplierOrder").'</td>';
print '<td colspan="3">';
@ -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);

View file

@ -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) {

View file

@ -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 "</td>\n";
print '</tr>';
}
if ($typeobject == 'CommandeFournisseur' && $object->origin_object->id && isModEnabled("propal")) {
if (($typeobject == 'supplier_order' || $typeobject == 'order_supplier') && $object->origin_object->id && isModEnabled("propal")) {
print '<tr><td>';
print $langs->trans("SupplierOrder").'</td>';
print '<td colspan="3">';

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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