diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e731200142e..58a120c29d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,6 +41,9 @@ repos: exclude_types: [markdown] # Fix the end of file - id: end-of-file-fixer + exclude: | + (?x)^(htdocs/public/includes/tinymce/.* + )$ # Check that there are no completely merged file conflicts - id: check-merge-conflict stages: [pre-rebase, pre-commit, pre-merge-commit] @@ -66,7 +69,7 @@ repos: # Check github actions - repo: https://github.com/rhysd/actionlint - rev: v1.7.11 + rev: v1.7.12 hooks: - id: actionlint stages: [manual] # To run: pre-commit run -a --hook-stage=manual actionlint @@ -218,7 +221,7 @@ repos: # Execute codespell to fix typo errors (setup of codespell into dev/tools/codespell/) - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell # Due to a current limitation of configuration files, @@ -269,7 +272,7 @@ repos: # Check sql file syntax - repo: https://github.com/sqlfluff/sqlfluff - rev: 4.0.4 + rev: 4.2.0 hooks: - id: sqlfluff-lint stages: [pre-commit, manual] # manual needed for ci diff --git a/AGENTS.md b/AGENTS.md index ae8314bd540..5ffcf545c57 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,7 @@ This project is full sources of Dolibarr ERP and CRM application. Every modifica - ❌ Do not break compatibility of PHP functions and methods - ❌ Do not introduce external dependencies without validation - ❌ Separate page actions in the `/* Actions */` section of the PHP code and the rendering part in the `/* Views */` section +- ❌ Never use PHP native curl functions to call a GET or POST URL, but use instead the Dolibarr function getURLContent() - ✅ Use Dolibarr hooks whenever possible - ✅ Respect existing naming conventions diff --git a/ChangeLog b/ChangeLog index 67e1fc2ebad..805f13a58fa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1851,7 +1851,7 @@ FIX: PHP8 warning if $conf->reception is checked the old fashion way (#29697) FIX: PHP 8 warning on output of successful cronjob (#29922) FIX: PHP exception on getSpecialCode (#29646) FIX: php warning if cookie doesn’t exist (#29723) -FIX: pos: invoice date incorrectly set because of timezome mismatches (reverts #36e91da) (#30184) +FIX: pos: invoice date incorrectly set because of timezone mismatches (reverts #36e91da) (#30184) FIX: public project form return an error if SOCIETE_EMAIL_UNIQUE (#29942) FIX: REPLENISH MANY FOURN WHEN ORDER ALREADY CREATE (#29710) FIX: Supplier Order search on date valid (#30448) @@ -1967,7 +1967,7 @@ FIX: navigation after filter in subaccount FIX: notification: error 500 in fixed emails due to a bad copy/paste (#29580) FIX: notification module: for supplier orders (any of the 3 triggers), user can choose an e-mail template in conf, but the conf is not used when sending the notification (#28216) FIX: not redirtect when error occurs on updating card (#29388) -FIX: Not trancate the multicurrency rate shown on cards (even if the global MAIN_MAX_DECIMALS_SHOWN is set to 0) (#28211) +FIX: Not truncate the multicurrency rate shown on cards (even if the global MAIN_MAX_DECIMALS_SHOWN is set to 0) (#28211) FIX: Oauth login FIX: Option MAIN_DOC_USE_TIMING can be a string with date format FIX: Payment on customer invoice - Remove accountid in url if empty for apply default value (#28156) @@ -2070,7 +2070,7 @@ FIX: Fix create shipping with product who have negative stocks on warehouse but FIX: migration missing 2 columns in llx_resource and 1 in llx_user FIX: missing trans FIX: notification module: for supplier orders (any of the 3 triggers), user can choose an e-mail template in conf, but the conf is not used when sending the notification (#28216) -FIX: Not trancate the multicurrency rate shown on cards (even if the global MAIN_MAX_DECIMALS_SHOWN is set to 0) (#28211) +FIX: Not truncate the multicurrency rate shown on cards (even if the global MAIN_MAX_DECIMALS_SHOWN is set to 0) (#28211) FIX: Option MAIN_DOC_USE_TIMING can be a string with date format FIX: Payment on customer invoice - Remove accountid in url if empty for apply default value (#28156) FIX: Pb in redirect of a website page in USEDOLIBARRSERVER mode @@ -2630,7 +2630,7 @@ FIX: notification: error 500 in fixed emails due to a bad copy/paste (#29580) FIX: notification module: for supplier orders (any of the 3 triggers), user can choose an e-mail template in conf, but the conf is not used when sending the notification (#28216) FIX: Not qualified lines for reception (#29473) FIX: not redirect when error occurs on updating card (#29388) -FIX: Not trancate the multicurrency rate shown on cards (even if the global MAIN_MAX_DECIMALS_SHOWN is set to 0) (#28211) +FIX: Not truncate the multicurrency rate shown on cards (even if the global MAIN_MAX_DECIMALS_SHOWN is set to 0) (#28211) FIX: on change ref for bank account attachment are lost (#30529) FIX: Option MAIN_DOC_USE_TIMING can be a string with date format FIX: orders to bill menu (#30179) @@ -2642,7 +2642,7 @@ FIX: php8: Fatal when empty $tmpvat is an empty string (no silent conversion to FIX: PHP 8 warning on output of successful cronjob (#29922) FIX: PHP exception on getSpecialCode (#29646) FIX: php warning global conf (#29478) -FIX: pos: invoice date incorrectly set because of timezome mismatches (reverts #36e91da) (#30184) +FIX: pos: invoice date incorrectly set because of timezone mismatches (reverts #36e91da) (#30184) FIX: public project form return an error if SOCIETE_EMAIL_UNIQUE (#29942) FIX: purge files cron: php warnings when rest module enabled (#30919) FIX: PUT /thirdparties/{id} and PUT /contacts/{id} should throw exception if update returns < 0 (#29596) diff --git a/dev/build/phpstan/phpstan-baseline.neon b/dev/build/phpstan/phpstan-baseline.neon index d467bb26174..6cb58c24b51 100644 --- a/dev/build/phpstan/phpstan-baseline.neon +++ b/dev/build/phpstan/phpstan-baseline.neon @@ -2664,12 +2664,6 @@ parameters: count: 1 path: ../../../htdocs/core/ajax/ajaxdirpreview.php - - - message: '#^Variable \$action might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/core/ajax/ajaxdirtree.php - - message: '#^Variable \$original_file might not be defined\.$#' identifier: variable.undefined @@ -3306,144 +3300,6 @@ parameters: count: 1 path: ../../../htdocs/core/class/interfaces.class.php - - - message: '#^If condition is always false\.$#' - identifier: if.alwaysFalse - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Negated boolean expression is always true\.$#' - identifier: booleanNot.alwaysTrue - count: 2 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_add expects LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_bind expects LDAP\\Connection, bool\|resource given\.$#' - identifier: argument.type - count: 2 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_delete expects LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_errno expects LDAP\\Connection, bool\|resource given\.$#' - identifier: argument.type - count: 9 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_errno expects LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 3 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_error expects LDAP\\Connection, bool\|resource given\.$#' - identifier: argument.type - count: 9 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_error expects LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 8 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_first_entry expects LDAP\\Connection, bool\|resource given\.$#' - identifier: argument.type - count: 2 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_first_entry expects LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_get_attributes expects LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_get_entries expects LDAP\\Connection, bool\|resource given\.$#' - identifier: argument.type - count: 3 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_get_option expects LDAP\\Connection, bool\|resource given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_get_values_len expects LDAP\\Connection, bool\|resource given\.$#' - identifier: argument.type - count: 2 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_mod_add expects LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_mod_del expects LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_mod_replace expects LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 2 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_rename expects LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_search expects array\|LDAP\\Connection, bool\|resource given\.$#' - identifier: argument.type - count: 6 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_search expects array\|LDAP\\Connection, resource\|true given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_set_option expects LDAP\\Connection\|null, bool\|resource given\.$#' - identifier: argument.type - count: 3 - path: ../../../htdocs/core/class/ldap.class.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_unbind expects LDAP\\Connection, resource given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/ldap.class.php - - message: '#^Negated boolean expression is always true\.$#' identifier: booleanNot.alwaysTrue @@ -3600,48 +3456,6 @@ parameters: count: 1 path: ../../../htdocs/core/db/mysqli.class.php - - - message: '#^Parameter \#1 \$result of function pg_affected_rows expects PgSql\\Result, bool\|resource given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/db/pgsql.class.php - - - - message: '#^Parameter \#1 \$result of function pg_fetch_array expects PgSql\\Result, bool\|resource given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/db/pgsql.class.php - - - - message: '#^Parameter \#1 \$result of function pg_fetch_object expects PgSql\\Result, bool\|resource given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/db/pgsql.class.php - - - - message: '#^Parameter \#1 \$result of function pg_fetch_row expects PgSql\\Result, bool\|resource given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/db/pgsql.class.php - - - - message: '#^Parameter \#1 \$result of function pg_free_result expects PgSql\\Result, resource given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/db/pgsql.class.php - - - - message: '#^Parameter \#1 \$result of function pg_num_rows expects PgSql\\Result, resource\|true given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/db/pgsql.class.php - - - - message: '#^Parameter \#1 \$resultset of method DoliDBPgsql\:\:fetch_row\(\) expects resource, PgSql\\Result given\.$#' - identifier: argument.type - count: 2 - path: ../../../htdocs/core/db/pgsql.class.php - - message: '#^Variable \$conf might not be defined\.$#' identifier: variable.undefined @@ -4026,18 +3840,6 @@ parameters: count: 1 path: ../../../htdocs/core/login/functions_ldap.php - - - message: '#^Parameter \#1 \$ldap of function ldap_errno expects LDAP\\Connection, resource given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/login/functions_ldap.php - - - - message: '#^Parameter \#1 \$ldap of function ldap_error expects LDAP\\Connection, resource given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/login/functions_ldap.php - - message: '#^If condition is always true\.$#' identifier: if.alwaysTrue @@ -5844,180 +5646,6 @@ parameters: count: 1 path: ../../../htdocs/imports/import.php - - - message: '#^Class Segment implements generic interface IteratorAggregate but does not specify its types\: TKey, TValue$#' - identifier: missingType.generics - count: 1 - path: ../../../htdocs/includes/odtphp/Segment.php - - - - message: '#^Default value of the parameter \#3 \$encode \(true\) of method Segment\:\:setVars\(\) is incompatible with type string\.$#' - identifier: parameter.defaultValue - count: 1 - path: ../../../htdocs/includes/odtphp/Segment.php - - - - message: '#^Method Segment\:\:__call\(\) has parameter \$args with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: ../../../htdocs/includes/odtphp/Segment.php - - - - message: '#^Method Segment\:\:macroReplace\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: ../../../htdocs/includes/odtphp/Segment.php - - - - message: '#^Parameter \#3 \$encode of method Segment\:\:setVars\(\) expects string, false given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/includes/odtphp/Segment.php - - - - message: '#^Property Segment\:\:\$children has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/Segment.php - - - - message: '#^Property Segment\:\:\$file has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/Segment.php - - - - message: '#^Property Segment\:\:\$images has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/Segment.php - - - - message: '#^Property Segment\:\:\$odf has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/Segment.php - - - - message: '#^Property Segment\:\:\$vars has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/Segment.php - - - - message: '#^Class SegmentIterator implements generic interface RecursiveIterator but does not specify its types\: TKey, TValue$#' - identifier: missingType.generics - count: 1 - path: ../../../htdocs/includes/odtphp/SegmentIterator.php - - - - message: '#^Method SegmentIterator\:\:__construct\(\) has parameter \$ref with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: ../../../htdocs/includes/odtphp/SegmentIterator.php - - - - message: '#^Method SegmentIterator\:\:getChildren\(\) return type with generic interface RecursiveIterator does not specify its types\: TKey, TValue$#' - identifier: missingType.generics - count: 1 - path: ../../../htdocs/includes/odtphp/SegmentIterator.php - - - - message: '#^Property SegmentIterator\:\:\$key has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/SegmentIterator.php - - - - message: '#^Property SegmentIterator\:\:\$keys has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/SegmentIterator.php - - - - message: '#^Property SegmentIterator\:\:\$ref has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/SegmentIterator.php - - - - message: '#^Method Odf\:\:__construct\(\) has parameter \$config with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Method Odf\:\:_getDataFromHtml\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Method Odf\:\:_isHtmlTag\(\) is unused\.$#' - identifier: method.unused - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Method Odf\:\:_replaceHtmlWithOdtTag\(\) has parameter \$customStyles with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Method Odf\:\:_replaceHtmlWithOdtTag\(\) has parameter \$fontDeclarations with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Method Odf\:\:_replaceHtmlWithOdtTag\(\) has parameter \$tags with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Method Odf\:\:getConfig\(\) should return string but returns false\.$#' - identifier: return.type - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Parameter \#1 \$prefix of function uniqid expects string, int\<0, max\> given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Property Odf\:\:\$config has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Property Odf\:\:\$images has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Property Odf\:\:\$segments has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Property Odf\:\:\$userdefined has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - - - message: '#^Property Odf\:\:\$vars has no type specified\.$#' - identifier: missingType.property - count: 1 - path: ../../../htdocs/includes/odtphp/odf.php - - message: '#^Ternary operator condition is always true\.$#' identifier: ternary.alwaysTrue diff --git a/dev/tools/codespell/codespell-lines-ignore.txt b/dev/tools/codespell/codespell-lines-ignore.txt index feb1f084ef2..32cda44fd5d 100644 --- a/dev/tools/codespell/codespell-lines-ignore.txt +++ b/dev/tools/codespell/codespell-lines-ignore.txt @@ -16,6 +16,7 @@ jQuery("#mouvement option[value=1]").attr("selected","selected").trigger("change"); jQuery("#mouvement").trigger("change"); $action = 'transfert'; + $currentpos = ftell($infp); $date_liv = dol_mktime(GETPOSTINT('rehour'), GETPOSTINT('remin'), GETPOSTINT('resec'), GETPOSTINT("remonth"), GETPOSTINT("reday"), GETPOSTINT("reyear")); $newfiletmp = preg_replace('/\.od(s|t)/i', '', $newfile); $newfiletmp = preg_replace('/\.od[ts]/i', '', $newfile); @@ -29,6 +30,7 @@ if (jQuery("#mouvement").val() == \'0\') jQuery("#unitprice").removeAttr("disabled"); print ''.$langs->trans("TransferStock").''; $action = 'transfert'; + $currentpos = ftell($infp); $date_com = dol_mktime(GETPOSTINT('rehour'), GETPOSTINT('remin'), GETPOSTINT('resec'), GETPOSTINT('remonth'), GETPOSTINT('reday'), GETPOSTINT('reyear')); $date_next_execution = dol_mktime($rehour, $remin, 0, $remonth, $reday, $reyear); $datesubscription = dol_mktime(12, 0, 0, GETPOSTINT("remonth"), GETPOSTINT("reday"), GETPOSTINT("reyear")); @@ -87,6 +89,7 @@ $datepaye = dol_mktime(GETPOSTINT("rehour"), GETPOSTINT("remin"), GETPOSTINT("resec"), GETPOSTINT("remonth"), GETPOSTINT("reday"), GETPOSTINT("reyear"), 'tzuserrel'); $ensemblereponses = $obj->reponses; $head[$h][1] = $langs->trans('Referers'); + $infp = fopen($infilename, "rt"); $inj += preg_match('/on(dblclick|drag|drop|durationchange|emptied|end|ended|error|focus|focusin|focusout|formdata|gotpointercapture|hashchange|input|invalid)[a-z]*\s*=/i', $val); $opensurveysondage->mail_admin = $_SESSION['adresse']; $pdf->SetXY($savx, $savy); @@ -108,9 +111,12 @@ // Ligne de la periode d'analyse du rapport // ceci afin d'etre compatible avec les cas ou la periode n'etait pas obligatoire TaskItem(pID, pName, pStart, pEnd, pColor, pLink, pMile, pRes, pComp, pGroup, pParent, pOpen, pDepend, pCaption, pNotes, pGantt)

