diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index e245a67683e..e7d330324dd 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -36,11 +36,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - - name: Setup MariaDB - uses: frederic34/setup-mariadb@v1 - with: - # mariadb-version: ${{ matrix.mariadb-version }} - database: travis # Specify your database name - name: Setup PHP uses: shivammathur/setup-php@v2 with: @@ -79,6 +74,12 @@ jobs: ${{ env.KEY_ROOT }}-${{ env.HASH }}- ${{ env.KEY_ROOT }}- + - name: Setup MariaDB + uses: frederic34/setup-mariadb@v1 + with: + # mariadb-version: ${{ matrix.mariadb-version }} + database: travis # Specify your database name + - name: Create local php.ini with open_basedir restrictions shell: cmd # Objective: separate step, and before database initialisation to verify open_basedir restrictions diff --git a/ChangeLog b/ChangeLog index 6f2c15dfe29..ee320d33ba3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,56 @@ English Dolibarr ChangeLog -------------------------------------------------------------- +***** ChangeLog for 20.0.1 compared to 20.0.0 ***** + +FIX: #30960 show and search extrafields (#31026) +FIX: #31076 Bad position of hooks +FIX: #31152 +FIX: #31237 (#31239) +FIX: #31241 (#31245) +FIX: accounting transfer when nb of lines is not zero but amount is zero +FIX: Add same security test when nuploading files from API than from GUI (#31114) +FIX: allow "class" into search string in website module +FIX: autoselect the fiscal period by default +FIX: avoid odt errors (#31126) +FIX: Backport fix fatal error on price with some truncating setup +FIX: better compatibility with some old extrafield syntax filter +FIX: box_actions.php still uses fk_user_done which no longer exists (#31190) +FIX: Browser Notification ko with firefox +FIX: custom CSS for WebPortal (#31022) +FIX: Debug calculation of the delay of purchase order +FIX: Debug option WORKFLOW_TICKET_LINK_CONTRACT. Bad id stored. Bad +FIX: dolFICalculatePaymentReference for Finland in functions_fi.lib.php (#31281) +FIX: Duplicate trigger printFieldPreListTitle +FIX: edit contract of intervention broken by CSRF protection +FIX: error return missing in mo creation when qty to consume is <= 0 (#31134) +FIX: Extrafields does not appear on form +FIX: FATAL ERROR abusively triggered due to incomplete regex (#31052) +FIX: if you call fetchLines several times, your $object->lines contains duplicates (#31167) +FIX: late order search option (v18+) (#30692) +FIX: late propal search option (v18+) (#30687) +FIX: Maxi debug edit/delete accounting transaction +FIX: member must be found to search the linked partnership (WebPortal) (#30977) +FIX: Missing picto on user link +FIX: mysql error during dump for enable sandbox M999999 (#31116) +FIX: OAuth generation of token for Microsoft, Stripe and Generic +FIX: param id in website account list from third-party card (#30975) +FIX: Position of box for shipping address in PDF +FIX: Protection to avoid an extrafield to be mandatory if computed +FIX: removed unreachable code (#31141) +FIX: Remove wrong button +FIX: retrieving user specific constant +FIX: Revert storing of ticket files into event dir, too many troubles. +FIX: Selection of country - state in resource +FIX: State dropdown is not working on User card #31198 (#31205) +FIX: Tool to convert into utf8 or utf8mb4 +FIX: use price() to display qty on a product's stats tab to avoid showing too many decimals when rounding errors are possible (#31165) +FIX: Warning visible when it should not +FIX: webhook must send POST in body. Add WEBHOOK_POST_SEND_DATA_IN_BODY +FIX: when qty is not an integer, apply price() (#31138) +FIX: Wrong price for BOM with workstation (#31142) +FIX: determine multi-currency price on object line create tpl (#28021) + ***** ChangeLog for 20.0.0 compared to 19.0 ***** diff --git a/htdocs/categories/class/api_categories.class.php b/htdocs/categories/class/api_categories.class.php index c0673d37d0b..0038a790be7 100644 --- a/htdocs/categories/class/api_categories.class.php +++ b/htdocs/categories/class/api_categories.class.php @@ -278,8 +278,8 @@ class Categories extends DolibarrApi throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login); } - if (!$this->category->delete(DolibarrApiAccess::$user)) { - throw new RestException(500, 'error when delete category'); + if ($this->category->delete(DolibarrApiAccess::$user) <= 0) { + throw new RestException(500, 'Error when delete category : ' . $this->category->error); } return array( diff --git a/htdocs/comm/action/card.php b/htdocs/comm/action/card.php index b54e23487fc..539aec3b8dd 100644 --- a/htdocs/comm/action/card.php +++ b/htdocs/comm/action/card.php @@ -2492,9 +2492,11 @@ if ($id > 0) { } // Priority - print ''.$langs->trans("Priority").''; - print($object->priority ? $object->priority : ''); - print ''; + if (getDolGlobalString('AGENDA_SUPPORT_PRIORITY_IN_EVENTS')) { + print '' . $langs->trans("Priority") . ''; + print($object->priority ? $object->priority : ''); + print ''; + } // Object linked (if link is for thirdparty, contact, project it is a recording error. We should not have links in link table // for such objects because there is already a dedicated field into table llx_actioncomm. diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index a4ddcecf418..2650e495ab8 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -3514,7 +3514,7 @@ class Form if (isModEnabled('stock') && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) { if ($user->hasRight('stock', 'lire')) { - $opt .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS')); + $opt .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'), 0, $langs, 0, 0); if ($objp->stock > 0) { $outval .= ' - '; diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 07dcbf87086..d1a278d8681 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -603,8 +603,12 @@ function getBrowserInfo($user_agent) $version = end($reg); } elseif (preg_match('/l[iy]n(x|ks)(\(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) { // MS products at end - $name = 'lynxlinks'; + $name = 'textbrowser'; $version = empty($reg[3]) ? '' : $reg[3]; + } elseif (preg_match('/w3m\/([\d\.]+)/i', $user_agent, $reg)) { + // MS products at end + $name = 'textbrowser'; + $version = empty($reg[1]) ? '' : $reg[1]; } if ($tablet) { @@ -6612,10 +6616,11 @@ function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $ if (dol_strlen($decpart) > $nbdecimal) { $nbdecimal = dol_strlen($decpart); } - // Si on depasse max - $max_nbdecimal = (int) str_replace('...', '', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN')); - if ($trunc && $nbdecimal > $max_nbdecimal) { - $nbdecimal = $max_nbdecimal; + + // If nbdecimal is higher than max to show + $nbdecimalmaxshown = (int) str_replace('...', '', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN')); + if ($trunc && $nbdecimal > $nbdecimalmaxshown) { + $nbdecimal = $nbdecimalmaxshown; if (preg_match('/\.\.\./i', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'))) { // If output is truncated, we show ... $end = '...'; @@ -12263,6 +12268,7 @@ function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $st * $arrayforbutaction = array( * 10 => array('attr' => array('class'=>''), 'lang'=>'propal', 'enabled'=>isModEnabled("propal"), 'perm'=>$user->hasRight('propal', 'creer'), 'label' => 'AddProp', 'url'=>'/comm/propal/card.php?action=create&projectid='.$object->id.'&socid='.$object->socid), * 20 => array('attr' => array('class'=>''), 'lang'=>'mymodule', 'enabled'=>isModEnabled("mymodule"), 'perm'=>$user->hasRight('mymodule', 'write'), 'label' => 'MyModuleAction', 'urlroot'=>dol_build_patch('/mymodule/mypage.php?action=create')), + * 30 => array('attr' => array('class'=>''), 'lang'=>'mymodule', 'enabled'=>isModEnabled("mymodule"), 'perm'=>$user->hasRight('mymodule', 'write'), 'label' => 'MyModuleOtherAction', 'urlraw' => '# || external Url || javascript: || tel: || mailto:' ), * ); ); * @param string $id Attribute id of action button. Example 'action-delete'. This can be used for full ajax confirm if this code is reused into the ->formconfirm() method. * @param int|boolean $userRight User action right @@ -12328,17 +12334,18 @@ function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = $langs->load($subbutton['lang']); } - if (!empty($subbutton['urlroot'])) { - $tmpurl = $subbutton['urlroot'].(empty($params['backtopage']) ? '' : '&backtopage='.urlencode($params['backtopage'])); + if (!empty($subbutton['urlraw'])) { + $tmpurl = $subbutton['urlraw']; // Use raw url, no url completion, use only what developer send } else { - $tmpurl = DOL_URL_ROOT.$subbutton['url'].(empty($params['backtopage']) ? '' : '&backtopage='.urlencode($params['backtopage'])); + $tmpurl = !empty($subbutton['urlroot']) ? $subbutton['urlroot'] : $subbutton['url']; + $tmpurl = dolCompletUrlForDropdownButton($tmpurl, $params, empty($subbutton['urlroot'])); } $subbuttonparam = array(); if (!empty($subbutton['attr'])) { $subbuttonparam['attr'] = $subbutton['attr']; } - $subbuttonparam['isDropDown'] = (empty($params['isDropDown']) ? $subbutton['isDropDown'] : $params['isDropDown']); + $subbuttonparam['isDropDown'] = (empty($params['isDropDown']) ? ($subbutton['isDropDown']??false) : $params['isDropDown']); $out .= dolGetButtonAction('', $langs->trans($subbutton['label']), 'default', $tmpurl, $subbutton['id'] ?? '', $subbutton['perm'], $subbuttonparam); } @@ -12350,10 +12357,11 @@ function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = $langs->load($subbutton['lang']); } - if (!empty($subbutton['urlroot'])) { - $tmpurl = $subbutton['urlroot'].(empty($params['backtopage']) ? '' : '&backtopage='.urlencode($params['backtopage'])); + if (!empty($subbutton['urlraw'])) { + $tmpurl = $subbutton['urlraw']; // Use raw url, no url completion, use only what developer send } else { - $tmpurl = DOL_URL_ROOT.$subbutton['url'].(empty($params['backtopage']) ? '' : '&backtopage='.urlencode($params['backtopage'])); + $tmpurl = !empty($subbutton['urlroot']) ? $subbutton['urlroot'] : $subbutton['url']; + $tmpurl = dolCompletUrlForDropdownButton($tmpurl, $params, empty($subbutton['urlroot'])); } $out .= dolGetButtonAction('', $langs->trans($subbutton['label']), 'default', $tmpurl, '', $subbutton['perm'], $params); @@ -12488,6 +12496,42 @@ function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = } } + +/** + * An function to complete dropdown url in dolGetButtonAction + * + * @param string $url the Url to complete + * @param array|array $params params of dolGetButtonAction function + * @param bool $addDolUrlRoot to add root url + * @return string + */ +function dolCompletUrlForDropdownButton(string $url, array $params, bool $addDolUrlRoot = true) +{ + if (empty($url)) { + return ''; + } + + $parsedUrl = parse_url($url); + if ((isset($parsedUrl['scheme']) && in_array($parsedUrl['scheme'], ['javascript', 'mailto', 'tel'])) || strpos($url, '#') === 0) { + return $url; + } + + if (!empty($parsedUrl['query'])) { + // Use parse_str() function to parse the string passed via URL + parse_str($parsedUrl['query'], $urlQuery); + if (!isset($urlQuery['backtopage']) && isset($params['backtopage'])) { + $url.= '&backtopage='.urlencode($params['backtopage']); + } + } + + if (!isset($parsedUrl['scheme']) && $addDolUrlRoot) { + $url = DOL_URL_ROOT.$url; + } + + return $url; +} + + /** * Add space between dolGetButtonTitle * diff --git a/htdocs/core/lib/functions_fi.lib.php b/htdocs/core/lib/functions_fi.lib.php index 75d28b51975..e09f49bd589 100644 --- a/htdocs/core/lib/functions_fi.lib.php +++ b/htdocs/core/lib/functions_fi.lib.php @@ -1,5 +1,6 @@ + * Copyright (C) 2024 Mikko Virtanen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,16 +38,16 @@ function dolFICalculatePaymentReference($invoice_number, $statut, $use_rf) { if ($statut >= 1) { $invoice_number = preg_replace('/[^0-9]/', '', $invoice_number); // Keep only numbers - $invoice_number = ltrim($invoice_number, '0'); //Remove any leading zero or zeros - $invoice_number = strrev($invoice_number); // Reverse the reference number - $coefficients = array(7, 3, 1, 7, 3); // Define the coefficient numbers + $invoice_number = ltrim($invoice_number, '0'); // Remove leading zeros + $coefficients = array(7, 3, 1); // Define the coefficient numbers (rotating 7, 3, 1) $sum = 0; $stlen_invoice_number = (int) strlen($invoice_number); - for ($i = 0; $i < $stlen_invoice_number; $i++) { // Calculate the sum using coefficients - $sum += (int) $invoice_number[$i] * $coefficients[$i % 5]; + // Calculate the weighted sum from right to left + for ($i = 0; $i < $stlen_invoice_number; $i++) { + $sum += (int) $invoice_number[$stlen_invoice_number - $i - 1] * $coefficients[$i % 3]; } $check_digit = (10 - ($sum % 10)) % 10; // Calculate the check digit - $bank_reference_fi = strrev($invoice_number) . $check_digit; // Concatenate the Reversed reference number and the check digit + $bank_reference_fi = $invoice_number . $check_digit; // Concatenate the reference number and the check digit if ($use_rf) { // SEPA RF creditor reference $reference_with_suffix = $bank_reference_fi . "271500"; // Append "271500" to the end of the payment reference number $remainder = (int) bcmod($reference_with_suffix, '97'); // Calculate the remainder when dividing by 97 diff --git a/htdocs/filefunc.inc.php b/htdocs/filefunc.inc.php index 2a3c0a7e4e8..ec66d6cf58b 100644 --- a/htdocs/filefunc.inc.php +++ b/htdocs/filefunc.inc.php @@ -35,7 +35,7 @@ if (!defined('DOL_APPLICATION_TITLE')) { define('DOL_APPLICATION_TITLE', 'Dolibarr'); } if (!defined('DOL_VERSION')) { - define('DOL_VERSION', '20.0.0'); // a.b.c-alpha, a.b.c-beta, a.b.c-rcX or a.b.c + define('DOL_VERSION', '20.0.1'); // a.b.c-alpha, a.b.c-beta, a.b.c-rcX or a.b.c } if (!defined('EURO')) { diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index 7a8f3cebb37..737053673f0 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -510,7 +510,7 @@ if (GETPOST('theme', 'aZ09')) { } // Set global MAIN_OPTIMIZEFORTEXTBROWSER (must be before login part) -if (GETPOSTINT('textbrowser') || (!empty($conf->browser->name) && $conf->browser->name == 'lynxlinks')) { // If we must enable text browser +if (GETPOSTINT('textbrowser') || (!empty($conf->browser->name) && $conf->browser->name == 'textbrowser')) { // If we must enable text browser $conf->global->MAIN_OPTIMIZEFORTEXTBROWSER = 2; } diff --git a/htdocs/societe/class/api_contacts.class.php b/htdocs/societe/class/api_contacts.class.php index 95e7de8d649..3499bf09b92 100644 --- a/htdocs/societe/class/api_contacts.class.php +++ b/htdocs/societe/class/api_contacts.class.php @@ -369,7 +369,7 @@ class Contacts extends DolibarrApi * Delete contact * * @param int $id Contact ID - * @return integer + * @return array[] */ public function delete($id) { @@ -385,7 +385,17 @@ class Contacts extends DolibarrApi throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login); } $this->contact->oldcopy = clone $this->contact; - return $this->contact->delete(DolibarrApiAccess::$user); + + if ($this->contact->delete(DolibarrApiAccess::$user) <= 0) { + throw new RestException(500, 'Error when delete contact ' . $this->contact->error); + } + + return array( + 'success' => array( + 'code' => 200, + 'message' => 'Contact deleted' + ) + ); } /**