+ Issue summary: If a victim had access to a spoofed version of ckeditor.com via HTTP (e.g. due to DNS spoofing, using a hacked public network or mailicious hotspot), then when using a link to the ckeditor.com website it was possible for the attacker to change the current URL of the opening page, even if the opening page was protected with SSL. + fclose($infp); if ($action == "transfert") { if (GETPOST('reday')) { if (GETPOSTINT("reyear") && GETPOSTINT("remonth") && GETPOSTINT("reday")) { + if (empty($infp)) { print ''; print ''; print ''; } diff --git a/htdocs/admin/bank_payments_extrafields.php b/htdocs/admin/bank_payments_extrafields.php index cf69d6f2c8d..ccbcdf6fbb5 100644 --- a/htdocs/admin/bank_payments_extrafields.php +++ b/htdocs/admin/bank_payments_extrafields.php @@ -29,7 +29,6 @@ // Load Dolibarr environment require '../main.inc.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -38,7 +37,6 @@ require '../main.inc.php'; * @var Translate $langs * @var User $user */ - require_once DOL_DOCUMENT_ROOT.'/core/lib/bank.lib.php'; // Load translation files required by the page diff --git a/htdocs/admin/bank_various_payment_extrafields.php b/htdocs/admin/bank_various_payment_extrafields.php new file mode 100644 index 00000000000..a81ff78d981 --- /dev/null +++ b/htdocs/admin/bank_various_payment_extrafields.php @@ -0,0 +1,109 @@ + + * Copyright (C) 2003 Jean-Louis Bergamo + * Copyright (C) 2004-2012 Laurent Destailleur + * Copyright (C) 2012 Regis Houssin + * Copyright (C) 2024 Alexandre Spangaro + * Copyright (C) 2024 Frédéric France + * Copyright (C) 2026 Solution Libre SAS + * + * 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 + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/admin/bank_various_payment_extrafields.php + * \ingroup bank + * \brief Page to setup extra fields of various payments + */ + +// Load Dolibarr environment +require '../main.inc.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/bank.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; + +// Load translation files required by the page +$langs->loadLangs(array("admin", "banks", "other")); + +$extrafields = new ExtraFields($db); +$form = new Form($db); + +// List of supported format +$type2label = ExtraFields::getListOfTypesLabels(); + +$action = GETPOST('action', 'aZ09'); +$attrname = GETPOST('attrname', 'alpha'); +$elementtype = 'payment_various'; // Must match the table_element of PaymentVarious + +if (!$user->admin) { + accessforbidden(); +} + + +/* + * Actions + */ + +require DOL_DOCUMENT_ROOT.'/core/actions_extrafields.inc.php'; + + +/* + * View + */ + +$textobject = $langs->transnoentitiesnoconv("VariousPayments"); + +llxHeader('', $langs->trans("BankSetupModule")); + +$linkback = ''.$langs->trans("BackToModuleList").''; +print load_fiche_titre($langs->trans("BankSetupModule"), $linkback, 'title_setup'); + +$head = bank_admin_prepare_head(null); + +print dol_get_fiche_head($head, 'bank_various_payment_extrafields', $langs->trans("BankSetupModule"), -1, ''); + +require DOL_DOCUMENT_ROOT.'/core/tpl/admin_extrafields_view.tpl.php'; + +print dol_get_fiche_end(); + + +// Buttons +if ($action != 'create' && $action != 'edit') { + print '
'; + print ''.$langs->trans("NewAttribute").''; + print "
"; +} + + +// Creation of an optional field +if ($action == 'create') { + print '
'; + require DOL_DOCUMENT_ROOT.'/core/tpl/admin_extrafields_add.tpl.php'; +} + +// Edition of an optional field +if ($action == 'edit' && !empty($attrname)) { + print "
"; + require DOL_DOCUMENT_ROOT.'/core/tpl/admin_extrafields_edit.tpl.php'; +} + +// End of page +llxFooter(); +$db->close(); diff --git a/htdocs/ai/admin/server_mcp.php b/htdocs/ai/admin/server_mcp.php index b914d41676d..59827e32945 100644 --- a/htdocs/ai/admin/server_mcp.php +++ b/htdocs/ai/admin/server_mcp.php @@ -223,6 +223,7 @@ print ajax_constantonoff('AI_MCP_ENABLED', array(), null, 0, 0, 1); //print ''; print ''; +print '
'; print '
'; if (getDolGlobalString('AI_MCP_ENABLED')) { @@ -237,21 +238,22 @@ if (getDolGlobalString('AI_MCP_ENABLED')) { // Input Mode print ''; - print 'Default Interface Mode'; + print ''; + print $form->textwithpicto('Default Interface', $langs->trans("InputMethodHelp")); + print ''; print ''; $input_modes = [ - 'text' => '💬 ' . $langs->trans('OptionTextOnly'), - 'native' => '☁️ ' . $langs->trans('OptionCloudFast') . ' - ' . $langs->trans('OptionCloudFasthelp'), - 'whisper' => '🔒 ' . $langs->trans('OptionWhisperLocal') . ' - ' . $langs->trans('OptionWhisperLocalhelp') + 'text' => $langs->trans('OptionTextOnly'), + 'native' => $langs->trans('OptionCloudFast') . ' - ' . $langs->trans('OptionCloudFasthelp'), + 'whisper' => $langs->trans('OptionWhisperLocal') . ' - ' . $langs->trans('OptionWhisperLocalhelp') ]; print $form->selectarray('AI_DEFAULT_INPUT_MODE', $input_modes, getDolGlobalString('AI_DEFAULT_INPUT_MODE'), 0, 0, 0); - print '
' . $langs->trans("InputMethodHelp") . ''; print ''; print ''; // Enhanced Privacy control print ''; - print '' . $langs->trans('ObfuscatePIIData') . ''; + print '' . $langs->trans('ObfuscatePIIData') . ''; print ''; print ajax_constantonoff('AI_PRIVACY_REDACTION'); print ' ' . $langs->trans("RedactionHelp") . ''; @@ -260,8 +262,7 @@ if (getDolGlobalString('AI_MCP_ENABLED')) { // Confirmation Level print ''; - print '' . $langs->trans("AskConfirmation"); - print ' '; + print '' . $form->textwithpicto($langs->trans("AskConfirmation"), $langs->trans("AskConfirmationHelp")).''; print ''; $confirmation_options = [ '0' => $langs->trans("ConfirmNever"), @@ -274,7 +275,7 @@ if (getDolGlobalString('AI_MCP_ENABLED')) { // Logging print ''; - print '' . $langs->trans('EnableLogging') . ''; + print '' . $langs->trans('EnableLogging') . ''; print ''; print ajax_constantonoff('AI_LOG_REQUESTS'); print ' View Logs'; @@ -283,7 +284,7 @@ if (getDolGlobalString('AI_MCP_ENABLED')) { print ''; print '' . $langs->trans("LogRetention") . ''; - print ' (0 = Forever)'; + print ' (0 = Forever)'; print ''; // System Prompt @@ -302,6 +303,8 @@ if (getDolGlobalString('AI_MCP_ENABLED')) { print '
'; print ''; + print '
'; + print '
'; // AI Provider Config and Connection testing @@ -332,11 +335,11 @@ if (getDolGlobalString('AI_MCP_ENABLED')) { print ''; } - print ''; - print ''; + + print '

'; // External Access Configuration - print load_fiche_titre($langs->trans("AiMcpExternalAccess"), '', 'fas fa-lock-open text-danger'); + print load_fiche_titre($langs->trans("AiMcpExternalAccess"), '', 'fas fa-lock-open'); print '
'; print ''; @@ -362,11 +365,10 @@ if (getDolGlobalString('AI_MCP_ENABLED')) { print ''; if ($apiKey) { print ''; - print ' '; - print ' Generate New Key'; + print ' Generate New Key'; } else { print '' . $langs->trans("NoKeyWarning") . ''; - print ' ' . $langs->trans("GenerateKey") . ''; + print ' ' . $langs->trans("GenerateKey") . ''; } print ''; print ''; @@ -390,7 +392,8 @@ if (getDolGlobalString('AI_MCP_ENABLED')) { print '
'; print '' . $langs->trans("ClaudeDesktopConfig") . '
'; print '
';
-	echo htmlspecialchars('{
+	echo htmlspecialchars('
+	{
 	  "mcpServers": {
 	    "dolibarr": {
 	      "command": "node",
diff --git a/htdocs/ai/assistant/parse_intent.php b/htdocs/ai/assistant/parse_intent.php
index 897f524f110..cfb04707ad7 100644
--- a/htdocs/ai/assistant/parse_intent.php
+++ b/htdocs/ai/assistant/parse_intent.php
@@ -270,7 +270,16 @@ try {
 		}
 
 		// If we are sending a lot of tools (Non-Latin or Fallback), we strip descriptions.
-		$isLargeSchema = count($toolsSchema) > 20;
+		// The 20-tool threshold was likely chosen for GPT-3.5 (4K context window). Modern
+		// LLMs handle the full schema trivially: Gemini 2.5 Flash has a 1M token context,
+		// GPT-4o has 128K, Claude Sonnet has 1M. Compression hurts more than it helps
+		// today because it also truncates tool *descriptions* (down to 3 words), which
+		// breaks tool selection (e.g. "create_other_document" becomes "Create documents
+		// other" -- the LLM then thinks supplier_invoice creation is not available).
+		// We raise the threshold to 100 to effectively disable compression for the
+		// default install (~30 tools), while still leaving a safety net for very large
+		// custom installs that register dozens of additional addMcpTools hooks.
+		$isLargeSchema = count($toolsSchema) > 100;
 		$toolsForLLM = cleanToolSchemaForLLM($toolsSchema, $isLargeSchema);
 
 		// Build System Prompt
@@ -293,8 +302,34 @@ try {
 
 		$defUrl = $servicesList[$serviceKey]['url'] ?? '';
 		$url = getDolGlobalString('AI_API_' . strtoupper($serviceKey) . '_URL') ?: $defUrl;
-		$defModel = $servicesList[$serviceKey]['textgeneration'] ?? 'gpt-4o-mini';
-		$model = getDolGlobalString('AI_API_' . strtoupper($serviceKey) . '_MODEL') ?: $defModel;
+		// The model defaults declared in getListOfAIServices() are nested:
+		//   $servicesList[$key]['textgeneration'] = ['default' => 'model-name']
+		// Reading 'textgeneration' without ['default'] returns the inner array, which
+		// then fails the (string) type-hint of UniversalLLMAdapter's 4th argument with:
+		//   "Argument #4 ($model) must be of type string, array given"
+		//
+		// The admin UI (htdocs/ai/admin/setup.php "Prompt and custom AI models" tab) also
+		// stores the per-function model under AI_API__MODEL_TEXT (matching the
+		// convention already used by Ai::generateContent() for the same data). The
+		// previous lookup used AI_API__MODEL which is never written by that
+		// form, so the user-configured model was silently ignored.
+		$rawDefault = $servicesList[$serviceKey]['textgeneration'] ?? null;
+		if (is_array($rawDefault)) {
+			$defModel = $rawDefault['default'] ?? 'gpt-4o-mini';
+		} else {
+			$defModel = $rawDefault ?: 'gpt-4o-mini';
+		}
+		$prefix = 'AI_API_' . strtoupper($serviceKey);
+		$model = getDolGlobalString($prefix . '_MODEL_TEXT')
+			?: getDolGlobalString($prefix . '_MODEL')
+			?: $defModel;
+		// Defensive: coerce to string if anyone stored an array in this constant
+		if (is_array($model)) {
+			$model = $model['default'] ?? $defModel;
+		}
+		if (!is_string($model) || $model === '') {
+			$model = (string) $defModel;
+		}
 		$adapterType = $servicesList[$serviceKey]['adapter_type'] ?? 'openai';
 
 		if (!empty($apiKey)) {
@@ -727,12 +762,15 @@ function cleanToolSchemaForLLM(array $tools, bool $isLargeSchema = false)
 	$cleaned = [];
 
 	foreach ($tools as $tool) {
-		// Tool Description: Max 3 words
+		// Tool descriptions are how the LLM selects the right tool -- never truncate
+		// them, even when the schema is large. Truncating to 3 words ("Create documents
+		// other", "Add a single") breaks tool selection. If the schema really is too
+		// big for the chosen model, the right answer is to filter the toolset before
+		// it reaches the LLM (which is what filterToolsProfessional() already does
+		// upstream of this function), not to mutilate each tool's description.
+		// Parameter-level compression (stripping defaults, descriptions of optional
+		// fields, etc.) remains gated on $isLargeSchema below.
 		$desc = $tool['description'];
-		if ($isLargeSchema) {
-			$words = explode(' ', $desc);
-			$desc = implode(' ', array_slice($words, 0, 3));
-		}
 
 		// Get parameters
 		$toolParams = $tool['parameters'] ?? $tool['inputSchema'] ?? [];
diff --git a/htdocs/ai/class/ai.class.php b/htdocs/ai/class/ai.class.php
index 3bd792c5e5e..a5396347ec8 100644
--- a/htdocs/ai/class/ai.class.php
+++ b/htdocs/ai/class/ai.class.php
@@ -140,7 +140,14 @@ class Ai
 				$this->apiEndpoint .= (preg_match('/\/$/', $this->apiEndpoint) ? '' : '/').'threads';
 			} else {	// if $function == 'docparsing', 'text...', ...
 				$this->apiEndpoint = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_URL', $arrayofai[$this->apiService]['url']);
-				$this->apiEndpoint .= (preg_match('/\/$/', $this->apiEndpoint) ? '' : '/').'chat/completions';
+				if ($this->apiService == 'google') {
+					// Google Gemini native API: the /models/:generateContent suffix is
+					// appended later (once $model has been resolved). The OpenAI-style
+					// /chat/completions does not exist on the native Gemini endpoint.
+					$this->apiEndpoint = rtrim($this->apiEndpoint, '/');
+				} else {
+					$this->apiEndpoint .= (preg_match('/\/$/', $this->apiEndpoint) ? '' : '/').'chat/completions';
+				}
 			}
 		}
 		if ($moreendpoint) {
@@ -170,6 +177,12 @@ class Ai
 			}
 		}
 
+		// Google Gemini: append /models/:generateContent now that $model is resolved.
+		if ($this->apiService == 'google' && !in_array($function, array('file', 'assistant', 'thread'))
+			&& strpos($this->apiEndpoint, ':generateContent') === false) {
+			$this->apiEndpoint .= '/models/'.rawurlencode($model).':generateContent';
+		}
+
 		dol_syslog("Call API for apiKey=".substr($this->apiKey, 0, 5).'***********, apiEndpoint='.$this->apiEndpoint.", model=".$model.", format=".$format);
 		if (getDolGlobalString("AI_DEBUG")) {
 			if (@is_writable($dolibarr_main_data_root)) {	// Avoid fatal error on fopen with open_basedir
@@ -269,18 +282,33 @@ class Ai
 					"top_p": 0.95
 				}*/
 
-				$arrayforpayload = array(
-					'messages' => array(array('role' => 'user', 'content' => $fullInstructions)),
-					'model' => $model,
-				);
-
 				// Add a system message
 				$addDateTimeContext = false;
 				if ($addDateTimeContext) {		// @phpstan-ignore-line
 					$prePrompt = ($prePrompt ? $prePrompt.(preg_match('/[\.\!\?]$/', $prePrompt) ? '' : '.').' ' : '').'Today we are '.dol_print_date(dol_now(), 'dayhourtext');
 				}
-				if ($prePrompt) {
-					$arrayforpayload['messages'][] = array('role' => 'system', 'content' => $prePrompt);
+
+				if ($this->apiService == 'google') {
+					// Google Gemini native payload format (different from OpenAI's "messages").
+					$arrayforpayload = array(
+						'contents' => array(
+							array('role' => 'user', 'parts' => array(array('text' => $fullInstructions)))
+						)
+					);
+					if ($prePrompt) {
+						$arrayforpayload['system_instruction'] = array(
+							'parts' => array(array('text' => $prePrompt))
+						);
+					}
+				} else {
+					// OpenAI-compatible payload format (chatgpt, mistral, groq, anthropic-compat, custom, ...)
+					$arrayforpayload = array(
+						'messages' => array(array('role' => 'user', 'content' => $fullInstructions)),
+						'model' => $model,
+					);
+					if ($prePrompt) {
+						$arrayforpayload['messages'][] = array('role' => 'system', 'content' => $prePrompt);
+					}
 				}
 			}
 
@@ -296,9 +324,16 @@ class Ai
 				$payload = json_encode($arrayforpayload);
 			}
 
-			$headers = array(
-				'Authorization: Bearer ' . $this->apiKey,
-			);
+			if ($this->apiService == 'google') {
+				// Google Gemini uses the x-goog-api-key header (Bearer is not accepted by the native API).
+				$headers = array(
+					'x-goog-api-key: ' . $this->apiKey,
+				);
+			} else {
+				$headers = array(
+					'Authorization: Bearer ' . $this->apiKey,
+				);
+			}
 			if ($function != 'file') {
 				$headers[] = 'Content-Type: application/json';
 			}
@@ -388,6 +423,18 @@ class Ai
 				} else {
 					$generatedContent = var_export($decodedResponse['error'], true);
 				}
+			} elseif ($this->apiService == 'google') {
+				// Google Gemini response shape: candidates[0].content.parts[*].text
+				// (parts is an array because Gemini can return mixed-modality output;
+				// we concatenate the textual parts.)
+				$generatedContent = '';
+				if (!empty($decodedResponse['candidates'][0]['content']['parts'])) {
+					foreach ($decodedResponse['candidates'][0]['content']['parts'] as $part) {
+						if (isset($part['text'])) {
+							$generatedContent .= $part['text'];
+						}
+					}
+				}
 			} else {
 				$generatedContent = $decodedResponse['choices'][0]['message']['content'];
 			}
diff --git a/htdocs/ai/class/llmadapter.class.php b/htdocs/ai/class/llmadapter.class.php
index b897f3d8402..b5bb2394f36 100644
--- a/htdocs/ai/class/llmadapter.class.php
+++ b/htdocs/ai/class/llmadapter.class.php
@@ -195,32 +195,42 @@ class UniversalLLMAdapter
 	 */
 	private function curl(string $url, array $data, array $headers, bool $isClaude = false, bool $isGemini = false): ?string
 	{
-		$ch = curl_init($url);
-		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-		curl_setopt($ch, CURLOPT_POST, true);
+		include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
 
-		$payload = json_encode($data);
-		if ($payload === false) {
-			return "Error: Failed to encode JSON payload.";
+		// By default, we accept only external endpoints ($dolibarr_ai_allow_local_endpoints is not set).
+		// To allow local endpoints, we must set $dolibarr_ai_allow_local_endpoints to 1 or 2 in conf.php.
+		global $dolibarr_ai_allow_local_endpoints;
+		$localurl = $dolibarr_ai_allow_local_endpoints ?? 0;
+
+		// Pass $this->timeout as the response timeout so the LLM-specific value configured
+		// at construction time is honored (getURLContent's $timeoutresponse is the 10th arg;
+		// preceding args $ssl_verifypeer=-1 and $timeoutconnect=0 keep their defaults).
+		$result = getURLContent($url, 'POST', json_encode($data), 1, $headers, array('http', 'https'), $localurl, -1, 0, $this->timeout);
+
+		$body         = (string) ($result['content'] ?? '');
+		$httpCode     = (int) ($result['http_code'] ?? 0);
+		$effectiveUrl = (string) ($result['url'] ?? $url);
+		// Store an enriched payload so the admin Log Viewer ("VIEW LOGS" in the AI Server
+		// MCP setup page) shows something actionable when something goes wrong, not just
+		// a bare "Invalid JSON response from API." with an empty body.
+		$this->lastResponse = "HTTP " . $httpCode . " from " . $effectiveUrl
+			. "\n--- body (" . strlen($body) . " bytes) ---\n"
+			. $body;
+
+		if (!empty($result['curl_error_no'])) {
+			return "Error: cURL #" . $result['curl_error_no'] . " " . $result['curl_error_msg'] . " (url=" . $effectiveUrl . ")";
 		}
-		curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
-		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
-		curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
 
-		$res = curl_exec($ch);
-		$this->lastResponse = (string) $res;
-
-		if (curl_errno($ch)) {
-			$error = curl_error($ch);
-			curl_close($ch);
-			return "Error: " . $error;
-		}
-		curl_close($ch);
-
-		$json = json_decode((string) $res, true);
+		$json = json_decode($body, true);
 
 		if ($json === null && json_last_error() !== JSON_ERROR_NONE) {
-			return "Error: Invalid JSON response from API.";
+			// Common real-world causes: HTTP 4xx/5xx with empty body, HTML error page
+			// from a proxy, gateway timeout, etc. Surface the HTTP code and a short
+			// body snippet so the admin can diagnose without re-running with curl.
+			$snippet = substr($body, 0, 500);
+			return "Error: Invalid JSON response from API (HTTP " . $httpCode . ", "
+				. strlen($body) . " bytes). Body snippet: "
+				. ($snippet !== '' ? $snippet : '');
 		}
 
 		if (isset($json['error'])) {
diff --git a/htdocs/ai/js/ai_assistant.js b/htdocs/ai/js/ai_assistant.js
index 9b00e48450d..ab175c42f2b 100644
--- a/htdocs/ai/js/ai_assistant.js
+++ b/htdocs/ai/js/ai_assistant.js
@@ -589,7 +589,20 @@ document.addEventListener('DOMContentLoaded', () => {
         try {
             const { getDocument } = await getPdfLib();
             const arrayBuffer = await file.arrayBuffer();
-            const pdf = await getDocument({ data: arrayBuffer }).promise;
+            // cMapUrl + standardFontDataUrl are REQUIRED for PDFs that ship with
+            // custom CID fonts referencing ToUnicode CMaps (extremely common with
+            // generators like TCPDF, wkhtmltopdf, iText, etc.). Without these,
+            // getTextContent() returns raw glyph codes that look like garbled
+            // base64. jsdelivr is used here because cdnjs (used elsewhere for the
+            // pdf.js bundle) does not distribute the cmaps/ and standard_fonts/
+            // resource directories.
+            const PDFJS_RES = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@5.4.149';
+            const pdf = await getDocument({
+                data: arrayBuffer,
+                cMapUrl: PDFJS_RES + '/cmaps/',
+                cMapPacked: true,
+                standardFontDataUrl: PDFJS_RES + '/standard_fonts/',
+            }).promise;
 
             let fullText = "";
             const totalPages = pdf.numPages; // Get total pages
@@ -688,7 +701,7 @@ document.addEventListener('DOMContentLoaded', () => {
 
                 class SpeechPipeline {
                     static task = 'automatic-speech-recognition';
-                    static model = 'Xenova/whisper-small.en'; 
+                    static model = 'Xenova/whisper-small.en';
                     static instance = null;
 
                     static async getInstance(progress_callback = null) {
diff --git a/htdocs/ai/server/mcp_server.php b/htdocs/ai/server/mcp_server.php
index b7fd42c1821..854170c07f6 100644
--- a/htdocs/ai/server/mcp_server.php
+++ b/htdocs/ai/server/mcp_server.php
@@ -76,12 +76,20 @@ $headers = array_change_key_case($headers, CASE_LOWER);
 
 $authHeader   = $headers['authorization'] ?? '';
 $apiKeyHeader = $headers['x-api-key'] ?? '';
+// Fallback: also accept the key in a query string parameter (?api_key=XXX or ?key=XXX).
+// Required for MCP clients that don't support custom auth headers in their connector UI
+// (e.g. Claude Desktop "Custom Connectors" in beta only exposes OAuth fields).
+// SECURITY NOTE: query-string keys appear in webserver access logs and possibly in Referer
+// headers. Header-based auth (X-API-Key / Authorization) remains preferred and is tried first.
+// Administrators relying on the fallback should restrict access at the webserver level
+// and/or rotate AI_MCP_API_KEY regularly.
+$apiKeyQuery  = $_GET['api_key'] ?? $_GET['key'] ?? '';
 $storedKey    = getDolGlobalString('AI_MCP_API_KEY');
 
 $valid = false;
 
 if (!empty($storedKey)) {
-	// X-API-Key
+	// X-API-Key header (preferred)
 	if (!empty($apiKeyHeader)) {
 		$valid = hash_equals($storedKey, $apiKeyHeader);
 	}
@@ -94,6 +102,11 @@ if (!empty($storedKey)) {
 			$valid = hash_equals($storedKey, $token);
 		}
 	}
+
+	// Query-string fallback (last resort for header-less clients)
+	if (!$valid && !empty($apiKeyQuery)) {
+		$valid = hash_equals($storedKey, $apiKeyQuery);
+	}
 }
 
 if (!$valid) {
@@ -133,8 +146,74 @@ if ($userId > 0) {
 	exit;
 }
 
+// Load the AI request log helper so we can persist tools/call invocations to
+// llx_ai_request_log (same table the AI Assistant web UI logs to). This gives
+// administrators a single place to audit external MCP client activity.
+require_once DOL_DOCUMENT_ROOT . '/ai/lib/ai.lib.php';
+
+/**
+ * Persist an MCP tools/call invocation to the llx_ai_request_log table.
+ * Mirrors what parse_intent.php does for the web AI Assistant.
+ * Only tools/call is logged -- lifecycle methods (initialize, ping,
+ * notifications/initialized, tools/list, ...) are skipped to avoid noise.
+ *
+ * @param array $req         The JSON-RPC request
+ * @param mixed               $resp        The JSON-RPC response (null for notifications)
+ * @param float               $tStart      microtime(true) captured before processing
+ * @param string              $rawInput    Raw request body
+ * @return void
+ */
+function mcp_log_request(array $req, $resp, float $tStart, string $rawInput): void
+{
+	global $db, $serviceUser;
+
+	$method = isset($req['method']) ? (string) $req['method'] : '';
+	if ($method !== 'tools/call' || !function_exists('ai_log_request')) {
+		return;
+	}
+
+	$params   = isset($req['params']) && is_array($req['params']) ? $req['params'] : [];
+	$toolName = isset($params['name']) ? (string) $params['name'] : '';
+	$toolArgs = isset($params['arguments']) ? $params['arguments'] : [];
+
+	$argsJson = is_string($toolArgs) ? $toolArgs : (string) json_encode($toolArgs);
+	$query    = '[MCP] ' . $toolName . ' ' . dol_substr($argsJson, 0, 1000);
+
+	$responseShape = ['tool' => $toolName, 'arguments' => $toolArgs];
+
+	$status   = 'Success';
+	$errorMsg = '';
+	if (is_array($resp) && isset($resp['error'])) {
+		$status   = 'Error';
+		$errorMsg = is_array($resp['error']) && isset($resp['error']['message'])
+			? (string) $resp['error']['message']
+			: (string) json_encode($resp['error']);
+	} elseif (is_array($resp) && isset($resp['result']['isError']) && $resp['result']['isError']) {
+		$status   = 'Error';
+		$errorMsg = is_array($resp['result']['content'] ?? null) ? (string) json_encode($resp['result']['content']) : '';
+	}
+
+	$rawResStr = is_string($resp) ? $resp : (string) json_encode($resp);
+
+	ai_log_request(
+		$db,
+		$serviceUser,
+		$query,
+		$responseShape,
+		'mcp',
+		microtime(true) - $tStart,
+		1.0,
+		$status,
+		$errorMsg,
+		$rawInput,
+		$rawResStr
+	);
+}
+
 // Request handling
 try {
+	$tStart = microtime(true);
+
 	// Basic payload size limit
 	$rawInput = file_get_contents('php://input');
 	if ($rawInput === false || strlen($rawInput) > 1024 * 1024) {
@@ -168,11 +247,15 @@ try {
 				continue;
 			}
 
+			$reqStart = microtime(true);
 			$res = $server->handleRequest($req);
 
 			if ($res !== null) {
 				$responses[] = $res;
 			}
+
+			// Log each tools/call separately so the admin log viewer shows them individually.
+			mcp_log_request($req, $res, $reqStart, (string) json_encode($req));
 		}
 
 		echo json_encode($responses);
@@ -187,6 +270,10 @@ try {
 		if ($response !== null) {
 			echo json_encode($response);
 		}
+
+		// Log this tools/call to llx_ai_request_log (no-op unless AI_LOG_REQUESTS is enabled
+		// and the method is tools/call).
+		mcp_log_request($request, $response, $tStart, $rawInput);
 	}
 } catch (Exception $e) {
 	dol_syslog(
diff --git a/htdocs/ai/tools/crud_objects.class.php b/htdocs/ai/tools/crud_objects.class.php
index 7c57d92a11f..61933736148 100644
--- a/htdocs/ai/tools/crud_objects.class.php
+++ b/htdocs/ai/tools/crud_objects.class.php
@@ -196,6 +196,12 @@ If user says 'order' without any qualifier, they mean a SALES ORDER - use this t
 						"header" => [
 							"type" => "object",
 							"description" => "Invoice header data. Must include 'socid' (Customer ID).",
+							"properties" => [
+								"socid" => ["type" => "integer", "description" => "Customer ID (Thirdparty ID)"],
+								"date" => ["type" => "string", "description" => "Invoice date (YYYY-MM-DD)"],
+								"note_public" => ["type" => "string", "description" => "Public note"],
+								"note_private" => ["type" => "string", "description" => "Private note"]
+							],
 							"required" => ["socid"]
 						],
 						"lines" => [
@@ -233,6 +239,13 @@ If user says 'order' without any qualifier, they mean a SALES ORDER - use this t
 						"header" => [
 							"type" => "object",
 							"description" => "Header data. Must include 'socid'.",
+							"properties" => [
+								"socid" => ["type" => "integer", "description" => "Thirdparty ID (Customer for proposal, Supplier for supplier_*)"],
+								"date" => ["type" => "string", "description" => "Document date (YYYY-MM-DD)"],
+								"duree_validite" => ["type" => "integer", "description" => "Validity in days (proposal only)"],
+								"note_public" => ["type" => "string", "description" => "Public note"],
+								"note_private" => ["type" => "string", "description" => "Private note"]
+							],
 							"required" => ["socid"]
 						],
 						"lines" => [
@@ -539,8 +552,17 @@ If user says 'order' without any qualifier, they mean a SALES ORDER - use this t
 		if ($productIdentifier !== '') {
 			$findResult = $this->findProduct($productIdentifier);
 			if (is_array($findResult) && isset($findResult['error'])) {
-				// Ensure the return array matches the expected shape by adding 'success' => false
-				return array_merge(['success' => false], $findResult);
+				// Only abort if the caller EXPLICITLY asked for a product (via the 'product'
+				// argument). If they only provided a free-text 'description', we silently
+				// fall through with $prod = null so Dolibarr creates a free-text line item,
+				// which is a perfectly valid Dolibarr feature.
+				// Previous behaviour aborted ALL line creations whose description didn't
+				// match an existing product reference, which broke AI-driven creation of
+				// invoices/orders/proposals from one-off line descriptions.
+				if (isset($args['product'])) {
+					return array_merge(['success' => false], $findResult);
+				}
+				// fall through: $prod stays null
 			}
 			if (is_object($findResult)) {
 				$prod = $findResult;
@@ -599,7 +621,14 @@ If user says 'order' without any qualifier, they mean a SALES ORDER - use this t
 			$res = $object->addline($desc, $price, $qty, $vat, 0, 0, $fkProduct, $discount, 'HT', 0, 0, $prodType);
 		} elseif ($docType === 'supplier_invoice') {
 			/** @var FactureFournisseur $object */
-			$res = $object->addline($desc, $price, $qty, $vat, 0, 0, $fkProduct, $discount, 0, 0, '', 0, 'HT', 0, $prodType);
+			// IMPORTANT: FactureFournisseur::addline() does NOT share the same signature
+			// as Facture::addline(). Its parameter order is:
+			//   ($desc, $pu, $txtva, $txlocaltax1, $txlocaltax2, $qty, $fk_product, $remise_percent, ...)
+			// i.e. $qty is in position 6, not 3 (unlike customer Facture / Commande / Propal).
+			// The previous call passed our $qty as $txtva (-> a 1% VAT rate) and our $vat
+			// as $txlocaltax1, and position 6 ended up being a hardcoded 0 -> a line was
+			// inserted with qty=0, which Dolibarr silently dropped from the visible totals.
+			$res = $object->addline($desc, $price, $vat, 0, 0, $qty, $fkProduct, $discount, '', '', 0, 0, 'HT', $prodType);
 		} elseif ($docType === 'supplier_order') {
 			/** @var CommandeFournisseur $object */
 			$res = $object->addline($desc, $price, $qty, $vat, 0, 0, $fkProduct, $discount, 0, 0, 'HT', 0, '', '', $prodType);
diff --git a/htdocs/ai/tools/reports.class.php b/htdocs/ai/tools/reports.class.php
index f795579b795..738eb244866 100644
--- a/htdocs/ai/tools/reports.class.php
+++ b/htdocs/ai/tools/reports.class.php
@@ -230,77 +230,129 @@ class ToolReports extends McpTool
 	{
 		global $langs;
 
-		$langs->loadLangs(array("main", "bills", "companies"));
+		$langs->loadLangs(array("main", "bills", "companies", "products"));
 
-		$limit = isset($args['limit']) ? (int) $args['limit'] : 50;
+		$limit     = isset($args['limit']) ? (int) $args['limit'] : 50;
 		$dateStart = dol_stringtotime($args['date_start']);
-		$dateEnd = dol_stringtotime($args['date_end']);
-		$socid = $this->resolveThirdparty($args);
-
-		$sql = "SELECT f.rowid, f.ref, f.total_ttc, f.fk_statut, f.paye, f.datef, s.nom
-				FROM " . MAIN_DB_PREFIX . "facture as f
-				LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid
-				WHERE f.entity IN (" . getEntity('facture') . ")";
-
-		$sql .= " AND f.datef >= '" . $this->db->idate($dateStart) . "'";
-		$sql .= " AND f.datef <= '" . $this->db->idate($dateEnd) . "'";
+		$dateEnd   = dol_stringtotime($args['date_end']);
+		$socid     = $this->resolveThirdparty($args);
+		$groupBy   = isset($args['group_by']) ? (string) $args['group_by'] : 'thirdparty';
 
+		$list      = [];
+		$totalSum  = 0.0;
 		// Status Filter: Valid (1) and Paid (2). Exclude Draft (0) and Abandoned (3).
-		$sql .= " AND f.fk_statut IN (1, 2)";
+		$dateRange = " AND f.datef >= '" . $this->db->idate($dateStart)
+			. "' AND f.datef <= '" . $this->db->idate($dateEnd)
+			. "' AND f.fk_statut IN (1, 2)";
 
+		// CASE 1 -- Detailed list for a specific thirdparty.
 		if ($socid) {
-			$sql .= " AND f.fk_soc = " . (int) $socid;
-		}
+			$sql = "SELECT f.rowid, f.ref, f.total_ttc, f.fk_statut, f.paye, f.datef, s.nom FROM "
+				. MAIN_DB_PREFIX . "facture as f LEFT JOIN "
+				. MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid WHERE f.entity IN ("
+				. getEntity('facture') . ")"
+				. $dateRange
+				. " AND f.fk_soc = " . (int) $socid
+				. " ORDER BY f.datef DESC LIMIT " . ((int) $limit);
 
-		$sql .= " ORDER BY f.datef DESC LIMIT " . ((int) $limit);
+			$resql = $this->db->query($sql);
+			if ($resql) {
+				while ($r = $this->db->fetch_object($resql)) {
+					$totalSum += (float) $r->total_ttc;
 
-		$resql = $this->db->query($sql);
-		$list = [];
-		$totalSum = 0.0;
+					$statusLabel = $langs->transnoentitiesnoconv("Unknown");
+					if ($r->fk_statut == 1 && $r->paye == 0) {
+						$statusLabel = $langs->transnoentitiesnoconv("BillStatusNotPaid");
+					} elseif ($r->fk_statut == 1 && $r->paye == 1) {
+						$statusLabel = $langs->transnoentitiesnoconv("BillStatusStarted");
+					} elseif ($r->fk_statut == 2) {
+						$statusLabel = $langs->transnoentitiesnoconv("BillStatusPaid");
+					}
 
-		if ($resql) {
-			while ($r = $this->db->fetch_object($resql)) {
-				$totalSum += (float) $r->total_ttc;
+					$url     = DOL_URL_ROOT . "/compta/facture/card.php?id=" . $r->rowid;
+					$refHtml = '' . $r->ref . '';
 
-				// Determine Localized Status
-				$statusLabel = $langs->transnoentitiesnoconv("Unknown");
-				if ($r->fk_statut == 1 && $r->paye == 0) {
-					$statusLabel = $langs->transnoentitiesnoconv("BillStatusNotPaid");
-				} elseif ($r->fk_statut == 1 && $r->paye == 1) {
-					$statusLabel = $langs->transnoentitiesnoconv("BillStatusStarted");
-				} elseif ($r->fk_statut == 2) {
-					$statusLabel = $langs->transnoentitiesnoconv("BillStatusPaid");
+					$list[] = [
+						$langs->transnoentitiesnoconv("Ref")      => $refHtml,
+						$langs->transnoentitiesnoconv("Date")     => dol_print_date($this->db->jdate($r->datef), 'day'),
+						$langs->transnoentitiesnoconv("Customer") => $r->nom,
+						$langs->transnoentitiesnoconv("Amount")   => price($r->total_ttc),
+						$langs->transnoentitiesnoconv("Status")   => $statusLabel
+					];
 				}
-
-				// Build relative URL
-				$url = DOL_URL_ROOT . "/compta/facture/card.php?id=" . $r->rowid;
-
-				// Make Ref clickable
-				$refHtml = '' . $r->ref . '';
-
-				$list[] = [
-					$langs->transnoentitiesnoconv("Ref") => $refHtml,
-					$langs->transnoentitiesnoconv("Date") => dol_print_date($this->db->jdate($r->datef), 'day'),
-					$langs->transnoentitiesnoconv("Customer") => $r->nom,
-					$langs->transnoentitiesnoconv("Amount") => price($r->total_ttc),
-					$langs->transnoentitiesnoconv("Status") => $statusLabel
-				];
+				$this->db->free($resql);
+			}
+		} else {
+			// CASE 2 -- Global grouped report.
+			// Mirrors the pattern already used by getPurchaseReport(); previous implementation
+			// of getSalesReport() ignored $groupBy entirely and always returned a flat list.
+			$sanitizedSqlGroup = '';
+			$colName           = '';
+			$sqlJoin           = " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid";
+
+			if ($groupBy === 'month') {
+				$sanitizedSqlGroup = "DATE_FORMAT(f.datef, '%Y-%m')";
+				$colName           = $langs->transnoentitiesnoconv("Month");
+			} elseif ($groupBy === 'product') {
+				// Aggregate on product line items. Lines without product_id fall back to their description.
+				$sanitizedSqlGroup = "COALESCE(p.ref, fd.description, '?')";
+				$colName           = $langs->transnoentitiesnoconv("Product");
+				$sqlJoin .= " INNER JOIN " . MAIN_DB_PREFIX . "facturedet as fd ON fd.fk_facture = f.rowid LEFT JOIN "
+					. MAIN_DB_PREFIX . "product as p ON fd.fk_product = p.rowid";
+			} else {
+				// Default: group by customer
+				$sanitizedSqlGroup = "s.nom";
+				$colName           = $langs->transnoentitiesnoconv("Customer");
+			}
+
+			// For product grouping we sum line totals (more accurate per-product);
+			// otherwise we sum the invoice total_ttc.
+			$amountExpr = ($groupBy === 'product') ? "SUM(fd.total_ttc)" : "SUM(f.total_ttc)";
+			$countExpr  = ($groupBy === 'product') ? "COUNT(DISTINCT f.rowid)" : "COUNT(f.rowid)";
+
+			$sql = "SELECT " . $sanitizedSqlGroup . " as group_key, "
+				. $amountExpr . " as total_amount, "
+				. $countExpr . " as count_inv FROM "
+				. MAIN_DB_PREFIX . "facture as f"
+				. $sqlJoin
+				. " WHERE f.entity IN (" . getEntity('facture') . ")"
+				. $dateRange
+				. " GROUP BY group_key ORDER BY total_amount DESC LIMIT "
+				. ((int) max(1, $limit));
+
+			$resql = $this->db->query($sql);
+			if ($resql) {
+				while ($r = $this->db->fetch_object($resql)) {
+					$totalSum += (float) $r->total_amount;
+					$list[] = [
+						$colName                                  => $r->group_key ? $r->group_key : $langs->transnoentitiesnoconv('Unknown'),
+						$langs->transnoentitiesnoconv("Number")   => (int) $r->count_inv,
+						$langs->transnoentitiesnoconv("Amount")   => price($r->total_amount)
+					];
+				}
+				$this->db->free($resql);
 			}
-			$this->db->free($resql);
 		}
 
 		if (empty($list)) {
 			return [[$langs->transnoentitiesnoconv("Info") => $langs->transnoentitiesnoconv("NoRecordFound")]];
 		}
 
-		// Append Total Row
-		$list[] = [
-			$langs->transnoentitiesnoconv("Ref") => $langs->transnoentitiesnoconv("Total"),
-			$langs->transnoentitiesnoconv("Date") => "",
-			$langs->transnoentitiesnoconv("Customer") => "",
-			$langs->transnoentitiesnoconv("Amount") => price($totalSum),
-			$langs->transnoentitiesnoconv("Status") => ""
-		];
+		// Append Total Row (shape depends on detailed-vs-grouped path)
+		if ($socid) {
+			$list[] = [
+				$langs->transnoentitiesnoconv("Ref")      => $langs->transnoentitiesnoconv("Total"),
+				$langs->transnoentitiesnoconv("Date")     => "",
+				$langs->transnoentitiesnoconv("Customer") => "",
+				$langs->transnoentitiesnoconv("Amount")   => price($totalSum),
+				$langs->transnoentitiesnoconv("Status")   => ""
+			];
+		} else {
+			$list[] = [
+				$langs->transnoentitiesnoconv("Total")  => $langs->transnoentitiesnoconv("Total"),
+				$langs->transnoentitiesnoconv("Amount") => price($totalSum)
+			];
+		}
 
 		return $list;
 	}
diff --git a/htdocs/ai/tools/thirdparty.class.php b/htdocs/ai/tools/thirdparty.class.php
index 2aea57b8137..2fa9541c612 100644
--- a/htdocs/ai/tools/thirdparty.class.php
+++ b/htdocs/ai/tools/thirdparty.class.php
@@ -72,8 +72,9 @@ class ToolThirdParty extends McpTool
 				"inputSchema" => [
 					"type" => "object",
 					"properties" => [
-						"id" => ["type" => "integer", "required" => true]
-					]
+						"id" => ["type" => "integer", "description" => "The unique numerical ID of the thirdparty."]
+					],
+					"required" => ["id"]
 				]
 			],
 			[
@@ -82,7 +83,7 @@ class ToolThirdParty extends McpTool
 				"inputSchema" => [
 					"type" => "object",
 					"properties" => [
-						"name" => ["type" => "string", "description" => "Name of the thirdparty", "required" => true],
+						"name" => ["type" => "string", "description" => "Name of the thirdparty"],
 						"type" => ["type" => "string", "enum" => ["customer", "prospect", "supplier", "both", "none"], "default" => "customer", "description" => "Type of thirdparty. 'none' means not a customer or prospect."],
 						"email" => ["type" => "string", "description" => "Email address"],
 						"phone" => ["type" => "string", "description" => "Phone number"],
@@ -105,7 +106,7 @@ class ToolThirdParty extends McpTool
 				"inputSchema" => [
 					"type" => "object",
 					"properties" => [
-						"id" => ["type" => "integer", "description" => "The ID of the thirdparty to update.", "required" => true],
+						"id" => ["type" => "integer", "description" => "The ID of the thirdparty to update."],
 						"name" => ["type" => "string", "description" => "The new name for the thirdparty."],
 						"email" => ["type" => "string", "description" => "The new email address."],
 						"phone" => ["type" => "string", "description" => "The new phone number."],
@@ -123,7 +124,7 @@ class ToolThirdParty extends McpTool
 				"inputSchema" => [
 					"type" => "object",
 					"properties" => [
-						"id" => ["type" => "integer", "description" => "The ID of the thirdparty.", "required" => true]
+						"id" => ["type" => "integer", "description" => "The ID of the thirdparty."]
 					],
 					"required" => ["id"]
 				]
@@ -136,11 +137,10 @@ class ToolThirdParty extends McpTool
 					"properties" => [
 						"thirdparty_identifier" => [
 							"type" => ["string", "integer"],
-							"description" => "The ID or name of the thirdparty to add the contact to.",
-							"required" => true
+							"description" => "The ID or name of the thirdparty to add the contact to."
 						],
-						"firstname" => ["type" => "string", "description" => "Contact's first name.", "required" => true],
-						"lastname" => ["type" => "string", "description" => "Contact's last name.", "required" => true],
+						"firstname" => ["type" => "string", "description" => "Contact's first name."],
+						"lastname" => ["type" => "string", "description" => "Contact's last name."],
 						"email" => ["type" => "string", "description" => "Contact's email address."],
 						"phone" => ["type" => "string", "description" => "Contact's phone number."],
 						"role" => ["type" => "string", "description" => "Contact's role or position within the company."]
diff --git a/htdocs/asset/admin/asset_extrafields.php b/htdocs/asset/admin/asset_extrafields.php
index 5883ec96a91..2bd0658b338 100644
--- a/htdocs/asset/admin/asset_extrafields.php
+++ b/htdocs/asset/admin/asset_extrafields.php
@@ -40,7 +40,6 @@ require '../../main.inc.php';
  */
 
 require_once DOL_DOCUMENT_ROOT.'/core/lib/asset.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
 
 // Load translation files required by the page
 $langs->loadLangs(array("assets", "admin", "companies"));
diff --git a/htdocs/asset/admin/assetmodel_extrafields.php b/htdocs/asset/admin/assetmodel_extrafields.php
index 6e0a320731f..57ae24740aa 100644
--- a/htdocs/asset/admin/assetmodel_extrafields.php
+++ b/htdocs/asset/admin/assetmodel_extrafields.php
@@ -40,7 +40,6 @@ require '../../main.inc.php';
  */
 
 require_once DOL_DOCUMENT_ROOT.'/core/lib/asset.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
 
 // Load translation files required by the page
 $langs->loadLangs(array("assets", "admin", "companies"));
diff --git a/htdocs/bookcal/admin/availabilities_extrafields.php b/htdocs/bookcal/admin/availabilities_extrafields.php
index f72a1c04462..bcc14522aa0 100644
--- a/htdocs/bookcal/admin/availabilities_extrafields.php
+++ b/htdocs/bookcal/admin/availabilities_extrafields.php
@@ -37,7 +37,6 @@ require '../../main.inc.php';
  * @var Translate $langs
  * @var User $user
  */
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
 require_once '../lib/bookcal.lib.php';
 
 // Load translation files required by the page
diff --git a/htdocs/bookcal/admin/calendar_extrafields.php b/htdocs/bookcal/admin/calendar_extrafields.php
index 6a9c813ed67..3ee4bcc4eba 100644
--- a/htdocs/bookcal/admin/calendar_extrafields.php
+++ b/htdocs/bookcal/admin/calendar_extrafields.php
@@ -38,7 +38,6 @@ require '../../main.inc.php';
  * @var Translate $langs
  * @var User $user
  */
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
 require_once '../lib/bookcal.lib.php';
 
 // Load translation files required by the page
diff --git a/htdocs/comm/action/card.php b/htdocs/comm/action/card.php
index c1227aec42f..ccefebf0843 100644
--- a/htdocs/comm/action/card.php
+++ b/htdocs/comm/action/card.php
@@ -36,11 +36,11 @@ require '../../main.inc.php';
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Translate $langs
  * @var User $user
  */
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
@@ -131,7 +131,6 @@ $donotclearsession = GETPOST('donotclearsession') ? GETPOST('donotclearsession')
 $object = new ActionComm($db);
 $cactioncomm = new CActionComm($db);
 $contact = new Contact($db);
-$extrafields = new ExtraFields($db);
 $formfile = new FormFile($db);
 
 $form = new Form($db);
@@ -420,11 +419,22 @@ if (empty($reshook) && $action == 'add' && $usercancreate) {
 			$elProp = getElementProperties(GETPOST("elementtype", 'alpha'));
 			$modulecodetouseforpermissioncheck = $elProp['module'];
 			// Keep permission check aligned with rights class aliases (see restrictedArea()).
-			if ($modulecodetouseforpermissioncheck == 'productbatch') {
-				$modulecodetouseforpermissioncheck = 'produit';
-			}
 			$submodulecodetouseforpermissioncheck = $elProp['subelement'];
 
+			switch ($modulecodetouseforpermissioncheck) {
+				case 'productbatch':
+					$modulecodetouseforpermissioncheck = 'produit';
+					break;
+				case 'eventorganization':
+					// Event organization relies on Project permissions
+					$modulecodetouseforpermissioncheck = 'projet';
+					$submodulecodetouseforpermissioncheck = ''; // Project doesn't use submodules for read
+					break;
+				default:
+					// No mapping needed, keep original values
+					break;
+			}
+
 			$hasPermissionOnLinkedObject = 0;
 			if ($user->hasRight($modulecodetouseforpermissioncheck, 'read')) {
 				$hasPermissionOnLinkedObject = 1;
@@ -1001,8 +1011,20 @@ if (empty($reshook) && $action == 'update' && $usercancreate) {
 			$elProp = getElementProperties(GETPOST("elementtype", 'alpha'));
 			$modulecodetouseforpermissioncheck = $elProp['module'];
 			// Keep permission check aligned with rights class aliases (see restrictedArea()).
-			if ($modulecodetouseforpermissioncheck == 'productbatch') {
-				$modulecodetouseforpermissioncheck = 'produit';
+			$submodulecodetouseforpermissioncheck = $elProp['subelement'];
+
+			switch ($modulecodetouseforpermissioncheck) {
+				case 'productbatch':
+					$modulecodetouseforpermissioncheck = 'produit';
+					break;
+				case 'eventorganization':
+					// Event organization relies on Project permissions
+					$modulecodetouseforpermissioncheck = 'projet';
+					$submodulecodetouseforpermissioncheck = ''; // Project doesn't use submodules for read
+					break;
+				default:
+					// No mapping needed, keep original values
+					break;
 			}
 
 			$hasPermissionOnLinkedObject = 0;
@@ -1869,9 +1891,22 @@ if ($action == 'create') {
 		$elProp = getElementProperties($origin);
 		$modulecodetouseforpermissioncheck = $elProp['module'];
 		// Keep permission check aligned with rights class aliases (see restrictedArea()).
-		if ($modulecodetouseforpermissioncheck == 'productbatch') {
-			$modulecodetouseforpermissioncheck = 'produit';
+		$submodulecodetouseforpermissioncheck = $elProp['subelement'];
+
+		switch ($modulecodetouseforpermissioncheck) {
+			case 'productbatch':
+				$modulecodetouseforpermissioncheck = 'produit';
+				break;
+			case 'eventorganization':
+				// Event organization relies on Project permissions
+				$modulecodetouseforpermissioncheck = 'projet';
+				$submodulecodetouseforpermissioncheck = ''; // Project doesn't use submodules for read
+				break;
+			default:
+				// No mapping needed, keep original values
+				break;
 		}
+
 		if ($user->hasRight($modulecodetouseforpermissioncheck, 'read') || $user->hasRight($modulecodetouseforpermissioncheck, $elProp['element'], 'read')) {
 			$hasPermissionOnLinkedObject = 1;
 		}
diff --git a/htdocs/comm/action/list.php b/htdocs/comm/action/list.php
index 6e865812432..3e7e1bcb71d 100644
--- a/htdocs/comm/action/list.php
+++ b/htdocs/comm/action/list.php
@@ -4,7 +4,7 @@
  * Copyright (C) 2004-2016	Laurent Destailleur			
  * Copyright (C) 2005-2012	Regis Houssin				
  * Copyright (C) 2017		Open-DSI					
- * Copyright (C) 2018-2025  Frédéric France				
+ * Copyright (C) 2018-2026  Frédéric France				
  * Copyright (C) 2020		Tobias Sekan				
  * Copyright (C) 2024-2025	MDW							
  * Copyright (C) 2024		Alexandre Spangaro			
@@ -34,6 +34,7 @@ require '../../main.inc.php';
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Translate $langs
  * @var User $user
@@ -42,9 +43,8 @@ require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/agenda.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
-include_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php';
-include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
 
 // Load translation files required by the page
 $langs->loadLangs(array("users", "companies", "agenda", "commercial", "other", "orders", "bills"));
@@ -123,8 +123,6 @@ $search_categ_cus = GETPOST("search_categ_cus", "intcomma", 3) ? GETPOST("search
 $object = new ActionComm($db);
 $hookmanager->initHooks(array('agendalist'));
 
-$extrafields = new ExtraFields($db);
-
 // fetch optionals attributes and labels
 $extrafields->fetch_name_optionals_label($object->table_element);
 
diff --git a/htdocs/comm/admin/propal_extrafields.php b/htdocs/comm/admin/propal_extrafields.php
index acc54325a48..7bc2b8c2810 100644
--- a/htdocs/comm/admin/propal_extrafields.php
+++ b/htdocs/comm/admin/propal_extrafields.php
@@ -3,7 +3,7 @@
  * Copyright (C) 2003		Jean-Louis Bergamo		
  * Copyright (C) 2004-2011	Laurent Destailleur		
  * Copyright (C) 2012		Regis Houssin			
- * Copyright (C) 2024       Frédéric France         
+ * Copyright (C) 2024-2026  Frédéric France         
  *
  * 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
@@ -27,21 +27,19 @@
 
 // Load Dolibarr environment
 require '../../main.inc.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/propal.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
-
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Translate $langs
  * @var User $user
  */
+require_once DOL_DOCUMENT_ROOT.'/core/lib/propal.lib.php';
 
 // Load translation files required by the page
 $langs->loadLangs(array('companies', 'admin', 'propal'));
 
-$extrafields = new ExtraFields($db);
 $form = new Form($db);
 
 // List of supported format
@@ -63,7 +61,6 @@ if (!$user->admin) {
 require DOL_DOCUMENT_ROOT.'/core/actions_extrafields.inc.php';
 
 
-
 /*
  * View
  */
diff --git a/htdocs/comm/admin/propaldet_extrafields.php b/htdocs/comm/admin/propaldet_extrafields.php
index 5f939d6edce..4f4463de61e 100644
--- a/htdocs/comm/admin/propaldet_extrafields.php
+++ b/htdocs/comm/admin/propaldet_extrafields.php
@@ -6,7 +6,7 @@
  * Copyright (C) 2012		Florian Henry			
  * Copyright (C) 2013		Philippe Grand			
  * Copyright (C) 2013		Florian Henry			
- * Copyright (C) 2024       Frédéric France         
+ * Copyright (C) 2024-2026  Frédéric France         
  *
  * 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
@@ -30,21 +30,19 @@
 
 // Load Dolibarr environment
 require '../../main.inc.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/propal.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
-
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Translate $langs
  * @var User $user
  */
+require_once DOL_DOCUMENT_ROOT.'/core/lib/propal.lib.php';
 
 // Load translation files required by the page
 $langs->loadlangs(array('admin', 'other', 'propal'));
 
-$extrafields = new ExtraFields($db);
 $form = new Form($db);
 
 // List of supported format
diff --git a/htdocs/comm/card.php b/htdocs/comm/card.php
index 0969aeab7da..c22610350f9 100644
--- a/htdocs/comm/card.php
+++ b/htdocs/comm/card.php
@@ -7,7 +7,7 @@
  * Copyright (C) 2008		Raphael Bertrand (Resultic)	
  * Copyright (C) 2010-2020	Juanjo Menent				
  * Copyright (C) 2013-2024	Alexandre Spangaro			
- * Copyright (C) 2021-2025  Frédéric France				
+ * Copyright (C) 2021-2026  Frédéric France				
  * Copyright (C) 2015		Marcos García				
  * Copyright (C) 2020		Open-Dsi					
  * Copyright (C) 2022		Anthony Berton				
@@ -38,6 +38,7 @@ require '../main.inc.php';
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Societe $mysoc
  * @var Translate $langs
@@ -128,7 +129,6 @@ if (!$sortfield) {
 $cancel = GETPOST('cancel', 'alpha');
 
 $object = new Client($db);
-$extrafields = new ExtraFields($db);
 $formfile = new FormFile($db);
 
 // fetch optionals attributes and labels
diff --git a/htdocs/comm/mailing/advtargetemailing.php b/htdocs/comm/mailing/advtargetemailing.php
index 53f9006a026..48b5b839c0d 100644
--- a/htdocs/comm/mailing/advtargetemailing.php
+++ b/htdocs/comm/mailing/advtargetemailing.php
@@ -2,7 +2,7 @@
 /* Copyright (C) 2014 Florian Henry        
  * Copyright (C) 2016 Laurent Destailleur  
  * Copyright (C) 2024 MDW                  
- * Copyright (C) 2024       Frédéric France         
+ * Copyright (C) 2024-2026  Frédéric France         
  *
  * 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
@@ -27,6 +27,14 @@
 
 // Load Dolibarr environment
 require '../../main.inc.php';
+/**
+ * @var Conf $conf
+ * @var DoliDB $db
+ * @var ExtraFields $extrafields
+ * @var HookManager $hookmanager
+ * @var Translate $langs
+ * @var User $user
+ */
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmailing.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
@@ -39,14 +47,6 @@ require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
 
-/**
- * @var Conf $conf
- * @var DoliDB $db
- * @var HookManager $hookmanager
- * @var Translate $langs
- * @var User $user
- */
-
 // Load translation files required by the page
 $langs->loadLangs(array('mails', 'admin', 'companies', 'categories'));
 
@@ -895,8 +895,6 @@ if ($object->fetch($id) >= 0) {
 			$socstatic = new Societe($db);
 			$elementtype = $socstatic->table_element;
 			// fetch optionals attributes and labels
-			require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
-			$extrafields = new ExtraFields($db);
 			$extrafields->fetch_name_optionals_label($elementtype);
 			foreach ($extrafields->attributes[$elementtype]['label'] as $key => $val) {
 				if ($key != 'ts_nameextra' && $key != 'ts_payeur') {
@@ -1086,8 +1084,6 @@ if ($object->fetch($id) >= 0) {
 			$contactstatic = new Contact($db);
 			$elementype = $contactstatic->table_element;
 			// fetch optionals attributes and labels
-			dol_include_once('/core/class/extrafields.class.php');
-			$extrafields = new ExtraFields($db);
 			$extrafields->fetch_name_optionals_label($elementype);
 			if (!empty($extrafields->attributes[$elementtype]['type'])) {
 				foreach ($extrafields->attributes[$elementtype]['type'] as $key => &$value) {
diff --git a/htdocs/comm/mailing/card.php b/htdocs/comm/mailing/card.php
index c1f742c2ab3..90c6cad64d8 100644
--- a/htdocs/comm/mailing/card.php
+++ b/htdocs/comm/mailing/card.php
@@ -5,7 +5,7 @@
  * Copyright (C) 2021		Waël Almoman            
  * Copyright (C) 2024-2025	MDW						
  * Copyright (C) 2025		Jon Bendtsen            
- * Copyright (C) 2024-2025  Frédéric France			
+ * Copyright (C) 2024-2026  Frédéric France			
  *
  * 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
@@ -36,6 +36,7 @@ require '../../main.inc.php';
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Translate $langs
  * @var User $user
@@ -49,7 +50,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/comm/mailing/class/mailing.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
 if (isModEnabled('project')) {
 	require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php';
 	require_once DOL_DOCUMENT_ROOT . '/core/class/html.formprojet.class.php';
@@ -70,7 +70,6 @@ $backtopageforcancel = GETPOST('backtopageforcancel');
 
 // Initialize a technical objects
 $object = new Mailing($db);
-$extrafields = new ExtraFields($db);
 $hookmanager->initHooks(array('mailingcard', 'globalcard'));
 
 // Fetch optionals attributes and labels
diff --git a/htdocs/comm/mailing/list.php b/htdocs/comm/mailing/list.php
index 9bc73290dac..6cfe5abf85a 100644
--- a/htdocs/comm/mailing/list.php
+++ b/htdocs/comm/mailing/list.php
@@ -3,8 +3,8 @@
  * Copyright (C) 2005-2010	Regis Houssin				
  * Copyright (C) 2024		MDW							
  * Copyright (C) 2024		Alexandre Spangaro			
- * Copyright (C) 2024       Frédéric France         
- * Copyright (C) 2025		Jon Bendtsen            
+ * Copyright (C) 2024-2026  Frédéric France             
+ * Copyright (C) 2025		Jon Bendtsen                
  * Copyright (C) 2025       Josep Lluís Amador Teruel   
  *
  * This program is free software; you can redistribute it and/or modify
@@ -32,6 +32,7 @@ require '../../main.inc.php';
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Translate $langs
  * @var User $user
@@ -82,7 +83,6 @@ $search_project = GETPOST('search_project', 'alpha');
 
 // Initialize a technical objects
 $object = new Mailing($db);
-$extrafields = new ExtraFields($db);
 if ($projectid > 0) {
 	$hookmanager->initHooks(array('projectmailing', 'globalcard'));
 } else {
diff --git a/htdocs/comm/mailing/note.php b/htdocs/comm/mailing/note.php
index b72bd4b87aa..ee87315edc3 100644
--- a/htdocs/comm/mailing/note.php
+++ b/htdocs/comm/mailing/note.php
@@ -1,6 +1,6 @@
 
- * Copyright (C) 2024-2025  Frédéric France         
+ * Copyright (C) 2024-2026  Frédéric France         
  *
  * 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
@@ -24,17 +24,16 @@
 
 // Load Dolibarr environment
 require '../../main.inc.php';
-
-require_once DOL_DOCUMENT_ROOT.'/comm/mailing/class/mailing.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/emailing.lib.php';
-
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Translate $langs
  * @var User $user
  */
+require_once DOL_DOCUMENT_ROOT.'/comm/mailing/class/mailing.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/emailing.lib.php';
 
 // Load translation files required by the page
 $langs->loadLangs(array("mails", "mailing", "companies"));
@@ -48,7 +47,6 @@ $backtopage = GETPOST('backtopage', 'alpha');
 
 // Initialize a technical objects
 $object = new Mailing($db);
-$extrafields = new ExtraFields($db);
 $diroutputmassaction = $conf->mailing->dir_output.'/temp/massgeneration/'.$user->id;
 $hookmanager->initHooks(array('mailingnote', 'globalcard')); // Note that conf->hooks_modules contains array
 // Fetch optionals attributes and labels
diff --git a/htdocs/comm/propal/card.php b/htdocs/comm/propal/card.php
index 05f06514944..f98dcb8f0ec 100644
--- a/htdocs/comm/propal/card.php
+++ b/htdocs/comm/propal/card.php
@@ -45,6 +45,15 @@
 
 // Load Dolibarr environment
 require '../../main.inc.php';
+/**
+ * @var Conf $conf
+ * @var DoliDB $db
+ * @var ExtraFields $extrafields
+ * @var HookManager $hookmanager
+ * @var Societe $mysoc
+ * @var Translate $langs
+ * @var User $user
+ */
 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
 require_once DOL_DOCUMENT_ROOT . '/core/class/html.formother.class.php';
 require_once DOL_DOCUMENT_ROOT . '/core/class/html.formfile.class.php';
@@ -66,14 +75,6 @@ if (isModEnabled('variants')) {
 	require_once DOL_DOCUMENT_ROOT . '/variants/class/ProductCombination.class.php';
 }
 
-/**
- * @var Conf $conf
- * @var DoliDB $db
- * @var HookManager $hookmanager
- * @var Societe $mysoc
- * @var Translate $langs
- * @var User $user
- */
 
 // Load translation files required by the page
 $langs->loadLangs(array('companies', 'propal', 'compta', 'bills', 'orders', 'products', 'sendings', 'other'));
@@ -109,7 +110,6 @@ $hidedesc = (GETPOSTINT('hidedesc') ? GETPOSTINT('hidedesc') : (getDolGlobalStri
 $hideref = (GETPOSTINT('hideref') ? GETPOSTINT('hideref') : (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0));
 
 $object = new Propal($db);
-$extrafields = new ExtraFields($db);
 
 // fetch optionals attributes and labels
 $extrafields->fetch_name_optionals_label($object->table_element);
diff --git a/htdocs/commande/index.php b/htdocs/commande/index.php
index 036f898424d..cf33b2d22a5 100644
--- a/htdocs/commande/index.php
+++ b/htdocs/commande/index.php
@@ -3,7 +3,7 @@
  * Copyright (C) 2004-2011 Laurent Destailleur  
  * Copyright (C) 2005-2012 Regis Houssin        
  * Copyright (C) 2019      Nicolas ZABOURI      
- * Copyright (C) 2019-2024  Frédéric France         
+ * Copyright (C) 2019-2026  Frédéric France         
  *
  * 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
@@ -28,12 +28,6 @@
 
 // Load Dolibarr environment
 require '../main.inc.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/notify.class.php';
-require_once DOL_DOCUMENT_ROOT.'/societe/class/client.class.php';
-require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/order.lib.php';
-
 /**
  * @var Conf $conf
  * @var DoliDB $db
@@ -41,6 +35,12 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/order.lib.php';
  * @var Translate $langs
  * @var User $user
  */
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/notify.class.php';
+require_once DOL_DOCUMENT_ROOT.'/societe/class/client.class.php';
+require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/order.lib.php';
+
 
 // Load translation files required by the page
 $langs->loadLangs(array('orders', 'bills'));
@@ -50,8 +50,6 @@ if (!$user->hasRight('commande', 'lire')) {
 	accessforbidden();
 }
 
-$hookmanager = new HookManager($db);
-
 // Initialize a technical object to manage hooks. Note that conf->hooks_modules contains array
 $hookmanager->initHooks(array('ordersindex'));
 
diff --git a/htdocs/compta/bank/class/paymentvarious.class.php b/htdocs/compta/bank/class/paymentvarious.class.php
index 5bdf7139c9d..e25edb535d7 100644
--- a/htdocs/compta/bank/class/paymentvarious.class.php
+++ b/htdocs/compta/bank/class/paymentvarious.class.php
@@ -1,7 +1,8 @@
 
- * Copyright (C) 2018-2025  Frédéric France         
- * Copyright (C) 2024-2026	MDW						
+/* Copyright (C) 2017-2021  Alexandre Spangaro          
+ * Copyright (C) 2018-2025  Frédéric France             
+ * Copyright (C) 2024-2026  MDW                         
+ * Copyright (C) 2026       Solution Libre SAS          
  *
  * 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
@@ -272,7 +273,13 @@ class PaymentVarious extends CommonObject
 			return -1;
 		}
 
-		if (!$notrigger) {
+		// Actions on extra fields
+		$result = $this->insertExtraFields();
+		if ($result < 0) {
+			$error++;
+		}
+
+		if (!$error && !$notrigger) {
 			// Call trigger
 			$result = $this->call_trigger('PAYMENT_VARIOUS_MODIFY', $user);
 			if ($result < 0) {
@@ -354,6 +361,10 @@ class PaymentVarious extends CommonObject
 				$this->rappro               = $obj->rappro;
 				$this->bank_num_releve      = $obj->bank_num_releve;
 			}
+
+			// Fetch extrafields
+			$this->fetch_optionals();
+
 			$this->db->free($resql);
 
 			return 1;
@@ -533,6 +544,12 @@ class PaymentVarious extends CommonObject
 			$this->ref = (string) $this->id;
 
 			if ($this->id > 0) {
+				// Insert extrafields
+				$result = $this->insertExtraFields();
+				if ($result < 0) {
+					$error++;
+				}
+
 				if (isModEnabled("bank") && !empty($this->amount)) {
 					// Insert into llx_bank
 					require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
diff --git a/htdocs/compta/bank/line.php b/htdocs/compta/bank/line.php
index 42eba27e714..332e4dbcffd 100644
--- a/htdocs/compta/bank/line.php
+++ b/htdocs/compta/bank/line.php
@@ -7,7 +7,7 @@
  * Copyright (C) 2015-2026 Alexandre Spangaro	
  * Copyright (C) 2015      Jean-François Ferry	
  * Copyright (C) 2016      Marcos García        
- * Copyright (C) 2018-2025 Frédéric France      
+ * Copyright (C) 2018-2026  Frédéric France      
  * Copyright (C) 2021      Gauthier VERDOL      
  * Copyright (C) 2024-2026	MDW					
  *
@@ -36,6 +36,7 @@ require '../../main.inc.php';
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Translate $langs
  * @var User $user
@@ -90,7 +91,6 @@ if (!$user->hasRight('banque', 'lire') && !$user->hasRight('banque', 'consolidat
 }
 
 $object = new AccountLine($db);
-$extrafields = new ExtraFields($db);
 $extrafields->fetch_name_optionals_label($object->element);
 
 if ($id > 0) {
diff --git a/htdocs/compta/bank/various_payment/card.php b/htdocs/compta/bank/various_payment/card.php
index b1414c13568..7b86b7b6984 100644
--- a/htdocs/compta/bank/various_payment/card.php
+++ b/htdocs/compta/bank/various_payment/card.php
@@ -1,9 +1,10 @@
 
- * Copyright (C) 2018-2025	Frédéric France			
- * Copyright (C) 2023		Laurent Destailleur		
- * Copyright (C) 2023		Joachim Kueter			
- * Copyright (C) 2024-2025	MDW						
+/* Copyright (C) 2017-2026  Alexandre Spangaro      
+ * Copyright (C) 2018-2025  Frédéric France         
+ * Copyright (C) 2023       Laurent Destailleur     
+ * Copyright (C) 2023       Joachim Kueter          
+ * Copyright (C) 2024-2025  MDW                     
+ * Copyright (C) 2026		Solution Libre SAS      
  *
  * 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
@@ -27,18 +28,6 @@
 
 // Load Dolibarr environment
 require '../../../main.inc.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/bank.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/paymentvarious.class.php';
-require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
-require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
-require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php';
-if (isModEnabled('project')) {
-	require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
-	require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php';
-}
 
 /**
  * @var Conf $conf
@@ -48,6 +37,20 @@ if (isModEnabled('project')) {
  * @var User $user
  */
 
+require_once DOL_DOCUMENT_ROOT.'/core/lib/bank.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/paymentvarious.class.php';
+require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
+require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
+require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
+if (isModEnabled('project')) {
+	require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
+	require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php';
+}
+
 // Load translation files required by the page
 $langs->loadLangs(array("accountancy", "banks", "bills", "categories", "compta", "users"));
 
@@ -84,6 +87,9 @@ $result = restrictedArea($user, 'banque', '', '', '');
 
 $object = new PaymentVarious($db);
 
+$extrafields = new ExtraFields($db);
+$extrafields->fetch_name_optionals_label($object->table_element);
+
 $permissiontoadd = $user->hasRight('banque', 'modifier');
 $permissiontodelete = $user->hasRight('banque', 'modifier');
 
@@ -148,6 +154,12 @@ if (empty($reshook)) {
 		$object->sens = GETPOSTINT('sens');
 		$object->fk_project = GETPOSTINT('fk_project');
 
+		// Fill array 'array_options' with data from add form
+		$ret = $extrafields->setOptionalsFromPost(null, $object);
+		if ($ret < 0) {
+			$error++;
+		}
+
 		if (!checkGeneralAccountAllowsAuxiliary($db, $object->accountancy_code, $object->subledger_account)) {
 			setEventMessages($langs->trans("ErrorAccountNotCentralized"). ". " . $langs->trans("RemoveSubsidiaryAccountOrAdjustTheGeneralAccount"), null, 'errors');
 			$error++;
@@ -538,6 +550,8 @@ if ($action == 'create') {
 	}
 
 	// Other attributes
+	print $object->showOptionals($extrafields, 'create');
+
 	$parameters = array();
 	$reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 	print $hookmanager->resPrint;
diff --git a/htdocs/compta/bank/various_payment/list.php b/htdocs/compta/bank/various_payment/list.php
index 492b35b55b1..23397c5ca4c 100644
--- a/htdocs/compta/bank/various_payment/list.php
+++ b/htdocs/compta/bank/various_payment/list.php
@@ -1,9 +1,10 @@
 
- * Copyright (C) 2017       Laurent Destailleur			
- * Copyright (C) 2018-2024  Frédéric France				
- * Copyright (C) 2020       Tobias Sekan				
- * Copyright (C) 2024-2025	MDW							
+/* Copyright (C) 2017-2026  Alexandre Spangaro          
+ * Copyright (C) 2017       Laurent Destailleur         
+ * Copyright (C) 2018-2024  Frédéric France             
+ * Copyright (C) 2020       Tobias Sekan                
+ * Copyright (C) 2024-2025  MDW                         
+ * Copyright (C) 2026       Solution Libre SAS          
  *
  * 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
@@ -27,6 +28,15 @@
 
 // Load Dolibarr environment
 require '../../../main.inc.php';
+
+/**
+ * @var Conf $conf
+ * @var DoliDB $db
+ * @var HookManager $hookmanager
+ * @var Translate $langs
+ * @var User $user
+ */
+
 require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/paymentvarious.class.php';
 require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
@@ -37,14 +47,6 @@ if (isModEnabled('project')) {
 	require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
 }
 
-/**
- * @var Conf $conf
- * @var DoliDB $db
- * @var HookManager $hookmanager
- * @var Translate $langs
- * @var User $user
- */
-
 // Load translation files required by the page
 $langs->loadLangs(array("compta", "banks", "bills", "accountancy"));
 
@@ -183,6 +185,9 @@ $arrayfields = array(
 	'credit'		=> array('label' => "Credit", 'checked' => '1', 'position' => 510),
 );
 
+// Extra fields
+include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_array_fields.tpl.php';
+
 $arrayfields = dol_sort_array($arrayfields, 'position');
 
 // Security check
@@ -266,9 +271,19 @@ $sql = "SELECT v.rowid, v.sens, v.amount, v.label, v.datep as datep, v.datev as
 $sql .= " ba.rowid as bid, ba.ref as bref, ba.number as bnumber, ba.account_number as bank_account_number, ba.fk_accountancy_journal as accountancy_journal, ba.label as blabel,";
 $sql .= " pst.code as payment_code";
 
+// Add fields from extrafields
+if (!empty($extrafields->attributes[$object->table_element]['label'])) {
+	foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) {
+		$sql .= ($extrafields->attributes[$object->table_element]['type'][$key] != 'separate' ? ", ef.".$key." as options_".$key : '');
+	}
+}
+
 $sqlfields = $sql; // $sql fields to remove for count total
 
 $sql .= " FROM ".MAIN_DB_PREFIX."payment_various as v";
+if (!empty($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
+	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$object->table_element."_extrafields as ef on (v.rowid = ef.fk_object)";
+}
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_paiement as pst ON v.fk_typepayment = pst.id";
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."bank as b ON v.fk_bank = b.rowid";
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."bank_account as ba ON b.fk_account = ba.rowid";
@@ -600,6 +615,9 @@ if ($arrayfields['credit']['checked']) {
 	print '';
 }
 
+// Extra fields
+include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_input.tpl.php';
+
 if (!$conf->main_checkbox_left_column) {
 	print '';
 	$searchpicto = $form->showFilterButtons();
@@ -893,6 +911,9 @@ while ($i < $imaxinloop) {
 			print '';
 		}
 
+		// Extra fields
+		include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_print_fields.tpl.php';
+
 		if (!$conf->main_checkbox_left_column) {
 			print '';
 			if (!$i) {
diff --git a/htdocs/compta/cashcontrol/cashcontrol_list.php b/htdocs/compta/cashcontrol/cashcontrol_list.php
index d2711e022b1..3429ccf7c4e 100644
--- a/htdocs/compta/cashcontrol/cashcontrol_list.php
+++ b/htdocs/compta/cashcontrol/cashcontrol_list.php
@@ -1,6 +1,6 @@
 
- * Copyright (C) 2024		Frédéric France			
+ * Copyright (C) 2024-2026  Frédéric France			
  * Copyright (C) 2025-2026	MDW						
  *
  * This program is free software; you can redistribute it and/or modify
@@ -28,6 +28,7 @@ require '../../main.inc.php';
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Translate $langs
  * @var User $user
@@ -73,7 +74,6 @@ $pagenext = $page + 1;
 
 // Initialize a technical objects
 $object = new CashControl($db);
-$extrafields = new ExtraFields($db);
 //$diroutputmassaction = $conf->mymodule->dir_output.'/temp/massgeneration/'.$user->id;
 $hookmanager->initHooks(array($contextpage)); // Note that conf->hooks_modules contains array
 
diff --git a/htdocs/compta/facture/card-rec.php b/htdocs/compta/facture/card-rec.php
index 9b57b35208e..85fcd2585db 100644
--- a/htdocs/compta/facture/card-rec.php
+++ b/htdocs/compta/facture/card-rec.php
@@ -52,7 +52,6 @@ if (isModEnabled('project')) {
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/invoice.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
 
 // Load translation files required by the page
 $langs->loadLangs(array('bills', 'companies', 'compta', 'admin', 'other', 'products', 'banks'));
diff --git a/htdocs/contact/agenda.php b/htdocs/contact/agenda.php
index 35e64572768..81660b7b294 100644
--- a/htdocs/contact/agenda.php
+++ b/htdocs/contact/agenda.php
@@ -8,7 +8,7 @@
  * Copyright (C) 2013-2024	Alexandre Spangaro			
  * Copyright (C) 2014		Juanjo Menent				
  * Copyright (C) 2015		Jean-François Ferry			
- * Copyright (C) 2024-2025  Frédéric France			
+ * Copyright (C) 2024-2026  Frédéric France			
  *
  * 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
@@ -35,6 +35,7 @@ require '../main.inc.php';
 /**
  * @var Conf $conf
  * @var DoliDB $db
+ * @var ExtraFields $extrafields
  * @var HookManager $hookmanager
  * @var Translate $langs
  * @var User $user
@@ -46,7 +47,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
 require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
@@ -61,8 +61,8 @@ $error = 0;
 $errors = array();
 
 // Get parameters
-$action		= (GETPOST('action', 'alpha') ? GETPOST('action', 'alpha') : 'view');
-$confirm	= GETPOST('confirm', 'alpha');
+$action = (GETPOST('action', 'alpha') ? GETPOST('action', 'alpha') : 'view');
+$confirm = GETPOST('confirm', 'alpha');
 $backtopage = GETPOST('backtopage', 'alpha');
 $contextpage = GETPOST('contextpage', 'aZ09');
 
@@ -71,7 +71,6 @@ $socid = GETPOSTINT('socid');
 
 // Initialize objects
 $object = new Contact($db);
-$extrafields = new ExtraFields($db);
 
 // fetch optionals attributes and labels
 $extrafields->fetch_name_optionals_label($object->table_element);
diff --git a/htdocs/contact/card.php b/htdocs/contact/card.php
index 9f30549754b..d45ffa78563 100644
--- a/htdocs/contact/card.php
+++ b/htdocs/contact/card.php
@@ -8,7 +8,7 @@
  * Copyright (C) 2013-2024	Alexandre Spangaro			
  * Copyright (C) 2014		Juanjo Menent				
  * Copyright (C) 2015		Jean-François Ferry			
- * Copyright (C) 2018-2025  Frédéric France				
+ * Copyright (C) 2018-2026  Frédéric France				
  * Copyright (C) 2019		Josep Lluís Amador			
  * Copyright (C) 2020-2026	Open-Dsi					
  * Copyright (C) 2024-2026	MDW							
@@ -36,6 +36,15 @@
 
 // Load Dolibarr environment
 require '../main.inc.php';
+/**
+ * @var Conf $conf
+ * @var DoliDB $db
+ * @var ExtraFields $extrafields
+ * @var HookManager $hookmanager
+ * @var Societe $mysoc
+ * @var Translate $langs
+ * @var User $user
+ */
 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/contact.lib.php';
@@ -44,21 +53,11 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
 require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
 
-/**
- * @var Conf $conf
- * @var DoliDB $db
- * @var HookManager $hookmanager
- * @var Societe $mysoc
- * @var Translate $langs
- * @var User $user
- */
-
 // Load translation files required by the page
 $langs->loadLangs(array('companies', 'users', 'other', 'commercial'));
 
@@ -75,7 +74,6 @@ $socid = GETPOSTINT('socid');
 
 // Initialize a technical object
 $object = new Contact($db);
-$extrafields = new ExtraFields($db);
 
 // fetch optionals attributes and labels
 $extrafields->fetch_name_optionals_label($object->table_element);
diff --git a/htdocs/contrat/card.php b/htdocs/contrat/card.php
index 7c384facac1..6d7325eb85f 100644
--- a/htdocs/contrat/card.php
+++ b/htdocs/contrat/card.php
@@ -9,11 +9,11 @@
  * Copyright (C) 2014-2020	Ferran Marcet				
  * Copyright (C) 2014-2016	Marcos García				
  * Copyright (C) 2015		Jean-François Ferry			
- * Copyright (C) 2018-2026  Frédéric France				
- * Copyright (C) 2023		Charlene Benke				
+ * Copyright (C) 2018-2026	Frédéric France				
+ * Copyright (C) 2023-2026	Charlene Benke				
  * Copyright (C) 2023		Nick Fragoulis
  * Copyright (C) 2024-2025	MDW							
- * Copyright (C) 2024		Alexandre Spangaro			
+ * Copyright (C) 2024-2026	Alexandre Spangaro			
  * Copyright (C) 2025		William Mead				
  *
  * This program is free software; you can redistribute it and/or modify
@@ -1138,6 +1138,7 @@ llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-contrat page-card');
 
 $form = new Form($db);
 $formfile = new FormFile($db);
+$staticcontractline = new ContratLigne($db);
 if (isModEnabled('project')) {
 	$formproject = new FormProjets($db);
 }
@@ -1525,10 +1526,10 @@ if ($action == 'create') {
 
 
 		print '
'; + print '
'; print '
'; - - print ''; + print '
'; // Line info of thirdparty discounts print '
'.$langs->trans('Discount').''; @@ -1560,6 +1561,86 @@ if ($action == 'create') { print "
"; print '
'; + print '
'; + + print ''."\n"; + print '
'; + + print ''; + + // Qty by service status + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + $all= $object->getTotalizedLines(-1, 0); + $draft= $object->getTotalizedLines(0, 0); + $enabled= $object->getTotalizedLines(4, 0); + $expired= $object->getTotalizedLines(4, 1); + $close= $object->getTotalizedLines(5, 0); + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + if ($mysoc->localtax1_assuj == "1" || $all['total_localtax1'] != 0) { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + + if ($mysoc->localtax2_assuj == "1" || $all['total_localtax2'] != 0) { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + print "
'."".''.$langs->trans('Total').''.$staticcontractline->LibStatut(0, 5, 0).''.$staticcontractline->LibStatut(4, 5, 0).''.$staticcontractline->LibStatut(4, 5, 1).''.$staticcontractline->LibStatut(5, 5, 0).'
'.$langs->trans("Qty").''.price2num($all['total_qty']).''.price2num($draft['total_qty']).''.price2num($enabled['total_qty']).''.price2num($expired['total_qty']).''.price2num($close['total_qty']).'
'.$langs->trans("TotalHT").''.price($all['total_ht']).''.price($draft['total_ht']).''.price($enabled['total_ht']).''.price($expired['total_ht']).''.price($close['total_ht']).'
'.$langs->trans("TotalVAT").''.price($all['total_tva']).''.price($draft['total_tva']).''.price($enabled['total_tva']).''.price($expired['total_tva']).''.price($close['total_tva']).'
' . $langs->trans("TotalLT1") . '' . price($all['total_localtax1']) . '' . price($draft['total_localtax1']) . '' . price($enabled['total_localtax1']) . '' . price($expired['total_localtax1']) . '' . price($close['total_localtax1']) . '
' . $langs->trans("TotalLT2") . '' . price($all['total_localtax2']) . '' . price($draft['total_localtax2']) . '' . price($enabled['total_localtax2']) . '' . price($expired['total_localtax2']) . '' . price($close['total_localtax2']) . '
'.$langs->trans("TotalTTC").''.price($all['total_ttc']).''.price($draft['total_ttc']).''.price($enabled['total_ttc']).''.price($expired['total_ttc']).''.price($close['total_ttc']).'
"; + + print '
'; + print '
'; + + print '

'; if ($object->status == $object::STATUS_DRAFT && $user->hasRight('contrat', 'creer')) { print ''; diff --git a/htdocs/contrat/class/contrat.class.php b/htdocs/contrat/class/contrat.class.php index 814e223449c..82ae9b8635c 100644 --- a/htdocs/contrat/class/contrat.class.php +++ b/htdocs/contrat/class/contrat.class.php @@ -1,18 +1,20 @@ - * Copyright (C) 2004-2012 Destailleur Laurent - * Copyright (C) 2005-2014 Regis Houssin - * Copyright (C) 2006 Andre Cianfarani - * Copyright (C) 2008 Raphael Bertrand - * Copyright (C) 2010-2016 Juanjo Menent - * Copyright (C) 2013 Christophe Battarel - * Copyright (C) 2013 Florian Henry - * Copyright (C) 2014-2015 Marcos García - * Copyright (C) 2018 Nicolas ZABOURI +/* Copyright (C) 2003 Rodolphe Quiedeville + * Copyright (C) 2004-2012 Destailleur Laurent + * Copyright (C) 2005-2014 Regis Houssin + * Copyright (C) 2006 Andre Cianfarani + * Copyright (C) 2008 Raphael Bertrand + * Copyright (C) 2010-2016 Juanjo Menent + * Copyright (C) 2013 Christophe Battarel + * Copyright (C) 2013 Florian Henry + * Copyright (C) 2014-2015 Marcos García + * Copyright (C) 2018 Nicolas ZABOURI * Copyright (C) 2018-2025 Frédéric France - * Copyright (C) 2015-2018 Ferran Marcet - * Copyright (C) 2024 William Mead - * Copyright (C) 2024-2026 MDW + * Copyright (C) 2015-2018 Ferran Marcet + * Copyright (C) 2024 William Mead + * Copyright (C) 2024-2026 MDW + * Copyright (C) 2026 Charlene Benke + * Copyright (C) 2026 Alexandre Spangaro |int Array of totalized lines or int if error + */ + public function getTotalizedLines($statut, int $expired) + { + $sql = "SELECT SUM(cd.qty) as total_qty, SUM(cd.total_ht) as total_ht, SUM(cd.total_tva) as total_tva, SUM(cd.total_ttc) as total_ttc,"; + $sql .= " SUM(cd.total_localtax1) as total_localtax1, SUM(cd.total_localtax2) as total_localtax2"; + $sql .= " FROM ".MAIN_DB_PREFIX."contratdet as cd"; + $sql .= " WHERE cd.fk_contrat =".((int) $this->id); + if ($statut >= 0) { + $sql .= " AND cd.statut = ".((int) $statut); + if ($expired > 0) { + $sql .= " AND cd.date_fin_validite < '".$this->db->idate(dol_now())."'"; + } else { + $sql .= " AND cd.date_fin_validite >= '".$this->db->idate(dol_now())."'"; + } + } + $ret = $this->db->query($sql); + $response = array(); + if ($ret) { + $obj = $this->db->fetch_object($ret); + $response['total_qty'] = $obj->total_qty; + $response['total_ht'] = $obj->total_ht; + $response['total_tva'] = $obj->total_tva; + $response['total_localtax1'] = $obj->total_localtax1; + $response['total_localtax2'] = $obj->total_localtax2; + $response['total_ttc'] = $obj->total_ttc; + } else { + dol_print_error($this->db); + $this->error = $this->db->lasterror(); + return -1; + } + return $response; + } + // @Todo getLibSignedStatus, LibSignedStatus /** diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 62822cfae36..7b9084f71f4 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -1123,7 +1123,7 @@ class Form /** * Return a select list of country phone calling codes * - * @param string $selected Pre-selected phone code value (e.g. "+33") + * @param string $selected Preselected phone code value (e.g. "+33") * @param string $htmlname Name of HTML select element * @param string $morecss More CSS classes * @param int $showempty Show empty option (1) or not (0) diff --git a/htdocs/core/class/ldap.class.php b/htdocs/core/class/ldap.class.php index 92f57ff1d31..054d05a469c 100644 --- a/htdocs/core/class/ldap.class.php +++ b/htdocs/core/class/ldap.class.php @@ -30,7 +30,7 @@ * Note: * LDAP_ESCAPE_FILTER is to escape char array('\\', '*', '(', ')', "\x00") * LDAP_ESCAPE_DN is to escape char array('\\', ',', '=', '+', '<', '>', ';', '"', '#') - * @phan-file-suppress PhanTypeMismatchArgumentInternal (notifications concern 'resource) + * @phan-file-suppress PhanTypeMismatchArgumentInternal (notifications concern 'resource') */ /** @@ -242,7 +242,8 @@ class Ldap public $ldapcharset = 'UTF-8'; /** - * @var bool|resource The internal LDAP connection handle. Was a resource before PHP 8.1 and is an object of class LDAP\Connection since PHP 8.1 + * @var false|resource The internal LDAP connection handle. Was a resource before PHP 8.1 and is an object of class LDAP\Connection since PHP 8.1 + * @phpstan-var LDAP\Connection */ public $connection; @@ -346,7 +347,7 @@ class Ldap if (empty($this->error)) { // Loop on each ldap server foreach ($this->server as $host) { - if ($connected) { + if ($connected) { // @phpstan-ignore if.alwaysFalse break; } if (empty($host)) { @@ -384,7 +385,7 @@ class Ldap } } - if (is_resource($this->connection) || is_object($this->connection)) { + if ($this->connection !== false) { if ($ldapdebug) { dol_syslog(get_class($this)."::connectBind this->connection is ok", LOG_DEBUG); } @@ -436,7 +437,7 @@ class Ldap } } // Try in anonymous - if (!$this->bind) { + if (!$this->bind) { // @phpstan-ignore booleanNot.alwaysTrue dol_syslog(get_class($this)."::connectBind try bind anonymously on ".$host, LOG_DEBUG); $result = $this->bind(); if ($result) { @@ -451,7 +452,7 @@ class Ldap } } - if (!$connected) { + if (!$connected) { // @phpstan-ignore booleanNot.alwaysTrue $this->unbind(); } } // End loop on each server @@ -540,7 +541,7 @@ class Ldap } } } else { - if (is_resource($this->connection)) { + if ($this->connection !== false) { // @phan-suppress-next-line PhanTypeMismatchArgumentInternalReal PhanTypeSuspiciousIndirectVariable $this->result = @ldap_unbind($this->connection); } diff --git a/htdocs/core/db/pgsql.class.php b/htdocs/core/db/pgsql.class.php index 361037e705f..0aff98276ab 100644 --- a/htdocs/core/db/pgsql.class.php +++ b/htdocs/core/db/pgsql.class.php @@ -62,7 +62,9 @@ class DoliDBPgsql extends DoliDB public $standard_conforming_strings = false; - /** @var resource|boolean Resultset of last query */ + /** + * @var false|resource|PgSql\Result Resultset of last query + */ private $_results; @@ -624,7 +626,8 @@ class DoliDBPgsql extends DoliDB /** * Return datas as an array * - * @param resource $resultset Resultset of request + * @param bool|resource $resultset Resultset of request + * @phpstan-param bool|resource|PgSql\Result $resultset * @return array|null|int<0,0> Array or null if KO or end of cursor or 0 if resultset is bool */ public function fetch_row($resultset) @@ -634,6 +637,9 @@ class DoliDBPgsql extends DoliDB if (!is_resource($resultset) && !is_object($resultset)) { $resultset = $this->_results; } + if (is_bool($resultset)) { + return 0; + } return pg_fetch_row($resultset); // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal } diff --git a/htdocs/core/lib/bank.lib.php b/htdocs/core/lib/bank.lib.php index cf11e59f5ff..d69f280395b 100644 --- a/htdocs/core/lib/bank.lib.php +++ b/htdocs/core/lib/bank.lib.php @@ -1,12 +1,13 @@ - * Copyright (C) 2012 Regis Houssin - * Copyright (C) 2015 Alexandre Spangaro - * Copyright (C) 2016 Juanjo Menent - * Copyright (C) 2019 Nicolas ZABOURI - * Copyright (C) 2021 Ferran Marcet - * Copyright (C) 2024 MDW +/* Copyright (C) 2006-2016 Laurent Destailleur + * Copyright (C) 2012 Regis Houssin + * Copyright (C) 2015 Alexandre Spangaro + * Copyright (C) 2016 Juanjo Menent + * Copyright (C) 2019 Nicolas ZABOURI + * Copyright (C) 2021 Ferran Marcet + * Copyright (C) 2024 MDW * Copyright (C) 2024-2026 Frédéric France + * Copyright (C) 2026 Solution Libre SAS * * 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 @@ -207,6 +208,7 @@ function bank_admin_prepare_head($object) $extrafields->fetch_name_optionals_label('bank_account'); $extrafields->fetch_name_optionals_label('bank'); $extrafields->fetch_name_optionals_label('paiement'); + $extrafields->fetch_name_optionals_label('payment_various'); $h = 0; $head = array(); @@ -255,6 +257,15 @@ function bank_admin_prepare_head($object) $head[$h][2] = 'bank_payments_extrafields'; $h++; + $head[$h][0] = dolBuildUrl(DOL_URL_ROOT.'/admin/bank_various_payment_extrafields.php'); + $head[$h][1] = $langs->trans("ExtraFields").' ('.$langs->trans("VariousPayments").')'; + $nbExtrafields = $extrafields->attributes['payment_various']['count']; + if ($nbExtrafields > 0) { + $head[$h][1] .= ''.$nbExtrafields.''; + } + $head[$h][2] = 'bank_various_payment_extrafields'; + $h++; + complete_head_from_modules($conf, $langs, $object, $head, $h, 'bank_admin', 'remove'); return $head; diff --git a/htdocs/core/lib/security.lib.php b/htdocs/core/lib/security.lib.php index 4c7ac5b137c..49d587073ca 100644 --- a/htdocs/core/lib/security.lib.php +++ b/htdocs/core/lib/security.lib.php @@ -546,13 +546,20 @@ function restrictedArea(User $user, $features, $object = 0, $tableandshare = '', $feature2 = 'evaluation'; } - // @todo check : project_task - // @todo possible ? - // elseif (substr($features, -3, 3) == 'det') { - // $features = substr($features, 0, -3); - // } elseif (substr($features, -4, 4) == '_det' || substr($features, -4, 4) == 'line') { - // $features = substr($features, 0, -4); - // } + // When the object is a task (element='project_task') and $feature2 is empty, + // $checkUserAccessToObject() falls into the $checkproject path and uses the task ID + // as project ID, which always fails. Setting $feature2='project_task' triggers the + // normalization at line 974 that redirects to the $checktask path, which correctly + // resolves $task->fk_project before calling getProjectsAuthorizedForUser(). + if (is_object($object) && in_array($object->element, array('project_task', 'task')) + && (empty($features) || in_array($features, array('projet', 'project'))) + && empty($feature2)) { + $features = 'projet'; + $feature2 = 'project_task'; + if (empty($tableandshare)) { + $tableandshare = 'projet_task'; + } + } // print $features.' - '.$tableandshare.' - '.$feature2.' - '.$dbt_select."\n"; diff --git a/htdocs/core/modules/modBanque.class.php b/htdocs/core/modules/modBanque.class.php index fd0adfd32e0..354cedc0663 100644 --- a/htdocs/core/modules/modBanque.class.php +++ b/htdocs/core/modules/modBanque.class.php @@ -1,9 +1,10 @@ - * Copyright (C) 2004-2008 Laurent Destailleur - * Copyright (C) 2004 Sebastien Di Cintio - * Copyright (C) 2004 Benoit Mortier - * Copyright (C) 2008-2011 Regis Houssin +/* Copyright (C) 2003-2005 Rodolphe Quiedeville + * Copyright (C) 2004-2008 Laurent Destailleur + * Copyright (C) 2004 Sebastien Di Cintio + * Copyright (C) 2004 Benoit Mortier + * Copyright (C) 2008-2011 Regis Houssin + * Copyright (C) 2026 Solution Libre SAS * * 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 @@ -230,10 +231,16 @@ class modBanque extends DolibarrModules 'v.note'=>"payment", 'v.datec'=>"payment", "p.ref"=>"project", "p.title"=>"project" ); + // Add extra fields + $keyforselect = 'payment_various'; + $keyforelement = 'payment'; + $keyforaliasextra = 'extra'; + include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php'; $this->export_sql_start[$r] = 'SELECT '; $this->export_sql_end[$r] = ' FROM '.MAIN_DB_PREFIX.'payment_various as v'; $this->export_sql_end[$r] .= ' LEFT JOIN '.MAIN_DB_PREFIX."projet as p ON v.fk_projet = p.rowid"; $this->export_sql_end[$r] .= ' LEFT JOIN '.MAIN_DB_PREFIX."c_paiement as cp ON v.fk_typepayment = cp.id"; + $this->export_sql_end[$r] .= ' LEFT JOIN '.MAIN_DB_PREFIX.'payment_various_extrafields as extra ON v.rowid = extra.fk_object'; $this->export_sql_end[$r] .= ' WHERE v.entity IN ('.getEntity('payment_various').')'; $this->export_sql_order[$r] = ' ORDER BY v.datep'; } diff --git a/htdocs/core/modules/modSubtotals.class.php b/htdocs/core/modules/modSubtotals.class.php index bcf6635bdec..45e5826eacd 100644 --- a/htdocs/core/modules/modSubtotals.class.php +++ b/htdocs/core/modules/modSubtotals.class.php @@ -33,6 +33,10 @@ include_once DOL_DOCUMENT_ROOT."/core/modules/DolibarrModules.class.php"; require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php'; +if (!defined('SUBTOTALS_SPECIAL_CODE')) { + define('SUBTOTALS_SPECIAL_CODE', 81); +} + /** * Description and activation class for module subtotals */ diff --git a/htdocs/core/tpl/objectline_create.tpl.php b/htdocs/core/tpl/objectline_create.tpl.php index 2997ad591de..80fa779b073 100644 --- a/htdocs/core/tpl/objectline_create.tpl.php +++ b/htdocs/core/tpl/objectline_create.tpl.php @@ -1304,7 +1304,8 @@ if (!empty($object->thirdparty)) { } // Deal with supplier ref price (idprodfournprice = int) - if (jQuery('#idprodfournprice').val() > 0) + var supplierVal = jQuery('#idprodfournprice').val(); + if (supplierVal && supplierVal !== '-1' && (supplierVal > 0 || supplierVal.indexOf('idprod_') === 0)) { console.log("objectline_create.tpl #idprodfournprice is an ID > 0, so we set some properties into page"); diff --git a/htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php b/htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php index caafde041c8..0a27e200bd4 100644 --- a/htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php +++ b/htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php @@ -8,7 +8,7 @@ * Copyright (C) 2022 Ferran Marcet * Copyright (C) 2023-2024 William Mead * Copyright (C) 2023 Christian Foellmann - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2025 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -209,13 +209,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ContractValidatedInDolibarr", (string) ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("ContractValidatedInDolibarr", (string) ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ContractValidatedInDolibarr", (string) ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("ContractValidatedInDolibarr", (string) ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -244,13 +244,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("PropalValidatedInDolibarr", (string) ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("PropalValidatedInDolibarr", (string) ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("PropalValidatedInDolibarr", (string) ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("PropalValidatedInDolibarr", (string) ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -261,13 +261,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("PropalBackToDraftInDolibarr", (string) ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("PropalBackToDraftInDolibarr", (string) ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("PropalBackToDraftInDolibarr", (string) ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("PropalBackToDraftInDolibarr", (string) ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -347,13 +347,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("OrderValidatedInDolibarr", (string) ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("OrderValidatedInDolibarr", (string) ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("OrderValidatedInDolibarr", (string) ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("OrderValidatedInDolibarr", (string) ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -433,13 +433,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { // Label if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InvoiceValidatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("InvoiceValidatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { // Description - $object->actionmsg = $langs->transnoentities("InvoiceValidatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("InvoiceValidatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -450,13 +450,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InvoiceBackToDraftInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InvoiceBackToDraftInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InvoiceBackToDraftInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InvoiceBackToDraftInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -467,13 +467,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InvoiceSentByEMail", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InvoiceSentByEMail", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InvoiceSentByEMail", $object->ref); + $object->actionmsg = $langs->transnoentities("InvoiceSentByEMail", (string) $object->ref); } // Parameters $object->sendtoid defined by caller @@ -486,13 +486,13 @@ class InterfaceActionsAuto extends DolibarrTriggers // Values for this action can't be defined by caller. if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InvoicePaidInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InvoicePaidInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InvoicePaidInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InvoicePaidInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -503,13 +503,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InvoiceCanceledInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InvoiceCanceledInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InvoiceCanceledInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InvoiceCanceledInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -520,13 +520,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InterventionCreatedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InterventionCreatedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InterventionCreatedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InterventionCreatedInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -539,13 +539,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InterventionValidatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("InterventionValidatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InterventionValidatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("InterventionValidatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -558,13 +558,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InterventionModifiedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InterventionModifiedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InterventionModifiedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InterventionModifiedInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -577,13 +577,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InterventionSentByEMail", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InterventionSentByEMail", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InterventionSentByEMail", $object->ref); + $object->actionmsg = $langs->transnoentities("InterventionSentByEMail", (string) $object->ref); } // Parameters $object->sendtoid defined by caller @@ -595,13 +595,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InterventionClassifiedBilledInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InterventionClassifiedBilledInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InterventionClassifiedBilledInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InterventionClassifiedBilledInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -612,13 +612,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InterventionClassifiedUnbilledInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InterventionClassifiedUnbilledInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InterventionClassifiedUnbilledInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InterventionClassifiedUnbilledInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -629,13 +629,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InterventionClosedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InterventionClosedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InterventionClosedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InterventionClosedInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -648,13 +648,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InterventionDeletedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InterventionDeletedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InterventionDeletedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InterventionDeletedInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -667,13 +667,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ShippingValidated", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("ShippingValidated", ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ShippingValidated", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("ShippingValidated", ($object->newref ? $object->newref : (string) $object->ref)); } // Parameters $object->sendtoid defined by caller @@ -685,13 +685,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ShippingSentByEMail", $object->ref); + $object->actionmsg2 = $langs->transnoentities("ShippingSentByEMail", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ShippingSentByEMail", $object->ref); + $object->actionmsg = $langs->transnoentities("ShippingSentByEMail", (string) $object->ref); } // Parameters $object->sendtoid defined by caller @@ -704,13 +704,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ReceptionValidated", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("ReceptionValidated", ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ReceptionValidated", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("ReceptionValidated", ($object->newref ? $object->newref : (string) $object->ref)); } // Parameters $object->sendtoid defined by caller @@ -723,13 +723,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ReceptionSentByEMail", $object->ref); + $object->actionmsg2 = $langs->transnoentities("ReceptionSentByEMail", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ReceptionSentByEMail", $object->ref); + $object->actionmsg = $langs->transnoentities("ReceptionSentByEMail", (string) $object->ref); } // Parameters $object->sendtoid defined by caller @@ -741,13 +741,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("PropalValidatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("PropalValidatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("PropalValidatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("PropalValidatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -758,13 +758,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ProposalSentByEMail", $object->ref); + $object->actionmsg2 = $langs->transnoentities("ProposalSentByEMail", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ProposalSentByEMail", $object->ref); + $object->actionmsg = $langs->transnoentities("ProposalSentByEMail", (string) $object->ref); } // Parameters $object->sendtoid defined by caller @@ -776,13 +776,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("PropalClosedSignedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("PropalClosedSignedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("PropalClosedSignedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("PropalClosedSignedInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -793,13 +793,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("PropalClosedRefusedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("PropalClosedRefusedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("PropalClosedRefusedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("PropalClosedRefusedInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -810,13 +810,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("OrderCreatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("OrderCreatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("OrderCreatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("OrderCreatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -827,13 +827,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("OrderValidatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("OrderValidatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("OrderValidatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("OrderValidatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -844,13 +844,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("OrderApprovedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("OrderApprovedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("OrderApprovedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("OrderApprovedInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -861,13 +861,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("OrderRefusedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("OrderRefusedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("OrderRefusedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("OrderRefusedInDolibarr", (string) $object->ref); } if (!empty($object->refuse_note)) { @@ -883,13 +883,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("OrderCanceledInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("OrderCanceledInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("OrderCanceledInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("OrderCanceledInDolibarr", (string) $object->ref); } if (!empty($object->cancel_note)) { @@ -905,13 +905,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("SupplierOrderSubmitedInDolibarr", ($object->newref ?: $object->ref), $object->getInputMethod()); + $object->actionmsg2 = $langs->transnoentities("SupplierOrderSubmitedInDolibarr", ($object->newref ?: (string) $object->ref), $object->getInputMethod()); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("SupplierOrderSubmitedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("SupplierOrderSubmitedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } if (!empty($object->context['comments'])) { @@ -927,13 +927,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("SupplierOrderReceivedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("SupplierOrderReceivedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("SupplierOrderReceivedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("SupplierOrderReceivedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -944,13 +944,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("SupplierOrderSentByEMail", $object->ref); + $object->actionmsg2 = $langs->transnoentities("SupplierOrderSentByEMail", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("SupplierOrderSentByEMail", $object->ref); + $object->actionmsg = $langs->transnoentities("SupplierOrderSentByEMail", (string) $object->ref); } // Parameters $object->sendtoid defined by caller @@ -962,13 +962,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("SupplierOrderClassifiedBilled", $object->ref); + $object->actionmsg2 = $langs->transnoentities("SupplierOrderClassifiedBilled", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("SupplierOrderClassifiedBilled", $object->ref); + $object->actionmsg = $langs->transnoentities("SupplierOrderClassifiedBilled", (string) $object->ref); } $object->sendtoid = array(); @@ -979,13 +979,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InvoiceCreatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("InvoiceCreatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InvoiceCreatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("InvoiceCreatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -996,13 +996,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InvoiceValidatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg2 = $langs->transnoentities("InvoiceValidatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InvoiceValidatedInDolibarr", ($object->newref ? $object->newref : $object->ref)); + $object->actionmsg = $langs->transnoentities("InvoiceValidatedInDolibarr", ($object->newref ? $object->newref : (string) $object->ref)); } $object->sendtoid = array(); @@ -1013,13 +1013,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InvoiceBackToDraftInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InvoiceBackToDraftInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InvoiceBackToDraftInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InvoiceBackToDraftInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -1030,13 +1030,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("SupplierInvoiceSentByEMail", $object->ref); + $object->actionmsg2 = $langs->transnoentities("SupplierInvoiceSentByEMail", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("SupplierInvoiceSentByEMail", $object->ref); + $object->actionmsg = $langs->transnoentities("SupplierInvoiceSentByEMail", (string) $object->ref); } // Parameters $object->sendtoid defined by caller @@ -1048,13 +1048,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InvoicePaidInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InvoicePaidInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InvoicePaidInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InvoicePaidInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -1064,13 +1064,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("InvoiceCanceledInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("InvoiceCanceledInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("InvoiceCanceledInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("InvoiceCanceledInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -1191,13 +1191,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("MemberSubscriptionDeletedInDolibarr", $object->ref, $member->getFullName($langs)); + $object->actionmsg2 = $langs->transnoentities("MemberSubscriptionDeletedInDolibarr", (string) $object->ref, $member->getFullName($langs)); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("MemberSubscriptionDeletedInDolibarr", $object->ref, $member->getFullName($langs)); + $object->actionmsg = $langs->transnoentities("MemberSubscriptionDeletedInDolibarr", (string) $object->ref, $member->getFullName($langs)); $object->actionmsg .= "\n".$langs->transnoentities("Member").': '.$member->getFullName($langs); $object->actionmsg .= "\n".$langs->transnoentities("Type").': '.$object->fk_type; $object->actionmsg .= "\n".$langs->transnoentities("Amount").': '.$object->amount; @@ -1273,14 +1273,14 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ProjectCreatedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("ProjectCreatedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ProjectCreatedInDolibarr", $object->ref); - $object->actionmsg .= "\n".$langs->transnoentities("Project").': '.$object->ref; + $object->actionmsg = $langs->transnoentities("ProjectCreatedInDolibarr", (string) $object->ref); + $object->actionmsg .= "\n".$langs->transnoentities("Project").': '.(string) $object->ref; } $object->sendtoid = array(); @@ -1291,14 +1291,14 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ProjectValidatedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("ProjectValidatedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ProjectValidatedInDolibarr", $object->ref); - $object->actionmsg .= "\n".$langs->transnoentities("Project").': '.$object->ref; + $object->actionmsg = $langs->transnoentities("ProjectValidatedInDolibarr", (string) $object->ref); + $object->actionmsg .= "\n".$langs->transnoentities("Project").': '.(string) $object->ref; } $object->sendtoid = array(); @@ -1309,13 +1309,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ProjectModifiedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("ProjectModifiedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ProjectModifiedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("ProjectModifiedInDolibarr", (string) $object->ref); } //$object->actionmsg .= "\n".$langs->transnoentities("Task").': ???'; @@ -1331,13 +1331,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ProjectSentByEMail", $object->ref); + $object->actionmsg2 = $langs->transnoentities("ProjectSentByEMail", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ProjectSentByEMail", $object->ref); + $object->actionmsg = $langs->transnoentities("ProjectSentByEMail", (string) $object->ref); } // Parameters $object->sendtoid defined by caller @@ -1348,9 +1348,9 @@ class InterfaceActionsAuto extends DolibarrTriggers $langs->loadLangs(array("agenda", "other", "projects")); if (empty($object->actionmsg2)) { - $object->actionmsg2 = $langs->transnoentities("ProjectDeletedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("ProjectDeletedInDolibarr", (string) $object->ref); } - $object->actionmsg = $langs->transnoentities("ProjectDeletedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("ProjectDeletedInDolibarr", (string) $object->ref); $object->sendtoid = array(); } elseif ($action == 'PROJECT_CLOSE' && $object instanceof Project) { @@ -1359,13 +1359,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("ProjectClosedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("ProjectClosedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("ProjectClosedInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("ProjectClosedInDolibarr", (string) $object->ref); } $object->sendtoid = array(); @@ -1377,14 +1377,14 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("TaskCreatedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("TaskCreatedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("TaskCreatedInDolibarr", $object->ref); - $object->actionmsg .= "\n".$langs->transnoentities("Task").': '.$object->ref; + $object->actionmsg = $langs->transnoentities("TaskCreatedInDolibarr", (string) $object->ref); + $object->actionmsg .= "\n".$langs->transnoentities("Task").': '.(string) $object->ref; } $object->sendtoid = array(); @@ -1395,14 +1395,14 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("TaskModifiedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("TaskModifiedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("TaskModifiedInDolibarr", $object->ref); - $object->actionmsg .= "\n".$langs->transnoentities("Task").': '.$object->ref; + $object->actionmsg = $langs->transnoentities("TaskModifiedInDolibarr", (string) $object->ref); + $object->actionmsg .= "\n".$langs->transnoentities("Task").': '.(string) $object->ref; } $object->sendtoid = array(); @@ -1413,14 +1413,14 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("TaskDeletedInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("TaskDeletedInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("TaskDeletedInDolibarr", $object->ref); - $object->actionmsg .= "\n".$langs->transnoentities("Task").': '.$object->ref; + $object->actionmsg = $langs->transnoentities("TaskDeletedInDolibarr", (string) $object->ref); + $object->actionmsg .= "\n".$langs->transnoentities("Task").': '.(string) $object->ref; } $object->sendtoid = array(); @@ -1431,13 +1431,13 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { if (empty($object->context['actionmsg2'])) { - $object->actionmsg2 = $langs->transnoentities("TICKET_ASSIGNEDInDolibarr", $object->ref); + $object->actionmsg2 = $langs->transnoentities("TICKET_ASSIGNEDInDolibarr", (string) $object->ref); } else { $object->actionmsg2 = $object->context['actionmsg2']; } } if (empty($object->actionmsg)) { - $object->actionmsg = $langs->transnoentities("TICKET_ASSIGNEDInDolibarr", $object->ref); + $object->actionmsg = $langs->transnoentities("TICKET_ASSIGNEDInDolibarr", (string) $object->ref); } if ($object->oldcopy->fk_user_assign > 0) { @@ -1475,21 +1475,21 @@ class InterfaceActionsAuto extends DolibarrTriggers if (empty($object->actionmsg2)) { // Load translation files required by the page $langs->loadLangs(array("agenda", "other")); - if ($langs->transnoentities($action."InDolibarr", (empty($object->newref) ? $object->ref : $object->newref)) != $action."InDolibarr") { // specific translation key - $object->actionmsg2 = $langs->transnoentities($action."InDolibarr", (empty($object->newref) ? $object->ref : $object->newref)); + if ($langs->transnoentities($action."InDolibarr", (empty($object->newref) ? (string) $object->ref : $object->newref)) != $action."InDolibarr") { // specific translation key + $object->actionmsg2 = $langs->transnoentities($action."InDolibarr", (empty($object->newref) ? (string) $object->ref : $object->newref)); } else { // generic translation key $tmp = explode('_', $action); - $object->actionmsg2 = $langs->transnoentities($tmp[count($tmp) - 1]."InDolibarr", (empty($object->newref) ? $object->ref : $object->newref)); + $object->actionmsg2 = $langs->transnoentities($tmp[count($tmp) - 1]."InDolibarr", (empty($object->newref) ? (string) $object->ref : $object->newref)); } } if (empty($object->actionmsg)) { // Load translation files required by the page $langs->loadLangs(array("agenda", "other")); - if ($langs->transnoentities($action."InDolibarr", (empty($object->newref) ? $object->ref : $object->newref)) != $action."InDolibarr") { // specific translation key - $object->actionmsg = $langs->transnoentities($action."InDolibarr", (empty($object->newref) ? $object->ref : $object->newref)); + if ($langs->transnoentities($action."InDolibarr", (empty($object->newref) ? (string) $object->ref : $object->newref)) != $action."InDolibarr") { // specific translation key + $object->actionmsg = $langs->transnoentities($action."InDolibarr", (empty($object->newref) ? (string) $object->ref : $object->newref)); } else { // generic translation key $tmp = explode('_', $action); - $object->actionmsg = $langs->transnoentities($tmp[count($tmp) - 1]."InDolibarr", (empty($object->newref) ? $object->ref : $object->newref)); + $object->actionmsg = $langs->transnoentities($tmp[count($tmp) - 1]."InDolibarr", (empty($object->newref) ? (string) $object->ref : $object->newref)); } if (isModEnabled('multicompany') && property_exists($object, 'entity') && $object->entity > 1) { $object->actionmsg .= ' ('.$langs->trans("Entity").' '.$object->entity.')'; diff --git a/htdocs/core/triggers/interface_50_modLdap_Ldapsynchro.class.php b/htdocs/core/triggers/interface_50_modLdap_Ldapsynchro.class.php index 019a1f6428b..1637a176edc 100644 --- a/htdocs/core/triggers/interface_50_modLdap_Ldapsynchro.class.php +++ b/htdocs/core/triggers/interface_50_modLdap_Ldapsynchro.class.php @@ -2,7 +2,7 @@ /* Copyright (C) 2005-2012 Laurent Destailleur * Copyright (C) 2005-2021 Regis Houssin * Copyright (C) 2014 Marcos García - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2025 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -551,79 +551,83 @@ class InterfaceLdapsynchro extends DolibarrTriggers $object->oldcopy = clone $object; // @phan-suppress-current-line PhanTypeMismatchProperty } - if (!method_exists($object->oldcopy, '_load_ldap_info')) { - dol_syslog("Trigger ".$action." was called by a function that did not set previously the method ->_load_ldap_info onto object", LOG_WARNING); - $object->oldcopy = clone $object; // @phan-suppress-current-line PhanTypeMismatchProperty - } + $oldcopy = $object->oldcopy; // Copy for static analysis - $oldinfo = $object->oldcopy->_load_ldap_info(); - $olddn = $object->oldcopy->_load_ldap_dn($oldinfo); + if (is_object($oldcopy)) { + if (!method_exists($oldcopy, '_load_ldap_info')) { + dol_syslog("Trigger ".$action." was called by a function that did not set previously the method ->_load_ldap_info onto object", LOG_WARNING); + $oldcopy = clone $object; // @phan-suppress-current-line PhanTypeMismatchProperty + } - // Verify if entry exist - $container = $object->oldcopy->_load_ldap_dn($oldinfo, 1); - $search = "(".$object->oldcopy->_load_ldap_dn($oldinfo, 2).")"; - $records = $ldap->search($container, $search); - if (count($records) && $records['count'] == 0) { - $olddn = ''; - } - - $info = $object->_load_ldap_info(); - $dn = $object->_load_ldap_dn($info); - $newrdn = $object->_load_ldap_dn($info, 2); - $newparent = $object->_load_ldap_dn($info, 1); - - $result = $ldap->update($dn, $info, $user, $olddn, $newrdn, $newparent); - - // For member type - if (getDolGlobalString('LDAP_MEMBER_TYPE_ACTIVE') && getDolGlobalInt('LDAP_MEMBER_TYPE_ACTIVE') === Ldap::SYNCHRO_DOLIBARR_TO_LDAP) { - require_once DOL_DOCUMENT_ROOT."/adherents/class/adherent_type.class.php"; - - /* - * Change member info - */ - $newmembertype = new AdherentType($this->db); - $newmembertype->fetch($object->typeid); - $newmembertype->listMembersForMemberType('', 1); - - $oldinfo = $newmembertype->_load_ldap_info(); - $olddn = $newmembertype->_load_ldap_dn($oldinfo); + $oldinfo = $oldcopy->_load_ldap_info(); + $olddn = $oldcopy->_load_ldap_dn($oldinfo); // Verify if entry exist - $container = $newmembertype->_load_ldap_dn($oldinfo, 1); - $search = "(".$newmembertype->_load_ldap_dn($oldinfo, 2).")"; + $container = $oldcopy->_load_ldap_dn($oldinfo, 1); + $search = "(".$oldcopy->_load_ldap_dn($oldinfo, 2).")"; $records = $ldap->search($container, $search); if (count($records) && $records['count'] == 0) { $olddn = ''; } - $info = $newmembertype->_load_ldap_info(); // Contains all members, included the new one (insert already done before trigger call) - $dn = $newmembertype->_load_ldap_dn($info); + $info = $object->_load_ldap_info(); + $dn = $object->_load_ldap_dn($info); + $newrdn = $object->_load_ldap_dn($info, 2); + $newparent = $object->_load_ldap_dn($info, 1); - $result = $ldap->update($dn, $info, $user, $olddn); + $result = $ldap->update($dn, $info, $user, $olddn, $newrdn, $newparent); + + // For member type + if (getDolGlobalString('LDAP_MEMBER_TYPE_ACTIVE') && getDolGlobalInt('LDAP_MEMBER_TYPE_ACTIVE') === Ldap::SYNCHRO_DOLIBARR_TO_LDAP) { + require_once DOL_DOCUMENT_ROOT."/adherents/class/adherent_type.class.php"; - if ($object->oldcopy->typeid != $object->typeid) { /* - * Remove member in old member type + * Change member info */ - $oldmembertype = new AdherentType($this->db); - $oldmembertype->fetch($object->oldcopy->typeid); - $oldmembertype->listMembersForMemberType('', 1); + $newmembertype = new AdherentType($this->db); + $newmembertype->fetch($object->typeid); + $newmembertype->listMembersForMemberType('', 1); - $oldinfo = $oldmembertype->_load_ldap_info(); - $olddn = $oldmembertype->_load_ldap_dn($oldinfo); + $oldinfo = $newmembertype->_load_ldap_info(); + $olddn = $newmembertype->_load_ldap_dn($oldinfo); // Verify if entry exist - $container = $oldmembertype->_load_ldap_dn($oldinfo, 1); - $search = "(".$oldmembertype->_load_ldap_dn($oldinfo, 2).")"; + $container = $newmembertype->_load_ldap_dn($oldinfo, 1); + $search = "(".$newmembertype->_load_ldap_dn($oldinfo, 2).")"; $records = $ldap->search($container, $search); if (count($records) && $records['count'] == 0) { $olddn = ''; } - $info = $oldmembertype->_load_ldap_info(); // Contains all members, included the new one (insert already done before trigger call) - $dn = $oldmembertype->_load_ldap_dn($info); + $info = $newmembertype->_load_ldap_info(); // Contains all members, included the new one (insert already done before trigger call) + $dn = $newmembertype->_load_ldap_dn($info); $result = $ldap->update($dn, $info, $user, $olddn); + + if ($oldcopy->typeid != $object->typeid) { + /* + * Remove member in old member type + */ + $oldmembertype = new AdherentType($this->db); + $oldmembertype->fetch($oldcopy->typeid); + $oldmembertype->listMembersForMemberType('', 1); + + $oldinfo = $oldmembertype->_load_ldap_info(); + $olddn = $oldmembertype->_load_ldap_dn($oldinfo); + + // Verify if entry exist + $container = $oldmembertype->_load_ldap_dn($oldinfo, 1); + $search = "(".$oldmembertype->_load_ldap_dn($oldinfo, 2).")"; + $records = $ldap->search($container, $search); + if (count($records) && $records['count'] == 0) { + $olddn = ''; + } + + $info = $oldmembertype->_load_ldap_info(); // Contains all members, included the new one (insert already done before trigger call) + $dn = $oldmembertype->_load_ldap_dn($info); + + $result = $ldap->update($dn, $info, $user, $olddn); + } } } } @@ -766,25 +770,29 @@ class InterfaceLdapsynchro extends DolibarrTriggers $object->oldcopy = clone $object; // @phan-suppress-current-line PhanTypeMismatchProperty } - $object->oldcopy->listMembersForMemberType('', 1); + $oldcopy = $object->oldcopy; // Local copy to help static analysis. - $oldinfo = $object->oldcopy->_load_ldap_info(); - $olddn = $object->oldcopy->_load_ldap_dn($oldinfo); + if (is_object($oldcopy)) { + $oldcopy->listMembersForMemberType('', 1); - // Verify if entry exist - $container = $object->oldcopy->_load_ldap_dn($oldinfo, 1); - $search = "(".$object->oldcopy->_load_ldap_dn($oldinfo, 2).")"; - $records = $ldap->search($container, $search); - if (count($records) && $records['count'] == 0) { - $olddn = ''; + $oldinfo = $oldcopy->_load_ldap_info(); + $olddn = $oldcopy->_load_ldap_dn($oldinfo); + + // Verify if entry exist + $container = $oldcopy->_load_ldap_dn($oldinfo, 1); + $search = "(".$oldcopy->_load_ldap_dn($oldinfo, 2).")"; + $records = $ldap->search($container, $search); + if (count($records) && $records['count'] == 0) { + $olddn = ''; + } + + $object->listMembersForMemberType('', 1); + + $info = $object->_load_ldap_info(); + $dn = $object->_load_ldap_dn($info); + + $result = $ldap->update($dn, $info, $user, $olddn); } - - $object->listMembersForMemberType('', 1); - - $info = $object->_load_ldap_info(); - $dn = $object->_load_ldap_dn($info); - - $result = $ldap->update($dn, $info, $user, $olddn); } if ($result <= 0) { diff --git a/htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php b/htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php index 3dedad04378..ec68dc2300d 100644 --- a/htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php +++ b/htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php @@ -1,7 +1,7 @@ * Copyright (C) 2016 Christophe Battarel - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2025 Frédéric France * Copyright (C) 2023-2025 Benjamin Falière * @@ -378,8 +378,8 @@ class InterfaceTicketEmail extends DolibarrTriggers $appli = $mysoc->name; /* Send email to admin */ - $subject = '['.$appli.'] '.$langs->transnoentities($base_subject, $object->ref, $object->track_id); - $message_admin = $langs->transnoentities($body, $object->track_id).'
'; + $subject = '['.$appli.'] '.$langs->transnoentities($base_subject, (string) $object->ref, (string) $object->track_id); + $message_admin = $langs->transnoentities($body, (string) $object->track_id).'
'; $message_admin .= '
  • '.$langs->trans('Title').' : '.$object->subject.'
  • '; $message_admin .= '
  • '.$langs->trans('Type').' : '.$langs->getLabelFromKey($this->db, 'TicketTypeShort'.$object->type_code, 'c_ticket_type', 'code', 'label', $object->type_code).'
  • '; $message_admin .= '
  • '.$langs->trans('TicketCategory').' : '.$langs->getLabelFromKey($this->db, 'TicketCategoryShort'.$object->category_code, 'c_ticket_category', 'code', 'label', $object->category_code).'
  • '; @@ -449,7 +449,7 @@ class InterfaceTicketEmail extends DolibarrTriggers $appli = $mysoc->name; $subject = '['.$appli.'] '.$langs->transnoentities($base_subject); - $message_customer = $langs->transnoentities($body, $object->track_id).'
    '; + $message_customer = $langs->transnoentities($body, (string) $object->track_id).'
    '; $message_customer .= '
    • '.$langs->trans('Title').' : '.$object->subject.'
    • '; $message_customer .= '
    • '.$langs->trans('Type').' : '.$langs->getLabelFromKey($this->db, 'TicketTypeShort'.$object->type_code, 'c_ticket_type', 'code', 'label', $object->type_code).'
    • '; $message_customer .= '
    • '.$langs->trans('TicketCategory').' : '.$langs->getLabelFromKey($this->db, 'TicketCategoryShort'.$object->category_code, 'c_ticket_category', 'code', 'label', $object->category_code).'
    • '; @@ -490,7 +490,7 @@ class InterfaceTicketEmail extends DolibarrTriggers $message_customer .= '

      '.$langs->trans('Message').' :

      '.$message.'


      '; if (getDolGlobalInt('TICKET_ENABLE_PUBLIC_INTERFACE')) { - $url_public_ticket = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/', 2)).'view.php?track_id='.urlencode($object->track_id); + $url_public_ticket = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/', 2)).'view.php?track_id='.urlencode((string) $object->track_id); $message_customer .= '

      '.$langs->trans($see_ticket).' : '.$url_public_ticket.'

      '; $message_customer .= '

      '.$langs->trans('TicketEmailPleaseDoNotReplyToThisEmail').'

      '; } else { @@ -547,7 +547,7 @@ class InterfaceTicketEmail extends DolibarrTriggers $appli = $mysoc->name; $subject = '['.$appli.'] '.$langs->transnoentities($base_subject); - $message = '

      '.$langs->transnoentities($body, $object->track_id, dolGetFirstLastname($user->firstname, $user->lastname))."

      "; + $message = '

      '.$langs->transnoentities($body, (string) $object->track_id, dolGetFirstLastname($user->firstname, $user->lastname))."

      "; $message .= '
      • '.$langs->trans('Title').' : '.$object->subject.'
      • '; $message .= '
      • '.$langs->trans('Type').' : '.$object->type_label.'
      • '; $message .= '
      • '.$langs->trans('Category').' : '.$object->category_label.'
      • '; diff --git a/htdocs/dav/dav.class.php b/htdocs/dav/dav.class.php index 1d528c9772b..906ead80db3 100644 --- a/htdocs/dav/dav.class.php +++ b/htdocs/dav/dav.class.php @@ -61,12 +61,12 @@ class CdavLib /** * Base sql request for calendar events * - * @param int $calid Calendar id + * @param int $calendarId Calendar id * @param int|bool $oid Oid * @param int|bool $ouri Ouri * @return string */ - public function getSqlCalEvents($calid, $oid = false, $ouri = false) + public function getSqlCalEvents($calendarId, $oid = false, $ouri = false) { // TODO : replace GROUP_CONCAT by $sql = 'SELECT @@ -94,7 +94,7 @@ class CdavLib FROM '.MAIN_DB_PREFIX.'actioncomm AS a'; $sql .= " LEFT JOIN '.MAIN_DB_PREFIX.'c_country as co ON co.rowid = sp.fk_pays LEFT JOIN '.MAIN_DB_PREFIX.'c_country as cos ON cos.rowid = s.fk_pays - WHERE a.id IN (SELECT ar.fk_actioncomm FROM '.MAIN_DB_PREFIX.'actioncomm_resources ar WHERE ar.element_type='user' AND ar.fk_element=".((int) $calid).") + WHERE a.id IN (SELECT ar.fk_actioncomm FROM '.MAIN_DB_PREFIX.'actioncomm_resources ar WHERE ar.element_type='user' AND ar.fk_element=".((int) $calendarId).") AND a.code IN (SELECT cac.code FROM '.MAIN_DB_PREFIX.'c_actioncomm cac WHERE cac.type <> 'systemauto') AND a.entity IN (".getEntity('societe', 1).")"; // TODO Restrict on external users @@ -112,11 +112,11 @@ class CdavLib /** * Convert calendar row to VCalendar string * - * @param int $calid Calendar id + * @param int $calendarId Calendar id * @param Object $obj Object id * @return string */ - public function toVCalendar($calid, $obj) + public function toVCalendar($calendarId, $obj) { /*$categ = array(); if($obj->soc_client) @@ -169,7 +169,7 @@ class CdavLib $caldata .= "LAST-MODIFIED:".gmdate('Ymd\THis', strtotime($obj->lastupd))."Z\n"; $caldata .= "DTSTAMP:".gmdate('Ymd\THis', strtotime($obj->lastupd))."Z\n"; if ($obj->sourceuid == '') { - $caldata .= "UID:".$obj->id.'-ev-'.$calid.'-cal-'.constant('CDAV_URI_KEY')."\n"; + $caldata .= "UID:".$obj->id.'-ev-'.$calendarId.'-cal-'.constant('CDAV_URI_KEY')."\n"; } else { $caldata .= "UID:".$obj->sourceuid."\n"; } @@ -251,24 +251,24 @@ class CdavLib */ public function getFullCalendarObjects($calendarId, $bCalendarData) { - $calid = (int) $calendarId; + $calendarId = (int) $calendarId; $calevents = array(); if (!$this->user->rights->agenda->myactions->read) { return $calevents; } - if ($calid != $this->user->id && (!isset($this->user->rights->agenda->allactions->read) || !$this->user->rights->agenda->allactions->read)) { + if ($calendarId != $this->user->id && (!isset($this->user->rights->agenda->allactions->read) || !$this->user->rights->agenda->allactions->read)) { return $calevents; } - $sql = $this->getSqlCalEvents($calid); + $sql = $this->getSqlCalEvents($calendarId); $result = $this->db->query($sql); if ($result) { while ($obj = $this->db->fetch_object($result)) { - $calendardata = $this->toVCalendar($calid, $obj); + $calendardata = $this->toVCalendar($calendarId, $obj); if ($bCalendarData) { $calevents[] = array( diff --git a/htdocs/eventorganization/class/conferenceorboothattendee.class.php b/htdocs/eventorganization/class/conferenceorboothattendee.class.php index dbec03466ae..a7c34d2243b 100644 --- a/htdocs/eventorganization/class/conferenceorboothattendee.class.php +++ b/htdocs/eventorganization/class/conferenceorboothattendee.class.php @@ -301,7 +301,7 @@ class ConferenceOrBoothAttendee extends CommonObject $result = $this->createCommon($user, $notrigger); if ($result > 0) { $result = $this->fetch($result); - if ($result > 0) { + if ($result > 0 && empty($this->ref)) { $this->ref = (string) $this->id; $result = $this->update($user); } diff --git a/htdocs/fichinter/card.php b/htdocs/fichinter/card.php index 869d6b0371a..94ba5059594 100644 --- a/htdocs/fichinter/card.php +++ b/htdocs/fichinter/card.php @@ -967,12 +967,14 @@ $help_url = 'EN:Module_Interventions|FR:Module_Fiches_d\'interventions'; llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-fichinter page-card'); +// Create new intervention if ($action == 'create') { - // Create new intervention - $soc = new Societe($db); + if ($socid > 0) { + $soc->fetch($socid); + } - print load_fiche_titre($langs->trans("AddIntervention"), '', 'intervention'); + print load_fiche_titre($langs->trans("NewIntervention"), '', 'intervention'); if ($error > 0) { dol_htmloutput_errors($mesg); @@ -980,10 +982,6 @@ if ($action == 'create') { dol_htmloutput_mesg($mesg); } - if ($socid) { - $res = $soc->fetch($socid); - } - if (GETPOST('origin', 'alphanohtml') && GETPOSTINT('originid')) { // Parse element/subelement (ex: project_task) $regs = array(); @@ -1036,6 +1034,8 @@ if ($action == 'create') { } } else { $projectid = GETPOSTINT('projectid'); + $note_private = GETPOST("note_private", "alpha"); + $note_public = GETPOST("note_public", "alpha"); } if (!$conf->global->FICHEINTER_ADDON) { @@ -1051,206 +1051,192 @@ if ($action == 'create') { //$modFicheinter = new $obj; //$numpr = $modFicheinter->getNextValue($soc, $object); + print '
        '; + print ''; + print ''; + print ''; + print ''."\n"; + print ''; + + print dol_get_fiche_head([]); + + print ''; + + // Ref + print ''; + + // Ref customer + print ''; + print ''; + + // Thirdparty + print ''; + print ''; if ($socid > 0) { - $soc = new Societe($db); - $soc->fetch($socid); - - print ''; - print ''; - print ''; - print ''; - print ''; - - print dol_get_fiche_head([]); - - print '
        '.$langs->trans('Ref').''.$langs->trans("Draft").'
        '.$langs->trans('RefCustomer').''; + print '
        '.$langs->trans('ThirdParty').'
        '; - - print ''; - - // Ref - print ''; - - // Ref customer - print ''; - print ''; - - // Description (must be a textarea and not html must be allowed (used in list view) - print ''; print ''; - - // Project - if (isModEnabled('project')) { - $formproject = new FormProjets($db); - - $langs->load("project"); - - print ''; - } - - // Category - if (isModEnabled('category')) { - // Categories - print '"; - } - - // Contract - if (isModEnabled('contract') && is_object($formcontract)) { - // This feature hang the application on large list of contracts, because the select component is not complete: it does not work like select of thirdparty or product to support large lists - // So we add a hidden option to avoid to have it used and the application locked, until the select_contract is fixed. - if (getDolGlobalString("CONTRACT_CAN_USE_THE_BUGGED_SELECT_COMPONENT")) { - $langs->load("contracts"); - print ''; - } - } - - // Model - $liste = ModelePDFFicheinter::liste_modeles($db); - if (count($liste) > 0) { - print ''; - print ''; - print '"; - } - - // Public note - print ''; - print ''; - print ''; - - // Private note - if (empty($user->socid)) { - print ''; - print ''; - print ''; - } - - // Other attributes - $parameters = array(); - $reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook - print $hookmanager->resPrint; - if (empty($reshook)) { - print $object->showOptionals($extrafields, 'create'); - } - - // Show link to origin object - if (!empty($origin) && !empty($originid) && is_object($objectsrc)) { - $newclassname = $classname; - if ($newclassname == 'Propal') { - $langs->load('propal'); - $newclassname = 'CommercialProposal'; - } - print ''; - - // Amount - /* Hide amount because we only copy services so amount may differ than source - print ''; - print '"; - if ($mysoc->localtax1_assuj == "1" || $objectsrc->total_localtax1 != 0) // Localtax1 RE - { - print '"; - } - - if ($mysoc->localtax2_assuj == "1" || $objectsrc->total_localtax2 != 0) // Localtax2 IRPF - { - print '"; - } - - print '"; - - if (isModEnabled("multicurrency")) - { - print ''; - print '"; - print '"; - } - */ - } - - print '
        '.$langs->trans("ThirdParty").''.$soc->getNomUrl(1).'
        '.$langs->trans('Ref').''.$langs->trans("Draft").'
        '.$langs->trans('RefCustomer').''; - print '
        '.$langs->trans("Description").''; - print ''; - print '
        '.$langs->trans("Project").''; - /* Fix: If a project must be linked to any companies (suppliers or not), project must be not be set as limited to customer but must be not linked to any particular thirdparty - if ($societe->fournisseur==1) - $numprojet=select_projects(-1, GETPOST("projectid", 'int'), 'projectid'); - else - $numprojet=select_projects($societe->id, GETPOST("projectid", 'int'), 'projectid'); - */ - $numprojet = $formproject->select_projects($soc->id, $projectid, 'projectid', 0, 0, 1, 0, 0, 0, 0, '', 0, 0, 'maxwidth500 widthcentpercentminusxx'); - if ($numprojet == 0) { - print '   '; - } - print '
        '.$langs->trans("Categories").''; - print $form->selectCategories(Categorie::TYPE_FICHINTER, 'categories', $object); - print "
        '.$langs->trans("Contract").''; - $numcontrat = $formcontract->select_contract($soc->id, GETPOSTINT('contratid'), 'contratid', 0, 1, 1); - if ($numcontrat == 0) { - print '   '; - } - print '
        '.$langs->trans("DefaultModel").''; - print $form->selectarray('model', $liste, getDolGlobalString('FICHEINTER_ADDON_PDF')); - print "
        '.$langs->trans('NotePublic').''; - $doleditor = new DolEditor('note_public', (string) $note_public, '', 80, 'dolibarr_notes', 'In', false, false, !getDolGlobalString('FCKEDITOR_ENABLE_NOTE_PUBLIC') ? 0 : 1, ROWS_3, '90%'); - print $doleditor->Create(1); - //print ''; - print '
        '.$langs->trans('NotePrivate').''; - $doleditor = new DolEditor('note_private', (string) $note_private, '', 80, 'dolibarr_notes', 'In', false, false, !getDolGlobalString('FCKEDITOR_ENABLE_NOTE_PRIVATE') ? 0 : 1, ROWS_3, '90%'); - print $doleditor->Create(1); - //print ''; - print '
        '.$langs->trans($newclassname).''.$objectsrc->getNomUrl(1).'
        ' . $langs->trans('AmountHT') . '' . price($objectsrc->total_ht) . '
        ' . $langs->trans('AmountVAT') . '' . price($objectsrc->total_tva) . "
        ' . $langs->transcountry("AmountLT1", $mysoc->country_code) . '' . price($objectsrc->total_localtax1) . "
        ' . $langs->transcountry("AmountLT2", $mysoc->country_code) . '' . price($objectsrc->total_localtax2) . "
        ' . $langs->trans('AmountTTC') . '' . price($objectsrc->total_ttc) . "
        ' . $langs->trans('MulticurrencyAmountHT') . '' . price($objectsrc->multicurrency_total_ht) . '
        ' . $langs->trans('MulticurrencyAmountVAT') . '' . price($objectsrc->multicurrency_total_tva) . "
        ' . $langs->trans('MulticurrencyAmountTTC') . '' . price($objectsrc->multicurrency_total_ttc) . "
        '; - - if (is_object($objectsrc)) { - print ''; - print ''; - } elseif ($origin == 'project' && !empty($projectid)) { - print ''; - } - - print dol_get_fiche_end(); - - print $form->buttonsSaveCancel("CreateDraftIntervention"); - - // Show origin lines - if (!empty($origin) && !empty($originid) && is_object($objectsrc) && !getDolGlobalInt('FICHINTER_DISABLE_DETAILS')) { - $title = $langs->trans('Services'); - print load_fiche_titre($title); - - print '
        '; - print ''; - - $objectsrc->printOriginLinesList(!getDolGlobalString('FICHINTER_PRINT_PRODUCTS') ? 'services' : ''); // Show only service, except if option FICHINTER_PRINT_PRODUCTS is on - - print '
        '; - print '
        '; - } - - print '
        '; + print $soc->getNomUrl(1); + print ''; + print ''; } else { - print '
        '; - print ''; - print ''; // We go back to create action - print ''; - - print dol_get_fiche_head([]); - - if (is_object($objectsrc)) { - print ''; - print ''; - } elseif ($origin == 'project' && !empty($projectid)) { - print ''; - } - print ''; - print ''; - print '
        '.$langs->trans("ThirdParty").''; - print $form->select_company('', 'socid', '', 'SelectThirdParty', 1, 0, array(), 0, 'minwidth300'); - print ' '; - print '
        '; - - print dol_get_fiche_end(); - - print $form->buttonsSaveCancel("CreateDraftIntervention"); - - print '
        '; + print ''; + print img_picto('', 'company', 'class="pictofixedwidth"'); + print $form->select_company('', 'socid', '', 'SelectThirdParty', 1, 0, array(), 0, 'minwidth150 widthcentpercentminusxx maxwidth500'); + print ' 'create'])]).'">'; + print ''; } + print ''."\n"; + + // Description (must be a textarea and not html must be allowed (used in list view) + print ''.$langs->trans("Description").''; + print ''; + print ''; + print ''; + + // Project + if (isModEnabled('project')) { + $formproject = new FormProjets($db); + + $langs->load("project"); + + print ''.$langs->trans("Project").''; + print img_picto('', 'project', 'class="pictofixedwidth"'); + /* Fix: If a project must be linked to any companies (suppliers or not), project must be not be set as limited to customer but must be not linked to any particular thirdparty + if ($societe->fournisseur==1) + $numprojet=select_projects(-1, GETPOST("projectid", 'int'), 'projectid'); + else + $numprojet=select_projects($societe->id, GETPOST("projectid", 'int'), 'projectid'); + */ + $numprojet = $formproject->select_projects($soc->id, $projectid, 'projectid', 0, 0, 1, 0, 0, 0, 0, '', 0, 0, 'maxwidth500 widthcentpercentminusxx'); + if ($numprojet == 0) { + print '   'create', 'socid' => $soc->id])]) . '">'; + } + print ''; + } + + // Category + if (isModEnabled('category')) { + // Categories + print ''.$langs->trans("Categories").''; + print $form->selectCategories(Categorie::TYPE_FICHINTER, 'categories', $object); + print ""; + } + + // Contract + if (isModEnabled('contract') && is_object($formcontract)) { + // This feature hang the application on large list of contracts, because the select component is not complete: it does not work like select of thirdparty or product to support large lists + // So we add a hidden option to avoid to have it used and the application locked, until the select_contract is fixed. + if (getDolGlobalString("CONTRACT_CAN_USE_THE_BUGGED_SELECT_COMPONENT")) { + $langs->load("contracts"); + print ''.$langs->trans("Contract").''; + $numcontrat = $formcontract->select_contract($soc->id, GETPOSTINT('contratid'), 'contratid', 0, 1, 1); + if ($numcontrat == 0) { + print '   '; + } + print ''; + } + } + + // Model + $liste = ModelePDFFicheinter::liste_modeles($db); + if (count($liste) > 0) { + print ''; + print ''.$langs->trans("DefaultModel").''; + print ''; + print $form->selectarray('model', $liste, getDolGlobalString('FICHEINTER_ADDON_PDF')); + print ""; + } + + // Public note + print ''; + print ''.$langs->trans('NotePublic').''; + print ''; + $doleditor = new DolEditor('note_public', (string) $note_public, '', 80, 'dolibarr_notes', 'In', false, false, !getDolGlobalString('FCKEDITOR_ENABLE_NOTE_PUBLIC') ? 0 : 1, ROWS_3, '90%'); + print $doleditor->Create(1); + //print ''; + print ''; + + // Private note + if (empty($user->socid)) { + print ''; + print ''.$langs->trans('NotePrivate').''; + print ''; + $doleditor = new DolEditor('note_private', (string) $note_private, '', 80, 'dolibarr_notes', 'In', false, false, !getDolGlobalString('FCKEDITOR_ENABLE_NOTE_PRIVATE') ? 0 : 1, ROWS_3, '90%'); + print $doleditor->Create(1); + //print ''; + print ''; + } + + // Other attributes + $parameters = array(); + $reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; + if (empty($reshook)) { + print $object->showOptionals($extrafields, 'create'); + } + + // Show link to origin object + if (!empty($origin) && !empty($originid) && is_object($objectsrc)) { + $newclassname = $classname; + if ($newclassname == 'Propal') { + $langs->load('propal'); + $newclassname = 'CommercialProposal'; + } + print ''.$langs->trans($newclassname).''.$objectsrc->getNomUrl(1).''; + + // Amount + /* Hide amount because we only copy services so amount may differ than source + print '' . $langs->trans('AmountHT') . '' . price($objectsrc->total_ht) . ''; + print '' . $langs->trans('AmountVAT') . '' . price($objectsrc->total_tva) . ""; + if ($mysoc->localtax1_assuj == "1" || $objectsrc->total_localtax1 != 0) // Localtax1 RE + { + print '' . $langs->transcountry("AmountLT1", $mysoc->country_code) . '' . price($objectsrc->total_localtax1) . ""; + } + + if ($mysoc->localtax2_assuj == "1" || $objectsrc->total_localtax2 != 0) // Localtax2 IRPF + { + print '' . $langs->transcountry("AmountLT2", $mysoc->country_code) . '' . price($objectsrc->total_localtax2) . ""; + } + + print '' . $langs->trans('AmountTTC') . '' . price($objectsrc->total_ttc) . ""; + + if (isModEnabled("multicurrency")) + { + print '' . $langs->trans('MulticurrencyAmountHT') . '' . price($objectsrc->multicurrency_total_ht) . ''; + print '' . $langs->trans('MulticurrencyAmountVAT') . '' . price($objectsrc->multicurrency_total_tva) . ""; + print '' . $langs->trans('MulticurrencyAmountTTC') . '' . price($objectsrc->multicurrency_total_ttc) . ""; + } + */ + } + + print ''; + + if (is_object($objectsrc)) { + print ''; + print ''; + } elseif ($origin == 'project' && !empty($projectid)) { + print ''; + } + + print dol_get_fiche_end(); + + print $form->buttonsSaveCancel("CreateDraftIntervention"); + + // Show origin lines + if (!empty($origin) && !empty($originid) && is_object($objectsrc) && !getDolGlobalInt('FICHINTER_DISABLE_DETAILS')) { + $title = $langs->trans('Services'); + print load_fiche_titre($title); + + print '
        '; + print ''; + + $objectsrc->printOriginLinesList(!getDolGlobalString('FICHINTER_PRINT_PRODUCTS') ? 'services' : ''); // Show only service, except if option FICHINTER_PRINT_PRODUCTS is on + + print '
        '; + print '
        '; + } + + print ''; } elseif ($id > 0 || !empty($ref)) { // View mode diff --git a/htdocs/fourn/card.php b/htdocs/fourn/card.php index a4e410e1dbe..5bb362af0a8 100644 --- a/htdocs/fourn/card.php +++ b/htdocs/fourn/card.php @@ -7,7 +7,7 @@ * Copyright (C) 2014 Jean Heimburger * Copyright (C) 2015 Marcos García * Copyright (C) 2015 Raphaël Doursenaud - * Copyright (C) 2021-2025 Frédéric France + * Copyright (C) 2021-2026 Frédéric France * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Alexandre Spangaro * @@ -33,6 +33,15 @@ // Load Dolibarr environment require '../main.inc.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var ExtraFields $extrafields + * @var HookManager $hookmanager + * @var Societe $mysoc + * @var Translate $langs + * @var User $user + */ require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.class.php'; require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php'; require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture-rec.class.php'; @@ -40,7 +49,6 @@ require_once DOL_DOCUMENT_ROOT.'/supplier_proposal/class/supplier_proposal.class require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; if (isModEnabled('member')) { require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; } @@ -49,15 +57,6 @@ if (isModEnabled('accounting')) { require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php'; } -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Societe $mysoc - * @var Translate $langs - * @var User $user - */ - // Load translation files required by page $langs->loadLangs(array('accountancy', 'companies', 'suppliers', 'products', 'bills', 'orders', 'commercial')); @@ -75,7 +74,6 @@ $hookmanager->initHooks(array('thirdpartysupplier', 'globalcard')); $result = restrictedArea($user, 'societe&fournisseur', $id, '&societe', '', 'rowid'); $object = new Fournisseur($db); -$extrafields = new ExtraFields($db); // fetch optionals attributes and labels $extrafields->fetch_name_optionals_label($object->table_element); diff --git a/htdocs/fourn/class/fournisseur.orderline.class.php b/htdocs/fourn/class/fournisseur.orderline.class.php index 10b3449fd7b..9603c0f03a7 100644 --- a/htdocs/fourn/class/fournisseur.orderline.class.php +++ b/htdocs/fourn/class/fournisseur.orderline.class.php @@ -171,12 +171,12 @@ class CommandeFournisseurLigne extends CommonOrderLine { $sql = 'SELECT cd.rowid, cd.fk_commande, cd.fk_product, cd.product_type, cd.description, cd.qty, cd.tva_tx, cd.special_code,'; $sql .= ' cd.localtax1_tx, cd.localtax2_tx, cd.localtax1_type, cd.localtax2_type, cd.ref as ref_supplier,'; - $sql .= ' cd.remise, cd.remise_percent, cd.subprice,'; + $sql .= ' cd.remise, cd.remise_percent, cd.subprice, cd.subprice_ttc,'; $sql .= ' cd.info_bits, cd.total_ht, cd.total_tva, cd.total_ttc,'; $sql .= ' cd.total_localtax1, cd.total_localtax2,'; $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,'; $sql .= ' cd.date_start, cd.date_end, cd.fk_unit, cd.extraparams,'; - $sql .= ' cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc,'; + $sql .= ' cd.multicurrency_subprice, cd.multicurrency_subprice_ttc, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc,'; $sql .= ' c.fk_soc as socid'; $sql .= ' FROM '.$this->db->prefix().'commande_fournisseur as c, '.$this->db->prefix().'commande_fournisseurdet as cd'; $sql .= ' LEFT JOIN '.$this->db->prefix().'product as p ON cd.fk_product = p.rowid'; diff --git a/htdocs/holiday/card.php b/htdocs/holiday/card.php index b891673854b..7c6e3929aa5 100644 --- a/htdocs/holiday/card.php +++ b/htdocs/holiday/card.php @@ -538,14 +538,18 @@ if (empty($reshook)) { } // option to notify the validator if the balance is less than the request + // (only for leave types that actually use the balance counter) if (!getDolGlobalString('HOLIDAY_HIDE_APPROVER_ABOUT_NEGATIVE_BALANCE')) { - $tmpUser = new User($db); - $tmpUser->fetch($object->fk_user); + $affectingtypes = $object->getTypes(1, 1); + if (!empty($affectingtypes[$object->fk_type])) { + $tmpUser = new User($db); + $tmpUser->fetch($object->fk_user); - $nbopenedday = num_open_day($object->date_debut_gmt, $object->date_fin_gmt, 0, 1, $object->halfday, $tmpUser->country_id, $object->fk_user); + $nbopenedday = num_open_day($object->date_debut_gmt, $object->date_fin_gmt, 0, 1, $object->halfday, $tmpUser->country_id, $object->fk_user); - if ($nbopenedday > $object->getCPforUser($object->fk_user, $object->fk_type)) { - $message .= "

        ".$langs->transnoentities("HolidaysToValidateAlertSolde")."

        \n"; + if ($nbopenedday > $object->getCPforUser($object->fk_user, $object->fk_type)) { + $message .= "

        ".$langs->transnoentities("HolidaysToValidateAlertSolde")."

        \n"; + } } } diff --git a/htdocs/holiday/card_group.php b/htdocs/holiday/card_group.php index ca6e1c8af7d..c598a1d31ad 100644 --- a/htdocs/holiday/card_group.php +++ b/htdocs/holiday/card_group.php @@ -760,11 +760,15 @@ function sendMail($id, $cancreate, $now, $autoValidation) } // option to notify the validator if the balance is less than the request + // (only for leave types that actually use the balance counter) if (!getDolGlobalString('HOLIDAY_HIDE_APPROVER_ABOUT_NEGATIVE_BALANCE')) { - $nbopenedday = num_open_day($object->date_debut_gmt, $object->date_fin_gmt, 0, 1, $object->halfday, $expediteur->country_id, $object->fk_user); + $affectingtypes = $object->getTypes(1, 1); + if (!empty($affectingtypes[$object->fk_type])) { + $nbopenedday = num_open_day($object->date_debut_gmt, $object->date_fin_gmt, 0, 1, $object->halfday, $expediteur->country_id, $object->fk_user); - if ($nbopenedday > $object->getCPforUser($object->fk_user, $object->fk_type)) { - $message .= "

        ".$langs->transnoentities("HolidaysToValidateAlertSolde")."

        \n"; + if ($nbopenedday > $object->getCPforUser($object->fk_user, $object->fk_type)) { + $message .= "

        ".$langs->transnoentities("HolidaysToValidateAlertSolde")."

        \n"; + } } } diff --git a/htdocs/install/mysql/data/llx_c_chargesociales.sql b/htdocs/install/mysql/data/llx_c_chargesociales.sql index 95eb0daa3e6..f7ec00f70ba 100644 --- a/htdocs/install/mysql/data/llx_c_chargesociales.sql +++ b/htdocs/install/mysql/data/llx_c_chargesociales.sql @@ -59,6 +59,22 @@ insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (2, 220, 'Prime existence', 1,1,'TAXBEPRIEXI'); insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (2, 230, 'Precompte immobilier', 1,1,'TAXBEPREIMMO'); +-- +-- Switzerland +-- + +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 600, 'AVS / AI / APG (Basic social contributions grouped)', 1, 1, 'TAXCHBASE'); +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 601, 'Old-age insurance (AVS)', 1, 1, 'TAXCHAVS'); +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 602, 'Disability insurance (AI)', 1, 1, 'TAXCHAI'); +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 603, 'Income compensation allowance (APG)', 1, 1, 'TAXCHAPG'); +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 604, 'Family allowances (CAF)', 1, 1, 'TAXCHAC'); +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 605, 'Unemployment insurance (AC)', 1, 1, 'TAXCHACHO'); +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 606, 'Occupational pension plan (LPP / 2nd pillar)', 1, 1, 'TAXCHLPP'); +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 607, 'Occupational accident insurance (LAA - employer share)', 1, 1, 'TAXCHAA'); +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 608, 'Non-occupational accident insurance (LAA - employer share)', 1, 1, 'TAXCHAA-NP'); +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 609, 'Health insurance (LAMal - optional employer contribution)', 1, 1, 'TAXCHLAMAL'); +insert into llx_c_chargesociales (fk_pays, id, libelle, deductible, active, code) values (6, 610, 'Withholding tax (employees taxed at source)', 1, 1, 'TAXCHIS'); + -- -- Austria -- diff --git a/htdocs/install/mysql/migration/23.0.0-24.0.0.sql b/htdocs/install/mysql/migration/23.0.0-24.0.0.sql index 1090814360f..349fda9fa96 100644 --- a/htdocs/install/mysql/migration/23.0.0-24.0.0.sql +++ b/htdocs/install/mysql/migration/23.0.0-24.0.0.sql @@ -530,4 +530,17 @@ ALTER TABLE llx_product_warehouse_properties ADD INDEX idx_product_warehouse_pro ALTER TABLE llx_product_warehouse_properties ADD CONSTRAINT fk_product_warehouse_properties_fk_product FOREIGN KEY (fk_product) REFERENCES llx_product (rowid); ALTER TABLE llx_product_warehouse_properties ADD CONSTRAINT fk_product_warehouse_properties_fk_entrepot FOREIGN KEY (fk_entrepot) REFERENCES llx_entrepot (rowid); +-- Add extrafields on various payment +CREATE TABLE llx_payment_various_extrafields +( + rowid integer AUTO_INCREMENT PRIMARY KEY, + tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + fk_object integer NOT NULL, + import_key varchar(14) -- import key +) ENGINE=innodb; + +ALTER TABLE llx_payment_various_extrafields ADD UNIQUE INDEX uk_payment_various_extrafields (fk_object); + +ALTER TABLE llx_actioncomm ADD COLUMN max_participants integer DEFAULT NULL AFTER status; +ALTER TABLE llx_actioncomm ADD INDEX idx_actioncomm_max_participants (max_participants); -- end of migration diff --git a/htdocs/install/mysql/tables/llx_actioncomm.key.sql b/htdocs/install/mysql/tables/llx_actioncomm.key.sql index 452a81dbf21..bee117d626d 100644 --- a/htdocs/install/mysql/tables/llx_actioncomm.key.sql +++ b/htdocs/install/mysql/tables/llx_actioncomm.key.sql @@ -30,5 +30,6 @@ ALTER TABLE llx_actioncomm ADD INDEX idx_actioncomm_ref_ext (ref_ext); ALTER TABLE llx_actioncomm ADD INDEX idx_actioncomm_percent (percent); ALTER TABLE llx_actioncomm ADD INDEX idx_actioncomm_ref (ref, entity); +ALTER TABLE llx_actioncomm ADD INDEX idx_actioncomm_max_participants (max_participants); -- To test performance to get fast next num on actioncomm -- ALTER TABLE llx_actioncomm ADD INDEX idx_actioncomm_entity_id (entity, id); diff --git a/htdocs/install/mysql/tables/llx_actioncomm.sql b/htdocs/install/mysql/tables/llx_actioncomm.sql index aab0efb1090..dd220a17ec6 100644 --- a/htdocs/install/mysql/tables/llx_actioncomm.sql +++ b/htdocs/install/mysql/tables/llx_actioncomm.sql @@ -76,6 +76,7 @@ create table llx_actioncomm num_vote integer DEFAULT NULL, -- use for Event Organization module event_paid smallint NOT NULL DEFAULT 0, -- use for Event Organization module status smallint NOT NULL DEFAULT 0, -- use for Event Organization module for now, but could be use after for event global status + max_participants integer DEFAULT NULL, -- Maximum number of participants allowed for this action (event, meeting, shift) fk_element integer DEFAULT NULL, -- For link to an element (proposal, invoice, order, ...) elementtype varchar(255) DEFAULT NULL, -- For link to an element (proposal, invoice, order, ...) diff --git a/htdocs/install/mysql/tables/llx_payment_various_extrafields.key.sql b/htdocs/install/mysql/tables/llx_payment_various_extrafields.key.sql new file mode 100644 index 00000000000..3760942ddce --- /dev/null +++ b/htdocs/install/mysql/tables/llx_payment_various_extrafields.key.sql @@ -0,0 +1,21 @@ +-- =================================================================== +-- Copyright (C) 2013 Jean-Francois FERRY +-- Copyright (C) 2026 Solution Libre SAS +-- +-- 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 +-- the Free Software Foundation; either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see . +-- +-- =================================================================== + + +ALTER TABLE llx_payment_various_extrafields ADD UNIQUE INDEX uk_payment_various_extrafields (fk_object); diff --git a/htdocs/install/mysql/tables/llx_payment_various_extrafields.sql b/htdocs/install/mysql/tables/llx_payment_various_extrafields.sql new file mode 100644 index 00000000000..187ac399885 --- /dev/null +++ b/htdocs/install/mysql/tables/llx_payment_various_extrafields.sql @@ -0,0 +1,26 @@ +-- =================================================================== +-- Copyright (C) 2013 Jean-Francois FERRY +-- Copyright (C) 2026 Solution Libre SAS +-- +-- 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 +-- the Free Software Foundation; either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see . +-- +-- =================================================================== + +CREATE TABLE llx_payment_various_extrafields +( + rowid integer AUTO_INCREMENT PRIMARY KEY, + tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + fk_object integer NOT NULL, + import_key varchar(14) -- import key +) ENGINE=innodb; diff --git a/htdocs/install/mysql/tables/llx_product_warehouse_properties-stock.key.sql b/htdocs/install/mysql/tables/llx_product_warehouse_properties-stock.key.sql index 0f60c355e8b..7f0f91e3f75 100644 --- a/htdocs/install/mysql/tables/llx_product_warehouse_properties-stock.key.sql +++ b/htdocs/install/mysql/tables/llx_product_warehouse_properties-stock.key.sql @@ -20,4 +20,4 @@ ALTER TABLE llx_product_warehouse_properties ADD INDEX idx_product_warehouse_pro ALTER TABLE llx_product_warehouse_properties ADD INDEX idx_product_warehouse_properties_fk_entrepot (fk_entrepot); ALTER TABLE llx_product_warehouse_properties ADD CONSTRAINT fk_product_warehouse_properties_fk_product FOREIGN KEY (fk_product) REFERENCES llx_product (rowid); -ALTER TABLE llx_product_warehouse_properties ADD CONSTRAINT fk_product_warehouse_properties_fk_entrepot FOREIGN KEY (fk_entrepot) REFERENCES llx_entrepot (rowid); \ No newline at end of file +ALTER TABLE llx_product_warehouse_properties ADD CONSTRAINT fk_product_warehouse_properties_fk_entrepot FOREIGN KEY (fk_entrepot) REFERENCES llx_entrepot (rowid); diff --git a/htdocs/install/repair.php b/htdocs/install/repair.php index 89eab5a6bcc..bd4d30bd477 100644 --- a/htdocs/install/repair.php +++ b/htdocs/install/repair.php @@ -1,13 +1,14 @@ - * Copyright (C) 2004-2012 Laurent Destailleur - * Copyright (C) 2005-2012 Regis Houssin - * Copyright (C) 2015 Raphaël Doursenaud - * Copyright (C) 2021-2025 Frédéric France - * Copyright (C) 2023 Gauthier VERDOL - * Copyright (C) 2024-2026 MDW - * Copyright (C) 2024 Vincent de Grandpré - * Copyright (C) 2025 Alexandre Spangaro +/* Copyright (C) 2004 Rodolphe Quiedeville + * Copyright (C) 2004-2012 Laurent Destailleur + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2015 Raphaël Doursenaud + * Copyright (C) 2021-2025 Frédéric France + * Copyright (C) 2023 Gauthier VERDOL + * Copyright (C) 2024-2026 MDW + * Copyright (C) 2024 Vincent de Grandpré + * Copyright (C) 2025 Alexandre Spangaro + * Copyright (C) 2026 Solution Libre SAS * * 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 @@ -414,7 +415,7 @@ if ($ok && GETPOST('standard', 'alpha')) { 'fichinter' => 'fichinter', 'fichinterdet' => 'fichinterdet', 'inventory' => 'inventory', 'actioncomm' => 'actioncomm', 'bom_bom' => 'bom_bom', 'mrp_mo' => 'mrp_mo', - 'adherent_type' => 'adherent_type', 'user' => 'user', 'partnership' => 'partnership', 'projet' => 'projet', 'projet_task' => 'projet_task', 'ticket' => 'ticket'); + 'adherent_type' => 'adherent_type', 'user' => 'user', 'partnership' => 'partnership', 'projet' => 'projet', 'projet_task' => 'projet_task', 'ticket' => 'ticket', 'payment_various' => 'payment_various'); //$listofmodulesextra = array('fichinter'=>'fichinter'); print '
        *** Check that fields into the extra table structure match the table of definition. If not, add column into table'; diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 9bfda85b1d9..607c2517949 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -2825,7 +2825,7 @@ ViewLogs=View Logs DisableMCPAI=Turn off to completely disable AI access to MCP server EnableMCPServer=Enable MCP Server EnableLogging=Enable Request Logging -ObfuscatePIIData=Obfuscate PII Data +ObfuscatePIIData=Obfuscate Prompt Data AIRequestLogs=AI Request Logs ErrorTableNotExists=Table %s does not exist PleaseRunSQLToEnableLogging=Please run the following SQL to enable logging diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index 319f95574bf..745792cd4af 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -1436,3 +1436,4 @@ Delay=Delay RefOfOnCustomerSide=Reference of %s on customer side RefOfOnVendorSide=Reference of %s on vendor side SeeDocumentation=See documentation +AIAssistant=AI Assistant diff --git a/htdocs/langs/en_US/other.lang b/htdocs/langs/en_US/other.lang index 966b980b345..fefc5b7c9af 100644 --- a/htdocs/langs/en_US/other.lang +++ b/htdocs/langs/en_US/other.lang @@ -419,7 +419,6 @@ AIWelcomeMessage=Hi! I'm your Dolibarr assistant. How can I help you today? ErrorThirdPartyNotFound=Error ThirdParty Not Found TotalValue=Total Value Expenses=Expenses -AIAssistant=AI Assistant ClearChatHistoryTitle=Clear Chat History Clear=Clear OptionTextOnly=Text Only diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index e63b1517ff2..cd6dc55dc06 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -2261,14 +2261,17 @@ function top_menu($head, $title = '', $target = '', $disablejs = 0, $disablehead $toprightmenu .= top_menu_search(); } + // Add AI picto + $toprightmenu .= top_menu_ai(); + + // Add bookmark dropdown + $toprightmenu .= top_menu_bookmark(); + if (getDolGlobalString('MAIN_USE_TOP_MENU_QUICKADD_DROPDOWN')) { // Add the quick add object dropdown $toprightmenu .= top_menu_quickadd(); } - // Add bookmark dropdown - $toprightmenu .= top_menu_bookmark(); - if (getDolGlobalString('MAIN_USE_TOP_MENU_IMPORT_FILE')) { // Add the import file link $toprightmenu .= top_menu_importfile(); @@ -2710,6 +2713,33 @@ function top_menu_user($hideloginname = 0, $urllogout = '') return $btnUser; } +/** + * Build the tooltip on top menu quick add. + * Called when option MAIN_USE_TOP_MENU_QUICKADD_DROPDOWN is set + * + * @return string HTML content + */ +function top_menu_ai() +{ + global $conf, $langs; + + // Button disabled on text browser + /*if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) { + return ''; + }*/ + + $html = ''; + + if (isModEnabled('ai')) { + $html .= ' + '; + } + + return $html; +} + /** * Build the tooltip on top menu quick add. * Called when option MAIN_USE_TOP_MENU_QUICKADD_DROPDOWN is set @@ -2730,7 +2760,7 @@ function top_menu_quickadd() if (!empty($conf->use_javascript_ajax)) { $html .= ' '; if (!defined('JS_JQUERY_DISABLE_DROPDOWN')) { // This may be set by some pages that use different jquery version to avoid errors diff --git a/htdocs/modulebuilder/template/stats/myobject_index.php b/htdocs/modulebuilder/template/stats/myobject_index.php index 55d542a7895..bb673216be3 100644 --- a/htdocs/modulebuilder/template/stats/myobject_index.php +++ b/htdocs/modulebuilder/template/stats/myobject_index.php @@ -174,8 +174,10 @@ $fileurlnb = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstats&file=ordersnbiny $px1 = new DolGraph(); +$displaypx1 = false; $mesg = $px1->isGraphKo(); if (!$mesg) { + $displaypx1 = true; $px1->SetData($data); $i = $startyear; $legend = array(); @@ -225,8 +227,10 @@ if (!$user->hasRight('societe', 'client', 'voir')) { } $px2 = new DolGraph(); +$displaypx2 = false; $mesg = $px2->isGraphKo(); if (!$mesg) { + $displaypx2 = true; $px2->SetData($data); $i = $startyear; $legend = array(); @@ -273,8 +277,10 @@ if (!$user->hasRight('societe', 'client', 'voir')) { } $px3 = new DolGraph(); +$displaypx3 = false; $mesg = $px3->isGraphKo(); if (!$mesg) { + $displaypx3 = true; $px3->SetData($data); $i = $startyear; $legend = array(); @@ -435,16 +441,16 @@ print '
        \n"; } /* - if (isset($px2)) { + if ($displaypx2) { print $px2->show(); print "
        \n"; } - if (isset($px2)) { + if ($displaypx3) { print $px3->show(); print "
        \n"; } diff --git a/htdocs/projet/card.php b/htdocs/projet/card.php index f39a27b6162..e9382424b51 100644 --- a/htdocs/projet/card.php +++ b/htdocs/projet/card.php @@ -503,6 +503,8 @@ if (empty($reshook)) { if ($result <= 0) { setEventMessages($object->error, $object->errors, 'errors'); $action = ''; + } else { + setEventMessages($langs->trans("FileGenerated"), null, 'mesgs'); } } diff --git a/htdocs/reception/card.php b/htdocs/reception/card.php index e023df29b7e..1d6e3cfef04 100644 --- a/htdocs/reception/card.php +++ b/htdocs/reception/card.php @@ -1612,7 +1612,27 @@ if ($action == 'create' && $permissiontoadd) { if ($reshook < 0) { setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); } elseif (empty($reshook)) { - $dispatchLines = array_merge($dispatchLines, $hookmanager->resArray); + // Only merge if resArray is a valid, non-empty array + if (is_array($hookmanager->resArray) && !empty($hookmanager->resArray)) { + // Check if the merge would overwrite valid data with NULL + $tempMerged = array_merge($dispatchLines, $hookmanager->resArray); + + // Sanity check: ensure our existing keys are still valid arrays + $corrupted = false; + foreach ($dispatchLines as $idx => $val) { + if (!is_array($tempMerged[$idx]) || !isset($tempMerged[$idx]['fk_commandefourndet'])) { + $corrupted = true; + break; + } + } + + if (!$corrupted) { + $dispatchLines = $tempMerged; + } else { + // If merge corrupted data, ignore the hook result and keep original + error_log("WARNING: Hook dataProcessing returned invalid data, ignoring merge."); + } + } } elseif ($reshook > 0) { // $resArray starts from [0], we need $dispatchLines to start from [1], so we shift it $dispatchLines = $hookmanager->resArray; @@ -1622,7 +1642,7 @@ if ($action == 'create' && $permissiontoadd) { } print ''; + }); + '; print '
        '; @@ -2526,7 +2546,7 @@ if ($action == 'create' && $permissiontoadd) { // Get list of products already sent for same source object into $alreadysent $alreadysent = array(); - if (empty($origin)) { + if (empty($origin) || $origin == 'order_supplier') { $origin = 'supplier_order'; } diff --git a/htdocs/societe/card.php b/htdocs/societe/card.php index 4c28f86c02e..eee2f624182 100644 --- a/htdocs/societe/card.php +++ b/htdocs/societe/card.php @@ -3482,6 +3482,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($canvasdisplayactio if ($result > 0) { $adh->ref = $adh->getFullName($langs); print $adh->getNomUrl(-1); + print ' — '.$adh->getLibStatut(0); } else { print ''.$langs->trans("ThirdpartyNotLinkedToMember").''; } diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index 5a7aef0f4bc..75400fef6d7 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -1024,8 +1024,14 @@ class Societe extends CommonObject $this->code_compta_client = trim($this->code_compta_client ?? ''); $this->accountancy_code_customer_general = trim($this->accountancy_code_customer_general ?? ''); + if ($this->accountancy_code_customer_general === '-1') { + $this->accountancy_code_customer_general = ''; + } $this->accountancy_code_customer = trim((string) $this->code_compta_client); $this->accountancy_code_supplier_general = trim($this->accountancy_code_supplier_general ?? ''); + if ($this->accountancy_code_supplier_general === '-1') { + $this->accountancy_code_supplier_general = ''; + } $this->accountancy_code_supplier = trim((string) $this->code_compta_fournisseur); $this->accountancy_code_buy = trim((string) $this->accountancy_code_buy); $this->accountancy_code_sell = trim((string) $this->accountancy_code_sell); @@ -1537,8 +1543,14 @@ class Societe extends CommonObject } $this->accountancy_code_customer_general = trim((string) $this->accountancy_code_customer_general); + if ($this->accountancy_code_customer_general === '-1') { + $this->accountancy_code_customer_general = ''; + } $this->code_compta_client = trim((string) $this->code_compta_client); $this->accountancy_code_supplier_general = trim((string) $this->accountancy_code_supplier_general); + if ($this->accountancy_code_supplier_general === '-1') { + $this->accountancy_code_supplier_general = ''; + } $this->code_compta_fournisseur = trim((string) $this->code_compta_fournisseur); // Check parameters. More tests are done later in the ->verify() diff --git a/htdocs/takepos/invoice.php b/htdocs/takepos/invoice.php index 11782ff9fc4..4663bc4d58a 100644 --- a/htdocs/takepos/invoice.php +++ b/htdocs/takepos/invoice.php @@ -351,7 +351,8 @@ if (empty($reshook)) { // Add the payment if (!$error && $res >= 0) { $remaintopay = $invoice->getRemainToPay(); - if ($remaintopay > 0) { + // Credit notes have negative remaintopay; regular invoices have positive + if (($remaintopay > 0 && $invoice->type != Facture::TYPE_CREDIT_NOTE) || ($remaintopay < 0 && $invoice->type == Facture::TYPE_CREDIT_NOTE)) { $payment = new Paiement($db); $payment->datepaye = $now; diff --git a/htdocs/theme/eldy/dropdown.inc.php b/htdocs/theme/eldy/dropdown.inc.php index cdbc21f1acc..0022849ce7b 100644 --- a/htdocs/theme/eldy/dropdown.inc.php +++ b/htdocs/theme/eldy/dropdown.inc.php @@ -330,7 +330,7 @@ a.top-menu-dropdown-link { border-radius: 0; } - +#topmenu-tool, #topmenu-global-search-dropdown, #topmenu-quickadd-dropdown, #topmenu-bookmark-dropdown, diff --git a/htdocs/theme/md/dropdown.inc.php b/htdocs/theme/md/dropdown.inc.php index 1d30b792077..8776970fd8d 100644 --- a/htdocs/theme/md/dropdown.inc.php +++ b/htdocs/theme/md/dropdown.inc.php @@ -51,7 +51,7 @@ button.dropdown-item.global-search-item { display: block; } - +#topmenu-tool a, #topmenu-global-search-dropdown a.login-dropdown-a, #topmenu-quickadd-dropdown a.login-dropdown-a, #topmenu-bookmark-dropdown a.login-dropdown-a, @@ -347,7 +347,7 @@ a.top-menu-dropdown-link { border-radius: 0; } - +#topmenu-tool, #topmenu-login-dropdown, #topmenu-quickadd-dropdown, #topmenu-bookmark-dropdown, diff --git a/htdocs/ticket/stats/index.php b/htdocs/ticket/stats/index.php index 37dd4c898bf..e4af4203f23 100644 --- a/htdocs/ticket/stats/index.php +++ b/htdocs/ticket/stats/index.php @@ -1,7 +1,7 @@ - * Copyright (C) 2024-2025 MDW - * Copyright (C) 2024-2025 Frédéric France +/* Copyright (C) 2019 Laurent Destailleur + * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 Frédéric France * * 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 @@ -25,10 +25,6 @@ // Load Dolibarr environment require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/ticket/class/actions_ticket.class.php'; -require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticketstats.class.php'; -require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; /** * @var Conf $conf * @var DoliDB $db @@ -36,6 +32,10 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/ticket/class/actions_ticket.class.php'; +require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticketstats.class.php'; +require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; $WIDTH = DolGraph::getDefaultGraphSizeForStats('width'); $HEIGHT = DolGraph::getDefaultGraphSizeForStats('height'); @@ -85,16 +85,18 @@ $help_url = ''; llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-ticket page-stats'); -$param = ''; -$mode = 'statistics'; -$url = DOL_URL_ROOT.'/ticket/card.php?action=create&mode=init'.($socid ? '&socid='.$socid : ''); +$params = [ + 'action' => 'create', + 'mode' => 'init', +]; if (!empty($socid)) { - $url .= '&socid='.$socid; + $params += ['socid' => $socid]; } +$url = dolBuildUrl(DOL_URL_ROOT.'/ticket/card.php', $params); $newcardbutton = ''; -$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', DOL_URL_ROOT.'/ticket/list.php?mode=common'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss' => 'reposition')); -$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', DOL_URL_ROOT.'/ticket/list.php?mode=kanban'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss' => 'reposition')); -$newcardbutton .= dolGetButtonTitle($langs->trans('Statistics'), '', 'fa fa-chart-bar imgforviewmode', DOL_URL_ROOT.'/ticket/stats/index.php?mode=statistics&objecttype=ticket@ticket'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', ($mode == 'statistics' ? 2 : 1), array('morecss' => 'reposition')); +$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', DOL_URL_ROOT.'/ticket/list.php?mode=common', '', 1, array('morecss' => 'reposition')); +$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', DOL_URL_ROOT.'/ticket/list.php?mode=kanban', '', 1, array('morecss' => 'reposition')); +$newcardbutton .= dolGetButtonTitle($langs->trans('Statistics'), '', 'fa fa-chart-bar imgforviewmode', DOL_URL_ROOT.'/ticket/stats/index.php?mode=statistics&objecttype=ticket@ticket', '', 2, array('morecss' => 'reposition')); $newcardbutton .= dolGetButtonTitleSeparator(); $newcardbutton .= dolGetButtonTitle($langs->trans('NewTicket'), '', 'fa fa-plus-circle', $url, '', $user->hasRight('ticket', 'write')); @@ -111,7 +113,6 @@ if ($object_status != '' && $object_status >= -1) { // Build graphic number of object $data = $stats->getNbByMonthWithPrevYear($endyear, $startyear); -//var_dump($data); // $data = array(array('Lib',val1,val2,val3),...) @@ -149,9 +150,8 @@ if (!$mesg) { // Build graphic amount of object $data = $stats->getAmountByMonthWithPrevYear($endyear, $startyear); -//var_dump($data); -// $data = array(array('Lib',val1,val2,val3),...) +// $data = array(array('Lib',val1,val2,val3),...) // Show array $data = $stats->getAllByYear(); diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 325dc931c05..49d7457ddcc 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -34,7 +34,7 @@ parameters: - htdocs/includes/mobiledetect/* - htdocs/includes/nusoap/* - htdocs/includes/OAuth/* - - htdocs/includes/odtphp/zip/* + - htdocs/includes/odtphp/* - htdocs/includes/parsedown/* - htdocs/includes/php-iban/* - htdocs/includes/phpoffice/*