Compare commits

..

512 Commits

Author SHA1 Message Date
Laurent Destailleur
5f5b4570c4 NEW Use the initAsSpecimen to generate the test invoice. 2026-03-18 14:44:30 +01:00
Laurent Destailleur
0e5170e491 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-18 12:51:38 +01:00
Laurent Destailleur
812b51a0b7 Fix position of TEMPORARY on pdf 2026-03-18 12:49:42 +01:00
atm-corentin
66ac556770 NEW|New - add drag-and-drop functionality for managing draft expedition lines (#36311)
* ticket progress nullable (#36178)

* ticket progress nullable

* ticket progress nullable

* clean code (#36180)

* clean code

* clean code

* clean code

* Fix update of color when using HTML5

* CSS

* css

* Fix date input on smartphone

* css

* css

* css

* Clean code

* Responsive

* NEW add hourly rate in list of users

* Fix reset of budget

* Clean code

* Debug v23

* Fix regression

* Debug v23

* CSS

* Qual: Update Phan baseline.txt (#36190)

#Qual: Update Phan baseline.txt

Update the Phan baseline.txt file to reflect changes in the number of occurrences of specific issues.

* clean code (#36179)

* clean code

* clean code

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW Holiday - Allow to specify a specific mail address from (#36184)

* NEW Holiday - Allow to specify a specific mail address from

* Update card.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Qual: Make $fields covariant, define type for $FIELDS in BankAccounts api (#36188)

* Qual: Make $fields covariant

* Qual: Update BankAccounts class phpDoc ($FIELDS)

# Qual: Update BankAccounts class phpDoc ($FIELDS)

Updated the documentation for the $FIELDS property.

* NEW Dashboard - Add option in ihm to disable MRP thumb (#36185)

* Fix CI

* Removed PROPALE_ADDON_NOTE_PUBLIC_DEFAULT. Must use the generic feature
"default value feature"

* Clean code

* css

* Fix CI

* Doc

* FIX Import/Export - Do not list imports or exports of Builder module backups of module descriptor files (#36192)

* FIX Import/Export - Do not list imports or exports of Builder module backups of module descriptor files

* FIX

* Add phone info in form public/members/new.php (#36119)

* Add phone info in form public/members/new.php

* Use GETPOST instead of GETPOSTISSET

* FIX wrong path of libraries (#36194)

* Updated PHPDoc (#36191)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* Fix 35017 accounts for local taxes 2 (#36160)

* FIX#35017 add locattaxes accounts to accounting defaults

* corr. libellés localtax pour afficher le bon pourcentage

* assurer la cohérence de la structure def_tva

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW stock API GET movement (#36193)

* Added stock movement GET method

* Updated PHPDoc

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* css

* Fix js not found

* Add sortorder parameter to selectForFormsList method (#36173)

there are a problem on many extrafields list : we can't sort list by decrease and sort from a specific fields since we have add an sql prevention on filter based on : separator (who is used to separate parameters)

@eldy  i propose to remplace on extrafields parameters the : in the sql injection prevention by another caracters, by exemple the ; or the § , and replace it after having explode all the parameters

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* css

* Removed duplicated code

* Debug v23

* Add property parent

* Debug v23

* Manage position of captcha modules. Fix responsive of setup page.

* Debug v23

* Try to fix phpstan

* Clean language files

* Debug v23

* Fix regression

* WIP LNE

* Drop a not used column to simplify

* Add test on data decryption

* PHPStan > Update baseline (#36208)

Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>

* FIX member photo and thirdparty logo not deleted in documents directory (#36197)

* FIX member photo with web portal don't need login

* FIX member photo not deleted

* FIX pre-commit

* FIX phan error and uniformize code

* FIX pfffffff pre-commit

* FIX uniformize code

* FIX remove TODO

* FIX change const name

* FIX missing add in ECM during creation

* FIX add user photo in ecm

* FIX phan error

* display member photo with hash (#36205)

* display member photo with hash

* display member photo with hash

* display member photo with hash

* display member photo with hash

* FIX adding tms and datec to email templates (#36189)

* NEW: adding tms and datec to email templates

* Escaping a timestamp string I just generated myself

* switching single and double quotes in escape line for tms and datec to make the build system happy?

* Add an id to the table

* showing better names than tms and datec

* Changing code to label to fix #29116

* using hregis advice to use idate not escape the string with date

* requested changes

* forgot to add my name to editors

* had to make class changes for API to work

* setting datec back to int|string

* removing unused code lines and using idate in the api file

* no tms in create or update, but select. No datec in update

* expanding hurl tests to prevent post with id or tms, put with id or datec

---------

Co-authored-by: Jon Bendtsen <xcodeauthor@jonb.dk>

* Update ticket.class.php (#34932)

* Update ticket.class.php

bad init in class

* Update ticket.class.php

* Update ticket.class.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* WIP LNE

* Debug v23

* fix stock display in replenish (#36209)

* [FEAT] Displaying the label instead of the id (#36210)

* Fix rename of function

* fix typo in interface_20_modWorkflow_WorkflowManager.class.php (#36212)

* add interventional feature on subtotal admin (#36207)

* Fi CI

* Fix CI

* Fix CI

* NEW Add Type, Description columns to Project Overview Expense Reports (#36214)

Co-authored-by: Jon Bendtsen <xcodeauthor@jonb.dk>

* Fix PHP Warning:  Undefined array key "nblinesnull" in /var/www/html/projet/element.php (#36198)

Co-authored-by: Jon Bendtsen <xcodeauthor@jonb.dk>

* FIX use $this->db instead $db (#36219)

* FIX use $this->db instead $db

* FIX use $db with static function

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* add date_c update on project_task (#36217)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Trans

* Responsive

* Fix autogenerate login

* Update commondocgenerator.class.php (#36227)

* clean code (#36228)

* Fix typo in DLLMissing message for MyGerman.isl (#36223)

* Fix typo in DLLMissing message for MyGerman.isl

Typos:
- s/Mcrsoft/Microsoft/g
- s/credist_x86.exe/vc_redist.x86.exe/g
- dl link

* Fix DLLMissing message link for Visual C++ Redistributable

Updated the link for the Microsoft Visual C++ Redistributable installation instructions.

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* harmonize fields orders between holiday and expensereport (#36222)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Update header_login.tpl.php (#36220)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* avoid changing object after call (#36159)

* avoid changing object after call

* avoid changing object after call

* avoid changing object after call

* avoid changing object after call

* avoid changing object after call

* avoid changing object after call

* avoid changing object after call

* clean code

* clean code

* clean code

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* clean code

* clean

* clean

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Implement setCategories method in task class (#36218)

Added setCategories method to manage task categories.

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* check access doc for massfilesarea_stock (#36229)

* clean code (#36230)

* New : action to clone ticket (#36231)

* Clean permissions

* Debug v23

* Tipo

* clean code (#36233)

* clean code

* Update html_cerfafr.modules.php

* Update interface_50_modEventOrganization_EventOrganization.class.php

* Fix case

* add phone mobile of target (#36234)

* Debug v23

* WIP

* Short version of Go back

* Fix bad translation string

* Fix trans

* CSS

* css

* css

* Debug v23

* css

* Debug v23

* Doc

* Fix CI

* FIX add constant for the member photo width in web portal + avoid error (#36221)

* Close #35917

* Fix regression

* Fix type

* Fix GETPOST

* Fix GETPOST

* Fix SQL

* Fix error reporting

* Fix warning

* CSS

* Fix CI

* Fix CI

* Fix CI

* Fix CI

* Fix CI GETPOST -> GETPOSTINT

* Fix CI

* Fix CI

* Trans

* add gps position for files (#36240)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* clean code (#36241)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* PHPStan > Update baseline (#36242)

Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>

* New import profile for leave requests (#36244)

Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>

* WIP LNE

* WIP LNE

* add show and filter in fields public and private notes (#36255)

* NEW Add SQL table for expensereport line extrafields support (#36251)

* Fix: Correct unit comparison bug in webportal document download (#36256)

The file size check was comparing bytes (from dol_filesize) directly with
kilobytes (from MAIN_SECURITY_MAXFILESIZE_DOWNLOADED config), causing false
positives that blocked downloads of small files.

Example: A 94 KB file (96678 bytes) was rejected because 96678 > 20480,
even though the limit was actually 20480 KB (20 MB).

Changes:
- Convert KB limit to bytes before comparison: $fileSizeMax * 1024
- Improve error message to display file size in KB for consistency
- Add detailed logging with both bytes and KB values

Fixes: Files under the configured limit are now correctly allowed to download

* NEW - Add a new API "product lots" (#36243)

* Add new API product lots

* fix precommit

* CSS

* NEW Introduce getCurrency(). $conf is no more allowed into computed
formulae.

* Doc

* Doc

* Trans tooltip

* NEW Invert logic of default date in proposal/order/invoice creation:
Need option to NOT autofill instead of the opposite.

* WIP LNE

* Fight against optionflation.

* Trans

* Trans

* The script generate_filelist_xml.php can check integrity from command
line.

* Doc

* Doc

* Debug

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

* Simple way to trap error to Close #36292

* Clean code

* Fix CI

* Fix CI

* Bump mdeweerd/logToCheckStyle from 2025.1.1 to 2025.11.2 (#36284)

Bumps [mdeweerd/logToCheckStyle](https://github.com/mdeweerd/logtocheckstyle) from 2025.1.1 to 2025.11.2.
- [Commits](https://github.com/mdeweerd/logtocheckstyle/compare/v2025.1.1...v2025.11.2)

---
updated-dependencies:
- dependency-name: mdeweerd/logToCheckStyle
  dependency-version: 2025.11.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Update admin.lang (#36293)

* Update loanschedule.class.php (#36272)

Solve Error when schedule loans : "ERROR: 55000: currval of sequence "llx_payment_loan_rowid_seq" is not yet defined in this session LOCATION: currval_oid, sequence.c:884"

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* Fix phpunit

* Update SecurityTest.php (#36295)

* Update SecurityTest.php

* Update SecurityTest.php

* Update SecurityTest.php

* PHPStan > Update baseline (#36264)

Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Qual: Update pre-commit hooks (#36262)

* Qual: Update pre-commit hooks

# Qual: Update pre-commit hook

Updated several pre-commit hooks as long as the new ones are not alpha or beta versions

* Qual: Fix/ignore some shellcheck issues

* Qual: Enhance hurl test documentation and run script (#36291)

- Added detailed documentation for running specific tests, including options and exclusions
- Improved installation instructions with platform-specific guidance
- Enhanced error handling and user feedback
- Added support for GitHub-compatible messages
- Improved script structure and readability

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Qual: (commonnumrefgenerator): Add abstract method getExample (#36271)

- `getExample()`: Returns an example of the numbering format

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* FIX: Fix version extraction in setup_conf.sh (#36281)

# FIX: Fix version extraction in setup_conf.sh

The version extraction regex was updated to handle both DOL_VERSION and DOL_MAJOR_VERSION constants in version.inc.php.
The version is now available as DOL_MAJOR_VERSION since 29b1e75

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* FIX QUAL intervention API close (#36278)

* Refactored close method

* Updated PHPDoc

* Updated PHPDoc

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* can edit color in dict.php (#36270)

* can edit color in dict.php

* Update html.form.class.php

* Update html.form.class.php

* New tooltip freeze from experimental to develop branch (#36266)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Look and feel Debug v23

* Debug v23

* Close #36298

* Qual: Change field name to let phan warn about argument order (#36261)

* FIX wrong $param parameter position

* FIX ok it's good ! ;-)

* Qual: Change field name to let phan warn about argument order

# Qual: Change field name to let phan warn about argument order

Most of the time print_liste_field_titre() is called with $param instead of $moreparam.
By changing the argument name, phan will warn if the position is likely incorrect.

* Fix: Fix parameter order in print_liste_field_titre calls

- Fixed parameter order in print_liste_field_titre calls to maintain consistency

---------

Co-authored-by: Regis Houssin <regis.houssin@inodbox.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* clean code (#36260)

* clean code

* clean code

* clean code

* clean code

* fix $val might not be defined

* fix phpstan errors reported

* fix phpstan errors reported

* fix phpstan errors reported

* fix phpstan errors reported

* fix phpstan errors reported

* fix phpstan errors reported

* ignore phpstan reported for later

* clean code

* clean code

* clean code

* clean

* test

* Clean code

* Clean code

* add drag-and-drop functionality for managing draft expedition lines

* WIP LNE

* Debug v23

* refactor expedition card: clean up unused code and improve table rendering logic

* fix expedition card: correct colspan increment for draft status

* fix expedition card: adjust colspan indentation for better readability

* fix expedition card: uncomment shipment creation code for standalone mode

* fix expedition card: adjust colspan indentation to match coding standards

* fix expedition card: correct indentation of colspan logic to follow standards

* NEW Add hook on calcula_price() and get_default_tva()

* Comment

* Clean code

* fix expedition: add permission check for expedition details and update fetch_lines ordering logic

* Secure the dol_eval

* NEW Add filter on agenda event progression on agenda page

* fix css login page patch 01 (#36313)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* FIX #36306 (#36307)

* NEW Disable by default obfuscation methods and function in extrafields
evaluable strings. Can re-enable with
MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL=1

* Doc

* MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL replaced with
MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL

* Doc

* Add config param $dolibarr_main_restrict_eval_methods with whitelist of
functionsallowed in dol_eval. Advisory GHSA-x3w7-24rq-gvc5

* fix expedition: ensure expeditiondet rows remain in sync during drag-and-drop reorder operation

* fix expedition card: reindent colspan logic for consistency with coding standards

* Fix CI

* Qual: Update phan baseline (#36318)

# Qual: Update phan baseline

Remove fixed notices from exceptions.

* fix expedition: add phpcs annotations to suppress naming convention warnings around scope

* Doc

* Fix CI

* Fix CI

* Fix ci

* Fix CI

* Fix CI

* Fix ci

* Fix css login page patch 02 (#36320)

* fix css login page patch 01

* fix css login page patch 02

* fix css login page patch 02

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Clean code

* Fix CSS

* Fix CI

* Add product type check in line validation (#36319)

* Add product type check in line validation

needed by sous total plugins style which use line with type 9 for title, subtitle or free text. without the chek inb line validation, title, sub title or free text disappears

* Update card.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Migrate holiday files

* FIX Move 'holiday' from old path array to new one (#36308)

* Remove 'supplier_invoice' from old path array

* Update module path in arrayforoldpath

Sorry Eldy, I was confused. You are absolutely right, it is already corrected.

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix: PhanTypeMismatchProperty error in invoice creation (#36323)

* 🐛 Fix PhanTypeMismatchProperty error in invoice creation

The error occurred due to incorrect type handling in the invoice creation process. The changes fix this by properly handling the subtype field as an integer. This ensures type consistency and prevents potential runtime errors.

* FIX: Correct assignment to linkedObjectsIds  in card.php

# FIX: Correct assignment to linkedObjectsIds  in card.php

Modified the structure of linkedObjectsIds to include the rowid as a key to match the property type.

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* PHPStan > Update baseline (#36305)

Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>

* Accountancy - Resolve some problem on new function "Discount in accountancy" (#36285)

* FIX Accountancy - Discount wrong function & problem HTML injection

* FIX Accountancy - Wrong base for already / not yet function - Piece_num is not enough strong et editable

* FIX Accountancy - Discount - Use closing date rather than the invoice date

* FIX

* PHPPhan

* Update accountingjournal.class.php

* Update accountingjournal.class.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Update expedition.class.php

* Update expedition.class.php

* Improve shipment line handling: fix origin/ID management, add extrafields display, and handle unchanged lines

* fix

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Frédéric FRANCE <frederic34@users.noreply.github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
Co-authored-by: MDW <mdeweerd@users.noreply.github.com>
Co-authored-by: Alexandre SPANGARO <alexandre.spangaro@gmail.com>
Co-authored-by: Aksanti Bahiga tacite <71480535+marcellintacite@users.noreply.github.com>
Co-authored-by: Regis Houssin <regis.houssin@inodbox.com>
Co-authored-by: William Mead <william@m34d.com>
Co-authored-by: Vincent de Grandpré <vincent@de-grandpre.quebec>
Co-authored-by: Charlène Benke <1179011+defrance@users.noreply.github.com>
Co-authored-by: Laurent Destailleur <eldy@users.sourceforge.net>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>
Co-authored-by: Jon Bendtsen <github@jonb.dk>
Co-authored-by: Jon Bendtsen <xcodeauthor@jonb.dk>
Co-authored-by: Norbert Penel <homer8173@gmail.com>
Co-authored-by: PierrickV <8960084+PierrickV@users.noreply.github.com>
Co-authored-by: Maxime Kohlhaas <maxime@atm-consulting.fr>
Co-authored-by: Eric - CAP-REL <1468823+rycks@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <45882981+Hystepik@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
Co-authored-by: HeilDenDus <rafagledesma@gmail.com>
Co-authored-by: Rudi Herouard <rudi.herouard@gmail.com>
Co-authored-by: Gigarun ingénierie <thomas@gigarun.eu>
Co-authored-by: atm-lucas <121817516+atm-lucasmantegari@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: sweetcorreze <76868896+sweetcorreze@users.noreply.github.com>
Co-authored-by: John BOTELLA <68917336+thersane-john@users.noreply.github.com>
Co-authored-by: Marc <99648320+emheyarssi@users.noreply.github.com>
Co-authored-by: iouston <4319513+iouston@users.noreply.github.com>
Co-authored-by: Yamil Esteban Garcia <120027058+developmentOYR@users.noreply.github.com>
Co-authored-by: Quentin VIAL--GOUTEYRON <quentin.vial-gouteyron@atm-consulting.fr>
2026-03-18 11:28:26 +01:00
Laurent Destailleur
d548dfe042 Look and feel v24 2026-03-18 03:11:17 +01:00
Laurent Destailleur
1b47fa3bd8 NEW Add phone of thirdparty available on list of contracts 2026-03-18 02:43:51 +01:00
Laurent Destailleur
74a202ed17 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-18 01:31:10 +01:00
Laurent Destailleur
0a07d73ed4 css 2026-03-18 01:30:12 +01:00
Maxime Kohlhaas
2c8bb1eac2 NEW : use select label for extrafield substitution in emails (#37005) 2026-03-17 15:53:55 +01:00
Rikard Bosnjakovic
5c3acbcc60 NEW Add thumbnails and stock value to product category listing (#37510)
* NEW 37509

* Fix entity casting in show_photos call

* Update viewcat.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-16 20:43:16 +01:00
dependabot[bot]
270f719e8a Bump actions/create-github-app-token from 2 to 3 (#37513)
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 2 to 3.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 10:21:26 +01:00
dependabot[bot]
c32f0cae0c Bump actions/checkout from 4 to 6 (#37512)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 10:20:46 +01:00
Laurent Destailleur
449f0cbd23 Automated merge from 23.0 to develop 2026-03-16 10:20:04 +01:00
Laurent Destailleur
2985c8bd21 Automated merge from 22.0 to 23.0 by tool pullmerge.sh 2026-03-16 10:20:01 +01:00
Laurent Destailleur
a222fe5360 Automated merge from 21.0 to 22.0 by tool pullmerge.sh 2026-03-16 10:19:58 +01:00
Laurent Destailleur
e98c441c4c Automated merge from 20.0 to 21.0 by tool pullmerge.sh 2026-03-16 10:19:56 +01:00
Laurent Destailleur
638fd27af2 Automated merge from 19.0 to 20.0 by tool pullmerge.sh 2026-03-16 10:19:43 +01:00
Laurent Destailleur
f9c6562de9 Automated merge from 18.0 to 19.0 by tool pullmerge.sh 2026-03-16 10:18:38 +01:00
Laurent Destailleur
8ede2e75f5 Automated merge from 17.0 to 18.0 by tool pullmerge.sh 2026-03-16 10:18:34 +01:00
Laurent Destailleur
86bb0b4cc5 Automated merge from 16.0 to 17.0 by tool pullmerge.sh 2026-03-16 10:17:50 +01:00
Laurent Destailleur
4098eafe24 Automated merge from 15.0 to 16.0 by tool pullmerge.sh 2026-03-16 10:16:32 +01:00
Laurent Destailleur
5294805890 Removed non relevant test 2026-03-16 10:14:43 +01:00
Laurent Destailleur
5915aa4846 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-16 10:11:40 +01:00
Laurent Destailleur
9db76503e4 Clean code 2026-03-16 10:11:31 +01:00
Laurent Destailleur
fe4da5614a Trans 2026-03-16 09:46:21 +01:00
minimexat
e5135ed8ab Fix missing POST/Redirect/GET after proposal validation (#37507)
After confirm_validate succeeds, redirect back to the card so the
page renders with the correct post-validation state. Without this,
the Validate button stays visible even though the proposal was
already validated in the database.

Fixed by adding a Location redirect at the end of the success branch,
same pattern used by confirm_cancel and confirm_clone.

Fixes #37480

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-16 09:38:51 +01:00
Laurent Destailleur
a33b97b311 CI 2026-03-16 09:20:41 +01:00
Laurent Destailleur
10d780ad60 CI 2026-03-16 09:01:39 +01:00
Laurent Destailleur
c783b305dc Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-15 15:29:16 +01:00
Laurent Destailleur
ae5d7b6fb6 CI 2026-03-15 15:29:01 +01:00
John BOTELLA
559454e4d4 Fix : fontawesome icon list in doc (#37481)
* Fix fontawesome icon list

* Label

* remove useless test

* Fix doc php stan

* Fix doc php stan

* Fix doc php stan
2026-03-15 14:54:59 +01:00
Laurent Destailleur
a81ba1847d Merge branch '14.0' of git@github.com:Dolibarr/dolibarr.git into 15.0 2026-03-15 14:53:27 +01:00
Laurent Destailleur
209323598a Restore parallel-lint only on last version 2026-03-15 14:52:53 +01:00
Laurent Destailleur
88b66741fc Merge branch '14.0' of git@github.com:Dolibarr/dolibarr.git into 15.0 2026-03-15 14:52:07 +01:00
Laurent Destailleur
6505e41b4d Merge branch '14.0' of git@github.com:Dolibarr/dolibarr.git into 15.0 2026-03-15 14:51:02 +01:00
Laurent Destailleur
a4bd180c72 Merge branch '14.0' of git@github.com:Dolibarr/dolibarr.git into 14.0 2026-03-15 14:50:22 +01:00
Laurent Destailleur
2605d33dbc Restore parallel-lint 2026-03-15 14:50:13 +01:00
William Desportes
9e13496975 Fix User creation API permission check (#37502)
Bug introduced by 3c9d8bc931

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-15 14:48:32 +01:00
Laurent Destailleur
d33e0dd302 Fix error management 2026-03-15 14:47:28 +01:00
Laurent Destailleur
cb96c5fbed FIX Missing field in fetch 2026-03-15 14:43:53 +01:00
Laurent Destailleur
7c399bd78b Fix CSS and look and feel v23 2026-03-15 14:34:28 +01:00
Laurent Destailleur
22804ef1d6 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-15 14:34:04 +01:00
Laurent Destailleur
0d50af64a2 Fix CSS and look and feel v23 2026-03-15 14:33:46 +01:00
minimexat
6a3300348e Fix missing billed field in expedition INSERT causing DB error (#37506)
The billed column has no default value in llx_expedition, but was
not included in the INSERT statement in Expedition::create().
This caused a strict SQL error when creating delivery notes.

- Initialize $this->billed = 0 on the property declaration
- Add billed to the INSERT column list with value 0

Fixes #37452

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-15 13:58:04 +01:00
minimexat
57812a1272 Fix PHP warnings for undefined property and array keys in purchasesjournal.php (#37505)
* Fix missing POST/Redirect/GET after proposal validation

After confirm_validate succeeds, redirect back to the card so the
page renders with the correct post-validation state. Without this,
the Validate button stays visible even though the proposal was
already validated in the database.

Fixes #37480

* Fix missing billed field in expedition INSERT causing DB error

The billed column has no default value in llx_expedition, but was
not included in the INSERT statement in Expedition::create().
This caused a strict SQL error when creating delivery notes.

- Initialize $this->billed = 0 on the property declaration
- Add billed to the INSERT column list with value 0

Fixes #37452

* Update expedition.class.php

* Refactor proposal closing and document generation logic

Reload the proposal object after closing and before generating the document. Removed the redundant fetch call and the header redirection.

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-15 13:52:13 +01:00
Laurent Destailleur
39f3abec0c Try to ignore travis 2026-03-15 13:47:49 +01:00
Laurent Destailleur
5760b67393 Avoid redirect 2026-03-15 13:34:41 +01:00
Laurent Destailleur
558064b9be Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-15 12:35:47 +01:00
Laurent Destailleur
46c764c689 CI 2026-03-15 12:35:29 +01:00
Rikard Bosnjakovic
6f4c1ef972 Whitespace in the middle of a tracking number will break the link when (#37501)
the user tries to follow the shipping, so we remove all whitespace.
2026-03-15 12:24:15 +01:00
Laurent Destailleur
a9d846dfd6 Disable paralle-lint that is no more available on travis (security bug) 2026-03-15 12:22:16 +01:00
Laurent Destailleur
bc29bc1a8a Automated merge from 23.0 to develop 2026-03-15 12:16:23 +01:00
Laurent Destailleur
4908f4acba Automated merge from 22.0 to 23.0 by tool pullmerge.sh 2026-03-15 12:16:21 +01:00
Laurent Destailleur
ce9f993856 Automated merge from 21.0 to 22.0 by tool pullmerge.sh 2026-03-15 12:16:18 +01:00
Laurent Destailleur
2a3982f2fd Automated merge from 20.0 to 21.0 by tool pullmerge.sh 2026-03-15 12:16:15 +01:00
Laurent Destailleur
e77af9c26c Automated merge from 19.0 to 20.0 by tool pullmerge.sh 2026-03-15 12:15:32 +01:00
Laurent Destailleur
1917a9bffc Enhance merge tool 2026-03-15 12:13:00 +01:00
Laurent Destailleur
0f6c62856b Automated merge from 23.0 to develop 2026-03-15 12:08:40 +01:00
Laurent Destailleur
e8a02f169a Automated merge from 22.0 to 23.0 by tool pullmerge.sh 2026-03-15 12:08:38 +01:00
Laurent Destailleur
ac1a56fd00 Automated merge from 21.0 to 22.0 by tool pullmerge.sh 2026-03-15 12:08:35 +01:00
Laurent Destailleur
e0ceff086e Automated merge from 20.0 to 21.0 by tool pullmerge.sh 2026-03-15 12:08:32 +01:00
Laurent Destailleur
28b77bc599 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-15 12:06:56 +01:00
Laurent Destailleur
84eae7147b Complete #37490 2026-03-15 12:06:34 +01:00
Laurent Destailleur
fbd80c35d8 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into
develop
2026-03-15 12:04:47 +01:00
William Desportes
2f5a74e34c Fix User creation API permission check (#37504)
Fixes #30465

Bug introduced by 3c9d8bc931
And still present in the change 7b54824d49
See: 7b54824d49 (diff-64e82e2f84fc5a1af766e17f0e533221c48231c6bbe3f342af899ee6854748fb)
2026-03-15 12:01:33 +01:00
Vincent Penel
cad1d11aee Fix Ticket Permission (#37490)
* Fix Permission

* message

* Update html.formticket.class.php

* Update card.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-15 11:53:00 +01:00
Laurent Destailleur
15ddf8b2ad Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-15 11:52:23 +01:00
Laurent Destailleur
72d6859473 CI 2026-03-15 11:52:13 +01:00
John BOTELLA
a0ddd26b63 Uiux : Update event-message doc to include JS context set event tool (#37444)
* Update event-message doc

* php cs

* Try fix trafix how see ghost

* Try fix trafix how see ghost

* Try fix trafix how see ghost

* Try fix trafix how see ghost

* Try fix travis

* Try fix travis

* Try fix travis

* Try fix travis

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-15 11:00:08 +01:00
Alain Cis
0f2d81203b NEW #36638 Feature: Add PDF document generation to Salaries module (#36785)
* Feature: Add PDF document generation to Salaries module

* Fix jobs errors: https://github.com/Dolibarr/dolibarr/actions/runs/21544265754

* Update CommonClassTest.class.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-14 19:55:08 +01:00
Laurent Destailleur
7753db3704 AI 2026-03-14 19:51:54 +01:00
sonikf
5a54415266 New MCP server and assistant admin part (#37408)
* New MCP server and assistant

* New MCP server and assistant

* Create log_viewer.php

* Update setup.php

* Update admin.lang

* New MCP server and assistant

* ADD AI Log Request, service connection testing and more providers

* fix pre commit

* fix pre-commit

* fix pre-commit

* fix pre-commit

* fix pre-commit

* fix pre-commit

* fix pre-commit

* fix pre-commit

* fix pre-commit

* fix pre-commit

* fix Variable $model in empty() is never defined.

* fix Variable $resql might not be defined.

* fix  php-stan

* fix Variable $badge might not be defined.

* fix phan

* fix phan

* fix phan

* add missing multicompany filtering

* fix php-stan

* Fix condition to check current AI service

* Update AI external access message for clarity

* Update entity filter in log viewer SQL queries

* Fix spelling and update model configurations

Corrected spelling of 'DocumentPArsing' to 'DocumentParsing' and updated model configurations to use arrays for default values.
2026-03-14 19:48:17 +01:00
Laurent Destailleur
06629610bf Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-14 19:43:51 +01:00
Laurent Destailleur
27f22cf073 CSS 2026-03-14 19:43:30 +01:00
MDW
50af72468b Qual: Update type hint for DoliStorage (#37497)
# Qual: Update type hint for DoliStorage

- Update type hint for DoliStorage to use fully qualified namespace

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-14 19:35:11 +01:00
Laurent Destailleur
402af2a929 Remove dead code 2026-03-14 19:34:42 +01:00
Laurent Destailleur
ea86d67269 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-14 19:21:11 +01:00
Laurent Destailleur
38d1b12bfe Fix CI 2026-03-14 19:20:59 +01:00
Laurent Destailleur
6aea3f6e16 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-14 19:18:35 +01:00
Laurent Destailleur
9400c2b358 Fix filter and CI 2026-03-14 19:18:14 +01:00
Laurent Destailleur
4b3443cd77 Doc 2026-03-14 19:07:25 +01:00
Zakaria Boushaba
d3de4529a7 Fix(accounting): exportWinfic now supports parameter like other expo… (#37483)
* Fix(accounting): exportWinfic now supports  parameter like other export methods

* Fix CI PULL REQUEST

---------

Co-authored-by: Zakaria Boushaba <z.boushaba@vold.africa>
2026-03-14 19:03:16 +01:00
Aloïs Micard
6bd574c307 Fix automatic barcode generation in product update (#37487)
* Fix automatic barcode generation in product update

* Fix comment
2026-03-14 19:01:19 +01:00
Aloïs Micard
324930d438 Fix automatic barcode generation in product update (#37487)
* Fix automatic barcode generation in product update

* Fix comment
2026-03-14 18:59:52 +01:00
Laurent Destailleur
fea51e5b92 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-14 18:49:49 +01:00
Laurent Destailleur
2680ec9652 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into
develop
2026-03-14 18:49:14 +01:00
Laurent Destailleur
a6e877c36e Fix test 2026-03-14 18:47:51 +01:00
Laurent Destailleur
d1e45ecf1a FIX #37482 2026-03-14 18:41:00 +01:00
Frédéric FRANCE
639a8cd789 enhance url (#37484) 2026-03-14 18:34:01 +01:00
Frédéric FRANCE
dc1fc47118 fix CI (#37486) 2026-03-14 18:32:18 +01:00
atm-lucas
f90e121650 You cannot cancel leave if the balance is negative (#37492) 2026-03-14 18:31:50 +01:00
Lucas Marcouiller
699718ab35 New mail template to send HR informations (#37491)
* New mail template to send RH informations

* Fix move from salaries to holiday

* Add fr

* Update holiday.lang

---------

Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-14 18:31:18 +01:00
lvessiller-opendsi
14dd9abcc1 FIX bad tab underlined in display setup (#37489) 2026-03-14 18:30:02 +01:00
Charlène Benke
ec67efed5f fichinterrec : bad habilitation on update fields (#37493) 2026-03-14 18:14:27 +01:00
Laurent Destailleur
c332faa956 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-14 18:12:44 +01:00
Laurent Destailleur
53c90968a2 Look and feel v24 2026-03-14 18:12:32 +01:00
Charlène Benke
e3d526db4b fichinterrec : bad habilitation on update fields (#37493) 2026-03-14 18:11:50 +01:00
Laurent Destailleur
561cbbe958 Look and feel v24 - Open margin en account summary of thirdparty in
popup
2026-03-14 17:56:40 +01:00
Laurent Destailleur
54c5141d16 FIX cell must be empty if amount not defined 2026-03-14 14:25:01 +01:00
Laurent Destailleur
55636c2519 Look and feel v24 2026-03-14 14:17:35 +01:00
Laurent Destailleur
acf6ce15d0 Better trans 2026-03-13 13:04:11 +01:00
Laurent Destailleur
992731c8ca FIX extrafield on pdf must not appears on doc if option off 2026-03-13 12:56:25 +01:00
Laurent Destailleur
138c1bf5b3 Fix CI 2026-03-13 11:50:21 +01:00
Laurent Destailleur
82dfa6e189 Trans 2026-03-13 11:45:17 +01:00
Laurent Destailleur
99bc406c49 Fix restore compatibility with filter on category 2026-03-13 11:08:43 +01:00
Laurent Destailleur
934123eef5 FIX restore use of user->id in dynamics conditions 2026-03-13 09:48:18 +01:00
Laurent Destailleur
482b8e8248 spellcheck war 2026-03-13 09:08:52 +01:00
Laurent Destailleur
b4ab809e97 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-13 09:07:23 +01:00
Laurent Destailleur
5a1ea120e6 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-13 08:55:27 +01:00
Laurent Destailleur
c531628ae3 Fix spellcheck 2026-03-13 08:55:00 +01:00
John BOTELLA
4039e17b89 NEW : Extrafield option for tooltip in getNomUrl (#37431)
* new extrafield option

* Fix php cs

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-13 08:25:40 +01:00
Laurent Destailleur
5d83e6feb3 Automated merge from 23.0 to develop 2026-03-12 23:40:28 +01:00
Laurent Destailleur
b9f5895c68 Automated merge from 22.0 to 23.0 by tool pullmerge.sh 2026-03-12 23:40:02 +01:00
Laurent Destailleur
c76d781e30 Automated merge from 21.0 to 22.0 by tool pullmerge.sh 2026-03-12 23:36:15 +01:00
Laurent Destailleur
488366cf06 Automated merge from 20.0 to 21.0 by tool pullmerge.sh 2026-03-12 23:36:12 +01:00
Laurent Destailleur
584e103ebf Automated merge from 19.0 to 20.0 by tool pullmerge.sh 2026-03-12 23:35:37 +01:00
Laurent Destailleur
058c8ee9b0 Automated merge from 18.0 to 19.0 by tool pullmerge.sh 2026-03-12 23:26:55 +01:00
Laurent Destailleur
f86955fcf9 Automated merge from 17.0 to 18.0 by tool pullmerge.sh 2026-03-12 23:15:12 +01:00
Vincent Maury
1582f1afb9 Fix Inventory bugs #33192 and #35207 (#37405)
* Fix Inventory bugs #33207 and #33292 : when click on "validate and generate movements" quantities not still recorded are not recorded

* Fix Inventory bugs #33207 and #33292 : when click on "validate and generate movements" quantities not still recorded are not recorded

* Fix Inventory bugs #33207 and #33292 : when click on "validate and generate movements" quantities not still recorded are not recorded

---------

Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
2026-03-12 23:10:36 +01:00
kkhelifa-opendsi
5475df50bf NEW: add new hook in BonPrelevement::EnregDestinataireSEPA() function (#37419)
* NEW: add new hook in BonPrelevement::EnregDestinataireSEPA() function

* Fix test CI

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 22:53:58 +01:00
Laurent Destailleur
cbf2adc238 CI 2026-03-12 21:58:42 +01:00
Laurent Destailleur
093d9634c8 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-12 21:53:05 +01:00
Laurent Destailleur
0e62a44ea1 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-12 21:49:14 +01:00
Laurent Destailleur
a12da79340 CI 2026-03-12 21:49:05 +01:00
Laurent Destailleur
f23b1801e5 Revert "Revert "Fix ticket categories dictionay (#37434)""
This reverts commit ff204097e3.
2026-03-12 21:42:20 +01:00
Zakaria Boushaba
c057d46a4c NEW|New Add hook selectForFormsListUrl in Form::selectForForms (#37447)
* Feat: add hook selectForFormsListUrl to allow modules to modify AJAX URL params

* Add

---------

Co-authored-by: Zakaria Boushaba <z.boushaba@vold.africa>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 21:41:08 +01:00
Frédéric FRANCE
677ec352d9 add notrigger in setMultilangs of product.class.php (#37478)
* add notrigger in setMultilangs of product.class.php

* Update product.class.php

* Update product.class.php

* Update product.class.php

* add notrigger
2026-03-12 21:39:58 +01:00
Laurent Destailleur
531c55a9fd NEW Show example of ai models in setup 2026-03-12 21:13:49 +01:00
Laurent Destailleur
86f08b429b css 2026-03-12 19:19:45 +01:00
Laurent Destailleur
f188ad64eb Picto for shared doc 2026-03-12 17:11:05 +01:00
Laurent Destailleur
30e28d5f8a CI 2026-03-12 16:37:01 +01:00
Laurent Destailleur
ffcbe0263d Look and feel v24 2026-03-12 16:11:26 +01:00
Guido Schratzer
95579849dc FIX: filling of field amount_main_currency for foreign m… (#37439)
* FIX: Issue #37425 filling of field amount_main_currency for foreign money accounts

The parameter amount_main_currency (argument #13 of addline()) was previously
filled even when the bank account was already using the company main currency.

This caused the field amount_main_currency to be populated incorrectly for
transactions that were not foreign currency operations.

The logic has been updated so that amount_main_currency is only filled when the
transaction involves a foreign currency account. For accounts already using the
company main currency, the value is now left NULL as expected.

This aligns the behavior with the intended usage of addline() and prevents
incorrect data in bank transaction records.

* FIX: Issue #37425 filling of field amount_main_currency for foreign money accounts
transfer.php: PhanTypeMismatchArgument: Argument 4 ($amount) is price2num((-1 * (float)($amount[$n]))) of type string but \Account::addline() takes float defined at htdocs/compta/bank/class/account.class.php:611
CI-PULL-REQUEST / phan / Run phan
Check warning on line R189
Check warning:
transfer.php: PhanTypeMismatchArgument: Argument 13 ($amount_main_currency) is $amount_main_currency_from of type ?string but \Account::addline() takes ?float defined at htdocs/compta/bank/class/account.class.php:611
CI-PULL-REQUEST / phan / Run phan

* declare dateo

* declare of variables before action script

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 14:58:58 +01:00
Laurent Destailleur
904f201de3 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into
develop
2026-03-12 14:58:42 +01:00
Laurent Destailleur
94d7642b2f Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-12 14:47:43 +01:00
Laurent Destailleur
69f75928e7 CI 2026-03-12 14:47:18 +01:00
boudet jean pascal
a4b8cb7008 NEW : adds visual indicators on the Sales Order card (commande/card.php) (#37460)
* This PR adds visual indicators on the Sales Order card (commande/card.php) to quickly identify if an order or its lines are shippable based on current stock levels. This feature mirrors the existing functionality found in the Orders List, bringing valuable logistics information directly into the Order Card context.

Key Features:
Global Shippable Status: Adds a "shippable" icon (Truck) next to the "Planned delivery date" field.

Logic: Checks if all products in the order are in stock.

Condition: Visible only if Stock and Shipment modules are enabled, a delivery date is set, and the order is validated.

Visual: Green icon if fully shippable, Red/Warning if stock is insufficient. Includes a detailed tooltip.

Per-Line Shippable Status: Adds a new "Shippable" column in the product lines table.

Logic: Compares real stock vs. remaining quantity to ship for each line.

Visual: Displays a green/red status bullet (standard Dolibarr status icons) directly in the line.

Implementation: Done by updating objectline_title.tpl.php and objectline_view.tpl.php with specific checks for the commande element.

New Configuration Option: Added a hidden option ORDER_DISABLE_SHIPPABLE_ICON_ON_CARD to disable this feature if needed (configurable in Home > Setup > Other Setup or via module settings).

Backend Optimization: Added a new method getShippableInfos() in commande.class.php to centralize the stock check logic (optimized with stock caching to avoid N+1 query issues).

Technical Details:
Modified Files:

commande/card.php: Integration of the global icon next to the date.

commande/class/commande.class.php: Added getShippableInfos() method.

core/tpl/objectline_title.tpl.php: Added "Shippable" column header (conditional for Orders).

core/tpl/objectline_view.tpl.php: Added "Shippable" column cell content (conditional for Orders).

Performance: Logic uses static caching for product stock to minimize database impact when rendering large orders.

* missed element

* img_picto

* phan add

* load_stock and global $i

* phan

* ci retour

* change icon
change phan directive

* phan

* Fight against optionflation

Signed-off-by: Laurent Destailleur <eldy@destailleur.fr>

* Change condition for displaying shippable icon

Signed-off-by: Laurent Destailleur <eldy@destailleur.fr>

* Change condition for displaying shippable status icon

Signed-off-by: Laurent Destailleur <eldy@destailleur.fr>

* Update card.php

Signed-off-by: Laurent Destailleur <eldy@destailleur.fr>

* Update shippable status condition in template

Signed-off-by: Laurent Destailleur <eldy@destailleur.fr>

* Update shippable status condition logic

Signed-off-by: Laurent Destailleur <eldy@destailleur.fr>

---------

Signed-off-by: Laurent Destailleur <eldy@destailleur.fr>
Co-authored-by: jpb <jean-pascal.boudet@atm-consulting>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 14:42:31 +01:00
Maxime Kohlhaas
e29b741958 Fix : disabled extrafield should not appear on PDF (#37442) 2026-03-12 14:40:36 +01:00
Francis Appels
b02f1a4ebf NEW: Show bom net needs treeview by line position. (#37459)
* New show bom net needs treeview by line position.

* bom_net_needs fix level is also position.

* bom_net_needs position need numeric sort.

* Fix sorting string

* Fix warning

* fix stan/phan

* Fix stan

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 14:38:31 +01:00
John BOTELLA
30c7e86687 Fix unique db lang name generator (#37467)
* fix_unique_db_lang_name

* Fix: add clear all db

* Fix: add clear all db

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 14:37:31 +01:00
Laurent Destailleur
70346cb3bb Move permission ticket->manage as an advanced permission 2026-03-12 14:35:26 +01:00
Laurent Destailleur
ff204097e3 Revert "Fix ticket categories dictionay (#37434)"
This reverts commit a9db2f6dba.
2026-03-12 14:14:17 +01:00
nateogroup-antoine
e62d193997 FIX deprecated statut variable and reload object (#37449)
* Fixed a bug that initialized the deprecated status variable without initializing the new status variable.

Upon quote approval, the interface is based on “status” rather than “statut.” The approved quote still had the edit button visible.

* Fix reload object if not MAIN_DISABLE_PDF_AUTOUPDATE after addline or updateline

Fix reload object if not MAIN_DISABLE_PDF_AUTOUPDATE after addline or updateline

* Update card.php
2026-03-12 14:04:59 +01:00
SAINT-PATRICE
b1e693a1ac Update contratligne.class.php for Postgres active_line and close_line (#37477)
* Update contratligne.class.php for Postgres

* Update contratligne.class.php close_line for postgres
2026-03-12 14:02:27 +01:00
Mohamed DAOUD
f0d6ae5b3f NEW: Update ticket read date when status changes (#37470)
* update read date on status change

* Fix CI

* Update card.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 14:01:51 +01:00
kkhelifa-opendsi
f01cfa9fad FIX : Fix SQL request for fix duplicate accounting account and remove wrong and obsolete SQL request who break the accounting account when we launch repair with multi-entities (#37430)
* FIX : Fix SQL request for fix duplicate accounting account and remove wrong and obsolete SQL request who break the accounting account when we launch repair with multi-entities

* Same fix for migration 8.0 to 9.0

* Add removed sql needed
2026-03-12 13:57:57 +01:00
Juan Pablo Farber
502bc82815 QUAL Replace $_REQUEST superglobals with GETPOST() in projet and compta/tva (#37456)
* Fix: replace $_REQUEST with GETPOST() in projet/note.php

Use GETPOST('mode', 'alpha') instead of direct $_REQUEST['mode']
access to follow Dolibarr coding conventions for input sanitization.

* Fix: replace $_REQUEST with GETPOSTINT() in compta/tva/clients.php

Use GETPOSTINT('extra_report') instead of direct $_REQUEST access
to follow Dolibarr coding conventions for input sanitization.
2026-03-12 13:51:23 +01:00
Juan Pablo Farber
5e8bb7be8e QUAL Replace $_REQUEST superglobals with GETPOST() in projet and compta/tva (#37456)
* Fix: replace $_REQUEST with GETPOST() in projet/note.php

Use GETPOST('mode', 'alpha') instead of direct $_REQUEST['mode']
access to follow Dolibarr coding conventions for input sanitization.

* Fix: replace $_REQUEST with GETPOSTINT() in compta/tva/clients.php

Use GETPOSTINT('extra_report') instead of direct $_REQUEST access
to follow Dolibarr coding conventions for input sanitization.
2026-03-12 13:50:58 +01:00
Juan Pablo Farber
93c6cb922c QUAL Add missing Spanish (es_ES) translations (#37457)
* QUAL Add missing Spanish translations in es_ES locale

Add missing keys in ticket.lang, agenda.lang and errors.lang:
- Ticket email notification template keys
- Auto-read on assign keys
- ManualActions key in agenda
- OIDC user auto-creation messages
- ErrorFailedToGetTokenFromClientIdAndSecret

* QUAL Fix untranslated key in es_ES errors.lang

Translate UserDoesNotHaveRightsToChangeHisPassword key that was
left in English in the Spanish locale file.
2026-03-12 13:50:19 +01:00
Zakaria Boushaba
fd71eba41e FIX: Fix mandatory custom fields JS validation in printCommonFooter (#37469)
* Fix mandatory fields JS: use name selector instead of id, fix undefined buttonName on create, fix HTML entities in alert message

* Update alert message for empty field validation

---------

Co-authored-by: Zakaria Boushaba <z.boushaba@vold.africa>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 13:47:12 +01:00
lvessiller-opendsi
ea60298eeb FIX show export full documents checkbox on change format in accountancy export (#37468)
* FIX show export full documents checkbox on change format in accountancy export

* FIX remove unused var
2026-03-12 13:46:23 +01:00
Laurent Destailleur
6a70193549 CI 2026-03-12 13:27:43 +01:00
Laurent Destailleur
b73d055cbb Look and feel v24 2026-03-12 13:12:29 +01:00
Laurent Destailleur
3016301ae8 Merge branch '18.0' of git@github.com:Dolibarr/dolibarr.git into 18.0 2026-03-12 11:40:07 +01:00
Laurent Destailleur
2065dee731 Merge branch '18.0' of git@github.com:Dolibarr/dolibarr.git into 18.0 2026-03-12 11:36:14 +01:00
Laurent Destailleur
6689f5bb52 Test 2026-03-12 11:36:05 +01:00
Laurent Destailleur
d8bce4e1e4 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-12 11:16:17 +01:00
Laurent Destailleur
cd0a4016ff Fix regressions 2026-03-12 11:12:35 +01:00
Laurent Destailleur
ad16be87eb Fix sql 2026-03-12 11:05:50 +01:00
Laurent Destailleur
8172916804 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-12 11:04:38 +01:00
Laurent Destailleur
f5e7d983b3 css 2026-03-12 11:04:22 +01:00
Eric - CAP-REL
bf77993fc9 Merge pull request #37429 from thomas-Ngr/18_fix_api_warehouse_by_id
FIX API Warehouse : Error 401 when getting warehouse by id (backport from 22)
2026-03-12 10:04:30 +01:00
Eric Seigne
4f289effa3 actions must be in default branch to be enabled 2026-03-12 09:27:16 +01:00
Laurent Destailleur
d615300b7a Doc 2026-03-12 02:11:48 +01:00
Laurent Destailleur
a8f7b6eb05 Add module 2026-03-12 02:07:16 +01:00
Laurent Destailleur
1ee077f797 CI 2026-03-12 02:02:58 +01:00
Laurent Destailleur
f1433d9234 CI 2026-03-12 01:59:35 +01:00
Laurent Destailleur
ab108d2b80 Look and feel v24 2026-03-12 01:54:19 +01:00
Vincent Penel
d2bbba5a21 Fixing the 'formConfirm' hook on tickets (#37473)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 01:20:03 +01:00
Laurent Destailleur
2176229ffb Look and feel v24 2026-03-12 01:18:10 +01:00
Laurent Destailleur
8bb0a5d5d8 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-12 00:27:59 +01:00
Laurent Destailleur
170a782a87 Menu open/close 2026-03-12 00:27:51 +01:00
Laurent Destailleur
1f952b6969 Fix leftmenu 2026-03-12 00:22:32 +01:00
ATM-NicolasV
ca957917e5 Fix mainmenu redirection in (#37471)
accounting configuration
2026-03-12 00:06:43 +01:00
Vincent de Grandpré
5079e49023 Fix #37465 (#37466) 2026-03-12 00:03:05 +01:00
Vincent de Grandpré
89eb9115b3 Fix #37465 (#37466) 2026-03-12 00:02:49 +01:00
Laurent Destailleur
d61ffe5872 CI 2026-03-12 00:00:34 +01:00
splohmer
45d862c0e8 FIX Order API: delete order returns wrong http response in case order could not be deleted (#37472) 2026-03-11 23:44:18 +01:00
Laurent Destailleur
02885b4a5b CI 2026-03-11 23:43:00 +01:00
Laurent Destailleur
dac22586ba CI 2026-03-11 23:33:46 +01:00
Laurent Destailleur
7762154f6d CI 2026-03-11 23:29:59 +01:00
Laurent Destailleur
6f1b27ddad Fix CI 2026-03-11 23:24:27 +01:00
Laurent Destailleur
432361304a FIX Better compatibility for module using condition object-> in tabs 2026-03-11 23:09:50 +01:00
Laurent Destailleur
f8a37ca704 Import 2026-03-11 20:52:39 +01:00
Laurent Destailleur
59f2abede8 Look and feel v24
Signed-off-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-11 20:47:00 +01:00
Laurent Destailleur
4522f558df Look and feel v24
Signed-off-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-11 20:40:45 +01:00
Laurent Destailleur
75ca8e04b4 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-11 19:43:39 +01:00
Laurent Destailleur
156fedd87a Restore old code until we found pb of js regression 2026-03-11 13:09:33 +01:00
Laurent Destailleur
8e68db2f58 css 2026-03-10 21:15:59 +01:00
Laurent Destailleur
a8b4e5d141 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-10 19:56:16 +01:00
Laurent Destailleur
92151198bf Fix css 2026-03-10 19:55:37 +01:00
Laurent Destailleur
e4d8d3047e FIX translation on multiselect with rich label - Fix CSS public ticket 2026-03-10 19:55:15 +01:00
Laurent Destailleur
d2e1cb18cb Look and feel v24 2026-03-10 19:27:59 +01:00
Laurent Destailleur
ddfb7c1274 Clean code 2026-03-10 18:32:41 +01:00
Laurent Destailleur
8ba2977d58 Trans 2026-03-10 18:27:23 +01:00
Laurent Destailleur
ce19a9df01 Clean code 2026-03-10 18:25:59 +01:00
Laurent Destailleur
f84ed50a8e Look and feel v24 2026-03-10 14:57:18 +01:00
Laurent Destailleur
d004cf01fc Tool to check archives integrity 2026-03-10 14:39:57 +01:00
Laurent Destailleur
0f2ee5e5c2 Look and feel v24 2026-03-10 14:20:25 +01:00
Laurent Destailleur
6c621b182e CSS 2026-03-10 14:05:58 +01:00
Laurent Destailleur
f4aac1bd9a Clean code 2026-03-10 12:07:29 +01:00
Laurent Destailleur
acb525524e NEW Better look and feel v24 in import/export tool 2026-03-10 11:33:18 +01:00
Laurent Destailleur
054720a474 Look and feel v24 2026-03-09 20:23:03 +01:00
Laurent Destailleur
70db1d57b9 Look and feel v24 2026-03-09 19:57:55 +01:00
Laurent Destailleur
7e7a11cef1 css 2026-03-09 19:02:57 +01:00
Laurent Destailleur
7e203114eb Disable featureof rule for lines date too buggied.
Substitution date are corrupted by bad date values and feature speak
about payments/prepayments when this is just related to date!
2026-03-09 10:38:56 +01:00
Laurent Destailleur
4ad6d1b5b3 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into
develop
2026-03-09 09:55:09 +01:00
Laurent Destailleur
6944c5584f Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into 23.0 2026-03-09 09:52:23 +01:00
Laurent Destailleur
d1e9781403 FIX CSS 2026-03-09 09:52:11 +01:00
Laurent Destailleur
ab3a6b37cc Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-09 09:35:38 +01:00
Laurent Destailleur
f597cf9310 Trans 2026-03-09 08:32:41 +01:00
Anthony Berton
dfd9e93bbc UX---Display-the-recorded-knowledge-at-the-top-of-the-table (#37441)
* Add the registration page

* WIP LNE

* Default value

* Fix trans and warning

* Doc

* Debug v23

* Debug v23

* Debug v23

* Doc

* New import Leave Balance (Datastructure) (#37001)

* New import Leave Balance (Datastructure)

* fix

---------

Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>

* Qual: Update Phan baseline.txt with reduced issue counts (#36996)

This commit updates the Phan baseline.txt file.

* Better log

* Log

* Better error management

* Doc

* Fix error message

* CI

* Try to fix CI

* fix phan (#37007)

* CI

* fix phan (#37008)

PhanTypeMismatchArgumentNullable Argument 3 ($txt) is $productlot->batch of type ?string but \TCPDF::MultiCell() takes string defined at htdocs/includes/tecnickcom/tcpdf/tcpdf.php:5871 (expected type to be non-nullable)

* CI

* NEW Add new EUID number (#36997)

* Doc

* ci

* Fix CI

* Improve OAuth HTTP error details (#36951)

Keep throwing TokenResponseException on HTTP 4xx/5xx while also capturing the response body (ignore_errors) to include a short snippet for admins.

Co-authored-by: caminotravelcenter <caminotravelcenter@localhost>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW add import of leave balance (Implementation) (#37011)

* New add import Leave Balance

* add eof

* Update modHoliday.class.php

---------

Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW add image format avif (#37014)

* add format avif

* Update functions.lib.php

* Exclude non interesting warning

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

* CI

* CI

* CI

* Trans

* CI

* CI

* CI

* Fix typo in phpstan.neon.dist for isset() rule (#37018)

See https://github.com/Dolibarr/dolibarr/actions/runs/21444577128/job/61756970301?pr=37017

* Remove jstz.min.js from JavaScript array (#37017)

jstz.min.js was deleted in 5599ac733b

* Remove jstz.min.js from JavaScript array (#37017)

jstz.min.js was deleted in 5599ac733b

* prevent ST_AsWKT() (#37013)

* prevent ST_AsWKT()

* Update commonobject.class.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* prevent ST_AsWKT() (#37013)

* prevent ST_AsWKT()

* Update commonobject.class.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Try to fix phan

* CI

* CI

* ci

* ci

* css

* qual: Update french texts (comments) to english (#37009)

* qual: Update french texts (comments) to english

* Qual: Update comment to english in card-rec.php and card.php

Translation of comments in the files card-rec.php and card.php.

* Qual: Translate comments to English

* Qual: Translate comments to English

* Fix space into tab

* Fix spelling

* Qual: Translate comments to English

* Update website.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* ci

* Clean code

* Fix test

* Debug v23

* Merge manually changes from #37012 that seems good.

* Log

* More robust phpunit

* Populate openid data from wellknow url (#37023)

* Delete inexistant file

File jstz.min.js was deleted in
5599ac733b

* Populate openid parameters from wellknow url

* CI

* Debug v23 - fix code for rounding

* MCP server sql part (#37025)

* Create llx_ai_request_log.sql

* Create llx_ai_request_log.key.sql

* Update 23.0.0-24.0.0.sql

* Update llx_ai_request_log.sql

* Update 23.0.0-24.0.0.sql

* Remove foreign key constraint from llx_ai_request_log

Removed foreign key constraint for fk_user in llx_ai_request_log.

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Debug v24

* Fix syntax error

* NEW allow to disable freezone product on takepos (#37004)

* allow to disable freezone product on takepos

* Conditionally add FreeZone product to menus

* Add NoFreeZoneProduct language entry

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix label of dispute

* [database] Minimum amount and subscription's amount formula description (#37021)

* Remove jstz.min.js from JavaScript array (#37017)

jstz.min.js was deleted in 5599ac733b

* prevent ST_AsWKT() (#37013)

* prevent ST_AsWKT()

* Update commonobject.class.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* css

* Update llx_adherent_type.sql

add minimumamount and amountformuladescription rows

* Update 22.0.0-23.0.0.sql

add minimumamount and amountformuladescription column

---------

Co-authored-by: hansemschnokeloch <hansemschnokeloch@users.noreply.github.com>
Co-authored-by: atm-jonathan <146709163+atm-jonathan@users.noreply.github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW: Display thirdparty name with ref in supplier orders linked objects (#36952)

Co-authored-by: Zakaria Boushaba <z.boushaba@vold.africa>

* css

* Debug v23

* Fix type

* CI

* CI

* Test without file list

* CI

* Add api document management for holiday (#36915)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW Invoice - List - use select2 multiselect for status (#36834)

* NEW Invoice - List - use select2 multiselect for status

* Optimize

* CI

* CI

* Review

* Duplicate line and last problem

* CI

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* FIX Pb in total price of line when adding the discount of a down payment
when unit price was using a high accuracy.

* Clean code

* try to fix phan

* Fix phan

* Clean code

* Clean code

* Debug v23

* Fix LDAP

* fix facture.class.php (#37031)

* Debug v23

* Qual: Update french comments with 'nombre' (#37032)

Update french comments with 'nombre' in their phrasing

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* fix phan (#37029)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* fix phan (#37030)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Doc

* Doc comment

* FIX When bank direct debit SEPA ref is > 99

* Fix when there is more than 99 direct debit in same month

* Debug v24

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

* CSS

* Fix when there is more than 99 direct debit in same month

* Debug v24

* FIX Division by zero

* FIX Division by zero

* More log

* More log

* More log

* More Log

* Fix IPN payment

* Fix IPN

* Log

* Clean code

* Debug v23 - Fix ipn for dispute and withdraw funds.

* fix CI (#37033)

* fix CI (#37033)

* CI

* fix french doc (#36897)

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix

* fix

* fix

* fix

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* Update html.form.class.php

* Update html.form.class.php

* Update index.php

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* Update modules_facture.php

* Update phpstan.neon.dist

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Qual: Translate French comments to English (#37044)

* Qual: Translate French comments to English

* Qual: Translate French comments to English

* Qual: Translate French comments to English ("niveau") (#37041)

Translate French comments to English (comments with "niveau")

* fix french doc (#37040)

* warning error if no societe->id defined (#37039)

* fix french doc (#37037)

* fix french doc

* Update dispatch.php

* Update dispatch.php

* Update graph.php

* fix french doc (#37047)

* fix french doc

* Update index.php

* Update index.php

* Fix CI

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

* CI

* Fix CI

* Minimum amount and subscription's amount formula description (#37006)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Warning on export/import - Add isset() protection in Export::load_arrays() & Import::load_arrays() (#37043)

* Warning on export - Add isset() protection in Export::load_arrays()

* Warning on import - Add isset() protection in Import::load_arrays()

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Clean comment

* Warning on account model (#37042)

* Error on account model

* Simplify rowid assignment in accountmodel.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix #35394 Display public note on first page (#36993)

* Display public note (an incoterms and sales rep signat) only on real first page

* Display public note (an incoterms and sales rep signat) only on real first page

---------

Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* FIX: Add htmlname and selectedrate to parameters array for hook (#36998)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* FIX: Add htmlname and selectedrate to parameters array for hook (#36998)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* Fix CI

* Fix CI

* Fix ci

* Qual: Translate French comments to English (#37052)

* Qual: Translate French 'valeur' in comments to English (#37051)

* NEW Accountancy - Select between accrual accounting and cash accounting (#37050)

* Qual: Translate french 'avec' comments to English (#37049)

# Qual: Translate french 'avec' comments to English

* FIX divisa rate indirect to direct because Dolibarr use indirect for default (#37046)

* FIX rate_indirect to rate_direct

Direct	: 	1 Divisa Currency = X Currency Main.
Indirect	: 	1 Currency Main = X Divisa Currency.
Then for Dolibarr use is Indirect for default

* FIX divisa rate indirect to direct

Direct	: 	1 Divisa Currency = X Currency Main.
Indirect: 	1 Currency Main = X Divisa Currency.
Then for Dolibarr use is Indirect for default

* FIX divisa rate indirect to direct

Direct	: 	1 Divisa Currency = X Currency Main.
Indirect: 	1 Currency Main = X Divisa Currency.
Then for Dolibarr use is Indirect for default

* FIX divisa rate indirect to direct

Direct	: 	1 Divisa Currency = X Currency Main.
Indirect: 	1 Currency Main = X Divisa Currency.
Then for Dolibarr use is Indirect for default

* FIX divisa rate indirect to direct

Direct	: 	1 Divisa Currency = X Currency Main.
Indirect: 	1 Currency Main = X Divisa Currency.
Then for Dolibarr use is Indirect for default

* Divisa rate direct

Direct	: 	1 Divisa Currency = X Currency Main.
Indirect: 	1 Currency Main = X Divisa Currency.
Then for Dolibarr use is Indirect for default

* Add col rate_direct because Dolibarr use for.default rate indirect

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* Add thumbnails for avif (#37045)

* vignette for avif

* vignette for avif

* vignette for avif

* vignette for avif

* vignette for avif

* vignette for avif

* vignette for avif

* vignette for avif

* vignette for avif

* vignette for avif

* vignette for avif

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* FIX #36892

* NEW : Manage discount for all lines for supplier elements. (#36954)

* NEW : Manage remise for all lines for supplier elements.

* Cast remise_percent to float in updateline call

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix Signature position (#37048)

Move up the position of the signature to fix its explosion on 3 pages

* remove french doc and fix travis migration missing (#37053)

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* Clean up comments in card.php

Removed unnecessary comments from card.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Debug v23

* Fix CSS

* Fix CSS

* Fix sql syntax error

* Fix sql syntax error

* Add optionnal img to openid login (#37060)

* fix french doc (#37056)

* fix french doc

* fix french doc

* fix french doc

* doc translation

* doc translation

* doc translation

* doc translation

* doc translation

* load tables in init for create categorie table associated (#37059)

* Fix situation invoice

* Fix situation invoice

* Debug v23

* Link and filter on ountry for holiday types

* Link and filter on ountry for holiday types

* Trans

* Debug v23

* css

* CSS on current day

* Debug v23

* css

* More complete message

* Make array of dispute status a shared constant

* Debug v23

* Debug v23

* bad link on sql in fichinter list (#37081)

* bad link on sql in fichinter list (#37081)

* FIX: TakePOS, webapp-hardware-bridge and TakePOS-Connector for modern weighing scales (#37078)

* feat: CustomerDisplay through Webapp-Hardware-Bridge

* feat: WeighingScale through Webapp-Hardware-Bridge

* feat: WeighingScale through Webapp-Hardware-Bridge

* fix: looks like a typo

* feat: for small screens

* fix: attribut contenant le prix du produit ajouté

* feat: ajout d'un callback de gestion d'erreur

* Clean code for Takepos

* Debug v23

* Fix new path of dolreceiptprinter.class.php

* Adjust signature appearance position in PDF generation (#37076)

* EmailCollector: exclude sender emails/domains (#37075)

# Conflicts:
#	htdocs/langs/en_US/admin.lang

Co-authored-by: braito4 <braito4@users.noreply.github.com>

* fix: dev/examples/zapier/package.json to reduce vulnerabilities (#37071)

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-LODASH-15053838

Co-authored-by: snyk-bot <snyk-bot@snyk.io>

* translate french doc to english (#37064)

* fix french doc

* fix french doc

* fix french doc

* fix french doc

* fix

* fix

* fix

* fix

* Trans

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

* Change translation function to transnoentities (#37065)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Change translation function to transnoentities (#37065)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW Show total multicurrency on payment (#37070)

* Total multicurrency

* Total multicurrency paiment

* total result multicurrency

* show total multicurrency

* Fix CI

* CLOSE #37085 - Popup notif is just under the menu bar

* fix: add missing geturl lib (#37088)

* Fix CI

* Restrict to modiied files

* #36843 Update card-rec.php (#37062)

The item desc is already added into the line when selecting the Item. No Need to concat it again when clicking the "add" button.

* Clean code

* Fix CI

* Doc

* Fix CI

* family assignment logic on user perms of external modules (#36456)

* family assignment logic on user perms of external modules

Updated family assignment logic to default to 'other' if family does not exist in $familyinfo.

* Update copyright year for Charlene Benke

* family assignment logic on user perms of external modules (#36456)

* family assignment logic on user perms of external modules

Updated family assignment logic to default to 'other' if family does not exist in $familyinfo.

* Update copyright year for Charlene Benke

* Fix comment and keep external modules out of core modules

* Close #36975

* Close #36975

* css

* NEW : hide remise_except unit price on invoice lines (#37066)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW(API): create fixed amount discount (#37091)

* NEW(API): create fixed amount discount

* FIX CI

* FIX CI

---------

Co-authored-by: Benjamin Falière <benjamin.faliere@altairis.fr>

* Fix phan

* CI

* FIX

* FIX

* Display the recorded knowledge at the top of the table

---------

Co-authored-by: Laurent Destailleur <eldy@users.sourceforge.net>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
Co-authored-by: Lucas Marcouiller <45882981+Hystepik@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
Co-authored-by: MDW <mdeweerd@users.noreply.github.com>
Co-authored-by: Frédéric FRANCE <frederic34@users.noreply.github.com>
Co-authored-by: Alexandre SPANGARO <alexandre.spangaro@gmail.com>
Co-authored-by: Braito <braito4@hotmail.com>
Co-authored-by: caminotravelcenter <caminotravelcenter@localhost>
Co-authored-by: hansemschnokeloch <hansemschnokeloch@users.noreply.github.com>
Co-authored-by: atm-jonathan <146709163+atm-jonathan@users.noreply.github.com>
Co-authored-by: sonikf <93765174+sonikf@users.noreply.github.com>
Co-authored-by: Charlène Benke <1179011+defrance@users.noreply.github.com>
Co-authored-by: Thatoo <Thatoo@users.noreply.github.com>
Co-authored-by: Zakaria Boushaba <48571684+Boushabazakaria@users.noreply.github.com>
Co-authored-by: Zakaria Boushaba <z.boushaba@vold.africa>
Co-authored-by: Vincent Maury <artec.vm@arnac.net>
Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
Co-authored-by: Jyhere <jyhere@gmail.com>
Co-authored-by: Lenin Rivas <53640168+leninrivas@users.noreply.github.com>
Co-authored-by: ThomasNgr-OpenDSI <tnegre@open-dsi.fr>
Co-authored-by: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com>
Co-authored-by: LePat <patrick.muscat@gmail.com>
Co-authored-by: braito4 <braito4@users.noreply.github.com>
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: Vanyo <vanyolai@gmail.com>
Co-authored-by: AWeerWolf <74211972+AWeerWolf@users.noreply.github.com>
Co-authored-by: Benjamin Falière <121813548+BenjaminFlr@users.noreply.github.com>
Co-authored-by: Benjamin Falière <benjamin.faliere@altairis.fr>
Co-authored-by: Anthony Berton <anthony.berton@bb2a.fr>
2026-03-09 01:09:43 +01:00
hansemschnokeloch
e74a333862 Fix migration script (#37436) 2026-03-09 01:07:40 +01:00
Laurent Destailleur
aa10d6062b CI 2026-03-09 01:03:50 +01:00
Laurent Destailleur
767ddd34a2 CI 2026-03-09 01:00:09 +01:00
Laurent Destailleur
b3b02c98a0 Fix key 2026-03-09 00:57:04 +01:00
Vincent de Grandpré
345a92c7db add local taxes to discount data structure (#37424) 2026-03-09 00:45:02 +01:00
Francis Appels
62cf38f227 Fix migration script (#37433) 2026-03-09 00:38:17 +01:00
Vincent Penel
a9db2f6dba Fix ticket categories dictionay (#37434) 2026-03-09 00:37:22 +01:00
hansemschnokeloch
3206535346 Fix migration script (#37436) 2026-03-09 00:36:48 +01:00
Laurent Destailleur
391df54913 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-08 22:40:41 +01:00
Laurent Destailleur
e58458efc6 Module BlockedLog can also now be used by tax auditors. 2026-03-08 22:40:32 +01:00
Laurent Destailleur
7fe6c0c873 Module BlockedLog can also now be used by tax auditors. 2026-03-08 22:27:57 +01:00
Laurent Destailleur
435935be01 Debug v24 2026-03-08 21:59:29 +01:00
Laurent Destailleur
f6ccc8664c WIP LNE 2026-03-08 21:19:21 +01:00
Laurent Destailleur
dde1fa3837 WIP 2026-03-08 13:22:54 +01:00
Laurent Destailleur
1969fcf195 FIX customreport menu must be greyed for external users. 2026-03-07 20:56:33 +01:00
Laurent Destailleur
a1aa7f8eb6 Fix debug v23 2026-03-07 20:07:52 +01:00
Laurent Destailleur
208d960dab NEW add rule MAIN_BUILD_LOGIN_RULE = 'flastname' to build login 2026-03-07 19:48:08 +01:00
Laurent Destailleur
68c8eded6c FIX update COPYRIGHT file to reflect removed libraries 2026-03-07 18:53:20 +01:00
Laurent Destailleur
8688ee5866 FIX option MAIN_USE_TITLE_FOR_USER was on update and not on create 2026-03-07 18:53:09 +01:00
Laurent Destailleur
676f6574f9 FIX update COPYRIGHT file to reflect removed libraries 2026-03-07 18:52:39 +01:00
Laurent Destailleur
2cf8e3809f Removed deprecated file 2026-03-07 18:52:32 +01:00
Laurent Destailleur
bfa1c0e874 Add option MAIN_USE_TITLE_FOR_MEMBER and MAIN_USE_TITLE_FOR_CONTACT 2026-03-07 18:45:06 +01:00
Laurent Destailleur
4d2359e1de FIX option MAIN_USE_TITLE_FOR_USER was on update and not on create 2026-03-07 18:44:45 +01:00
Laurent Destailleur
b9ef1caa46 Debug v24 2026-03-07 18:40:31 +01:00
Laurent Destailleur
7e123903cf CSS 2026-03-07 17:49:33 +01:00
Laurent Destailleur
42900e2c8c css 2026-03-07 15:50:31 +01:00
Laurent Destailleur
6b9d5a59d9 FIX Several trouble with demo docker packages. More secured way to use
the force_password in setup process.
2026-03-07 14:52:53 +01:00
Laurent Destailleur
b31856eaf8 Removed the print 2026-03-07 00:23:04 +01:00
Laurent Destailleur
62c9dbe689 Removed a subrequest if not requested 2026-03-07 00:22:32 +01:00
Laurent Destailleur
bbf9ff2c4c Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-07 00:13:43 +01:00
Laurent Destailleur
d704f6f532 PERF Remove the "Group by" on list of contratcs 2026-03-07 00:13:27 +01:00
Bahfir Abbes
cf11ecd085 Fix:warehouses table name is entrepot which does not hold an fk_soc field, so _checkAccessToResource returns always false and must be disabled before fix. (#25135)
* Fix:warehouses table name is entrepot which does not hold an fk_soc field, so _checkAccessToResource returns always false and must be disabled before fix.

* Fix:warehouses table name is entrepot which does not hold an fk_soc field, so _checkAccessToResource returns always false and must be disabled before fix.

* Fix check params

It is sufficient to fix check parameters to get  it working

* Update api_warehouses.class.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-06 14:06:40 +01:00
Laurent Destailleur
f4109abe56 css 2026-03-06 11:20:35 +01:00
Laurent Destailleur
baa1359021 FIX Bad value when entering price with multicurrency included tax.
Debug option PRODUCT_USE_CUSTOMER_PACKAGING not completely implemented
2026-03-06 01:21:04 +01:00
Braito
6db0b14702 EmailCollector: make generic References usage configurable for isnotanswer (#37397)
Co-authored-by: caminotravelcenter <caminotravelcenter@vl28401.dinaserver.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-06 00:37:07 +01:00
Laurent Destailleur
ddf784c0a7 Automated merge from 23.0 to develop 2026-03-06 00:18:43 +01:00
Laurent Destailleur
4646cf8417 Automated merge from 22.0 to 23.0 by tool pullmerge.sh 2026-03-06 00:18:41 +01:00
Laurent Destailleur
d165a8a557 Automated merge from 21.0 to 22.0 by tool pullmerge.sh 2026-03-06 00:18:38 +01:00
Laurent Destailleur
c723aec696 Automated merge from 20.0 to 21.0 by tool pullmerge.sh 2026-03-06 00:18:35 +01:00
Laurent Destailleur
f23d590749 Merge branch '20.0' of git@github.com:Dolibarr/dolibarr.git into 20.0 2026-03-06 00:18:13 +01:00
Laurent Destailleur
2a14bdc20f Add parameter to start merge from a param on command line 2026-03-06 00:17:16 +01:00
Laurent Destailleur
a3d5c0b30b Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into
develop
2026-03-06 00:12:01 +01:00
Laurent Destailleur
f4278b2c6f Add missing user modif in import 2026-03-06 00:04:06 +01:00
Laurent Destailleur
bc44247cd1 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-05 23:59:06 +01:00
Laurent Destailleur
a7afc55fde CI 2026-03-05 23:57:07 +01:00
Laurent Destailleur
c449bcf9f4 Merge branch '22.0' of git@github.com:Dolibarr/dolibarr.git into 23.0 2026-03-05 23:55:56 +01:00
Laurent Destailleur
dea8c0cf0e FIX #37412 Better fix 2026-03-05 23:36:54 +01:00
Henry
ba9f70dc92 Fix: Sales order add line shows 0 when only multicurrency unit price is filled (#37412)
When adding a product line to a sales order in multicurrency mode, if the user
fills only the 'UP currency' (multicurrency_price_ht) and not the local unit
price, the line was saved with price 0. The form values for multicurrency
price were never applied to pu_ht_devise, and pu_ht was not derived from
pu_ht_devise using the order rate.

- Set pu_ht_devise from multicurrency_price_ht when user entered it
- When local price is empty/zero, derive pu_ht from pu_ht_devise / multicurrency_tx
  (same convention as core/lib/price.lib.php calcul_price_total)
- Same for multicurrency_price_ttc -> pu_ttc_devise and pu_ttc

Made-with: Cursor
2026-03-05 22:37:29 +01:00
sonikf
015c9859cc fix broken label and its tooltip in Social or fiscal taxes table (#37401)
* fix label tooltip

* fix broken label and tooltip

* Update chargesociales.class.php

* Update index.php
2026-03-05 22:31:39 +01:00
Laurent Destailleur
e3a0d9fda4 Fix socid not forced for external users 2026-03-05 22:25:57 +01:00
Noé Cendrier
9b54dcf4af FIX: read_supplier_price filter for stock complement (#37417)
* FIX: read_supplier_price filter for stock complement

complements #37119

* FIX: use existing vars

* Update movement_list.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-05 22:25:53 +01:00
Charlène Benke
be2957816a Add alternative element type for facturerec (#37418)
fail on extrafields showinputfield link
2026-03-05 22:25:48 +01:00
Frédéric FRANCE
2070883cfd fix "capital" for mysoc (#37414)
* fix "capital" for mysoc

* Update societe.class.php

* Update societe.class.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-05 22:25:40 +01:00
Laurent Destailleur
8e1477a6c8 Fix socid not forced for external users 2026-03-05 22:25:04 +01:00
Noé Cendrier
6caa4cd194 FIX: read_supplier_price filter for stock complement (#37417)
* FIX: read_supplier_price filter for stock complement

complements #37119

* FIX: use existing vars

* Update movement_list.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-05 22:23:03 +01:00
Laurent Destailleur
14de760244 Fix install script 2026-03-05 22:14:14 +01:00
Charlène Benke
3ab609726e Add alternative element type for facturerec (#37418)
fail on extrafields showinputfield link
2026-03-05 22:11:25 +01:00
Anthony Berton
0eb3c9e68d FIX - Reload page after check holiday for save param (#37410)
Co-authored-by: Anthony Berton <anthony.berton@bb2a.fr>
2026-03-05 13:46:02 +01:00
Frédéric FRANCE
6a76c6c0ed fix "capital" for mysoc (#37414)
* fix "capital" for mysoc

* Update societe.class.php

* Update societe.class.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-05 13:45:14 +01:00
Laurent Destailleur
d97351526d CI 2026-03-05 13:11:54 +01:00
Laurent Destailleur
35eb6853fd FIX Fetch of lead status 2026-03-05 12:45:25 +01:00
Laurent Destailleur
6ba163880a Fix filter 2026-03-05 12:34:36 +01:00
Laurent Destailleur
03d1fe4e4f Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-05 11:40:20 +01:00
Laurent Destailleur
a3530f0329 CI 2026-03-05 11:40:00 +01:00
hansemschnokeloch
e2a1544dd6 fix typo (#37413) 2026-03-05 11:33:30 +01:00
Laurent Destailleur
73fb69abca Add microtime in datesys 2026-03-05 10:38:52 +01:00
Eric Seigne
e049222877 github.event_name is pull_request_target not pull_request 2026-03-05 09:55:51 +01:00
lvessiller-opendsi
c428e079e3 Merge pull request #37057 from ATM-Consulting/FIX/docPreviewInCommCard
FIX - Fix doc preview in comm card
2026-03-05 09:52:24 +01:00
Laurent Destailleur
d3252f0846 CSS 2026-03-05 01:34:36 +01:00
Laurent Destailleur
0dd56ce3bd Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-05 01:20:31 +01:00
Laurent Destailleur
8d97afc6ed Fix look and feel 2026-03-05 01:20:14 +01:00
Alexandre SPANGARO
e596db45e8 NEW Accountancy - Add confirmation message with link after transaction validation (#37383)
* NEW Accountancy - Add confirmation message with link after transaction validation & hidden redirect constant

* FIX wrong piece_num obtainded

* FIX Phan
2026-03-05 01:17:13 +01:00
Anthony Berton
418f0c2e30 FIX - Added user filtering for displaying leave in the calendar (#37385)
* FIX - Added user filtering for displaying leave in the calendar

* Add restric

* Save param check_holiday

* Copy

---------

Co-authored-by: Anthony Berton <anthony.berton@bb2a.fr>
2026-03-05 01:16:38 +01:00
John BOTELLA
2a6d7739ba UIUX : initNewContent event system (Dolibarr JS context) (#37394)
* Fix doc page

* wip doc

* Add doc

* Factoring
2026-03-05 01:13:38 +01:00
Laurent Destailleur
9587c1302b CSS 2026-03-05 01:12:14 +01:00
John BOTELLA
e71464d907 Qual : Jquery dialog design (#37399)
* Fix dialog design

* factorise css
2026-03-05 00:52:05 +01:00
John BOTELLA
d5fab0b625 WIP : Implement page reload detection to clear JS context language cache (#37402)
* add reload detection to clear cache

* fix clear

* comment

* Fix lang clear cache name

* change clear detection
2026-03-05 00:50:35 +01:00
Laurent Destailleur
500bd9c1f2 FIX height of confirm popup 2026-03-05 00:46:30 +01:00
Laurent Destailleur
ce3609ce83 FIX height of confirm popup 2026-03-05 00:46:08 +01:00
Laurent Destailleur
3dfce11ae8 Merge 2026-03-05 00:42:13 +01:00
Laurent Destailleur
7a8e0ace2e Automated merge from 22.0 to 23.0 by tool pullmerge.sh 2026-03-04 19:50:18 +01:00
Laurent Destailleur
3a15740c73 Automated merge from 21.0 to 22.0 by tool pullmerge.sh 2026-03-04 19:50:16 +01:00
Laurent Destailleur
7bb2713bd9 Merge branch '21.0' of git@github.com:Dolibarr/dolibarr.git into 21.0 2026-03-04 19:49:33 +01:00
Laurent Destailleur
96205a6a09 Fix CSS 2026-03-04 19:46:06 +01:00
Laurent Destailleur
e6f6231c48 WIP Code to set payment request to processed also for credit cards 2026-03-04 18:50:44 +01:00
Laurent Destailleur
9bc6dd0294 Setup of stripe hooks easier to understand 2026-03-04 18:29:15 +01:00
Laurent Destailleur
1650d96091 FIX Pb with import of agendaevents. Date and import id not visible. 2026-03-04 16:52:48 +01:00
Laurent Destailleur
a0b7852d65 Fix missing link to list of events 2026-03-04 14:16:40 +01:00
Laurent Destailleur
cf0ab8d300 Fix label of type 2026-03-04 12:51:56 +01:00
Laurent Destailleur
01a94ed23f NEW Add a separtor between won/lost and other opportunity status in list 2026-03-04 12:11:35 +01:00
Laurent Destailleur
641333e2ce Fix trans 2026-03-04 11:59:32 +01:00
Laurent Destailleur
9c0cdee7c6 Fix css and truncation on creation of event 2026-03-04 11:52:05 +01:00
Laurent Destailleur
5da7a05d01 Add picto 2026-03-04 11:12:30 +01:00
Laurent Destailleur
6b73193846 Fix delete datapolicy batch. Bad sql 2026-03-04 11:03:17 +01:00
Laurent Destailleur
8cb84700e2 css 2026-03-04 03:35:41 +01:00
Laurent Destailleur
23716a217e Default radius to 8 2026-03-04 03:19:31 +01:00
Laurent Destailleur
92f76fcef4 Add info fulldayevent 2026-03-04 03:06:42 +01:00
Laurent Destailleur
9940db94d9 Debug v23 2026-03-04 03:04:31 +01:00
Laurent Destailleur
3e7805acd7 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-04 02:21:26 +01:00
Laurent Destailleur
dc794ff2fb NEW Add method dolOutputDates() 2026-03-04 02:20:56 +01:00
Laurent Destailleur
0e566ac49f Fix restore priority as it is the sorted field 2026-03-04 00:47:40 +01:00
Laurent Destailleur
f852915171 Fix migration 2026-03-04 00:45:33 +01:00
Laurent Destailleur
1f09296717 Debug v23 2026-03-03 21:11:57 +01:00
Laurent Destailleur
8f8c70922e Debug v23 2026-03-03 21:11:38 +01:00
Laurent Destailleur
d072d1fc0a Fix CSS 2026-03-03 21:06:00 +01:00
Laurent Destailleur
cc9b9e06b6 Fix CSS 2026-03-03 21:05:07 +01:00
Laurent Destailleur
aaf64cf696 CSS 2026-03-03 20:58:33 +01:00
Laurent Destailleur
17857b271c Fix regression 2026-03-03 20:30:37 +01:00
Laurent Destailleur
eaf2faebdb Fix regression 2026-03-03 20:24:59 +01:00
Laurent Destailleur
fc19612af0 Fix critical perf trouble making IO scan at each image output 2026-03-03 20:15:57 +01:00
Laurent Destailleur
6a12f5c6d5 Clean code 2026-03-03 20:13:58 +01:00
Laurent Destailleur
4213a06ad8 Fix load of cache currencies is doing ton of SQL access 2026-03-03 20:12:34 +01:00
Laurent Destailleur
4406dbe2c4 Fix critical perf trouble making IO scan at each image output 2026-03-03 19:25:02 +01:00
Laurent Destailleur
6fb8b21471 css 2026-03-03 17:17:01 +01:00
Laurent Destailleur
6f12d1dfcc css 2026-03-03 17:11:18 +01:00
Laurent Destailleur
a4e077a0f7 Merge branch '20.0' of git@github.com:Dolibarr/dolibarr.git into 20.0 2026-03-03 14:24:43 +01:00
Laurent Destailleur
749b45a16d Fix from may be empty 2026-03-03 14:24:04 +01:00
Laurent Destailleur
f1c2ab19af Fix migration in wrong file 2026-03-03 13:13:16 +01:00
Laurent Destailleur
f7151c3cd9 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-03 13:11:53 +01:00
Laurent Destailleur
fffbf75d33 Fix migration 2026-03-03 13:11:03 +01:00
Laurent Destailleur
b9c244ab05 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-03 12:47:59 +01:00
Laurent Destailleur
89d3afe517 Fix error management 2026-03-03 12:47:24 +01:00
Alexandre SPANGARO
497a3ac3a2 NEW Accountancy - Add comparative balance with closure snapshot support (#37382)
* NEW Accountancy - Add comparative balance with closure snapshot support

* Update llx_accounting_balance_snapshot-accounting.key.sql

* Contraint must exist only if link is critical

Contraint must exist only if link is critical. Here table is for history data. if parent is lost, the history jut becoe orhpelin but there is no corrupted data or false result.
in this case, it is better to not have constraintbecause constraint generate a lot of trouble for backup/restoration/migration, etc...
So if we can avoid them, we should.

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-03 00:52:55 +01:00
Laurent Destailleur
b956ea5257 Show localtaxes in tooltip of POS only if you use them 2026-03-02 20:16:28 +01:00
Günter Lukas
293b5a0778 Fix #37388 Change GETPOSTINT to GETPOST for form question (#37390)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-02 20:10:33 +01:00
Laurent Destailleur
61c935b827 Debug v23 2026-03-02 20:07:43 +01:00
Laurent Destailleur
343edad341 Debug v23 2026-03-02 20:06:19 +01:00
Laurent Destailleur
864191e881 NEW Refactoring how the counter of print is done.
Now done synchronously in documents.php or receipt.php
2026-03-02 20:05:50 +01:00
Laurent Destailleur
6ca6462466 Clean code 2026-03-02 15:32:13 +01:00
Laurent Destailleur
5077058df5 Fix CI 2026-03-02 15:23:26 +01:00
Laurent Destailleur
b4d326e30b Add mention on IA report refused. 2026-03-02 15:19:55 +01:00
Laurent Destailleur
8bb8553fbd Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-02 15:15:56 +01:00
Laurent Destailleur
939930dade Fix CI 2026-03-02 15:15:44 +01:00
Pierre Ardoin
fcb11df3b8 NEW : enable supplier external download links and expose substitution variables for supplier documents (#37386)
* Implement external download option for supplier invoices

Added a new option to allow external downloads for supplier invoices, enhancing user flexibility.

* Add option for external download in supplier order

* Enable external download for supplier orders and invoices

Added conditions to enable external download for supplier orders and invoices based on configuration settings.

* Add direct download URLs for supplier orders and invoices

* Update copyright information in supplier_invoice.php

Added copyright notice for Pierre Ardoin for 2026.

* Update supplier_order.php

* Cast thirdparty ID to integer in fetch call

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-02 15:11:10 +01:00
dependabot[bot]
cd74b34708 Bump actions/upload-artifact from 6 to 7 (#37384)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 15:06:23 +01:00
Laurent Destailleur
b73291cbca Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-02 14:59:49 +01:00
Laurent Destailleur
603e1ff877 Fix phan 2026-03-02 14:58:01 +01:00
Laurent Destailleur
eb778141d6 css 2026-03-02 14:09:11 +01:00
Laurent Destailleur
a3fd7e57f9 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-02 12:48:01 +01:00
Laurent Destailleur
38142b31d1 NEW Remove legacy crabe template of invoice 2026-03-02 12:47:45 +01:00
Laurent Destailleur
2454b651d4 Switch historic and setup from crabe to sponge 2026-03-02 12:45:38 +01:00
Laurent Destailleur
e0e77b203c Fix script 2026-03-02 06:05:45 +01:00
minimexat
175e677f2a fix: use client language for recurring invoice PDF generation (#37377)
When generating PDF for recurring invoices via cron, the system was
using the global $langs (system language) instead of the client's
configured default language.

Now uses the thirdparty's default_lang when MAIN_MULTILANGS is enabled,
matching the same behavior used in manual invoice PDF generation.

Fixes Dolibarr/dolibarr#27022
2026-03-02 05:57:28 +01:00
Laurent Destailleur
e85fd10a2b CI 2026-03-02 05:57:02 +01:00
Laurent Destailleur
82d3d3ba92 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-02 05:54:03 +01:00
Laurent Destailleur
57a1f05d49 FIX #37372 FIX #37374 2026-03-02 05:52:05 +01:00
Laurent Destailleur
913d732d7f Trans 2026-03-02 05:20:41 +01:00
Laurent Destailleur
bcedf595f3 Update phpunit tests 2026-03-02 04:49:12 +01:00
Laurent Destailleur
0818f97b96 Doc 2026-03-02 04:37:51 +01:00
Laurent Destailleur
9273cbd919 Clean code 2026-03-02 04:26:03 +01:00
Laurent Destailleur
894c4d8f9e FIX #GHSA-crgg-h74r-2m8r 2026-03-02 04:25:53 +01:00
Laurent Destailleur
d928b816d7 Fix #GHSA-q29x-7fmx-7gq3 2026-03-02 03:58:48 +01:00
Laurent Destailleur
1ea2a2b0fd Clean code 2026-03-02 03:20:11 +01:00
Laurent Destailleur
848c5b9482 NEW MAIN_DISALLOW_UNSECURED_SELECT_INTO_EXTRAFIELDS_FILTER is on by
default
2026-03-02 03:18:46 +01:00
Laurent Destailleur
29aceee503 Complete fix of MAIN_DISALLOW_UNSECURED_SELECT_INTO_EXTRAFIELDS_FILTER 2026-03-02 03:17:24 +01:00
Laurent Destailleur
19f8fe8e63 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-02 03:14:24 +01:00
Laurent Destailleur
2cbde4df2a Report fix of v23 2026-03-02 03:08:41 +01:00
Laurent Destailleur
50eeab9211 Try to avoid false postivie on ia vulnerability analysis. 2026-03-02 03:08:15 +01:00
Laurent Destailleur
7c2e2f4c82 Add a protection against typo error of user. 2026-03-02 02:53:29 +01:00
Laurent Destailleur
115b1e27b0 Try to reduce false postivie of ia scanner 2026-03-02 02:40:06 +01:00
Laurent Destailleur
26c38c52f9 Clean code 2026-03-02 02:15:10 +01:00
Laurent Destailleur
743c22e57c Sec: Fix #GHSA-2mfj-r695-5h9r 2026-03-02 02:04:30 +01:00
Laurent Destailleur
4bfe5d2d4a Protect includes (in case dol_include is used without harcoded filename) 2026-03-02 01:03:34 +01:00
Laurent Destailleur
58852eaa57 Debug v23 2026-03-01 20:01:01 +01:00
Laurent Destailleur
5f99494fed Debug v23 2026-03-01 19:51:08 +01:00
Laurent Destailleur
8976231f35 Fix trans 2026-03-01 19:29:34 +01:00
Laurent Destailleur
1febc39a56 css 2026-03-01 19:19:58 +01:00
Laurent Destailleur
6f2adf3457 Debug v23 2026-03-01 18:52:35 +01:00
Laurent Destailleur
d1a78bf3ac Debug v23 2026-03-01 18:33:28 +01:00
Laurent Destailleur
5756ba2369 css 2026-03-01 18:27:17 +01:00
Laurent Destailleur
6ba0c37ccd css 2026-03-01 18:23:08 +01:00
Laurent Destailleur
78b1259b51 Automated merge from 23.0 to develop 2026-03-01 18:18:08 +01:00
Laurent Destailleur
3e95f2b456 Automated merge from 22.0 to 23.0 by tool pullmerge.sh 2026-03-01 15:47:51 +01:00
Laurent Destailleur
5133603afd Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-01 15:29:07 +01:00
Laurent Destailleur
518f16aea3 Trans 2026-03-01 14:47:27 +01:00
Laurent Destailleur
d44ab2f9b5 Trans 2026-03-01 14:27:46 +01:00
Laurent Destailleur
2e56f40c39 Trans 2026-03-01 14:26:02 +01:00
Laurent Destailleur
f7ba251a35 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-01 14:21:37 +01:00
Laurent Destailleur
91e5b19541 Trans 2026-03-01 14:21:05 +01:00
Laurent Destailleur
454c688403 Trans 2026-03-01 14:20:48 +01:00
Laurent Destailleur
f6241de8a0 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-03-01 14:15:34 +01:00
Laurent Destailleur
5d1c39440d Fix % 2026-03-01 14:13:19 +01:00
Laurent Destailleur
fc13435730 Debug v23 2026-03-01 14:07:41 +01:00
Laurent Destailleur
53ecd7de15 Debug v23 2026-03-01 14:00:16 +01:00
Laurent Destailleur
271af9f8f7 FIX #37171 2026-03-01 12:01:56 +01:00
Laurent Destailleur
ec8898ac50 Add more param 2026-02-28 19:14:00 +01:00
Laurent Destailleur
011eb736b8 Debug v23 2026-02-28 18:47:22 +01:00
Laurent Destailleur
3a84044b6d Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-28 17:52:18 +01:00
Laurent Destailleur
1af0a8db4f Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-28 17:52:08 +01:00
Laurent Destailleur
060d7d7a60 Merge branch '22.0' of git@github.com:Dolibarr/dolibarr.git into 23.0 2026-02-28 17:46:49 +01:00
Charlène Benke
0f777b1cd8 QUAL use shorter label on display lang setting (#37243)
* NEW Add status canceled on direct debit payment

* Link are clicable on page

* Debug v24

* Fix link

* Fix link

* Fix list of status

* Fix status code

* Debug v23

* NEW Add direct debit to close on home thumbs

* Fix link

* Debug v23

* Debug v23

* Clean code

* Debug extrafields chckbxlist v23

* Debug extrafields chckbxlist v23

* Debug v23

* Fix missing lagal requirement

* Better rate showing

* Debug v23 - sql request for list of actioncomm must not use a OR

* Clean code

* Code comment

* Debug v23

* Fix the limit

* css

* Debug v23

* Fix max

* Fix CI

* More debug info

* Increase editor height for note input fields (#37128)

* Edit form must match have order of field similat to create form

* Update card.php (#37108)

@defrance https://github.com/Dolibarr/dolibarr/issues/36767

* Update card.php (#37108)

@defrance https://github.com/Dolibarr/dolibarr/issues/36767

* Fix CI

* Fix CI

* Fix CI

* Correct linked_objects assignment for 'propal' #Wrong sourcetype 'commande' instead of 'propal' when creating recurring invoice template from invoice linked to proposal #37099 @defrance (#37112)

* Correct linked_objects assignment for 'propal'

@defrance Adam Hocini

* Refactor linked objects assignment for proposals

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fixed Bug : email sending test #36741 (#37107)

Fixed Bug : email sending test #36741
@defrance 
-->
Issue fixed
The problem was caused by initializing $result = 0 in core/actions_sendmails.inc.php.
When sending a test email from Setup → Emails, there is no $object to fetch. As a result, $result remained 0 and the code incorrectly triggered ErrorFailedToReadObject.

The fix consists in explicitly setting $result = 1 when no $object is provided (test email / generic email context). This correctly treats the absence of an object as a valid case and restores the ability to send test emails, while keeping $result properly initialized.

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* Fixed Bug : email sending test #36741 (#37107)

Fixed Bug : email sending test #36741
@defrance 
-->
Issue fixed
The problem was caused by initializing $result = 0 in core/actions_sendmails.inc.php.
When sending a test email from Setup → Emails, there is no $object to fetch. As a result, $result remained 0 and the code incorrectly triggered ErrorFailedToReadObject.

The fix consists in explicitly setting $result = 1 when no $object is provided (test email / generic email context). This correctly treats the absence of an object as a valid case and restores the ability to send test emails, while keeping $result properly initialized.

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Correct linked_objects assignment for 'propal' #Wrong sourcetype 'commande' instead of 'propal' when creating recurring invoice template from invoice linked to proposal #37099 @defrance (#37112)

* Correct linked_objects assignment for 'propal'

@defrance Adam Hocini

* Refactor linked objects assignment for proposals

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* Fix CI

* FIX Replace direct $_POST access with GETPOST() in origin/originid recovery paths (#37143)

When a create action fails and rolls back, several list/card pages
restore origin parameters by assigning $_POST values directly to
$_GET. This causes 'undefined array key' warnings on PHP 8.1+ if
the POST data is missing or malformed.

Replace raw $_POST['origin'] with GETPOST('origin', 'alpha') and
raw $_POST['originid'] with GETPOSTINT('originid') which safely
handle missing parameters.

Files fixed:
- htdocs/expedition/list.php
- htdocs/fourn/commande/list.php
- htdocs/commande/list.php
- htdocs/compta/facture/card.php
- htdocs/reception/list.php

These files had TODO-style comments ('Keep this ?', 'Keep GET and
POST here ?') indicating the pattern was already questionable.

Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>

* FIX Add missing isset() check for $_GET['file'] in viewimage.php (#37141)

On line 65, $_GET['file'] is accessed without isset() check inside
the modulepart=='mycompany' condition. This causes an 'undefined array
key' warning on PHP 8.1+ when modulepart is 'mycompany' but no file
parameter is provided.

Note: GETPOST() is intentionally not used here as it is not available
before main.inc.php is loaded (see existing code comment).

Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>

* CI

* Debug v23

* Debug v23

* FIX Show total on multicurrency only if currency are the same on all
lines

* FIX Show total on multicurrency only if currency are the same on all
lines

* Fix regression

* fix avoids undefined array key (#37103)

* NEW Add method formatLogObject to allow logs in 1 line #37135

* Initialize arrayforbutaction before hook (#37149)

* Initialize arrayforbutaction before hook (#37149)

* FIX #36923 Fix undefined array key warnings in opensurvey create_survey.php (#37140)

The session variable initialization logic was inverted: it set variables
to null only when they already existed, instead of initializing them
when they were missing. This caused 'undefined array key' warnings on
PHP 8.1+ when accessing the poll creation form for the first time.

Changes:
- Inverted isset() condition to !isset() to properly initialize missing
  session variables
- Initialize to empty string instead of null
- Added missing session variables (allow_comments, allow_spy, champdatefin)
  to the initialization array
- Added dol_escape_htmltag() for title output (XSS hardening)

Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* Debug v23

* FIX #34342 PHP 8.1 undefined array key warnings in Product::getSellPrice() multiprices (#37144)

When using multiprices (PRODUIT_MULTIPRICES or
PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES), getSellPrice() accesses
$this->multiprices[$level], multiprices_ttc, multiprices_min,
multiprices_min_ttc, and multiprices_base_type arrays using the
buyer's price_level as key without checking if the key exists.

This causes 'undefined array key' warnings on PHP 8.1+ when a
third party has a price_level set but the product doesn't have
prices defined for that specific level.

Similarly, prices_by_qty array accesses for PRODUIT_CUSTOMER_PRICES_BY_QTY
and PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES modes could trigger
warnings when the array index doesn't exist.

Changes:
- Add isset() checks with safe defaults for all multiprices array
  accesses (0 for prices, 'HT' for price_base_type)
- Replace direct prices_by_qty access with !empty() checks

Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* Qual: Update comments from French to English (#37097)

* Qual: Update comments from French to English

# Qual: Update comments from French to English

* Qual: Fix missing initialisations

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Debug v23

* Ensure that if $object->members is not loaded either we do not send m… (#37126)

* Ensure that if $object->members is not loaded either we do not send members to LDAP or we explicitly loads them before according to LDAP configuration

* Fix typo

* Sec: Add param $dolibarr_website_allow_custom_php to block by default
any PHP content in website module.

* Fix CI

* Comment

* Debug v23 - Min price was wrong in multicurrency mode

* Debug v23 - Filter on action type ko

* Debug v23 - Filter on action type ko

* add fk_parent on group group for permission inheritance (#37152)

* FIX PHP 8.1 undefined array key warnings in ProductCombination multiprices loop (#37142)

When PRODUIT_MULTIPRICES is enabled and a product variant's parent has
multiprices configured, the updateChildPrice() method iterates through
all price levels up to PRODUIT_MULTIPRICES_LIMIT. For price levels that
don't have prices defined, accessing $parent->multiprices[$i] and
related arrays directly causes 'undefined array key' warnings on
PHP 8.1+.

Changes:
- Add isset() check before comparing $parent->multiprices[$i]
- Use isset() ternary for $parent->multiprices_min[$i] (default: 0)
- Use !empty() for $parent->prices_by_qty_list[$i] check
- Use isset() ternary for $parent->multiprices_ttc[$i] (default: 0)
- Use isset() ternary for second $parent->multiprices[$i] (default: 0)

Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Add error message

* Avoid ( in sql

* Clean sql

* Fix spellcheck

* Debug v23

* TakePOS hook “AddAction” jamais exécuté (#35961) (#37113)

@defrance 
Adam Hocini

* Update modProduct.class.php (#37087)

Fix: allow import of sell_or_eat_by_mandatory for products

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Update modProduct.class.php (#37087)

Fix: allow import of sell_or_eat_by_mandatory for products

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Clean code

* Fix package debian

* NEW Use the js lib into htdocs/public/includes instead of htdocs/includes

* New LNE Collect of buisness informations (#37084)

* Working LNE ping

* remove GPDA

* Add of other informations

* remove testing var

* fix Ci

* fix Ci

* fix ci

* fix CI

* Fix Ci

* fix Ci

---------

Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>

* CI

* CI

* CI

* CI

* Clean code. File not used.

* CSS

* CSS

* Fix phpunit

* More legal info

* CI

* Fix CI

* Fix: IRPF tax not applied when creating invoice from project times (#37077)

* Remove 'supplier_invoice' from old path array

* Update module path in arrayforoldpath

Sorry Eldy, I was confused. You are absolutely right, it is already corrected.

* Replace localtax2 assignment with get_localtax function


Error when creating an invoice with personal income tax from project times. The rate does not apply

* Refactor localtax1 calculation using get_localtax

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix: IRPF tax not applied when creating invoice from project times (#37077)

* Remove 'supplier_invoice' from old path array

* Update module path in arrayforoldpath

Sorry Eldy, I was confused. You are absolutely right, it is already corrected.

* Replace localtax2 assignment with get_localtax function


Error when creating an invoice with personal income tax from project times. The rate does not apply

* Refactor localtax1 calculation using get_localtax

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Protect module

* Add currency

* CSS

* WIP LNE

* Fix navigation

* cs

* Debug registration process

* Debug setup navigation

* CI

* CI

* Factorize code

* Fix CI

* Fix CI

* Fix CI

* CI

* CI

* CI

* CI

* Disable phan on v23

* CI

* CI

* FIX: missing include for blockedlog lib (#37165)

* Debug CI

* fix ternary always true (#37161)

* fix ternary always true

* Update requests.php

* Update registration.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* FIX: missing include for blockedlog lib (#37165)

* FIX #37134 Use json_encode for IMAP search logging in EmailCollector (#37135)

var_export() produces multiline output that breaks log aggregators
(Loki, Splunk, Elasticsearch) as each line becomes a separate log entry.

Using json_encode() produces single-line structured output that works
correctly with all log aggregation tools.

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Populate syslog with placeholder (#37163)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Doc

* datamodel for user change password next time (#37155)

* Qual: Update phan baseline (#37172)

* Fix typo in file path (#37160)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix phpunit

* Complete call to setStatus so we have a trigy as 4th parameter (help to
fix the #37129)

* FIX Product - Warning on admin (#37157)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* FIX Product - Warning on admin + CSS (#37158)

* FIX Product - Warning on admin

* CSS

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* CI

* CI

* Avoid error if include fails

* More functions in blacklist (even if nw we use the whitelist by default)

* #37166 [SQL] add: email template for ticket admin creation (#37182)

* Replace var_export with new function formatLogObject (#37178)

* FIX BOM - Class product missing, column offset and information recording (form/input overlap) in admin (#37177)

* FIX BOM - Class product missing in admin

* Fix column offset and information recording (form/input overlap)

* ci

* ci

* FIX Intracommreport - Warning & link problem on tab (#37176)

* FIX Bank transfer admin - Warning & save 0 on constant PAYMENTBYBANKTRANSFER_ADDDAYS (#37175)

* Look and feel v24

* Update default time handling in index.php (#37150)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix file path comment in supplier invoice module (#37133)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix file path comment in supplier invoice module (#37133)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* Add template in migration

* fix phpdoc comment (#37184)

* fix phpdoc comment

* fix phpdoc comment

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* Fix phpstan

* Replace var_export by formatLogObject (continued) (#37188)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Debug amount suggested on membership public form

* Issue 36923 Fix session title handling in survey creation (#37105)

* Issue 36923 Fix session title handling in survey creation

* Change input field to use id attribute for title

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* fix phpstan errors blocking action baseline (#37189)

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* refresh baseline

* QUAL Replace var_export() with json_encode() in dol_syslog() calls (#37138)

var_export() produces multiline output that breaks log aggregators
(Loki, Splunk, Elasticsearch, Graylog) as each line becomes a separate
log entry.

json_encode() produces single-line structured output that works correctly
with all log aggregation tools. This pattern is already used elsewhere
in Dolibarr (accountancy, install modules).

Files changed:
- core/class/commoninvoice.class.php (payment intent logging)
- core/class/commonobject.class.php (payment terms logging)
- core/modules/mailings/advthirdparties.modules.php (mailing targets)
- core/modules/oauth/google_oauthcallback.php (userinfo logging)
- core/modules/oauth/generic_oauthcallback.php (userinfo logging)
- public/payment/newpayment.php (GET/POST debug logging)
- public/payment/paymentok.php (payment tag logging)
- public/stripe/ipn.php (Stripe event data logging)
- paypal/lib/paypal.lib.php (PayPal response logging)
- api/index.php (API debug logging)
- stripe/class/stripe.class.php (payment/setup intent logging)

Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* CI

* Check if upload_max_filesize is not empty

* clean code

* Debug v23

* CI

* Fix #33521 VAT total false (#36990)

* - Fix #33521 VAT total false
- Fix some warnings
- Fix : delete $this->vat_rate

* - Fix #33521 VAT total false
- Fix some warnings
- Fix  :delete $this->tva array (replaced by $this->tva_array)

* - Fix #33521 VAT total false
- Fix some warnings
- Fix  :delete $this->tva array (replaced by $this->tva_array)

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

---------

Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW #25829 Automatically send the invoice generated from a template (#36967)

* Update DB

* ADD email template

* Ajout d'une clé de trad

* Ajout des traductions

* Suppression des traductions, sauf en_US

* Add flag auto send

* Modif form + cron auto send

* Suppression auto_send

* correction loopError

* ajout du selected au model de mail

* Prise en compte default model

* Fix pre-commit

* ménage

* precommit

* Correction Phan

* Correction Phan

* Correction, double cal du trigger

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Develop force user change pass userclass (#37174)

* datamodel for user change password next time

* add force_pass_change in user object

* Initialize force_pass_change to 0

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Another step for #37171

* Qual: Partial phan run on PR's, complete run on integration branches (#37186)

* Qual: Partial phan run on PR's, complete on main

# Qual: Partial phan run on PR's, complete on main

The selection is based on the branch name.
To run a complete phan run in a PR, the branch name of the PR must include phan_full.
This can help to fix remaining phan issue before re-integrating to the develop branch.

* qual: Update workflow and pre-commit configurations

- Enable phan workflow by uncommenting the relevant lines
- Update actionlint version to v1.7.10
- Add manual stage to actionlint hook in pre-commit-config.yaml

* qual: Update Phan analysis conditions

The conditions for running Phan analysis have been updated to include an additional check for branches containing 'phan_full'.

* qual: Update Phan workflow

- Replace github.event.ref with github.ref_name
- Add FILE_CHANGE_LIST environment variable for better file handling
- Update file list creation and usage in the workflow

* qual: Update Phan workflow conditions

Fix the branch reference (head_ref in PR, ref_name otherwise)

* Add step for debug information

* Remove debug step

* Fix: Missing initialisations members/new.php

Following a suppression of assignments, the variables disabledphy and disabledmor were undefined.

* fix: Update budget selection dropdown arguments in member creation form

Correct the arguments in the member creation form.

* qual: Add cs2pr to phan workflow

- Add cs2pr to the tools list in the phan workflow
- Change the output mode of phan to checkstyle
- Add a step to add results to PR as Github notices
- Add a step to provide phan log as artifact

* qual: Update Phan workflow to use environment variable for file list

The change fixes the Phan workflow to use the environment variable `$FILE_CHANGED_LIST` to clear the file

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix phan

* Fix phan

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

* Doc

* Qual: Fix ambigious redirect error on Phan workflow (#37200)

# Qual: Fix ambigious redirect error on Phan workflow

Rewrote the shell command that is supposed to suppress a file contents
but is flagged by the environment.

* PHPStan > Update baseline (#37197)

Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>

* Typo fix (#37195)

* css

* qual: Update PHPStan workflow to run on all files in integration (#37207)

The PHPStan workflow has been updated to run on all files in integration branches.

* Fix CI

* CLOSE #37190 ODT Templates for thirdparties - Birthday is returned in epoch format (#37198)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* fix: Remove HTML from accounting menu tooltips in eldy theme (#37203)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Qual: Update spelling (#37199)

* Qual: Update spelling for pre-select variants

# Qual: Update spelling for pre-select variants

In English, preselect is without the hyphen.  Update text and made some translations
related to preselect.

* Qual: Update composant to component and/or adequate translation.

# Qual: Update composant to component and/or adequate translation.

"Composant(s)" was mostly referenced in french file/class comments.
Updated

* Qual: Fix misspellings related to "criteria"

# Qual: Fix misspellings related to "criteria"

* Qual: Fix produt misspellings

# Qual: Fix produt misspellings

Change 'produt' to 'product'.

* Qual: Update French comments with "composants"

#Qual: Update French comments with "composants"

- Translating French comments to English (avoid codespell notice)

* Qual: Fixed typo 'bad practive' to 'bad practice'

# Qual: Fixed typo 'bad practive' to 'bad practice'

* Qual: Update phan.yml to exclude specific files from analysis

- Added file exclusion pattern to match phan configuration
- Added check for empty file list to avoid unnecessary phan execution

* Qual: Update file filtering in phan.yml workflow

The change updates the file filtering process in the phan.yml workflow to correctly redirect the output of the grep command to a temporary file.

* Qual: Ignore $systemfunction always exists

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Error handling methods for commonobject (#37201)

* NEW Can request and force user to change its password (#37196)

* force user to change password : redirect to user card on login

* force user to change password : redirect to user card on login

* redirect to a dedicated page

* bad old idea : self change passwd on user card + edit mode and rights: it makes a hole on security check

* only apply on dolibarr auth mode context

* only on dolibarr auth mode context

* Fix force_pass_change SQL assignment logic

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Doc

* Setup easier to understand between INVOICE_CHECK_POSTERIOR_DATE and
FAC_FORCE_DATE_VALIDATION

* Setup easier to understand between INVOICE_CHECK_POSTERIOR_DATE and
FAC_FORCE_DATE_VALIDATION

* Qual: Ignore exit code from `grep -v` in phan flow (#37213)

# Qual: Ignore exit code from `grep -v` in phan flow

`grep -v` returns 1 when the resulting filtered list is empty and would stop the execution.
This is fixed with `|| true` to have a final exit code that is 0.

* WIP LNE

* Debug v23

* WIP LNE

* FIX Bad header name

* Fix CSP for ping

* Compatibility with multicompany

* Debug

* Fix include

* FIX phpdoc on createFixedAmountDiscount() (#37212)

* FIX phpdoc on createFixedAmountDiscount

* FIX phpdoc

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix in card css modal display (#36569)

* Fix display of cards in a modal

* fix php stan

* fix php stan

* Try a change to force CI

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* select language display AutoDetectLangShort if no showcode

* Add short version for AutoDetectLang

* Add AutoDetectLangShort translation to French

* Update html.formadmin.class.php

* Fix spacing in condition for showcode

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
Co-authored-by: AnthoXic <a.sapet-dev@proton.me>
Co-authored-by: adamhocini <72007447+adamhocini@users.noreply.github.com>
Co-authored-by: DarmonNoah <152853486+DarmonNoah@users.noreply.github.com>
Co-authored-by: minimexat <minimexat@gmail.com>
Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Mathieu G. <118812426+MathieuGDev@users.noreply.github.com>
Co-authored-by: Vanyo <vanyolai@gmail.com>
Co-authored-by: MDW <mdeweerd@users.noreply.github.com>
Co-authored-by: David Beniamine <david.beniamine@tetras-libre.fr>
Co-authored-by: Laurent Destailleur <eldy@users.sourceforge.net>
Co-authored-by: Eric - CAP-REL <1468823+rycks@users.noreply.github.com>
Co-authored-by: Jarvis <94354305+Jarvis-69@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <45882981+Hystepik@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
Co-authored-by: Yamil Esteban Garcia <120027058+developmentOYR@users.noreply.github.com>
Co-authored-by: Noé Cendrier <81741011+altairis-noe@users.noreply.github.com>
Co-authored-by: Frédéric FRANCE <frederic34@users.noreply.github.com>
Co-authored-by: hansemschnokeloch <hansemschnokeloch@users.noreply.github.com>
Co-authored-by: Alexandre SPANGARO <alexandre.spangaro@gmail.com>
Co-authored-by: evarisk-kilyan <kilyan.evarisk@gmail.com>
Co-authored-by: jeremydassaud <49372108+jeremydassaud@users.noreply.github.com>
Co-authored-by: Vincent Maury <artec.vm@arnac.net>
Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
Co-authored-by: Vincent Penel <vincent.penel@atm-consulting.fr>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>
Co-authored-by: Joris Le Blansch <jleblansch@gmail.com>
Co-authored-by: intelliking <tyleradams93226@gmail.com>
Co-authored-by: Benjamin Falière <121813548+BenjaminFlr@users.noreply.github.com>
Co-authored-by: John BOTELLA <68917336+thersane-john@users.noreply.github.com>
2026-02-28 17:44:03 +01:00
Laurent Destailleur
3aaa626fd6 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-28 17:42:49 +01:00
Laurent Destailleur
97f1bbc219 Merge branch '22.0' of git@github.com:Dolibarr/dolibarr.git into 23.0 2026-02-28 17:42:25 +01:00
Laurent Destailleur
2f13c73c20 Trans 2026-02-28 17:39:29 +01:00
Laurent Destailleur
e051c6df2a Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-28 17:25:17 +01:00
Laurent Destailleur
66132ee94b Trans 2026-02-28 17:24:58 +01:00
Laurent Destailleur
c99e556ba8 FIX #36907 Close #37100 2026-02-28 16:50:41 +01:00
Noé Cendrier
c73cc82294 NEW: add hooks in reception card (#37214)
* Comment

* Debug v23 - Min price was wrong in multicurrency mode

* Debug v23 - Filter on action type ko

* Debug v23 - Filter on action type ko

* add fk_parent on group group for permission inheritance (#37152)

* FIX PHP 8.1 undefined array key warnings in ProductCombination multiprices loop (#37142)

When PRODUIT_MULTIPRICES is enabled and a product variant's parent has
multiprices configured, the updateChildPrice() method iterates through
all price levels up to PRODUIT_MULTIPRICES_LIMIT. For price levels that
don't have prices defined, accessing $parent->multiprices[$i] and
related arrays directly causes 'undefined array key' warnings on
PHP 8.1+.

Changes:
- Add isset() check before comparing $parent->multiprices[$i]
- Use isset() ternary for $parent->multiprices_min[$i] (default: 0)
- Use !empty() for $parent->prices_by_qty_list[$i] check
- Use isset() ternary for $parent->multiprices_ttc[$i] (default: 0)
- Use isset() ternary for second $parent->multiprices[$i] (default: 0)

Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Add error message

* Avoid ( in sql

* Clean sql

* Fix spellcheck

* Debug v23

* TakePOS hook “AddAction” jamais exécuté (#35961) (#37113)

@defrance 
Adam Hocini

* Update modProduct.class.php (#37087)

Fix: allow import of sell_or_eat_by_mandatory for products

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Update modProduct.class.php (#37087)

Fix: allow import of sell_or_eat_by_mandatory for products

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Clean code

* Fix package debian

* NEW Use the js lib into htdocs/public/includes instead of htdocs/includes

* New LNE Collect of buisness informations (#37084)

* Working LNE ping

* remove GPDA

* Add of other informations

* remove testing var

* fix Ci

* fix Ci

* fix ci

* fix CI

* Fix Ci

* fix Ci

---------

Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>

* CI

* CI

* CI

* CI

* Clean code. File not used.

* CSS

* CSS

* Fix phpunit

* More legal info

* CI

* Fix CI

* Fix: IRPF tax not applied when creating invoice from project times (#37077)

* Remove 'supplier_invoice' from old path array

* Update module path in arrayforoldpath

Sorry Eldy, I was confused. You are absolutely right, it is already corrected.

* Replace localtax2 assignment with get_localtax function


Error when creating an invoice with personal income tax from project times. The rate does not apply

* Refactor localtax1 calculation using get_localtax

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix: IRPF tax not applied when creating invoice from project times (#37077)

* Remove 'supplier_invoice' from old path array

* Update module path in arrayforoldpath

Sorry Eldy, I was confused. You are absolutely right, it is already corrected.

* Replace localtax2 assignment with get_localtax function


Error when creating an invoice with personal income tax from project times. The rate does not apply

* Refactor localtax1 calculation using get_localtax

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Protect module

* Add currency

* CSS

* WIP LNE

* Fix navigation

* cs

* Debug registration process

* Debug setup navigation

* CI

* CI

* Factorize code

* Fix CI

* Fix CI

* Fix CI

* CI

* CI

* CI

* CI

* Disable phan on v23

* CI

* CI

* FIX: missing include for blockedlog lib (#37165)

* Debug CI

* fix ternary always true (#37161)

* fix ternary always true

* Update requests.php

* Update registration.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* FIX: missing include for blockedlog lib (#37165)

* FIX #37134 Use json_encode for IMAP search logging in EmailCollector (#37135)

var_export() produces multiline output that breaks log aggregators
(Loki, Splunk, Elasticsearch) as each line becomes a separate log entry.

Using json_encode() produces single-line structured output that works
correctly with all log aggregation tools.

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Populate syslog with placeholder (#37163)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Doc

* datamodel for user change password next time (#37155)

* Qual: Update phan baseline (#37172)

* Fix typo in file path (#37160)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix phpunit

* Complete call to setStatus so we have a trigy as 4th parameter (help to
fix the #37129)

* FIX Product - Warning on admin (#37157)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* FIX Product - Warning on admin + CSS (#37158)

* FIX Product - Warning on admin

* CSS

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* CI

* CI

* Avoid error if include fails

* More functions in blacklist (even if nw we use the whitelist by default)

* #37166 [SQL] add: email template for ticket admin creation (#37182)

* Replace var_export with new function formatLogObject (#37178)

* FIX BOM - Class product missing, column offset and information recording (form/input overlap) in admin (#37177)

* FIX BOM - Class product missing in admin

* Fix column offset and information recording (form/input overlap)

* ci

* ci

* FIX Intracommreport - Warning & link problem on tab (#37176)

* FIX Bank transfer admin - Warning & save 0 on constant PAYMENTBYBANKTRANSFER_ADDDAYS (#37175)

* Look and feel v24

* Update default time handling in index.php (#37150)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix file path comment in supplier invoice module (#37133)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix file path comment in supplier invoice module (#37133)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* Add template in migration

* fix phpdoc comment (#37184)

* fix phpdoc comment

* fix phpdoc comment

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* Fix phpstan

* Replace var_export by formatLogObject (continued) (#37188)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Debug amount suggested on membership public form

* Issue 36923 Fix session title handling in survey creation (#37105)

* Issue 36923 Fix session title handling in survey creation

* Change input field to use id attribute for title

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* fix phpstan errors blocking action baseline (#37189)

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* refresh baseline

* QUAL Replace var_export() with json_encode() in dol_syslog() calls (#37138)

var_export() produces multiline output that breaks log aggregators
(Loki, Splunk, Elasticsearch, Graylog) as each line becomes a separate
log entry.

json_encode() produces single-line structured output that works correctly
with all log aggregation tools. This pattern is already used elsewhere
in Dolibarr (accountancy, install modules).

Files changed:
- core/class/commoninvoice.class.php (payment intent logging)
- core/class/commonobject.class.php (payment terms logging)
- core/modules/mailings/advthirdparties.modules.php (mailing targets)
- core/modules/oauth/google_oauthcallback.php (userinfo logging)
- core/modules/oauth/generic_oauthcallback.php (userinfo logging)
- public/payment/newpayment.php (GET/POST debug logging)
- public/payment/paymentok.php (payment tag logging)
- public/stripe/ipn.php (Stripe event data logging)
- paypal/lib/paypal.lib.php (PayPal response logging)
- api/index.php (API debug logging)
- stripe/class/stripe.class.php (payment/setup intent logging)

Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* CI

* Check if upload_max_filesize is not empty

* clean code

* Debug v23

* CI

* Fix #33521 VAT total false (#36990)

* - Fix #33521 VAT total false
- Fix some warnings
- Fix : delete $this->vat_rate

* - Fix #33521 VAT total false
- Fix some warnings
- Fix  :delete $this->tva array (replaced by $this->tva_array)

* - Fix #33521 VAT total false
- Fix some warnings
- Fix  :delete $this->tva array (replaced by $this->tva_array)

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

---------

Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW #25829 Automatically send the invoice generated from a template (#36967)

* Update DB

* ADD email template

* Ajout d'une clé de trad

* Ajout des traductions

* Suppression des traductions, sauf en_US

* Add flag auto send

* Modif form + cron auto send

* Suppression auto_send

* correction loopError

* ajout du selected au model de mail

* Prise en compte default model

* Fix pre-commit

* ménage

* precommit

* Correction Phan

* Correction Phan

* Correction, double cal du trigger

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Develop force user change pass userclass (#37174)

* datamodel for user change password next time

* add force_pass_change in user object

* Initialize force_pass_change to 0

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Another step for #37171

* Qual: Partial phan run on PR's, complete run on integration branches (#37186)

* Qual: Partial phan run on PR's, complete on main

# Qual: Partial phan run on PR's, complete on main

The selection is based on the branch name.
To run a complete phan run in a PR, the branch name of the PR must include phan_full.
This can help to fix remaining phan issue before re-integrating to the develop branch.

* qual: Update workflow and pre-commit configurations

- Enable phan workflow by uncommenting the relevant lines
- Update actionlint version to v1.7.10
- Add manual stage to actionlint hook in pre-commit-config.yaml

* qual: Update Phan analysis conditions

The conditions for running Phan analysis have been updated to include an additional check for branches containing 'phan_full'.

* qual: Update Phan workflow

- Replace github.event.ref with github.ref_name
- Add FILE_CHANGE_LIST environment variable for better file handling
- Update file list creation and usage in the workflow

* qual: Update Phan workflow conditions

Fix the branch reference (head_ref in PR, ref_name otherwise)

* Add step for debug information

* Remove debug step

* Fix: Missing initialisations members/new.php

Following a suppression of assignments, the variables disabledphy and disabledmor were undefined.

* fix: Update budget selection dropdown arguments in member creation form

Correct the arguments in the member creation form.

* qual: Add cs2pr to phan workflow

- Add cs2pr to the tools list in the phan workflow
- Change the output mode of phan to checkstyle
- Add a step to add results to PR as Github notices
- Add a step to provide phan log as artifact

* qual: Update Phan workflow to use environment variable for file list

The change fixes the Phan workflow to use the environment variable `$FILE_CHANGED_LIST` to clear the file

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix phan

* Fix phan

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

* Doc

* Qual: Fix ambigious redirect error on Phan workflow (#37200)

# Qual: Fix ambigious redirect error on Phan workflow

Rewrote the shell command that is supposed to suppress a file contents
but is flagged by the environment.

* PHPStan > Update baseline (#37197)

Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>

* Typo fix (#37195)

* Debug v23 - filters on agenda pages

* css

* css

* css

* qual: Update PHPStan workflow to run on all files in integration (#37207)

The PHPStan workflow has been updated to run on all files in integration branches.

* Fix CI

* Add hook in isEditable()

* Debug v23

* CLOSE #37190 ODT Templates for thirdparties - Birthday is returned in epoch format (#37198)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* fix: Remove HTML from accounting menu tooltips in eldy theme (#37203)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Qual: Update spelling (#37199)

* Qual: Update spelling for pre-select variants

# Qual: Update spelling for pre-select variants

In English, preselect is without the hyphen.  Update text and made some translations
related to preselect.

* Qual: Update composant to component and/or adequate translation.

# Qual: Update composant to component and/or adequate translation.

"Composant(s)" was mostly referenced in french file/class comments.
Updated

* Qual: Fix misspellings related to "criteria"

# Qual: Fix misspellings related to "criteria"

* Qual: Fix produt misspellings

# Qual: Fix produt misspellings

Change 'produt' to 'product'.

* Qual: Update French comments with "composants"

#Qual: Update French comments with "composants"

- Translating French comments to English (avoid codespell notice)

* Qual: Fixed typo 'bad practive' to 'bad practice'

# Qual: Fixed typo 'bad practive' to 'bad practice'

* Qual: Update phan.yml to exclude specific files from analysis

- Added file exclusion pattern to match phan configuration
- Added check for empty file list to avoid unnecessary phan execution

* Qual: Update file filtering in phan.yml workflow

The change updates the file filtering process in the phan.yml workflow to correctly redirect the output of the grep command to a temporary file.

* Qual: Ignore $systemfunction always exists

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Error handling methods for commonobject (#37201)

* Error handling methods for commonobject (#37201)

* NEW Can request and force user to change its password (#37196)

* force user to change password : redirect to user card on login

* force user to change password : redirect to user card on login

* redirect to a dedicated page

* bad old idea : self change passwd on user card + edit mode and rights: it makes a hole on security check

* only apply on dolibarr auth mode context

* only on dolibarr auth mode context

* Fix force_pass_change SQL assignment logic

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Doc

* Setup easier to understand between INVOICE_CHECK_POSTERIOR_DATE and
FAC_FORCE_DATE_VALIDATION

* Setup easier to understand between INVOICE_CHECK_POSTERIOR_DATE and
FAC_FORCE_DATE_VALIDATION

* Prepare 23.0

* Qual: Ignore exit code from `grep -v` in phan flow (#37213)

# Qual: Ignore exit code from `grep -v` in phan flow

`grep -v` returns 1 when the resulting filtered list is empty and would stop the execution.
This is fixed with `|| true` to have a final exit code that is 0.

* WIP LNE

* Debug v23

* Debug v23

* WIP LNE

* FIX Bad header name

* Fix CSP for ping

* Compatibility with multicompany

* Debug

* Fix include

* FIX phpdoc on createFixedAmountDiscount() (#37212)

* FIX phpdoc on createFixedAmountDiscount

* FIX phpdoc

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix in card css modal display (#36569)

* Fix display of cards in a modal

* fix php stan

* fix php stan

* Try a change to force CI

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* More complete data on pings.

* Debug v23

* Debug initdemo

* Debug initdemo

* Debug initdemo

* Debug v23

* Update initdemo

* Update initdemo

* Cleaner GET call

* Add id of perm in url

* Init demo for v24

* Fix translation of "various payment" (not english, not pro) with the
term used in english to mean "Opérations diverses (OD)"

* Sync transifex

* CSS

* Trans

* Doc

* Trans

* Debug v23

* Debug v23

* Trans

* Debug v24

* Debug v23

* Debug v23

* WIP

* Debug v23

* Debug v23

* Debug v24

* Work on LNE

* WIP LNE

* Debug v24

* Doc

* Translation

* Sync transifex

* Debug

* Debug

* Debug

* Responsive

* ADD: hooks in reception card

* Dev control archive integrity

* Debug

* Update supplier_proposal.lang (#37238)

* add company name on dropdown contract list (#37245)

* Clean dump file

* Fix CI

* FIX #37246 Modifying resteapayer calculation for credit note (#37247)

* Add 'type' parameter to completeTabsHead hook (#37235)

* #37257 FIX for piesemicircle (#37258)

* Fix #37227 Fix #37233

* Fix #37227 Fix #37233

* QUAL French comment in English (#37225)

* Edit label of $fields

* QUAL French comment in English

* Bump actions/upload-artifact from 4 to 6 (#37250)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* UI/UX : Transfert feedback css from experimental to core (#37226)

* Transfert feedback css from experimental to core

* fix php phan

* Fix phan

* Fix phan

* Fix phan

* Missing label

* Debug

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
Co-authored-by: Laurent Destailleur <eldy@users.sourceforge.net>
Co-authored-by: Eric - CAP-REL <1468823+rycks@users.noreply.github.com>
Co-authored-by: minimexat <minimexat@gmail.com>
Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: adamhocini <72007447+adamhocini@users.noreply.github.com>
Co-authored-by: Jarvis <94354305+Jarvis-69@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <45882981+Hystepik@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
Co-authored-by: Yamil Esteban Garcia <120027058+developmentOYR@users.noreply.github.com>
Co-authored-by: Frédéric FRANCE <frederic34@users.noreply.github.com>
Co-authored-by: hansemschnokeloch <hansemschnokeloch@users.noreply.github.com>
Co-authored-by: MDW <mdeweerd@users.noreply.github.com>
Co-authored-by: Alexandre SPANGARO <alexandre.spangaro@gmail.com>
Co-authored-by: evarisk-kilyan <kilyan.evarisk@gmail.com>
Co-authored-by: Vanyo <vanyolai@gmail.com>
Co-authored-by: jeremydassaud <49372108+jeremydassaud@users.noreply.github.com>
Co-authored-by: Vincent Maury <artec.vm@arnac.net>
Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
Co-authored-by: Vincent Penel <vincent.penel@atm-consulting.fr>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>
Co-authored-by: Joris Le Blansch <jleblansch@gmail.com>
Co-authored-by: intelliking <tyleradams93226@gmail.com>
Co-authored-by: Benjamin Falière <121813548+BenjaminFlr@users.noreply.github.com>
Co-authored-by: John BOTELLA <68917336+thersane-john@users.noreply.github.com>
Co-authored-by: Charlène Benke <1179011+defrance@users.noreply.github.com>
Co-authored-by: demiton <fabien.cisse@gmail.com>
Co-authored-by: Jyhere <jyhere@gmail.com>
Co-authored-by: Delthair <41671350+Delthair@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-28 16:22:58 +01:00
Braito
965709a2d0 cron: unlock stale PID jobs in runners (#37333)
* cron: unlock stale PID jobs in runners

* Update cron_run_jobs_by_url.php

---------

Co-authored-by: braito4 <1913498+braito4@users.noreply.github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-28 16:22:00 +01:00
Laurent Destailleur
047a91d205 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-28 16:20:25 +01:00
Laurent Destailleur
dbcac5bb33 css 2026-02-28 16:20:02 +01:00
Braito
51ff4766e8 NEW cron: unlock job on unexpected shutdown (#37332)
* cron: unlock job on unexpected shutdown

* Refactor database handler variable in cronjob class

---------

Co-authored-by: braito4 <1913498+braito4@users.noreply.github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-28 15:38:00 +01:00
Sacha Pignot
4810534025 NEW: Add OIDC user auto-creation and fix login bugs (#37314)
* NEW #25922 Add OIDC user auto-creation and fix login bugs

NEW #25922 Add OIDC user auto-creation and fix login bugs

* Fix indentation in OIDC auto-create user admin settings block

* Move require_once statements to file level in openid_connect.lib.php

* Cast login_claim to string before user creation

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-28 15:37:48 +01:00
Laurent Destailleur
3ff4fedab1 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-28 15:36:00 +01:00
Laurent Destailleur
38590d0357 Fix missing import key 2026-02-28 15:35:42 +01:00
Laurent Destailleur
302dcd4aa0 Fix import key not visible 2026-02-28 15:34:30 +01:00
Laurent Destailleur
58909659f0 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-28 15:31:52 +01:00
Laurent Destailleur
5aee214f75 Add missing import_id 2026-02-28 15:31:19 +01:00
Vincent de Grandpré
5ae08ab240 FIX 37359 - pass over thirdpartystatic id to emailcollector hook (#37360)
* pass over loaded third party in thirdpartystatic

* woopsie! keep $thirdpartyid if exists

* Refactor thirdpartyid assignment for clarity

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-28 12:36:53 +01:00
Laurent Destailleur
342abeaa70 CI 2026-02-28 12:24:44 +01:00
Laurent Destailleur
a9c85c78e2 Automated merge from 23.0 to develop 2026-02-28 12:17:11 +01:00
Laurent Destailleur
189b762162 Debug 2026-02-28 11:51:01 +01:00
Laurent Destailleur
e30a4376af Debug 2026-02-28 11:49:49 +01:00
Laurent Destailleur
5322020005 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into
develop
2026-02-28 11:31:19 +01:00
Laurent Destailleur
6a458223b1 Automated merge from 22.0 by tool pullmerge.sh 2026-02-28 11:31:01 +01:00
Laurent Destailleur
ad90eb7a7c Automated merge from 21.0 by tool pullmerge.sh 2026-02-28 11:28:30 +01:00
Laurent Destailleur
c65c7678b1 Automated merge from 20.0 by tool pullmerge.sh 2026-02-28 11:28:27 +01:00
Laurent Destailleur
dc12867364 Automated merge from 19.0 by tool pullmerge.sh 2026-02-28 11:28:25 +01:00
Laurent Destailleur
a1e255a846 Automated merge from 18.0 by tool pullmerge.sh 2026-02-28 11:28:22 +01:00
Laurent Destailleur
3daff81110 Automated merge from 17.0 by tool pullmerge.sh 2026-02-28 11:27:20 +01:00
Laurent Destailleur
dc12e93f6e Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-28 11:22:31 +01:00
Laurent Destailleur
ac5cf30583 Debug 2026-02-28 11:22:16 +01:00
Laurent Destailleur
2112d6a1b5 Add tool to make pull and cascading merges 2026-02-28 11:17:18 +01:00
Laurent Destailleur
a424b368a0 css 2026-02-28 10:58:30 +01:00
minimexat
6e2da14e0e fix(core): validate contact exists before inserting in add_contact() (#37308)
Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-27 17:20:36 +01:00
Alexandre SPANGARO
01ac580441 FIX "Undefined array key" warnings in import_csv.modules.php on $this->cacheconvert access (#37349)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-27 17:17:26 +01:00
Anthony Berton
ce02010542 FIX - Show POS in Order and Invoice list (#37364)
* FIX - Show POS in Order and Invoice list

* Update list.php

* Change $showpos value type from int to string

* Refactor POS fields visibility logic

---------

Co-authored-by: Anthony Berton <anthony.berton@bb2a.fr>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-27 17:15:41 +01:00
minimexat
4ad18d1219 Fix external user 'Access Forbidden' on agenda event card (#37362)
External users (fk_soc set) were blocked from opening agenda events
because GETPOSTINT('socid') returns 0 when no socid param is in the URL
(ActionComm::getNomUrl() only generates ?id=XXXXX).

The condition 0 != $user->socid then triggers accessforbidden() for
every external user.

Fix: fall back to $user->socid when socid is not in the URL, consistent
with the pattern used in all other card pages (contracts, invoices,
orders, etc.).

The existing restrictedArea() check still validates that the event's
fk_soc matches the user's company, so security is preserved.

Fixes #37361

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-27 16:38:01 +01:00
minimexat
2e02df9002 Fix external user 'Access Forbidden' on agenda event card (#37362)
External users (fk_soc set) were blocked from opening agenda events
because GETPOSTINT('socid') returns 0 when no socid param is in the URL
(ActionComm::getNomUrl() only generates ?id=XXXXX).

The condition 0 != $user->socid then triggers accessforbidden() for
every external user.

Fix: fall back to $user->socid when socid is not in the URL, consistent
with the pattern used in all other card pages (contracts, invoices,
orders, etc.).

The existing restrictedArea() check still validates that the event's
fk_soc matches the user's company, so security is preserved.

Fixes #37361

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-27 16:37:29 +01:00
VIAL-GOUTEYRON Quentin
23f9aeded0 NEW Add CSV/XLSX import mode to disable triggers execution (refactoring of import, while preserving legacy SQL compatibility) (#37367)
* Add support for optimized import trigger modes and bulk processing

Introduced new features and optimizations for the import module:
- **Trigger Modes**: Added `strict_line` (default, triggers per row) and `fast_bulk` (triggers aggregate events for bulk imports).
- **Simulation Mode**: Implemented a simulation flag to enable preview of imports without executing triggers.
- **Trigger Cache/Optimization**: Added caching for trigger objects and hook-resolved actions, enhancing performance during legacy SQL imports.
- **Bulk Trigger Stats**: Integrated stats tracking for bulk operations (insert/update) by table, facilitating analytical insight during bulk imports.
- **Extended Trigger Logic**: Enhanced trigger handling:
  - Dynamically resolved actions based on context (table, operation, object class).
  - Introduced hooks for custom import trigger actions.
  - Provided fallback mechanisms for unmapped table-element relationships.
- **Compatibility**: Maintained compatibility with legacy strict-line mode using object-based trigger execution.

These updates improve import performance, scalability, and flexibility while maintaining robust backward compatibility.

* fix

* fix

* refacto

* precommit

* retour

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-27 16:35:36 +01:00
Regis Houssin
cd4833e6de FIX #37339 (#37340)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-27 16:05:47 +01:00
Anthony Berton
e522839aa0 FIX - Twice the check in resource list column (#37342)
* FIX - Twice the check in resource list column

* Update copyright information in list.php

Added copyright notice for 2026.

---------

Co-authored-by: Anthony Berton <anthony.berton@bb2a.fr>
2026-02-27 16:02:24 +01:00
HENRY Florian
cfb6fa89d9 fix: php 8 warning Array To String (#37371)
* fix: php 8 warning Array To String

* Change logging to use json_encode for parameters

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-27 16:00:54 +01:00
Braito
bd3754dc8b emailcollector: set IMAP timeouts in collector (#37334)
Co-authored-by: braito4 <1913498+braito4@users.noreply.github.com>
2026-02-27 15:43:51 +01:00
Charlène Benke
3beb29eb0a remove link elements display on user (not used) (#37338) 2026-02-27 15:42:39 +01:00
github-actions[bot]
392badd940 PHPStan > Update baseline (#37341)
Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>
2026-02-27 15:40:24 +01:00
atm-adrien
d0860270ac FIX : Entity on group ticket insertion (#37370) 2026-02-27 15:40:10 +01:00
Milamber
40414d35a3 Fix issue with amount in letters for French (soixante-dix+ and quatre-vingt dix+) (#37369)
Comments in EN
2026-02-27 15:23:01 +01:00
Milamber
7cfa8be4a5 Fix issue with amount in letters for French (soixante-dix+ and quatre-vingt dix+) (#37369)
Comments in EN
2026-02-27 15:22:47 +01:00
Laurent Destailleur
448f06fe5b ci 2026-02-27 15:09:53 +01:00
Laurent Destailleur
e72b89b117 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-27 15:05:43 +01:00
Laurent Destailleur
90e54882ef ci 2026-02-27 15:05:35 +01:00
Vincent Penel
07e0d4e992 Add endpoints link/unlink contacts on tickets (#37277)
* Avoid ( in sql

* Clean sql

* Fix spellcheck

* Debug v23

* TakePOS hook “AddAction” jamais exécuté (#35961) (#37113)

@defrance 
Adam Hocini

* Update modProduct.class.php (#37087)

Fix: allow import of sell_or_eat_by_mandatory for products

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Update modProduct.class.php (#37087)

Fix: allow import of sell_or_eat_by_mandatory for products

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Clean code

* Fix package debian

* NEW Use the js lib into htdocs/public/includes instead of htdocs/includes

* New LNE Collect of buisness informations (#37084)

* Working LNE ping

* remove GPDA

* Add of other informations

* remove testing var

* fix Ci

* fix Ci

* fix ci

* fix CI

* Fix Ci

* fix Ci

---------

Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>

* CI

* CI

* CI

* CI

* Clean code. File not used.

* CSS

* CSS

* Fix phpunit

* More legal info

* CI

* Fix CI

* Fix: IRPF tax not applied when creating invoice from project times (#37077)

* Remove 'supplier_invoice' from old path array

* Update module path in arrayforoldpath

Sorry Eldy, I was confused. You are absolutely right, it is already corrected.

* Replace localtax2 assignment with get_localtax function


Error when creating an invoice with personal income tax from project times. The rate does not apply

* Refactor localtax1 calculation using get_localtax

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix: IRPF tax not applied when creating invoice from project times (#37077)

* Remove 'supplier_invoice' from old path array

* Update module path in arrayforoldpath

Sorry Eldy, I was confused. You are absolutely right, it is already corrected.

* Replace localtax2 assignment with get_localtax function


Error when creating an invoice with personal income tax from project times. The rate does not apply

* Refactor localtax1 calculation using get_localtax

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Protect module

* Add currency

* CSS

* WIP LNE

* Fix navigation

* cs

* Debug registration process

* Debug setup navigation

* CI

* CI

* Factorize code

* Fix CI

* Fix CI

* Fix CI

* CI

* CI

* CI

* CI

* Disable phan on v23

* CI

* CI

* FIX: missing include for blockedlog lib (#37165)

* Debug CI

* fix ternary always true (#37161)

* fix ternary always true

* Update requests.php

* Update registration.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* FIX: missing include for blockedlog lib (#37165)

* FIX #37134 Use json_encode for IMAP search logging in EmailCollector (#37135)

var_export() produces multiline output that breaks log aggregators
(Loki, Splunk, Elasticsearch) as each line becomes a separate log entry.

Using json_encode() produces single-line structured output that works
correctly with all log aggregation tools.

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Populate syslog with placeholder (#37163)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Doc

* datamodel for user change password next time (#37155)

* Qual: Update phan baseline (#37172)

* Fix typo in file path (#37160)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix phpunit

* Complete call to setStatus so we have a trigy as 4th parameter (help to
fix the #37129)

* FIX Product - Warning on admin (#37157)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* FIX Product - Warning on admin + CSS (#37158)

* FIX Product - Warning on admin

* CSS

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* CI

* CI

* Avoid error if include fails

* More functions in blacklist (even if nw we use the whitelist by default)

* #37166 [SQL] add: email template for ticket admin creation (#37182)

* Replace var_export with new function formatLogObject (#37178)

* FIX BOM - Class product missing, column offset and information recording (form/input overlap) in admin (#37177)

* FIX BOM - Class product missing in admin

* Fix column offset and information recording (form/input overlap)

* ci

* ci

* FIX Intracommreport - Warning & link problem on tab (#37176)

* FIX Bank transfer admin - Warning & save 0 on constant PAYMENTBYBANKTRANSFER_ADDDAYS (#37175)

* Look and feel v24

* Update default time handling in index.php (#37150)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix file path comment in supplier invoice module (#37133)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix file path comment in supplier invoice module (#37133)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* Add template in migration

* fix phpdoc comment (#37184)

* fix phpdoc comment

* fix phpdoc comment

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* CI

* Fix phpstan

* Replace var_export by formatLogObject (continued) (#37188)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Debug amount suggested on membership public form

* Issue 36923 Fix session title handling in survey creation (#37105)

* Issue 36923 Fix session title handling in survey creation

* Change input field to use id attribute for title

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* fix phpstan errors blocking action baseline (#37189)

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* fix phpstan errors blocking action baseline

* refresh baseline

* QUAL Replace var_export() with json_encode() in dol_syslog() calls (#37138)

var_export() produces multiline output that breaks log aggregators
(Loki, Splunk, Elasticsearch, Graylog) as each line becomes a separate
log entry.

json_encode() produces single-line structured output that works correctly
with all log aggregation tools. This pattern is already used elsewhere
in Dolibarr (accountancy, install modules).

Files changed:
- core/class/commoninvoice.class.php (payment intent logging)
- core/class/commonobject.class.php (payment terms logging)
- core/modules/mailings/advthirdparties.modules.php (mailing targets)
- core/modules/oauth/google_oauthcallback.php (userinfo logging)
- core/modules/oauth/generic_oauthcallback.php (userinfo logging)
- public/payment/newpayment.php (GET/POST debug logging)
- public/payment/paymentok.php (payment tag logging)
- public/stripe/ipn.php (Stripe event data logging)
- paypal/lib/paypal.lib.php (PayPal response logging)
- api/index.php (API debug logging)
- stripe/class/stripe.class.php (payment/setup intent logging)

Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix CI

* CI

* Check if upload_max_filesize is not empty

* clean code

* Debug v23

* CI

* Fix #33521 VAT total false (#36990)

* - Fix #33521 VAT total false
- Fix some warnings
- Fix : delete $this->vat_rate

* - Fix #33521 VAT total false
- Fix some warnings
- Fix  :delete $this->tva array (replaced by $this->tva_array)

* - Fix #33521 VAT total false
- Fix some warnings
- Fix  :delete $this->tva array (replaced by $this->tva_array)

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

* Update pdf_octopus.modules.php

---------

Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* NEW #25829 Automatically send the invoice generated from a template (#36967)

* Update DB

* ADD email template

* Ajout d'une clé de trad

* Ajout des traductions

* Suppression des traductions, sauf en_US

* Add flag auto send

* Modif form + cron auto send

* Suppression auto_send

* correction loopError

* ajout du selected au model de mail

* Prise en compte default model

* Fix pre-commit

* ménage

* precommit

* Correction Phan

* Correction Phan

* Correction, double cal du trigger

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Develop force user change pass userclass (#37174)

* datamodel for user change password next time

* add force_pass_change in user object

* Initialize force_pass_change to 0

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Another step for #37171

* Qual: Partial phan run on PR's, complete run on integration branches (#37186)

* Qual: Partial phan run on PR's, complete on main

# Qual: Partial phan run on PR's, complete on main

The selection is based on the branch name.
To run a complete phan run in a PR, the branch name of the PR must include phan_full.
This can help to fix remaining phan issue before re-integrating to the develop branch.

* qual: Update workflow and pre-commit configurations

- Enable phan workflow by uncommenting the relevant lines
- Update actionlint version to v1.7.10
- Add manual stage to actionlint hook in pre-commit-config.yaml

* qual: Update Phan analysis conditions

The conditions for running Phan analysis have been updated to include an additional check for branches containing 'phan_full'.

* qual: Update Phan workflow

- Replace github.event.ref with github.ref_name
- Add FILE_CHANGE_LIST environment variable for better file handling
- Update file list creation and usage in the workflow

* qual: Update Phan workflow conditions

Fix the branch reference (head_ref in PR, ref_name otherwise)

* Add step for debug information

* Remove debug step

* Fix: Missing initialisations members/new.php

Following a suppression of assignments, the variables disabledphy and disabledmor were undefined.

* fix: Update budget selection dropdown arguments in member creation form

Correct the arguments in the member creation form.

* qual: Add cs2pr to phan workflow

- Add cs2pr to the tools list in the phan workflow
- Change the output mode of phan to checkstyle
- Add a step to add results to PR as Github notices
- Add a step to provide phan log as artifact

* qual: Update Phan workflow to use environment variable for file list

The change fixes the Phan workflow to use the environment variable `$FILE_CHANGED_LIST` to clear the file

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix phan

* Fix phan

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

* Doc

* Qual: Fix ambigious redirect error on Phan workflow (#37200)

# Qual: Fix ambigious redirect error on Phan workflow

Rewrote the shell command that is supposed to suppress a file contents
but is flagged by the environment.

* PHPStan > Update baseline (#37197)

Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>

* Typo fix (#37195)

* Debug v23 - filters on agenda pages

* css

* css

* css

* qual: Update PHPStan workflow to run on all files in integration (#37207)

The PHPStan workflow has been updated to run on all files in integration branches.

* Fix CI

* Add hook in isEditable()

* Debug v23

* CLOSE #37190 ODT Templates for thirdparties - Birthday is returned in epoch format (#37198)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* fix: Remove HTML from accounting menu tooltips in eldy theme (#37203)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Qual: Update spelling (#37199)

* Qual: Update spelling for pre-select variants

# Qual: Update spelling for pre-select variants

In English, preselect is without the hyphen.  Update text and made some translations
related to preselect.

* Qual: Update composant to component and/or adequate translation.

# Qual: Update composant to component and/or adequate translation.

"Composant(s)" was mostly referenced in french file/class comments.
Updated

* Qual: Fix misspellings related to "criteria"

# Qual: Fix misspellings related to "criteria"

* Qual: Fix produt misspellings

# Qual: Fix produt misspellings

Change 'produt' to 'product'.

* Qual: Update French comments with "composants"

#Qual: Update French comments with "composants"

- Translating French comments to English (avoid codespell notice)

* Qual: Fixed typo 'bad practive' to 'bad practice'

# Qual: Fixed typo 'bad practive' to 'bad practice'

* Qual: Update phan.yml to exclude specific files from analysis

- Added file exclusion pattern to match phan configuration
- Added check for empty file list to avoid unnecessary phan execution

* Qual: Update file filtering in phan.yml workflow

The change updates the file filtering process in the phan.yml workflow to correctly redirect the output of the grep command to a temporary file.

* Qual: Ignore $systemfunction always exists

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Error handling methods for commonobject (#37201)

* Error handling methods for commonobject (#37201)

* NEW Can request and force user to change its password (#37196)

* force user to change password : redirect to user card on login

* force user to change password : redirect to user card on login

* redirect to a dedicated page

* bad old idea : self change passwd on user card + edit mode and rights: it makes a hole on security check

* only apply on dolibarr auth mode context

* only on dolibarr auth mode context

* Fix force_pass_change SQL assignment logic

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Doc

* Setup easier to understand between INVOICE_CHECK_POSTERIOR_DATE and
FAC_FORCE_DATE_VALIDATION

* Setup easier to understand between INVOICE_CHECK_POSTERIOR_DATE and
FAC_FORCE_DATE_VALIDATION

* Prepare 23.0

* Qual: Ignore exit code from `grep -v` in phan flow (#37213)

# Qual: Ignore exit code from `grep -v` in phan flow

`grep -v` returns 1 when the resulting filtered list is empty and would stop the execution.
This is fixed with `|| true` to have a final exit code that is 0.

* WIP LNE

* Debug v23

* Debug v23

* WIP LNE

* FIX Bad header name

* Fix CSP for ping

* Compatibility with multicompany

* Debug

* Fix include

* FIX phpdoc on createFixedAmountDiscount() (#37212)

* FIX phpdoc on createFixedAmountDiscount

* FIX phpdoc

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* Fix in card css modal display (#36569)

* Fix display of cards in a modal

* fix php stan

* fix php stan

* Try a change to force CI

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* More complete data on pings.

* Debug v23

* Debug initdemo

* Debug initdemo

* Debug initdemo

* Debug v23

* Update initdemo

* Update initdemo

* Cleaner GET call

* Add id of perm in url

* Init demo for v24

* Fix translation of "various payment" (not english, not pro) with the
term used in english to mean "Opérations diverses (OD)"

* Sync transifex

* CSS

* Trans

* Doc

* Trans

* Debug v23

* Debug v23

* Trans

* Debug v24

* Debug v23

* Debug v23

* WIP

* Debug v23

* Debug v23

* Debug v24

* Work on LNE

* WIP LNE

* Debug v24

* Doc

* Translation

* Sync transifex

* Debug

* Debug

* Debug

* Responsive

* Dev control archive integrity

* Debug

* Update supplier_proposal.lang (#37238)

* add company name on dropdown contract list (#37245)

* Clean dump file

* Fix CI

* FIX #37246 Modifying resteapayer calculation for credit note (#37247)

* Add 'type' parameter to completeTabsHead hook (#37235)

* #37257 FIX for piesemicircle (#37258)

* Fix #37227 Fix #37233

* Fix #37227 Fix #37233

* QUAL French comment in English (#37225)

* Edit label of $fields

* QUAL French comment in English

* Bump actions/upload-artifact from 4 to 6 (#37250)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* UI/UX : Transfert feedback css from experimental to core (#37226)

* Transfert feedback css from experimental to core

* fix php phan

* Fix phan

* Fix phan

* Fix phan

* Missing label

* Debug

* Add method to chech if invoice can be replaced

* Fix return

* Debug v24

* Fix CI

* CI

* CI

* CI

* Fix CI

* Add endpoints link/unlink contacts on tickets

* Check source

* Typage

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
Co-authored-by: adamhocini <72007447+adamhocini@users.noreply.github.com>
Co-authored-by: Jarvis <94354305+Jarvis-69@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <45882981+Hystepik@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
Co-authored-by: Laurent Destailleur <eldy@users.sourceforge.net>
Co-authored-by: Yamil Esteban Garcia <120027058+developmentOYR@users.noreply.github.com>
Co-authored-by: Noé Cendrier <81741011+altairis-noe@users.noreply.github.com>
Co-authored-by: Frédéric FRANCE <frederic34@users.noreply.github.com>
Co-authored-by: minimexat <minimexat@gmail.com>
Co-authored-by: hansemschnokeloch <hansemschnokeloch@users.noreply.github.com>
Co-authored-by: Eric - CAP-REL <1468823+rycks@users.noreply.github.com>
Co-authored-by: MDW <mdeweerd@users.noreply.github.com>
Co-authored-by: Alexandre SPANGARO <alexandre.spangaro@gmail.com>
Co-authored-by: evarisk-kilyan <kilyan.evarisk@gmail.com>
Co-authored-by: Vanyo <vanyolai@gmail.com>
Co-authored-by: jeremydassaud <49372108+jeremydassaud@users.noreply.github.com>
Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Vincent Maury <artec.vm@arnac.net>
Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>
Co-authored-by: Joris Le Blansch <jleblansch@gmail.com>
Co-authored-by: intelliking <tyleradams93226@gmail.com>
Co-authored-by: Benjamin Falière <121813548+BenjaminFlr@users.noreply.github.com>
Co-authored-by: John BOTELLA <68917336+thersane-john@users.noreply.github.com>
Co-authored-by: Charlène Benke <1179011+defrance@users.noreply.github.com>
Co-authored-by: demiton <fabien.cisse@gmail.com>
Co-authored-by: Jyhere <jyhere@gmail.com>
Co-authored-by: Delthair <41671350+Delthair@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 14:49:57 +01:00
Anthony Berton
c326ab68f0 FIX - If a specific pricing system is applied, the price type is not specified (#37348)
* FIX - If a specific pricing system is applied, the price type is not specified

* Add 2026 copyright for Anthony Berton

Added copyright notice for Anthony Berton for 2026.

---------

Co-authored-by: Anthony Berton <anthony.berton@bb2a.fr>
2026-02-27 14:49:14 +01:00
Laurent Destailleur
023061e370 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into 23.0 2026-02-27 14:46:53 +01:00
Laurent Destailleur
fce2f218f5 Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-27 14:46:41 +01:00
John BOTELLA
6e73f627d6 Fix missing trans no entities (#37344) 2026-02-27 14:45:59 +01:00
Vincent de Grandpré
4817b4e235 fix for #37357 (#37358)
* fix for #37357

* copyright

* phpstan
2026-02-27 14:45:14 +01:00
Laurent Destailleur
37cc867537 CI 2026-02-27 14:43:58 +01:00
Alexandre SPANGARO
4349cc83e4 FIX Variant - CSS (#37337) 2026-02-27 14:37:58 +01:00
Frédéric FRANCE
6282af73bf do not keep date_creation when cloning product (#37351) 2026-02-27 14:37:20 +01:00
Lucas Marcouiller
f09e8916c3 Fix bad rounding when spliting discounts (#37354)
* Fix bad rounding when spliting discounts

* fix ci

---------

Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
2026-02-27 14:36:28 +01:00
Laurent Destailleur
7acb0e0d4d Merge branch '22.0' of git@github.com:Dolibarr/dolibarr.git into 23.0 2026-02-27 14:36:22 +01:00
Laurent Destailleur
77ba059635 Merge branch '21.0' of git@github.com:Dolibarr/dolibarr.git into 22.0 2026-02-27 14:35:48 +01:00
Laurent Destailleur
1642d20d46 Merge branch '21.0' of git@github.com:Dolibarr/dolibarr.git into 21.0 2026-02-27 14:33:58 +01:00
Laurent Destailleur
ec97514bdb Merge branch '20.0' of git@github.com:Dolibarr/dolibarr.git into 21.0 2026-02-27 14:33:43 +01:00
Laurent Destailleur
e1dedd638b Disable beautysh. Overload of work with this is 100x higher than the
gain. Disable definitely.
2026-02-27 14:32:47 +01:00
atm-jonathan
82ed704060 FIX duplicate str_replace() (#37355)
* duplcate str_replace()

* update addline and updateline logic TVA NPR
2026-02-27 14:30:06 +01:00
Laurent Destailleur
c2a3ea6f12 Fix regression 2026-02-27 14:29:35 +01:00
atm-lucas
2b0696080c Verif odt template extension (#37366) 2026-02-27 14:24:52 +01:00
Laurent Destailleur
edbd58b102 Doc 2026-02-27 14:19:50 +01:00
Laurent Destailleur
ec2f4146c3 FIX Doc and log 2026-02-27 13:51:45 +01:00
Laurent Destailleur
cd1c327173 FIX Doc and log 2026-02-27 13:49:09 +01:00
Laurent Destailleur
b2b079fc5b Trans 2026-02-27 12:23:51 +01:00
Laurent Destailleur
a25a60ad55 NEW Add lifetime amount on the list of blockedlog 2026-02-27 12:08:16 +01:00
Laurent Destailleur
4108515fc6 NEW Add lifetime amount on the list of blockedlog 2026-02-27 11:36:41 +01:00
Laurent Destailleur
e30cffc425 CSS 2026-02-26 22:46:02 +01:00
Laurent Destailleur
2e7a691371 Doc 2026-02-26 18:44:06 +01:00
Laurent Destailleur
15dac27fac Sync transifex 2026-02-26 16:31:57 +01:00
Laurent Destailleur
b7e2749312 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into develop 2026-02-26 15:47:36 +01:00
Laurent Destailleur
3c0c7af552 Debug v23 - Update of price in proposals 2026-02-26 15:47:15 +01:00
Laurent Destailleur
6b0e63f9e5 CSS 2026-02-26 15:33:49 +01:00
Laurent Destailleur
5879acbedf Fix label of button 2026-02-26 14:45:34 +01:00
Laurent Destailleur
000c855038 Debug v23 2026-02-26 14:42:38 +01:00
ATM-Lucas
c5b4d149b5 delete more & 2026-02-26 14:11:00 +01:00
Laurent Destailleur
0b920df95a Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into 23.0 2026-02-26 11:55:45 +01:00
Laurent Destailleur
57451d9970 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into 23.0 2026-02-26 11:51:50 +01:00
Laurent Destailleur
486f5ad65e FIX #37350 2026-02-26 11:51:19 +01:00
Alexandre SPANGARO
c1dcded268 FIX Warning - Remove two declaration of DOL_VERSION (#37350) 2026-02-26 11:49:37 +01:00
ATM-Lucas
ba8ab407fe delete & 2026-02-26 10:10:09 +01:00
Laurent Destailleur
9cd7af2648 Trans 2026-02-26 09:23:51 +01:00
Laurent Destailleur
f92d77cea5 Standardize code for cash control closing 2026-02-26 09:17:40 +01:00
Laurent Destailleur
8540a02561 Fix data saved with generic oauth 2026-02-25 22:50:39 +01:00
Laurent Destailleur
2cc352cacd Fix data saved with generic oauth 2026-02-25 22:46:27 +01:00
Laurent Destailleur
5fdf0f8094 Fix error management in oauth handlers 2026-02-25 21:49:13 +01:00
Laurent Destailleur
2796ffa9af Fix error management in oauth handlers 2026-02-25 21:48:27 +01:00
Laurent Destailleur
bde08e0868 Merge v23 2026-02-25 19:18:10 +01:00
Laurent Destailleur
9459d72590 Fix packaging 2026-02-25 19:16:10 +01:00
Laurent Destailleur
b93a2cbf04 Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into 23.0 2026-02-25 19:07:38 +01:00
Laurent Destailleur
82af80d411 Prepare 23.0.0 2026-02-25 19:07:24 +01:00
Alexandre SPANGARO
f85c6d6495 FIX #37323 Accountancy closure page - Problem with GETPOSTINT cannot read checkbox values properly (#37325) 2026-02-24 17:49:26 +01:00
ATM-NicolasV
11e25b25fb New/add support microsoft exchange oauth (#37329)
* backport de la liste des ressources v23

* fix oauth api conexion

* fix oauth api conexion

* fix oauth api conexion

* Fix CI

* fix: in some case on create invoice when create PDF the lang is not correcly defined because ->thirdparty is not set (#37287)

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>

* add microsoft exchange oauth

* add microsoft exchange oauth

* add microsoft exchange oauth

* add microsoft exchange oauth

* add microsoft exchange oauth

* Revert unintended changes to resource list

* Tidy OAuth logging and consent prompt

* Fix PHPCS blank line warnings

---------

Co-authored-by: ATM-Lucas <lucas.mantegari@atm-consulting.fr>
Co-authored-by: VIAL-GOUTEYRON Quentin <quentin.vial-gouteyron@atm-consulting.fr>
Co-authored-by: Laurent Destailleur <eldy@users.sourceforge.net>
Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-24 17:48:29 +01:00
HENRY Florian
125caf1d4b fix: filter on shipping list on mass action (#37281)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-23 20:06:13 +01:00
Laurent Destailleur
7b2761790f Debug v23 2026-02-23 19:49:45 +01:00
Laurent Destailleur
0a5b75582a Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into 23.0 2026-02-23 02:19:47 +01:00
Laurent Destailleur
65490c00c9 CI 2026-02-23 02:19:30 +01:00
HENRY Florian
d6f4bf8f03 fix: in some case on create invoice when create PDF the lang is not correcly defined because ->thirdparty is not set (#37287)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-02-23 02:10:32 +01:00
Alexandre SPANGARO
a9aea129e7 Warning in emailing module with partnership module (#37270) 2026-02-23 01:42:16 +01:00
Laurent Destailleur
16b2735ca4 Fix CI 2026-02-23 01:33:35 +01:00
ATM-Lucas
eaee0a952d Fix doc preview in comm card 2026-02-02 12:05:11 +01:00
1640 changed files with 37438 additions and 20656 deletions

View File

@@ -11,7 +11,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.RELEASE_DOCKER_ID }}
private-key: ${{ secrets.RELEASE_DOCKER_SECRET }}

View File

@@ -90,7 +90,7 @@ jobs:
cs2pr --prepend-filename --prepend-source --notices-as-warnings _phan.xml
- name: Provide phan log as artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: ${{ always() }}
with:
name: phan-srcrt

View File

@@ -122,7 +122,7 @@ jobs:
key: phpstan-cache-${{ matrix.php-version }}-${{ env.CACHE_KEY_PART }}-${{
github.run_id }}
- name: Provide phpstan log as artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: ${{ always() }}
with:
name: phpstan-srcrt

158
.github/workflows/pr-18.yml vendored Normal file
View File

@@ -0,0 +1,158 @@
# Action to prepare the GitHub Action
# Prerequisites (create in the organization / repo):
# - Secret: PR18_SECRET_KEY (private key generated for the GitHub App)
# - Variable: PR18_APP_ID (GitHub App ID)
#
# Behavior:
# - On pull_request_target (opened, synchronize, reopened) against branch 18.0:
# - Generate a GitHub App token
# - Add the label "Issue for v18 maintenance Team" to the PR (error-tolerant)
# - Assign the reviewers listed below, excluding the PR author
# -> attempt per reviewer (if a reviewer fails, log and continue)
# -> the step fails only if no reviewer could be added
# - On push to 18.0: workflow runs but PR-specific actions are skipped
#
name: Set reviewer and label for v18
on:
pull_request_target:
types: [opened, synchronize, reopened]
branches:
- "18.0"
push:
branches:
- "18.0"
permissions:
pull-requests: write
issues: write
jobs:
assign-and-label-v18:
runs-on: ubuntu-latest
# Mergeers / reviewers list: edit here as needed (comma-separated)
env:
REVIEWERS: "lvessiller-opendsi,rycks"
# Label name to apply
V18_LABEL: "Issue for v18 maintenance Team"
steps:
# 1) Generate a GitHub App token (via actions/create-github-app-token)
- name: Generate GitHub App token
id: generate-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.PR18_APP_ID }}
private-key: ${{ secrets.PR18_SECRET_KEY }}
# 2) Checkout repository (useful if repo content is needed later)
- name: Checkout repository
uses: actions/checkout@v6
# Debug information (useful for diagnostics)
- name: Debug info
run: |
echo "Event: $GITHUB_EVENT_NAME"
echo "Ref: $GITHUB_REF"
echo "Run id: $GITHUB_RUN_ID"
echo "Reviewers configured: $REVIEWERS"
# 3) Add the label to the PR (PR events only)
# -> tolerant to errors: log on failure but do not fail the job
- name: Add label to PR (pull_request events only)
if: ${{ github.event_name == 'pull_request_target' }}
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
V18_LABEL: ${{ env.V18_LABEL }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
echo "Adding label '$V18_LABEL' to PR #${PR_NUMBER}"
response=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/labels" \
-d "{\"labels\": [\"${V18_LABEL}\"]}")
if [ "$response" -eq 200 ]; then
echo "Label added successfully."
else
echo "Warning: failed to add label '$V18_LABEL' to PR #${PR_NUMBER} (HTTP $response). Continuing."
fi
# 4) Compute final reviewers list excluding the PR author
- name: Compute reviewers (exclude PR author)
if: ${{ github.event_name == 'pull_request_target' }}
id: set-reviewers
run: |
set -euo pipefail
IFS=',' read -ra ALL_REVIEWERS <<< "${REVIEWERS}"
AUTHOR="${{ github.event.pull_request.user.login }}"
FINAL=()
for r in "${ALL_REVIEWERS[@]}"; do
r_trimmed="$(echo "$r" | xargs)"
if [ -z "$r_trimmed" ]; then
continue
fi
if [ "$r_trimmed" != "$AUTHOR" ]; then
FINAL+=("$r_trimmed")
fi
done
if [ ${#FINAL[@]} -eq 0 ]; then
echo "reviewers=" >> $GITHUB_OUTPUT
else
reviewers_csv="$(IFS=, ; echo "${FINAL[*]}")"
echo "reviewers=${reviewers_csv}" >> $GITHUB_OUTPUT
fi
echo "author=$AUTHOR" >> $GITHUB_OUTPUT
echo "Computed reviewers: ${reviewers_csv:-<none>}"
# 5) Assign reviewers one-by-one with fine-grained error handling
# - try each reviewer, track successes and failures
# - fail the step only if none could be added
# - succeed if at least one was added (but log failures)
- name: Assign reviewers on PR (per-reviewer, tolerant errors)
if: ${{ github.event_name == 'pull_request_target' && steps.set-reviewers.outputs.reviewers != '' }}
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REVIEWERS_CSV: ${{ steps.set-reviewers.outputs.reviewers }}
REPO: ${{ github.repository }}
run: |
set -uo pipefail
IFS=',' read -ra TO_ADD <<< "${REVIEWERS_CSV}"
SUCCESS=0
FAILED=()
for r in "${TO_ADD[@]}"; do
r_trimmed="$(echo "$r" | xargs)"
if [ -z "$r_trimmed" ]; then
continue
fi
echo "Attempting to add reviewer: $r_trimmed"
response=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/requested_reviewers" \
-d "{\"reviewers\": [\"${r_trimmed}\"]}")
if [ "$response" -eq 201 ] || [ "$response" -eq 200 ]; then
echo "Added reviewer: $r_trimmed"
SUCCESS=$((SUCCESS+1))
else
echo "Warning: failed to add reviewer: $r_trimmed (HTTP $response)"
FAILED+=("$r_trimmed")
fi
done
if [ $SUCCESS -eq 0 ]; then
echo "Error: none of the configured reviewers could be added: ${FAILED[*]:-<none>}"
exit 1
else
echo "Reviewers added: ${SUCCESS}. Failed to add: ${FAILED[*]:-none}"
fi
# 6) Push event notice (no PR-specific actions performed)
- name: Push event notice
if: ${{ github.event_name == 'push' }}
run: |
echo "Triggered by push on branch 18.0. No PR-specific actions performed."

View File

@@ -169,7 +169,7 @@ jobs:
key: pre-commit-4|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }}
# Upload result log files of precommit into the Artifact shared store
- name: Provide log as artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: ${{ ! cancelled() }}
with:
name: precommit-logs

View File

@@ -173,7 +173,7 @@ jobs:
in: ${{ env.PHPUNIT_LOG }}
- name: Provide dolibarr and phpunit logs as artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: ${{ ! cancelled() }}
with:
name: win-ci-logs

View File

@@ -157,6 +157,7 @@ install:
#sudo apt install composer
composer -V
sudo composer -n config -g vendor-dir htdocs/includes
sudo composer -n config audit.block-insecure false
sudo chmod -R a+rwx /home/travis/.config/composer
echo

View File

@@ -12,11 +12,16 @@ These dependencies are listed in the bottom of this file.
The Dolibarr images resources (available in the doc directory) is distributed under the Creative Commons Attribution 4.0 International license (CC BY 4.0).
Trademark usage
---------------
The name Dolibarr is a trademark initially registered by Laurent Destailleur and ceased to the Dolibarr foundation. You can use the name Dolibarr
for your own need as long as you follow the rules defined on the page https://wiki.dolibarr.org/index.php/Rules_to_use_the_brand_name_%22Dolibarr%22
The use of the name DoliStore is also restricted to the same rules defined on https://wiki.dolibarr.org/index.php/Rules_to_use_the_brand_name_%22Dolibarr%22
License of third party libraries
--------------------------------
Licence of dependencies of third-party components used by Dolibarr (all compatible with the Licence of Dolibarr):
@@ -39,7 +44,6 @@ PHP-Iban 4.1.1 LGPL-3+ Yes
PHP-Imap 2.7.2 MIT License Yes Library to use IMAP with OAuth
PHPoAuthLib 0.8.2 MIT License Yes Library to provide oauth1 and oauth2 to different service
PHPPrintIPP 1.3 GPL-2+ Yes Library to send print IPP requests
PrestaShop-WS-Lib 94feb5f OSL-3.0 No Library providing API client for Prestashop.
PSR/Logs 1.0 MIT License Yes Library for logs (used by DebugBar)
PSR/simple-cache ? MIT License Yes Library for cache (used by PHPSpreadSheet)
Restler 3.1.1 LGPL-3+ Yes Library to develop REST Web services (+ swagger-ui js lib into dir explorer)
@@ -67,13 +71,9 @@ jQuery jPicker 1.1.6 GPL and MIT License Yes
jQuery jqueryFileTree 1.0.1 GPL and MIT License Yes JS library for filetree
jQuery jquerytreeview 1.4.1 MIT License Yes JS library for filetree
jQuery TableDnD 0.6 GPL and MIT License Yes JS library plugin TableDnD (to reorder table rows)
jQuery Timepicker 1.1.0 GPL and MIT License Yes JS library Timepicker addon for Datepicker
jsGanttImproved 2.8.10 BSD License Yes JS library (to build Gantt reports)
SwaggerUI 2.2.10 GPL-2+ Yes JS library to offer the REST API explorer
Image libraries:
Octicons 8.1 MIT Yes
Font libraries:
Fontawesome 5.13 Font Awesome Free Licence Yes

View File

@@ -2,6 +2,28 @@
English Dolibarr ChangeLog
--------------------------------------------------------------
***** ChangeLog for 23.0.1 compared to 23.0.0 *****
FIX: Removed SQL error on install process.
FIX: #37412 Better fix
FIX: Added user filtering for displaying leave in the calendar (#37385)
FIX: Bad value when entering price with multicurrency included tax.
FIX: Better compatibility for module using condition object-> in tabs
FIX: CSS
FIX: Fetch of lead status
FIX: Fix doc preview in comm card
FIX: height of confirm popup
FIX: option MAIN_USE_TITLE_FOR_USER was on update and not on create
FIX: Order API: delete order returns wrong http response in case order could not be deleted (#37472)
FIX: Pb with import of agendaevents. Date and import id not visible.
FIX: read_supplier_price filter for stock complement (#37417)
FIX: Reload page after check holiday for save param (#37410)
FIX: Several trouble with demo docker packages. More secured way to use install.force.php file
FIX: translation on multiselect with rich label - Fix CSS public ticket
FIX: update COPYRIGHT file to reflect removed libraries in v23.0.0
***** ChangeLog for 23.0.0 compared to 22.0 *****
For users:
@@ -205,18 +227,18 @@ The following changes may create regressions for some external modules, but were
* Because of the new TRIGGER_PREFIX property, the triggers SUPPLIER_PRODUCT_BUYPRICE_XXX are renamed into PRODUCT_BUYPRICE_XXX.
* Function img_pdf() has been removed. Replace it with img_picto('', 'pdf') if you were using it.
* The method run_trigger() was deprecated 10+ years ago in favor of runTrigger(). It has been removed. Change your trigger file if you still use it.
* Property ->picto of module descriptors must contains the image extension if it is not a font awesome tag. Example: $this->picto="mymoduleimg.png";
* Property ->picto of module descriptors or menu entries must contains the image extension if it is not a font awesome tag. Example: $this->picto="mymoduleimg.png";
* Stock movement API GET method output variable names has been harmonized with POST input parameter names
* Concatenation into computed property of extrafields is off by default. You can enable it from conf.php file by adding dol_concat to list of allowed function in $dolibarr_main_restrict_eval_methods,
For example: $dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,dol_concat,preg_match';
* Old variable $obj and $object are no more allowed into on the fly evaluated strings like computed or conditions on extrafields. Use $objectoffield to get current object
Also if you were using temporary variables int a computed extrafields, the nameot temporary variable must match $var123.
Also if you were using temporary variables in a computed extrafield, the name of the temporary variable must match $var123.
* The hidden constant MAIN_ALLOW_UNSECURED_SPECIAL_CHARS_IN_DOL_EVAL in database has been replaced with the
variable $dolibarr_main_allow_unsecured_special_chars_in_dol_eval into file conf/conf.php
* $conf use into "computed formulae" of etrafields is now deprecated (not yet forbidden). You can replace use of $conf->currency by the new method getCurrency() and $conf->global->xxx by getDolGlobalString('xxx').
* $conf use into "computed formulae" of extrafields is now deprecated (not yet forbidden). You can replace use of $conf->currency by the new method getCurrency() and $conf->global->xxx by getDolGlobalString('xxx').
* $user->rights->module->perms is also deprecated. You can use $user->hasRight() instead.
* The API endpoint /proposals/{id}/contact/{contactid}/{type}/{source} is now {id}/contact/{contactid}/{type} to match same behaviour than order and invoices.
* The API endpoint /interventions/{id}/reopen hase been removed in favor of /interventions/{id}/settodraft
* The API endpoint /interventions/{id}/reopen has been removed in favor of /interventions/{id}/settodraft
* If the module geoipmaxmind is used, you must resubmit the geoip data file as it is now stored differently.

View File

@@ -12,7 +12,7 @@ Security report are valid only on any current stable version for the last 5 majo
To report a vulnerability, for a private report, you can:
- Send your report as an issue on https://github.com/Dolibarr/dolibarr/issues or on GitHub Vulnerability Disclosure Program tool (VDP): https://github.com/Dolibarr/dolibarr/security/advisories (recommended)
- Send your report as an issue on https://github.com/Dolibarr/dolibarr/issues or on GitHub Vulnerability Disclosure Program tool (VDP): https://github.com/Dolibarr/dolibarr/security/advisories (recommended). Do 1 report only per vulnerability. Reports combining several vulnerabilities, as well as reports generated using IA will be rejected.
<!--
- Send your report on Vulnerability Disclosure Program (VDP) [https://app.yogosha.com/cvd/dolibarr/10VxeNx6Ui3rSEhAgX63US](https://app.yogosha.com/cvd/dolibarr/10VxeNx6Ui3rSEhAgX63US) (recommended for everybody)
- Or if you have permissions, use GitHub security advisory at [https://github.com/Dolibarr/dolibarr/security/advisories/new](https://github.com/Dolibarr/dolibarr/security/advisories/new)

View File

@@ -3,19 +3,30 @@
# See README.md to know how to create a Dolibarr env with docker
set -ex
if [ "${PHP_INI_DIR}" == "" ]; then
echo
echo This script must not be run directly. It is used by the Dockerfile
echo See README.md
echo
exit
fi
if [ "${HOST_USER_ID}" == "" ]; then
echo "Define HOST_USER_ID to your user ID before starting"
echo "Define HOST_USER_ID to your user ID before starting (example: www-data)"
exit 1
fi
if [ "${HOST_GROUP_ID}" == "" ]; then
echo "Define HOST_GROUP_ID to your group ID before starting (example: www-data)"
exit 1
fi
usermod -u "${HOST_USER_ID}" www-data
groupmod -g "${HOST_USER_ID}" www-data
groupmod -g "${HOST_GROUP_ID}" www-data
echo "[docker-run] => update '${PHP_INI_DIR}/conf.d/dolibarr-php.ini'"
cat <<EOF > "${PHP_INI_DIR}/conf.d/dolibarr-php.ini"
date.timezone = ${PHP_INI_DATE_TIMEZONE:-UTC}
memory_limit = ${PHP_INI_MEMORY_LIMIT:-256M}
display_errors = Off
EOF
exec apache2-foreground

View File

@@ -1,24 +1,51 @@
# How to use or run Dolibarr with Docker ?
# How to build and run your local version of Dolibarr, quickly with Docker ?
## For a fast run of a demo of the local version, you can build the docker image from the source repository by running
## For a fast run of a demo of the local version, you can build a docker image pointing to your source repository
This process using the Dockerfile and docker-compose.yml file is a config file to use to build and run Dolibarr in the
current workspace with Docker. This docker image is intended for **development usage** (See next chapter for a production usage).
Warning: There is no persistency of database. This process is for dev purpose only.
Build the containers:
git clone https://github.com/Dolibarr/dolibarr.git dolibarr
cd dolibarr/docker
cd dolibarr/dev/build/docker
sudo docker-compose build
sudo docker-compose build [--no-cache]
or
sudo docker compose build [--no-cache]
Launch the containers:
sudo -s
export HOST_USER_ID=$(id -u)
export HOST_GROUP_ID=$(id -g)
export MYSQL_ROOT_PWD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13; echo)
docker-compose up -d
or
docker compose up -d
Check that server process is on:
docker compose ps
Warning: There is no persistency of data. This process is for dev purpose only.
Check also logs of starting container:
docker logs dolibarr-web-dev
The URL to go to the installed Dolibarr is :
http://0.0.0.0:8080
To delete processes:
docker compose kill stop
## For a more robust or a production usage

View File

@@ -1,28 +1,27 @@
version: "3"
services:
mariadb-prod:
container_name: dolibarr-mariadb-prod
mariadb-dev:
container_name: dolibarr-mariadb-dev
image: mariadb:latest
environment:
MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PWD
MYSQL_DATABASE: "dolibarr-prod"
MARIADB_ROOT_PASSWORD: $MYSQL_ROOT_PWD
MARIADB_DATABASE: "dolibarr-dev"
web:
container_name: dolibarr-web-prod
container_name: dolibarr-web-dev
build: .
volumes:
- ../../htdocs:/var/www/html/
- ../../documents:/var/www/html/documents
- ../../../htdocs:/var/www/html/
- ../../../documents:/var/www/documents
depends_on:
- mariadb-prod
- mariadb-dev
environment:
HOST_USER_ID: $HOST_USER_ID
HOST_GROUP_ID: $HOST_GROUP_ID
DOLI_ROOT_PASSWORD: $MYSQL_ROOT_PWD
DOLI_DATABASE: "dolibarr-prod"
DOLI_DB_SERVER: "mariadb-prod"
DOLI_DATABASE: "dolibarr-dev"
DOLI_DB_SERVER: "mariadb-dev"
DOLI_DB_USER: "root"
DOLI_DB_PASSWORD: $MYSQL_ROOT_PWD
ports:
- "8080:80"

View File

@@ -10,6 +10,16 @@ if [ "${PHP_INI_DIR}" == "" ]; then
exit
fi
if [ "${HOST_USER_ID}" == "" ]; then
echo "Define HOST_USER_ID to your user ID before starting (example: www-data)"
exit 1
fi
if [ "${HOST_GROUP_ID}" == "" ]; then
echo "Define HOST_GROUP_ID to your group ID before starting (example: www-data)"
exit 1
fi
usermod -u "${HOST_USER_ID}" www-data
groupmod -g "${HOST_GROUP_ID}" www-data
@@ -29,6 +39,7 @@ date.timezone = ${PHP_INI_DATE_TIMEZONE:-UTC}
memory_limit = ${PHP_INI_MEMORY_LIMIT:-256M}
EOF
cp /var/www/html/install/install.forced.docker.php /var/www/html/install/install.forced.php
echo cp /var/www/html/install/install.forced.sample.php /var/www/html/install/install.forced.php
cp /var/www/html/install/install.forced.sample.php /var/www/html/install/install.forced.php
exec apache2-foreground

View File

@@ -60,9 +60,19 @@ my %ALTERNATEPATH = (
);
my $RPMSUBVERSION = "auto"; # auto use value found into BUILD
my $RPMDIR = "$ENV{HOME}/rpmbuild"; # by default
if ( -d "/usr/src/redhat" ) { $RPMDIR = "/usr/src/redhat"; } # redhat
if ( -d "/usr/src/packages" ) { $RPMDIR = "/usr/src/packages"; } # opensuse
if ( -d "/usr/src/RPM" ) { $RPMDIR = "/usr/src/RPM"; } # mandrake
if ( ! -d "$RPMDIR/SOURCES" ) {
mkdir("$RPMDIR/SOURCES");
if ( ! -d "$RPMDIR/SOURCES" ) {
print "Failed to create dir $RPMDIR/SOURCES\n";
sleep 2;
exit 1;
}
}
use vars qw/ $REVISION $VERSION /;
my $VERSION = "4.0";
@@ -203,10 +213,10 @@ while (<$IN>) {
}
}
close $IN;
open( my $IN2, "<", $SOURCE . "/htdocs/filefunc.inc.php" )
open( my $IN2, "<", $SOURCE . "/htdocs/version.inc.php" )
or die "Error: Can't open version file "
. $SOURCE
. "/htdocs/filefunc.inc.php\n";
. "/htdocs/version.inc.php\n";
while (<$IN2>) {
if ( $_ =~ /define\('DOL_MINOR_VERSION',\s*'([\d\.a-z\-]+)'\)/ ) {
$MINORVERSION = $1;
@@ -625,10 +635,8 @@ if ($nboftargetok) {
. $BUILD . '"' . "\n";
$ret =
`git tag -a -f -m "$MAJOR.$MINOR.$BUILD" "$MAJOR.$MINOR.$BUILD"`;
print 'Run git push '
. $GITREMOTENAME
. ' -f "$MAJOR.$MINOR.$BUILD"' . "\n";
$ret = `git push $GITREMOTENAME -f -"$MAJOR.$MINOR.$BUILD"`;
print "Run git push $GITREMOTENAME -f '$MAJOR.$MINOR.$BUILD'\n";
$ret = `git push $GITREMOTENAME -f "$MAJOR.$MINOR.$BUILD"`;
#$ret=`git push -f origin "$MAJOR.$MINOR.$BUILD"`;
}

View File

@@ -1,7 +1,7 @@
diff -up htdocs/filefunc.inc.php.patch htdocs/filefunc.inc.php
--- htdocs/filefunc.inc.php.patch 2011-09-03 02:32:48.666952000 +0200
+++ htdocs/filefunc.inc.php 2011-09-03 02:33:00.510952001 +0200
@@ -63,8 +63,8 @@
@@ -128,8 +128,8 @@
$conffile = "conf/conf.php";
$conffiletoshow = "htdocs/conf/conf.php";
// For debian/redhat like systems

View File

@@ -189,7 +189,6 @@ done >>%{name}.lang
%_datadir/dolibarr/htdocs/expedition
%_datadir/dolibarr/htdocs/expensereport
%_datadir/dolibarr/htdocs/exports
%_datadir/dolibarr/htdocs/externalsite
%_datadir/dolibarr/htdocs/fichinter
%_datadir/dolibarr/htdocs/fourn
%_datadir/dolibarr/htdocs/ftp

View File

@@ -270,7 +270,6 @@ done >>%{name}.lang
%_datadir/dolibarr/htdocs/expedition
%_datadir/dolibarr/htdocs/expensereport
%_datadir/dolibarr/htdocs/exports
%_datadir/dolibarr/htdocs/externalsite
%_datadir/dolibarr/htdocs/fichinter
%_datadir/dolibarr/htdocs/fourn
%_datadir/dolibarr/htdocs/ftp

View File

@@ -187,7 +187,6 @@ done >>%{name}.lang
%_datadir/dolibarr/htdocs/expedition
%_datadir/dolibarr/htdocs/expensereport
%_datadir/dolibarr/htdocs/exports
%_datadir/dolibarr/htdocs/externalsite
%_datadir/dolibarr/htdocs/fichinter
%_datadir/dolibarr/htdocs/fourn
%_datadir/dolibarr/htdocs/ftp

View File

@@ -197,7 +197,6 @@ done >>%{name}.lang
%_datadir/dolibarr/htdocs/expedition
%_datadir/dolibarr/htdocs/expensereport
%_datadir/dolibarr/htdocs/exports
%_datadir/dolibarr/htdocs/externalsite
%_datadir/dolibarr/htdocs/fichinter
%_datadir/dolibarr/htdocs/fourn
%_datadir/dolibarr/htdocs/ftp

View File

@@ -184,7 +184,7 @@ if ($confirm == 'regenerate') {
print "For ROWID ".$obj->rowid." - Previous hash = ".$previoushash."\n";
print "Signature in db: ".$obj->signature." - New calculated: ".$signature['calculatedsignature']."\n";
if ($obj->signature != $signature['calculatedsignature']) {
if (!empty($signature['calculatedsignature']) && $obj->signature != $signature['calculatedsignature']) {
$tmpsql = "UPDATE ".MAIN_DB_PREFIX."blockedlog SET signature = '".$db->escape($signature['calculatedsignature'])."'";
$tmpsql .= " WHERE rowid = ".((int) $obj->rowid);

125
dev/pullmerge.sh Executable file
View File

@@ -0,0 +1,125 @@
#!/bin/bash
param1="$1"
# Configuration des versions
START=14
END=23
DEV_DIR="dolibarr_dev"
DEV_BRANCH="develop"
if [ "x$2" != "x" ]; then
START=$2
fi
if [[ "$param1" != 'pull' && "$param1" != 'merge' && "$param1" != 'all' ]]; then
echo "Usage: $0 <pull|merge|all> [xx]"
echo " where xx is version to start from (14, 18, ...)"
echo
echo "Example:"
echo "$0 pull # For pull only"
echo "$0 merge 20 # For merge only"
echo "$0 all # For pull then merge"
echo
exit 1
fi
if [ "x$param1" = "xall" ] || [ "x$param1" = "xpull" ]; then
echo "--- STEP Pull : Update directories (git pull) ---"
for (( i=$START; i<=$END; i++ ))
do
DIR="dolibarr_$i.0"
if [ -d "$DIR" ]; then
echo "*** Update of $DIR..."
cd "$DIR" || exit
# We want to be sure we are on good brnahc and we pull
git checkout "$i.0" && git pull
if [ $? -ne 0 ]; then
echo "ERREUR : The pull on $DIR has failed. We stop here."
exit 1
fi
cd ..
else
echo "Warning : The directory $DIR does not exists, switch to next one."
fi
done
# Update branch dev
if [ -d "$DEV_DIR" ]; then
echo "*** Update of $DEV_DIR..."
cd "$DEV_DIR" || exit
git checkout "$DEV_BRANCH" && git pull || { echo "Error pull $DEV_DIR"; exit 1; }
cd ..
fi
fi
if [ "x$param1" = "xall" ] || [ "x$param1" = "xmerge" ]; then
echo -e "\n--- STEP Merge : Cascade of Merges (xx-1 -> xx) ---"
for (( i=$((START+1)); i<=$END; i++ ))
do
PREV=$((i-1))
CURRENT=$i
DIR_CURRENT="dolibarr_$CURRENT.0"
DIR_PREV="dolibarr_$PREV.0"
if [ -d "$DIR_CURRENT" ] && [ -d "$DIR_PREV" ]; then
echo "*** Merge of dolibarr_$PREV to dolibarr_$CURRENT..."
echo "Go on dir $DIR_CURRENT"
cd "$DIR_CURRENT" || exit
# Add previous directory to a local remote for the merge
# This allow to merge without the need of a common remote repository, which may not be the case here as we have only local clones.
REMOTE_NAME="local_prev"
git remote remove $REMOTE_NAME 2>/dev/null
git remote add $REMOTE_NAME "../$DIR_PREV"
git fetch $REMOTE_NAME
# Try to merge
git merge "$REMOTE_NAME/$PREV.0" -m "Automated merge from $PREV.0 to $CURRENT.0 by tool pullmerge.sh"
if [ $? -eq 0 ]; then
echo "Success on merge. Try to push..."
git push
if [ $? -ne 0 ]; then
echo "ERROR : The push of $DIR_CURRENT has failed. We stop here."
exit 1
fi
else
echo "ERROR : Conflict or error on merge into $DIR_CURRENT. We stop here."
# Optionnel : git merge --abort pour laisser le repo propre
exit 1
fi
# Nettoyage du remote temporaire
git remote remove $REMOTE_NAME
cd ..
fi
done
if [ -d "$DEV_DIR" ] && [ -d "dolibarr_$END.0" ]; then
echo "*** Merge final of dolibarr_$END.0 vers $DEV_DIR..."
cd "$DEV_DIR" || exit
REMOTE_NAME="local_prev"
git remote remove $REMOTE_NAME 2>/dev/null
git remote add $REMOTE_NAME "../dolibarr_$END.0"
git fetch $REMOTE_NAME
git merge "$REMOTE_NAME/$END.0" -m "Automated merge from $END.0 to $DEV_BRANCH" && git push || exit 1
git remote remove $REMOTE_NAME
cd ..
fi
fi
echo -e "\nSuccess !"

View File

@@ -47,7 +47,7 @@ function save_db_cache() (
# Get the target version from the version.inc.php file
target_version=$(sed -n "s/.*define('DOL_\\(MAJOR_\\)\\?VERSION',[[:space:]]*'\\([0-9.]*\\).*/\\2/p" ../version.inc.php) ; echo $target_version
# Default in case that failed
target_version=${target_version:=22.0.0}
target_version=${target_version:=23.0.0}
# Sequence of versions for upgrade process (to be completed)
VERSIONS=("3.5.0" "3.6.0" "3.7.0" "3.8.0" "3.9.0")

View File

@@ -97,7 +97,7 @@
$sql .= " AND cs.periode >= '".$db->idate($search_date_limit_start)."'";
$sql .= " OR (cs.periode IS NULL AND cs.date_ech between '".$db->idate(dol_get_first_day($year))."' AND '".$db->idate(dol_get_last_day($year))."')";
$sql .= " SET reponses = '".$db->escape($nouveauchoix)."'";
$sql .= " cs.rowid, cs.libelle, cs.fk_type as type, cs.periode as period, cs.date_ech, cs.amount as total,";
$sql .= " cs.rowid, cs.libelle as label, cs.fk_type as type, cs.periode as period, cs.date_ech, cs.amount as total,";
$sql.= " ".MAIN_DB_PREFIX."notify_def as nd,";
$sql.= " AND nd.fk_action = ad.rowid";
$sql.= " WHERE u.rowid = nd.fk_user";

View File

@@ -274,7 +274,6 @@ return [
'disableremove' => 'int<0,1>',
'dolibarr_main_authentication' => 'string',
'dolibarr_main_data_root' => 'string',
'dolibarr_main_data_root' => 'string',
'dolibarr_main_db_encrypted_pass' => 'string',
'dolibarr_main_db_host' => 'string',
'dolibarr_main_db_pass' => 'string',
@@ -346,6 +345,7 @@ return [
'htdocs/includes/',
'htdocs/install/doctemplates/websites/',
'htdocs/core/class/lessc.class.php', // External library
'htdocs/admin/tools/ui/',
PHAN_DIR . '/stubs/',
],
//'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@',
@@ -451,6 +451,7 @@ return [
'PhanCompatibleNegativeStringOffset', // return false positive
'PhanPluginConstantVariableBool', // a lot of false positive, in most cases, we want to keep the code as it is
'PhanPluginConstantVariableNull', // a lot of false positive, in most cases, we want to keep the code as it is
// 'PhanPluginUnknownArrayPropertyType', // Helps find missing array keys or mismatches, remaining occurrences are likely unused properties
'PhanTypeArraySuspiciousNullable', // About 440 occurrences
// 'PhanTypeInvalidDimOffset', // Helps identify missing array indexes in types or reference to unset indexes

View File

@@ -68,6 +68,7 @@ $config['suppress_issue_types'] = [
'PhanCompatibleNegativeStringOffset', // return false positive
'PhanPluginConstantVariableBool', // a lot of false positive, in most cases, we want to keep the code as it is
'PhanPluginConstantVariableNull', // a lot of false positive, in most cases, we want to keep the code as it is
// 'PhanPluginUnknownArrayPropertyType', // Helps find missing array keys or mismatches, remaining occurrences are likely unused properties
'PhanTypeArraySuspiciousNullable', // About 400 cases
// 'PhanTypeInvalidDimOffset', // Helps identify missing array indexes in types or reference to unset indexes

View File

@@ -100,7 +100,7 @@ if (!$sortorder) {
$object = new AccountingAccount($db);
$arrayfields = array(
'aa.account_number' => array('label' => "AccountNumber", 'checked' => '1'),
'aa.account_number' => array('label' => "AccountNumber", 'checked' => '1', 'csslist' => 'maxwidth50'),
'aa.label' => array('label' => "Label", 'checked' => '1'),
'aa.labelshort' => array('label' => "ShortLabel", 'checked' => '1'),
'aa.account_parent' => array('label' => "Accountparent", 'checked' => '1'),
@@ -451,6 +451,8 @@ if ($resql) {
include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php';
print '<div class="neutral">';
// Box to select active chart of account
print $langs->trans("Selectchartofaccounts")." : ";
print '<select class="flat minwidth200" name="chartofaccounts" id="chartofaccounts">';
@@ -485,7 +487,7 @@ if ($resql) {
print ajax_combobox("chartofaccounts");
print '<input type="'.(empty($conf->use_javascript_ajax) ? 'submit' : 'button').'" class="button button-edit small" name="change_chart" id="change_chart" value="'.dol_escape_htmltag($langs->trans("ChangeAndLoad")).'">';
print '<br>';
print '</div>';
$parameters = array('chartofaccounts' => $chartofaccounts, 'permissiontoadd' => $permissiontoadd, 'permissiontodelete' => $permissiontodelete);
$reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $accounting, $action); // Note that $action and $object may have been modified by hook
@@ -530,10 +532,10 @@ if ($resql) {
print '</td>';
}
if (!empty($arrayfields['aa.account_number']['checked'])) {
print '<td class="liste_titre"><input type="text" class="flat width100" name="search_account" value="'.$search_account.'"></td>';
print '<td class="liste_titre"><input type="text" class="flat width75" name="search_account" value="'.$search_account.'"></td>';
}
if (!empty($arrayfields['aa.label']['checked'])) {
print '<td class="liste_titre"><input type="text" class="flat width150" name="search_label" value="'.$search_label.'"></td>';
print '<td class="liste_titre"><input type="text" class="flat width100" name="search_label" value="'.$search_label.'"></td>';
}
if (!empty($arrayfields['aa.labelshort']['checked'])) {
print '<td class="liste_titre"><input type="text" class="flat width100" name="search_labelshort" value="'.$search_labelshort.'"></td>';

View File

@@ -27,9 +27,6 @@
// Load Dolibarr environment
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
/**
* @var Conf $conf
* @var DoliDB $db
@@ -37,13 +34,14 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
// Load translation files required by the page
$langs->loadLangs(array("accountancy", "admin", "bills", "compta", "errors", "hrm", "salaries"));
$action = GETPOST('action', 'aZ09');
$cancel = GETPOST('cancel', 'alpha');
$massaction = GETPOST('massaction', 'aZ09');
$optioncss = GETPOST('optioncss', 'alpha');
$mode = GETPOST('mode', 'aZ'); // The output mode ('list', 'kanban', 'hierarchy', 'calendar', ...)
@@ -350,6 +348,7 @@ if ($resql) {
print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'accounting_account', 0, '', '', $limit, 0, 0, 1);
print '<div class="info">'.$langs->trans("WarningCreateSubAccounts").'</div>';
print '<br>';
$varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage;
$htmlofselectarray = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage, getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')); // This also change content of $arrayfields with user setup

View File

@@ -450,7 +450,19 @@ if (empty($reshook)) {
if ($result < 0) {
setEventMessages($object->error, $object->errors, 'errors');
} else {
header("Location: " . $backtopage . "?sortfield=t.piece_num&sortorder=asc" . ($type ? '&type='.$type : ''));
// Retrieve the actual final part number (not the temporary number)
$piece_num = $object->piece_num;
$linkEntry = '<a href="'.$_SERVER["PHP_SELF"].'?piece_num='.(int) $piece_num.'">'.$langs->trans('NumMvts').': '.(int) $piece_num.'</a>';
setEventMessages($langs->trans('RecordSaved').' - '.$linkEntry, null, 'mesgs', '', 1);
if (getDolGlobalInt('ACCOUNTANCY_VALID_REDIRECT_TO_CARD')) {
// Redirection to the validated entry record
header("Location: ".$_SERVER["PHP_SELF"]."?piece_num=".(int) $piece_num);
} else {
// Default behavior: return on journal view
header("Location: ".$backtopage."?sortfield=t.piece_num&sortorder=asc".($type ? '&type='.$type : ''));
}
exit;
}
}

View File

@@ -846,30 +846,50 @@ if ($action == 'export_file') {
}
// add documents in an archive for some accountancy export format
if (getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV') == AccountancyExport::$EXPORT_TYPE_QUADRATUS
|| getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV') == AccountancyExport::$EXPORT_TYPE_FEC
|| getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV') == AccountancyExport::$EXPORT_TYPE_FEC2
) {
$except = array();
if (getDolGlobalInt('ACCOUNTING_EXPORT_REMOVE_INVOICE_SOURCE_FILE')) {
$except[] = $langs->trans('Invoice');
}
if (getDolGlobalInt('ACCOUNTING_EXPORT_REMOVE_EXPENSEREPORT_SOURCE_FILE')) {
$except[] = $langs->trans('ExpenseReport');
}
if (getDolGlobalInt('ACCOUNTING_EXPORT_REMOVE_SUPPLIERINVOICE_SOURCE_FILE')) {
$except[] = $langs->trans('SupplierInvoice');
}
$exportTypesWithDocs = array(
AccountancyExport::$EXPORT_TYPE_QUADRATUS,
AccountancyExport::$EXPORT_TYPE_FEC,
AccountancyExport::$EXPORT_TYPE_FEC2
);
$form_question['notifiedexportfull'] = array(
'name' => 'notifiedexportfull',
'type' => 'checkbox',
'label' => $langs->trans('NotifiedExportFull').(empty($except) ? '' : ' <spanc class="opacitymedium">('.$langs->trans("except").' '.implode(', ', $except).')</span>'),
'value' => 'false',
);
$except = array();
if (getDolGlobalInt('ACCOUNTING_EXPORT_REMOVE_INVOICE_SOURCE_FILE')) {
$except[] = $langs->trans('Invoice');
}
if (getDolGlobalInt('ACCOUNTING_EXPORT_REMOVE_EXPENSEREPORT_SOURCE_FILE')) {
$except[] = $langs->trans('ExpenseReport');
}
if (getDolGlobalInt('ACCOUNTING_EXPORT_REMOVE_SUPPLIERINVOICE_SOURCE_FILE')) {
$except[] = $langs->trans('SupplierInvoice');
}
$form_question['notifiedexportfull'] = array(
'name' => 'notifiedexportfull',
'type' => 'checkbox',
'label' => $langs->trans('NotifiedExportFull').(empty($except) ? '' : ' <spanc class="opacitymedium">('.$langs->trans("except").' '.implode(', ', $except).')</span>'),
'value' => 'false',
);
$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").'...', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 500, 700);
$formconfirm .= '<script>
jQuery(document).ready(function() {
const exportTypesWithDocs = ['.implode(',', $exportTypesWithDocs).'];
const $formatExport = jQuery("#formatexport");
function toggleExportFull() {
const $checkbox = jQuery("#notifiedexportfull");
const show = exportTypesWithDocs.indexOf(parseInt($formatExport.val())) !== -1;
$checkbox.closest(".tagtr").toggle(show);
if (!show) {
$checkbox.prop("checked", false); // remove checked if hidden
}
}
$formatExport.on("change", toggleExportFull);
toggleExportFull();
});
</script>';
}
// Print form confirm

View File

@@ -750,7 +750,8 @@ if (!empty($socid)) {
print '</div>';
print dol_get_fiche_end();
print info_admin($langs->trans("WarningThisPageContainsOnlyEntriesTransferredInAccounting")).'';
print info_admin($langs->trans("WarningThisPageContainsOnlyEntriesTransferredInAccounting"));
print '<br>';
// Choice of mode (customer / supplier)
if (!empty($conf->dol_use_jmobile)) {

View File

@@ -1204,7 +1204,12 @@ class AccountancyExport
$tab['end_line'] = $end_line;
print implode('|', $tab);
$output = implode('|', $tab);
if ($exportFile) {
fwrite($exportFile, $output);
} else {
print $output;
}
$index++;
}

View File

@@ -2258,6 +2258,9 @@ class BookKeeping extends CommonObject
$error++;
}
// Store the new piece_num so caller can use it after transformTransaction()
$this->piece_num = $next_piecenum;
if (!$error) {
// Delete if there is an empty line
$sql = 'DELETE FROM '.$this->db->prefix().$this->table_element.'_tmp WHERE piece_num = '.((int) $piece_num).' AND entity = ' .((int) $conf->entity)." AND numero_compte IS NULL AND debit = 0 AND credit = 0";

View File

@@ -132,6 +132,11 @@ class BookkeepingTemplateLine extends CommonObject
*/
public $subledger_label;
/**
* @var ?string label operation
*/
public $operation_label;
/**
* @var string Debit amount (stored as string for precision)
*/

View File

@@ -155,8 +155,8 @@ if (empty($reshook)) {
}
} elseif ($action == 'confirm_step_2' && $confirm == "yes" && $user->hasRight('accounting', 'fiscalyear', 'write')) {
$new_fiscal_period_id = GETPOSTINT('new_fiscal_period_id');
$separate_auxiliary_account = GETPOSTINT('separate_auxiliary_account');
$generate_bookkeeping_records = GETPOSTINT('generate_bookkeeping_records');
$separate_auxiliary_account = (!empty(GETPOST('separate_auxiliary_account')) ? 1 : 0);
$generate_bookkeeping_records = (!empty(GETPOST('generate_bookkeeping_records')) ? 1 : 0);
$error = 0;
if ($generate_bookkeeping_records) {

View File

@@ -29,12 +29,6 @@
// Load Dolibarr environment
require '../../main.inc.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.'/core/lib/company.lib.php';
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
/**
* @var Conf $conf
* @var DoliDB $db
@@ -43,6 +37,11 @@ require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
* @var Translate $langs
* @var User $user
*/
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.'/core/lib/company.lib.php';
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
// Load translation files required by the page
$langs->loadLangs(array("compta", "bills", "other", "accountancy"));
@@ -292,6 +291,7 @@ if ($action == 'validatehistory' && $user->hasRight('accounting', 'bind', 'write
/*
* View
*/
$help_url = 'EN:Module_Double_Entry_Accounting|FR:Module_Comptabilit&eacute;_en_Partie_Double#Liaisons_comptables';
llxHeader('', $langs->trans("CustomersVentilation"), $help_url, '', 0, 0, '', '', '', 'mod-accountancy accountancy-customer page-index');
@@ -302,12 +302,14 @@ $textnextyear = '&nbsp;<a href="'.$_SERVER["PHP_SELF"].'?year='.($year_current +
print load_fiche_titre($langs->trans("CustomersVentilation")." ".$textprevyear." ".$langs->trans("Year")." ".$year_start." ".$textnextyear, '', 'title_accountancy');
print '<span class="opacitymedium">'.$langs->trans("DescVentilCustomer").'</span><br>';
print '<span class="opacitymedium hideonsmartphone">'.$langs->trans("DescVentilMore", $langs->transnoentitiesnoconv("ValidateHistory"), $langs->transnoentitiesnoconv("ToBind")).'<br>';
print '</span><br>';
print '<div class="info">';
print '<span class="">'.$langs->trans("DescVentilCustomer").'</span><br>';
print '<span class="hideonsmartphone">'.$langs->trans("DescVentilMore", $langs->transnoentitiesnoconv("ValidateHistory"), $langs->transnoentitiesnoconv("ToBind")).'<br>';
print '</span>';
print '</div>';
if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
print info_admin($langs->trans("SorryThisModuleIsNotCompatibleWithTheExperimentalFeatureOfSituationInvoices"));
print info_admin($langs->trans("SorryThisModuleIsNotCompatibleWithTheExperimentalFeatureOfSituationInvoices"), 0, 0, 'warning');
print "<br>";
}

View File

@@ -27,6 +27,14 @@
*/
require '../../main.inc.php';
/**
* @var Conf $conf
* @var DoliDB $db
* @var HookManager $hookmanager
* @var Societe $mysoc
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
@@ -37,15 +45,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.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("bills", "compta", "accountancy", "productbatch", "products"));
@@ -441,11 +440,12 @@ if ($result) {
print '<input type="hidden" name="page" value="'.$page.'">';
// @phan-suppress-next-line PhanPluginSuspiciousParamOrder
print_barre_liste($langs->trans("InvoiceLinesDone").'<br><span class="opacitymedium small">'.$langs->trans("DescVentilDoneCustomer").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print_barre_liste($langs->trans("InvoiceLinesDone").'<br><span class="opacityhigh small">'.$langs->trans("DescVentilDoneCustomer").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print '<br>'.$langs->trans("ChangeAccount").' <div class="inline-block paddingbottom marginbottomonly">';
print '<br>'.$langs->trans("ChangeAccount").' <div class="inline-block paddingbottom paddingtop">';
print $formaccounting->select_account($account_parent, 'account_parent', 2, array(), 0, 0, 'maxwidth300 maxwidthonsmartphone valignmiddle');
print '<input type="submit" class="button small smallpaddingimp valignmiddle" value="'.$langs->trans("ChangeBinding").'"/></div>';
print '<br><br>';
$moreforfilter = '';
@@ -700,7 +700,7 @@ if ($result) {
}
// Ref Product
if (!empty($arrayfields['p.ref']['checked'])) {
print '<td class="tdoverflowmax100">';
print '<td class="tdoverflowmax100 cell2linesheight">';
if ($productstatic->id > 0) {
print $productstatic->getNomUrl(1);
} else {
@@ -766,9 +766,10 @@ if ($result) {
}
// Country
if (!empty($arrayfields['co.label']['checked'])) {
print '<td class="tdoverflowmax125" title="'.dolPrintHTML($langs->trans("Country".$objp->country_code).' ('.$objp->country_code.')').'">';
print '<td class="tdoverflowmax125" title="'.dolPrintHTMLForAttribute($langs->trans("Country".$objp->country_code).' ('.$objp->country_code.')').'">';
if ($objp->country_code) {
print $langs->trans("Country".$objp->country_code).' ('.$objp->country_code.')';
print $langs->trans("Country".$objp->country_code);
//print ' ('.$objp->country_code.')';
}
print '</td>';
$totalarray['nbfield']++;

View File

@@ -111,6 +111,8 @@ if (!$sortorder) {
// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
$hookmanager->initHooks(array($contextpage));
$object = new AccountingAccount($db);
$formaccounting = new FormAccounting($db);
$accountingAccount = new AccountingAccount($db);
@@ -254,7 +256,6 @@ if (GETPOST('sortfield') == 'f.datef, f.ref, l.rowid') {
*/
$form = new Form($db);
$formother = new FormOther($db);
$help_url = 'EN:Module_Double_Entry_Accounting|FR:Module_Comptabilit&eacute;_en_Partie_Double#Liaisons_comptables';
@@ -503,7 +504,8 @@ if ($result) {
print '<input type="hidden" name="page" value="'.$page.'">';
// @phan-suppress-next-line PhanPluginSuspiciousParamOrder
print_barre_liste($langs->trans("InvoiceLines").'<br><span class="opacitymedium small">'.$langs->trans("DescVentilTodoCustomer").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, (string) $massactionbutton, $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print_barre_liste($langs->trans("InvoiceLines").'<br><span class="opacityhigh small">'.$langs->trans("DescVentilTodoCustomer").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, (string) $massactionbutton, $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print '<br>';
if ($massaction == 'set_default_account') {
$formquestion = array();
@@ -826,7 +828,7 @@ if ($result) {
}
// Ref Invoice
if (!empty($arrayfields['f.ref']['checked'])) {
print '<td class="nowraponall">'.$facture_static->getNomUrl(1).'</td>';
print '<td class="nowraponall cell2linesheight">'.$facture_static->getNomUrl(1).'</td>';
$totalarray['nbfield']++;
}
// Invoice date
@@ -836,7 +838,7 @@ if ($result) {
}
// Ref Product
if (!empty($arrayfields['p.ref']['checked'])) {
print '<td class="tdoverflowmax125">';
print '<td class="tdoverflowmax125 cell2linesheight">';
if ($product_static->id > 0) {
print $product_static->getNomUrl(1);
}
@@ -889,8 +891,8 @@ if ($result) {
if ($product_static->tva_tx !== $facture_static_det->tva_tx && price2num($product_static->tva_tx) && price2num($facture_static_det->tva_tx)) { // Note: having a vat rate of 0 is often the normal case when sells is intra b2b or to export
$code_vat_differ = 'warning bold';
}
print '<td class="right'.($code_vat_differ ? ' '.$code_vat_differ : '').'">';
print vatrate($facture_static_det->tva_tx.($facture_static_det->vat_src_code ? ' ('.$facture_static_det->vat_src_code.')' : ''));
print '<td class="right cell2linesheight'.($code_vat_differ ? ' '.$code_vat_differ : '').'">';
print vatrate($facture_static_det->tva_tx.($facture_static_det->vat_src_code ? ' ('.$facture_static_det->vat_src_code.')' : ''), false, 0, 0, 1);
print '</td>';
$totalarray['nbfield']++;
}
@@ -914,7 +916,7 @@ if ($result) {
}
// Found accounts
if (!empty($arrayfields['aa.data_suggest']['checked'])) {
print '<td class="small">';
print '<td class="small cell2linesheight">';
// First show default account for any products
$s = '1. '.(($facture_static_det->product_type == 1) ? $langs->trans("DefaultForService") : $langs->trans("DefaultForProduct")).': ';
$shelp = '';

View File

@@ -28,10 +28,6 @@
// Load Dolibarr environment
require '../../main.inc.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.'/expensereport/class/expensereport.class.php';
/**
* @var Conf $conf
* @var DoliDB $db
@@ -39,6 +35,9 @@ require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php';
* @var Translate $langs
* @var User $user
*/
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.'/expensereport/class/expensereport.class.php';
// Load translation files required by the page
$langs->loadLangs(array("compta", "bills", "other", "accountancy"));
@@ -85,6 +84,7 @@ if (!$user->hasRight('accounting', 'bind', 'write')) {
/*
* Actions
*/
$error = 0;
if (($action == 'clean' || $action == 'validatehistory') && $user->hasRight('accounting', 'bind', 'write')) {
@@ -197,10 +197,11 @@ $textnextyear = '&nbsp;<a href="'.$_SERVER["PHP_SELF"].'?year='.($year_current +
print load_fiche_titre($langs->trans("ExpenseReportsVentilation")."&nbsp;".$textprevyear."&nbsp;".$langs->trans("Year")."&nbsp;".$year_start."&nbsp;".$textnextyear, '', 'title_accountancy');
print '<span class="opacitymedium">'.$langs->trans("DescVentilExpenseReport").'</span><br>';
print '<span class="opacitymedium hideonsmartphone">'.$langs->trans("DescVentilExpenseReportMore", $langs->transnoentitiesnoconv("ValidateHistory"), $langs->transnoentitiesnoconv("ToBind")).'<br>';
print '</span><br>';
print '<div class="info">';
print '<span class="">'.$langs->trans("DescVentilExpenseReport").'</span><br>';
print '<span class="hideonsmartphone">'.$langs->trans("DescVentilExpenseReportMore", $langs->transnoentitiesnoconv("ValidateHistory"), $langs->transnoentitiesnoconv("ToBind")).'<br>';
print '</span>';
print '</div>';
$y = $year_current;

View File

@@ -27,14 +27,6 @@
*/
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php';
require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
/**
* @var Conf $conf
* @var DoliDB $db
@@ -42,6 +34,13 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php';
require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
// Load translation files required by the page
$langs->loadLangs(array("compta", "bills", "other", "accountancy", "trips", "productbatch", "hrm"));
@@ -372,11 +371,12 @@ if ($result) {
print '<input type="hidden" name="page" value="'.$page.'">';
// @phan-suppress-next-line PhanPluginSuspiciousParamOrder
print_barre_liste($langs->trans("ExpenseReportLinesDone").'<br><span class="opacitymedium small">'.$langs->trans("DescVentilDoneExpenseReport").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print_barre_liste($langs->trans("ExpenseReportLinesDone").'<br><span class="opacityhigh small">'.$langs->trans("DescVentilDoneExpenseReport").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print '<br>'.$langs->trans("ChangeAccount").' <div class="inline-block paddingbottom marginbottomonly">';
print '<br>'.$langs->trans("ChangeAccount").' <div class="inline-block paddingbottom paddingtop">';
print $formaccounting->select_account($account_parent, 'account_parent', 2, array(), 0, 0, 'maxwidth300 maxwidthonsmartphone valignmiddle');
print '<input type="submit" class="button small smallpaddingimp valignmiddle" value="'.$langs->trans("ChangeBinding").'"/></div>';
print '<br><br>';
$moreforfilter = '';
@@ -639,7 +639,7 @@ if ($result) {
}
// Accounting account affected
if (!empty($arrayfields['aa.account_number']['checked'])) {
print '<td class="tdoverflowmax200" title="'.dol_escape_htmltag($accountingaccountstatic->label).'">';
print '<td class="tdoverflowmax200" title="'.dolPrintHTMLForAttribute($accountingaccountstatic->label).'">';
print '<a class="editfielda reposition marginleftonly marginrightonly" href="./card.php?id='.$objp->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"].($param ? '?'.$param : '')).'">';
print img_edit();
print '</a> ';

View File

@@ -421,7 +421,9 @@ if ($result) {
print '<input type="hidden" name="page" value="'.$page.'">';
// @phan-suppress-next-line PhanPluginSuspiciousParamOrder
print_barre_liste($langs->trans("ExpenseReportLines").'<br><span class="opacitymedium small">'.$langs->trans("DescVentilTodoExpenseReport").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, (string) $massactionbutton, $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print_barre_liste($langs->trans("ExpenseReportLines").'<br><span class="opacityhigh small">'.$langs->trans("DescVentilTodoExpenseReport").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, (string) $massactionbutton, $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print '<br>';
if (!empty($msg)) {
print $msg.'<br>';

View File

@@ -26,10 +26,6 @@
// Load Dolibarr environment
require '../main.inc.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.'/core/class/html.formother.class.php';
/**
* @var Conf $conf
* @var DoliDB $db
@@ -38,6 +34,9 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
* @var Translate $langs
* @var User $user
*/
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.'/core/class/html.formother.class.php';
// Load translation files required by the page
$langs->loadLangs(array("compta", "bills", "other", "accountancy", "loans", "banks", "admin", "dict"));
@@ -147,12 +146,13 @@ if (isModEnabled('accounting')) {
print '<div class="'.($helpisexpanded ? '' : 'hideobject').' idfaq">'; // hideobject is to start hidden
print "<br>\n";
print '<span class="opacitymedium">'.$langs->trans("AccountancyAreaDescIntro")."</span><br>\n";
//print '<span class="opacitymedium">'.$langs->trans("AccountancyAreaDescIntro")."</span><br>\n";
if ($user->hasRight('accounting', 'chartofaccount')) {
print '<br>';
//print '<br>';
//print '<br>';
print load_fiche_titre('<span class="fa fa-calendar"></span> '.$langs->trans("AccountancyAreaDescActionOnce"), '', '', 0, '', 'nomarginbottom')."\n";
print '<hr>';
print "<br>\n";
print '<div class="info">';
// STEPS
$step++;
@@ -199,13 +199,15 @@ if (isModEnabled('accounting')) {
print $s;
print "<br>\n";
}
print '</div>';
print "<br>\n";
print $langs->trans("AccountancyAreaDescActionOnceBis");
print "<br>\n";
print '<span class="opacitymedium">'.$langs->trans("AccountancyAreaDescActionOnceBis").'</span>';
print "<br>\n";
print '<div class="info">';
$step++;
$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescDefault", $step, '{s}');
$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/defaultaccounts.php?leftmenu=accountancy_admin" target="setupaccountancy"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuDefaultAccounts").'</strong></a>', $s);
@@ -247,6 +249,8 @@ if (isModEnabled('accounting')) {
print $s;
print "<br>\n";
print '</div>';
print '<br>';
}
@@ -254,12 +258,13 @@ if (isModEnabled('accounting')) {
print "<br>\n";
print load_fiche_titre('<span class="fa fa-calendar"></span> '.$langs->trans("AccountancyAreaDescActionFreq"), '', '', 0, '', 'nomarginbottom')."\n";
print '<hr>';
print "<br>\n";
$step = 0;
$langs->loadLangs(array('bills', 'trips'));
print '<div class="info">';
$step++;
$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescBind", chr(64 + $step), $langs->transnoentitiesnoconv("BillsCustomers"), '{s}')."\n";
$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/customer/index.php" target="setupaccountancy"><strong>'.$langs->transnoentitiesnoconv("TransferInAccounting").' - '.$langs->transnoentitiesnoconv("CustomersVentilation").'</strong></a>', $s);
@@ -293,6 +298,9 @@ if (isModEnabled('accounting')) {
$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescClosePeriod", chr(64 + $step))."<br>\n";
print $s;
print '</div>';
if (!empty($resultboxes['boxlista']) || !empty($resultboxes['boxlistb'])) {
print "<br>\n";
print '<br>';

View File

@@ -28,12 +28,6 @@
// Load Dolibarr environment
require '../../main.inc.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.'/core/lib/company.lib.php';
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
/**
* @var Conf $conf
* @var DoliDB $db
@@ -42,6 +36,11 @@ require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
* @var Translate $langs
* @var User $user
*/
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.'/core/lib/company.lib.php';
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
// Load translation files required by the page
$langs->loadLangs(array("compta", "bills", "other", "accountancy"));
@@ -298,6 +297,7 @@ if ($action == 'validatehistory' && $user->hasRight('accounting', 'bind', 'write
/*
* View
*/
$help_url = 'EN:Module_Double_Entry_Accounting|FR:Module_Comptabilit&eacute;_en_Partie_Double#Liaisons_comptables';
llxHeader('', $langs->trans("SuppliersVentilation"), $help_url, '', 0, 0, '', '', '', 'mod-accountancy accountancy-supplier page-index');
@@ -307,9 +307,11 @@ $textnextyear = '&nbsp;<a href="'.$_SERVER["PHP_SELF"].'?year='.($year_current +
print load_fiche_titre($langs->trans("SuppliersVentilation")." ".$textprevyear."&nbsp;".$langs->trans("Year")."&nbsp;".$year_start."&nbsp;".$textnextyear, '', 'title_accountancy');
print '<span class="opacitymedium">'.$langs->trans("DescVentilSupplier").'</span><br>';
print '<span class="opacitymedium hideonsmartphone">'.$langs->trans("DescVentilMore", $langs->transnoentitiesnoconv("ValidateHistory"), $langs->transnoentitiesnoconv("ToBind")).'<br>';
print '</span><br>';
print '<div class="info">';
print '<span class="">'.$langs->trans("DescVentilSupplier").'</span><br>';
print '<span class="hideonsmartphone">'.$langs->trans("DescVentilMore", $langs->transnoentitiesnoconv("ValidateHistory"), $langs->transnoentitiesnoconv("ToBind")).'<br>';
print '</span>';
print '</div>';
$y = $year_current;

View File

@@ -26,7 +26,14 @@
* \brief Page of detail of the lines of ventilation of invoices suppliers
*/
require '../../main.inc.php';
/**
* @var Conf $conf
* @var DoliDB $db
* @var HookManager $hookmanager
* @var Societe $mysoc
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
@@ -38,18 +45,10 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.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("compta", "bills", "other", "accountancy", "productbatch", "products"));
$action = GETPOST('action', 'aZ');
$optioncss = GETPOST('optioncss', 'aZ'); // Option for the css output (always '' except when 'print')
$account_parent = GETPOST('account_parent');
@@ -102,6 +101,8 @@ if (!$sortorder) {
$contextpage = 'accountancysupplierlines';
$hookmanager->initHooks([$contextpage]);
$object = new AccountingAccount($db);
// Security check
if (!isModEnabled('accounting')) {
accessforbidden();
@@ -121,7 +122,7 @@ $arrayfields = array(
'f.libelle' => array('label' => "InvoiceLabel", 'position' => 1, 'checked' => '1', 'enabled' => '1'),
'f.datef' => array('label' => "Date", 'position' => 1, 'checked' => '1', 'enabled' => '1'), // f.datef, f.ref, l.rowid
'p.ref' => array('label' => "ProductRef", 'position' => 1, 'checked' => '1', 'enabled' => '1'),
'l.description' => array('label' => "ProductDescription", 'position' => 1, 'checked' => '1', 'enabled' => '1'),
'l.description' => array('label' => "ProductDescription", 'position' => 1, 'checked' => '-1', 'enabled' => '1'),
'l.total_ht' => array('label' => "Amount", 'position' => 1, 'checked' => '1', 'enabled' => '1'),
'l.tva_tx' => array('label' => "VATRate", 'position' => 1, 'checked' => '1', 'enabled' => '1'),
's.nom' => array('label' => "ThirdParty", 'position' => 1, 'checked' => '1', 'enabled' => '1'),
@@ -441,11 +442,12 @@ if ($result) {
print '<input type="hidden" name="page" value="'.$page.'">';
// @phan-suppress-next-line PhanPluginSuspiciousParamOrder
print_barre_liste($langs->trans("InvoiceLinesDone").'<br><span class="opacitymedium small">'.$langs->trans("DescVentilDoneSupplier").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print_barre_liste($langs->trans("InvoiceLinesDone").'<br><span class="opacityhigh small">'.$langs->trans("DescVentilDoneSupplier").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print '<br>'.$langs->trans("ChangeAccount").' <div class="inline-block paddingbottom marginbottomonly">';
print '<br>'.$langs->trans("ChangeAccount").' <div class="inline-block paddingbottom paddingtop">';
print $formaccounting->select_account($account_parent, 'account_parent', 2, array(), 0, 0, 'maxwidth300 maxwidthonsmartphone valignmiddle');
print '<input type="submit" class="button small smallpaddingimp valignmiddle" value="'.$langs->trans("ChangeBinding").'"/></div>';
print '<br><br>';
$moreforfilter = '';
@@ -703,7 +705,7 @@ if ($result) {
}
// Ref Invoice
if (!empty($arrayfields['f.ref']['checked'])) {
print '<td class="nowraponall tdoverflowmax125">';
print '<td class="nowraponall tdoverflowmax125 cell2linesheight">';
print $facturefournisseur_static->getNomUrl(1);
if ($objp->ref_supplier) {
print '<br><span class="opacitymedium small">'.dol_escape_htmltag($objp->ref_supplier).'</span>';
@@ -731,7 +733,7 @@ if ($result) {
}
// Ref Product
if (!empty($arrayfields['p.ref']['checked'])) {
print '<td class="tdoverflowmax100">';
print '<td class="tdoverflowmax100 cell2linesheight">';
if ($productstatic->id > 0) {
print $productstatic->getNomUrl(1);
}
@@ -739,7 +741,7 @@ if ($result) {
print '<br>';
}
if ($objp->product_label) {
print '<span class="opacitymedium">'.$objp->product_label.'</span>';
print '<span class="opacitymedium">'.dolPrintHTML($objp->product_label).'</span>';
}
print '</td>';
$totalarray['nbfield']++;
@@ -747,7 +749,7 @@ if ($result) {
// Description
if (!empty($arrayfields['l.description']['checked'])) {
$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->description, 1));
print '<td class="tdoverflowmax200 small" title="'.dol_escape_htmltag($text).'">';
print '<td class="tdoverflowmax200 small" title="'.dolPrintHTMLForAttribute($text).'">';
$trunclength = getDolGlobalInt('ACCOUNTING_LENGTH_DESCRIPTION', 32);
print $form->textwithtooltip(dol_trunc($text, $trunclength), $objp->description);
print '</td>';
@@ -770,21 +772,22 @@ if ($result) {
}
// Country
if (!empty($arrayfields['co.label']['checked'])) {
print '<td class="tdoverflowmax100">';
print '<td class="tdoverflowmax100" title="'.dolPrintHTMLForAttribute($langs->trans("Country".$objp->country_code).' ('.$objp->country_code.')').'">';
if ($objp->country_code) {
print $langs->trans("Country".$objp->country_code).' ('.$objp->country_code.')';
print $langs->trans("Country".$objp->country_code);
//print ' ('.$objp->country_code.')';
}
print '</td>';
$totalarray['nbfield']++;
}
// TVA Intracom
if (!empty($arrayfields['s.tva_intra']['checked'])) {
print '<td class="tdoverflowmax80" title="'.dol_escape_htmltag($objp->tva_intra).'">'.dol_escape_htmltag($objp->tva_intra).'</td>';
print '<td class="tdoverflowmax80" title="'.dolPrintHTMLForAttribute($objp->tva_intra).'">'.dol_escape_htmltag($objp->tva_intra).'</td>';
$totalarray['nbfield']++;
}
// Account
if (!empty($arrayfields['aa.account_number']['checked'])) {
print '<td class="tdoverflowmax200" title="'.dol_escape_htmltag($accountingaccountstatic->label).'">';
print '<td class="tdoverflowmax200" title="'.dolPrintHTMLForAttribute($accountingaccountstatic->label).'">';
print '<a class="editfielda" href="./card.php?id='.$objp->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"].($param ? '?'.$param : '')).'">';
print img_edit();
print '</a> ';

View File

@@ -29,7 +29,14 @@
* \brief Ventilation page from suppliers invoices
*/
require '../../main.inc.php';
/**
* @var Conf $conf
* @var DoliDB $db
* @var HookManager $hookmanager
* @var Societe $mysoc
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
@@ -41,15 +48,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.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("bills", "companies", "compta", "accountancy", "other", "productbatch", "products"));
@@ -516,7 +514,8 @@ if ($result) {
print '<input type="hidden" name="page" value="'.$page.'">';
// @phan-suppress-next-line PhanPluginSuspiciousParamOrder
print_barre_liste($langs->trans("InvoiceLines").'<br><span class="opacitymedium small">'.$langs->trans("DescVentilTodoCustomer").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, (string) $massactionbutton, $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print_barre_liste($langs->trans("InvoiceLines").'<br><span class="opacityhigh small">'.$langs->trans("DescVentilTodoCustomer").'</span>', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, (string) $massactionbutton, $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit, 0, 0, 1);
print '<br>';
if ($massaction == 'set_default_account') {
$formquestion = array();
@@ -850,7 +849,7 @@ if ($result) {
}
// Ref Invoice
if (!empty($arrayfields['f.ref']['checked'])) {
print '<td class="nowraponall">'.$facturefourn_static->getNomUrl(1);
print '<td class="nowraponall cell2linesheight">'.$facturefourn_static->getNomUrl(1);
if ($objp->ref_supplier) {
print '<br><span class="opacitymedium small">'.dol_escape_htmltag($objp->ref_supplier).'</span>';
}
@@ -866,7 +865,7 @@ if ($result) {
// Supplier invoice label
if (!empty($arrayfields['f.libelle']['checked'])) {
print '<td class="tdoverflowmax100 small" title="'.dol_escape_htmltag($objp->invoice_label).'">';
print dol_escape_htmltag($objp->invoice_label);
print dolPrintHTML($objp->invoice_label);
print '</td>';
$totalarray['nbfield']++;
}
@@ -877,7 +876,7 @@ if ($result) {
}
// Ref Product
if (!empty($arrayfields['p.ref']['checked'])) {
print '<td class="tdoverflowmax100">';
print '<td class="tdoverflowmax100 cell2linesheight">';
if ($product_static->id > 0) {
print $product_static->getNomUrl(1);
}
@@ -908,7 +907,7 @@ if ($result) {
//if ($objp->vat_tx_l != $objp->vat_tx_p && price2num($objp->vat_tx_p) && price2num($objp->vat_tx_l)) { // Note: having a vat rate of 0 is often the normal case when sells is intra b2b or to export
// $code_vat_differ = 'warning bold';
//}
print '<td class="right'.($code_vat_differ ? ' '.$code_vat_differ : '').'">';
print '<td class="right cell2linesheight'.($code_vat_differ ? ' '.$code_vat_differ : '').'">';
print vatrate($facturefourn_static_det->tva_tx.($facturefourn_static_det->vat_src_code ? ' ('.$facturefourn_static_det->vat_src_code.')' : ''), false, 0, 0, 1);
print '</td>';
$totalarray['nbfield']++;
@@ -933,7 +932,7 @@ if ($result) {
}
// Found accounts
if (!empty($arrayfields['aa.data_suggest']['checked'])) {
print '<td class="small">';
print '<td class="small cell2linesheight">';
$s = '1. '.(($facturefourn_static_det->product_type == 1) ? $langs->trans("DefaultForService") : $langs->trans("DefaultForProduct")).': ';
$shelp = '';
$ttype = 'help';

View File

@@ -1189,9 +1189,12 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
print '<tr><td id="tdcompany">'.$langs->trans("Company").' / '.$langs->trans("Organization").'</td><td>'.img_picto('', 'company', 'class="pictofixedwidth"').'<input type="text" name="societe" class="minwidth300" maxlength="128" value="'.(GETPOSTISSET('societe') ? GETPOST('societe', 'alphanohtml') : $soc->name).'"></td></tr>';
// Civility
print '<tr><td>'.$langs->trans("UserTitle").'</td><td>';
print $formcompany->select_civility(GETPOSTISSET('civility_code') ? GETPOST('civility_code') : $object->civility_code, 'civility_code', 'maxwidth150', 1).'</td>';
print '</tr>';
if (getDolGlobalString('MAIN_USE_TITLE_FOR_MEMBER')) {
print '<tr><td>'.$langs->trans("UserTitle").'</td><td>';
print $formcompany->select_civility(GETPOSTISSET('civility_code') ? GETPOST('civility_code', "aZ09") : $object->civility_code, 'civility_code', 'maxwidth150', 1);
print '</td>';
print '</tr>';
}
// Lastname
print '<tr><td id="tdlastname">'.$langs->trans("Lastname").'</td><td><input type="text" name="lastname" class="minwidth300" maxlength="50" value="'.(GETPOSTISSET('lastname') ? GETPOST('lastname', 'alphanohtml') : $object->lastname).'"></td>';
@@ -1415,18 +1418,20 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
$morphys["mor"] = $langs->trans("Moral");
$checkednature = (GETPOSTISSET("morphy") ? GETPOST("morphy", 'alpha') : $object->morphy);
print '<tr><td><span class="fieldrequired">'.$langs->trans("MemberNature").'</span></td><td>';
print '<span id="spannature1" class="member-individual-back spannature paddinglarge marginrightonly"><label for="phisicalinput" class="valignmiddle">'.$morphys["phy"].'<input id="phisicalinput" class="flat checkforselect marginleftonly valignmiddle" type="radio" name="morphy" value="phy"'.($checkednature == "phy" ? ' checked="checked"' : '').'></label></span>';
print '<span id="spannature1" class="member-company-back spannature paddinglarge marginrightonly"><label for="moralinput" class="valignmiddle">'.$morphys["mor"].'<input id="moralinput" class="flat checkforselect marginleftonly valignmiddle" type="radio" name="morphy" value="mor"'.($checkednature == "mor" ? ' checked="checked"' : '').'></label></span>';
print '<span id="spannature1" class="member-individual-back spannature paddinglarge marginrightonly"><label for="phisicalinput" class="">'.$morphys["phy"].'<input id="phisicalinput" class="flat checkforselect marginleftonly valignmiddle" type="radio" name="morphy" value="phy"'.($checkednature == "phy" ? ' checked="checked"' : '').'></label></span>';
print '<span id="spannature1" class="member-company-back spannature paddinglarge marginrightonly"><label for="moralinput" class="">'.$morphys["mor"].'<input id="moralinput" class="flat checkforselect marginleftonly valignmiddle" type="radio" name="morphy" value="mor"'.($checkednature == "mor" ? ' checked="checked"' : '').'></label></span>';
print "</td></tr>";
// Company
print '<tr><td id="tdcompany">'.$langs->trans("Company").'</td><td>'.img_picto('', 'company', 'class="pictofixedwidth"').'<input type="text" name="societe" class="minwidth300" maxlength="128" value="'.(GETPOSTISSET("societe") ? GETPOST("societe", 'alphanohtml', 2) : $object->company).'"></td></tr>';
// Civility
print '<tr><td>'.$langs->trans("UserTitle").'</td><td>';
print $formcompany->select_civility(GETPOSTISSET("civility_code") ? GETPOST("civility_code", 'alpha') : $object->civility_code, 'civility_code', 'maxwidth150', 1);
print '</td>';
print '</tr>';
if (getDolGlobalString('MAIN_USE_TITLE_FOR_MEMBER')) {
print '<tr><td>'.$langs->trans("UserTitle").'</td><td>';
print $formcompany->select_civility(GETPOSTISSET("civility_code") ? GETPOST("civility_code", 'aZ09') : $object->civility_code, 'civility_code', 'maxwidth150', 1);
print '</td>';
print '</tr>';
}
// Lastname
print '<tr><td id="tdlastname">'.$langs->trans("Lastname").'</td><td><input type="text" name="lastname" class="minwidth300" maxlength="50" value="'.(GETPOSTISSET("lastname") ? GETPOST("lastname", 'alphanohtml', 2) : $object->lastname).'"></td>';

View File

@@ -5,7 +5,7 @@
* Copyright (C) 2016 Charlie Benke <charlie@patas-monkey.com>
* Copyright (C) 2018-2019 Thibault Foucart <support@ptibogxiv.net>
* Copyright (C) 2021 Waël Almoman <info@almoman.com>
* Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
*
* This program is free software; you can redistribute it and/or modify
@@ -305,9 +305,10 @@ class AdherentType extends CommonObject
* Update or add a translation for this member type
*
* @param User $user Object user making update
* @param int $notrigger Do not execute trigger
* @return int Return integer <0 if KO, >0 if OK
*/
public function setMultiLangs($user)
public function setMultiLangs($user, $notrigger = 0)
{
global $langs;
@@ -375,13 +376,15 @@ class AdherentType extends CommonObject
}
}
// Call trigger
$result = $this->call_trigger('MEMBER_TYPE_SET_MULTILANGS', $user);
if ($result < 0) {
$this->error = $this->db->lasterror();
return -1;
if (empty($notrigger)) {
// Call trigger
$result = $this->call_trigger('MEMBER_TYPE_SET_MULTILANGS', $user);
if ($result < 0) {
$this->error = $this->db->lasterror();
return -1;
}
// End call triggers
}
// End call triggers
return 1;
}
@@ -391,9 +394,10 @@ class AdherentType extends CommonObject
*
* @param string $langtodelete Language code to delete
* @param User $user Object user making delete
* @return int Return integer <0 if KO, >0 if OK
* @param int $notrigger Do not execute trigger
* @return int Return integer <0 if KO, >0 if OK
*/
public function delMultiLangs($langtodelete, $user)
public function delMultiLangs($langtodelete, $user, $notrigger = 0)
{
$sql = "DELETE FROM ".MAIN_DB_PREFIX."adherent_type_lang";
$sql .= " WHERE fk_type = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
@@ -401,14 +405,16 @@ class AdherentType extends CommonObject
dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
$result = $this->db->query($sql);
if ($result) {
// Call trigger
$result = $this->call_trigger('MEMBER_TYPE_DEL_MULTILANGS', $user);
if ($result < 0) {
$this->error = $this->db->lasterror();
dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
return -1;
if (empty($notrigger)) {
// Call trigger
$result = $this->call_trigger('MEMBER_TYPE_DEL_MULTILANGS', $user);
if ($result < 0) {
$this->error = $this->db->lasterror();
dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
return -1;
}
// End call triggers
}
// End call triggers
return 1;
} else {
$this->error = $this->db->lasterror();
@@ -523,7 +529,7 @@ class AdherentType extends CommonObject
// Multilangs
if (getDolGlobalInt('MAIN_MULTILANGS')) {
if ($this->setMultiLangs($user) < 0) {
if ($this->setMultiLangs($user, $notrigger) < 0) {
$this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
return -2;
}
@@ -927,23 +933,68 @@ class AdherentType extends CommonObject
* Return translated label by the nature of a adherent (physical or moral)
*
* @param string $morphy Nature of the adherent (physical or moral)
* @param int<0,2> $addbadge Add badge (1=Full label, 2=First letters only)
* @return string Label
*/
public function getmorphylib($morphy = '')
public function getmorphylib($morphy = '', $addbadge = 0)
{
global $langs;
$s = '';
if ($morphy == 'phy') {
return $langs->trans("Physical");
$s = $langs->trans("Physical");
} elseif ($morphy == 'mor') {
return $langs->trans("Moral");
$s = $langs->trans("Moral");
} else {
return $langs->trans("MorAndPhy");
$s = $langs->trans("MorAndPhy");
}
//return $morphy;
if ($addbadge) {
$labeltoshowm = $langs->trans("Moral");
$labeltoshowp = $langs->trans("Physical");
$labeltoshow = $s;
if ($morphy === 'phy') {
if ($addbadge == 2) {
$labeltoshow = dol_strtoupper(dolGetFirstLetters($labeltoshowp));
if ($labeltoshow == dol_strtoupper(dolGetFirstLetters($labeltoshow))) {
$labeltoshow = dol_strtoupper(dolGetFirstLetters($labeltoshow, 2));
}
}
$s = '<span class="member-individual-back paddingleftimp paddingrightimp" title="'.$langs->trans("Physical").'">'.$labeltoshow.'</span>';
}
if ($morphy === 'mor') {
if ($addbadge == 2) {
$labeltoshow = dol_strtoupper(dolGetFirstLetters($labeltoshowm));
if ($labeltoshow == dol_strtoupper(dolGetFirstLetters($labeltoshow))) {
$labeltoshow = dol_strtoupper(dolGetFirstLetters($labeltoshow, 2));
}
}
$s = '<span class="member-company-back paddingleftimp paddingrightimp" title="'.$langs->trans("Moral").'">'.$labeltoshow.'</span>';
}
if ($morphy === '') {
if ($addbadge == 2) {
$labeltoshow1 = dol_strtoupper(dolGetFirstLetters($labeltoshowp));
if ($labeltoshow1 == dol_strtoupper(dolGetFirstLetters($labeltoshow1))) {
$labeltoshow1 = dol_strtoupper(dolGetFirstLetters($labeltoshow1, 2));
}
$labeltoshow2 = dol_strtoupper(dolGetFirstLetters($labeltoshowm));
if ($labeltoshow2 == dol_strtoupper(dolGetFirstLetters($labeltoshow2))) {
$labeltoshow2 = dol_strtoupper(dolGetFirstLetters($labeltoshow2, 2));
}
$labeltoshow = $labeltoshow1.' '.$langs->trans("or").' '.$labeltoshow2;
}
$s = '<span class="member-individual-company-back paddingleftimp paddingrightimp" title="'.$langs->trans("MorAndPhy").'">'.$labeltoshow.'</span>';
}
}
return $s;
}
/**
* getTooltipContentArray
*
* @param array<string,mixed> $params params to construct tooltip data
* @since v18
* @return array{picto?:string,ref?:string,refsupplier?:string,label?:string,date?:string,date_echeance?:string,amountht?:string,total_ht?:string,totaltva?:string,amountlt1?:string,amountlt2?:string,amountrevenustamp?:string,totalttc?:string}|array{optimize:string}

View File

@@ -31,12 +31,6 @@
// Load Dolibarr environment
require '../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php';
require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent_type.class.php';
require_once DOL_DOCUMENT_ROOT.'/adherents/class/subscription.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
/**
* @var Conf $conf
* @var DoliDB $db
@@ -44,6 +38,10 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php';
require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent_type.class.php';
require_once DOL_DOCUMENT_ROOT.'/adherents/class/subscription.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
// Load translation files required by the page
$langs->loadLangs(array("companies", "members"));
@@ -108,7 +106,7 @@ if ($conf->use_javascript_ajax) {
$boxgraph .= '<div class="div-table-responsive-no-min">';
$boxgraph .= '<table class="noborder nohover centpercent">';
$boxgraph .= '<tr class="liste_titre"><th colspan="2">'.$langs->trans("Statistics").($numberyears ? ' ('.($year - $numberyears).' - '.$year.')' : '').'</th></tr>';
$boxgraph .= '<tr class="liste_titre"><th colspan="2">'.$langs->trans("Statistics").' - '.$langs->trans("Status").' '.($numberyears ? ' ('.($year - $numberyears).' - '.$year.')' : '').'</th></tr>';
$boxgraph .= '<tr><td class="center" colspan="2">';
require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherentstats.class.php';

View File

@@ -1334,7 +1334,7 @@ while ($i < $imaxinloop) {
}
// Firstname
if (!empty($arrayfields['d.firstname']['checked'])) {
print '<td class="tdoverflowmax125" title="'.dol_escape_htmltag($obj->firstname).'">';
print '<td class="tdoverflowmax125">';
print $memberstatic->getNomUrl(0, 0, 'card', 'firstname');
//print $obj->firstname;
print "</td>\n";
@@ -1344,7 +1344,7 @@ while ($i < $imaxinloop) {
}
// Lastname
if (!empty($arrayfields['d.lastname']['checked'])) {
print '<td class="tdoverflowmax125" title="'.dol_escape_htmltag($obj->lastname).'">';
print '<td class="tdoverflowmax125">';
print $memberstatic->getNomUrl(0, 0, 'card', 'lastname');
//print $obj->lastname;
print "</td>\n";
@@ -1365,7 +1365,7 @@ while ($i < $imaxinloop) {
}
// Company
if (!empty($arrayfields['d.societe']['checked'])) {
print '<td class="tdoverflowmax125" title="'.dolPrintHTMLForAttribute((string) $companyname).'">';
print '<td class="tdoverflowmax125">';
print $companynametoshow;
print "</td>\n";
}

View File

@@ -44,10 +44,6 @@ $mode = GETPOST('mode') ? GETPOST('mode') : '';
// Security check
if ($user->socid > 0) {
$action = '';
$socid = $user->socid;
}
restrictedArea($user, 'adherent', '', '', 'cotisation');
$year = (int) dol_print_date(dol_now('gmt'), "%Y", 'gmt');
@@ -187,7 +183,10 @@ foreach ($data as $val) {
$nbactive = $val['nbactive'];
print '<tr class="oddeven">';
print '<td>'.$memberstatic->getmorphylib($val['label']).'</td>';
print '<td>';
print $memberstatic->getmorphylib($val['label'], 1);
print '</td>';
print '<td class="right">'.$nb.'</td>';
print '<td class="right">'.$nbactive.'</td>';
print '<td class="center">'.dol_print_date($val['lastdate'], 'dayhour', 'auto', null, false, 1).'</td>';

View File

@@ -473,13 +473,7 @@ if (!$rowid && $action != 'create' && $action != 'edit') {
}
if (!empty($arrayfields['t.morphy']['checked'])) {
print '<td class="center">';
if ($objp->morphy == 'phy') {
print $langs->trans("Physical");
} elseif ($objp->morphy == 'mor') {
print $langs->trans("Moral");
} else {
print $langs->trans("MorAndPhy");
}
print $membertype->getmorphylib($objp->morphy, 1);
print '</td>';
}
if (!empty($arrayfields['t.duration']['checked'])) {
@@ -502,8 +496,12 @@ if (!$rowid && $action != 'create' && $action != 'edit') {
if (!empty($arrayfields['t.caneditamount']['checked'])) {
print '<td class="center">'.yn($objp->caneditamount).'</td>';
}
// Minimum amount
if (!empty($arrayfields['t.minimumamount']['checked'])) {
print '<td class="center">'.price($objp->minimumamount).'</td>';
print '<td class="center">';
$minimumamount = ((is_null($objp->minimumamount) || $objp->minimumamount === '') ? '' : price($objp->minimumamount));
print $minimumamount;
print '</td>';
}
if (!empty($arrayfields['t.amount']['checked'])) {
print '<td class="center">';
@@ -603,7 +601,7 @@ if ($action == 'create') {
print '</td></tr>';
print '<tr><td>'.$langs->trans("MinimumAmountShort").'</td><td>';
print '<input name="minimumamount" size="5" value="'.(GETPOSTISSET('minimumamount') ? GETPOST('minimumamount') : price($minimumamount)).'">';
print '<input name="minimumamount" size="5" value="'.(GETPOSTISSET('minimumamount') ? GETPOST('minimumamount') : ($minimumamount ? price($minimumamount): '')).'">';
print '</td></tr>';
print '<tr><td>'.$langs->trans("RecommendedAmount").'</td><td>';
@@ -676,7 +674,9 @@ if ($rowid > 0) {
print '<table class="tableforfield border centpercent">';
// Morphy
print '<tr><td>'.$langs->trans("MembersNature").'</td><td class="valeur" >'.$object->getmorphylib($object->morphy).'</td>';
print '<tr><td>'.$langs->trans("MembersNature").'</td><td class="valeur" >';
print $object->getmorphylib($object->morphy, 1);
print '</td>';
print '</tr>';
print '<tr><td>'.$form->textwithpicto($langs->trans("SubscriptionRequired"), $langs->trans("SubscriptionRequiredDesc")).'</td><td>';

View File

@@ -30,11 +30,6 @@
// Load Dolibarr environment
require '../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/defaultvalues.class.php';
/**
* @var Conf $conf
* @var DoliDB $db
@@ -42,6 +37,10 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/defaultvalues.class.php';
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/defaultvalues.class.php';
// Load translation files required by the page
$langs->loadLangs(array('companies', 'products', 'admin', 'sms', 'other', 'errors'));
@@ -52,6 +51,7 @@ if (!$user->admin) {
$id = GETPOSTINT('rowid');
$action = GETPOST('action', 'aZ09');
$contextpage = GETPOST('contextpage', 'aZ09');
$optioncss = GETPOST('optionscss', 'alphanohtml');
$mode = GETPOST('mode', 'aZ09') ? GETPOST('mode', 'aZ09') : 'createform'; // 'createform', 'filters', 'sortorder', 'focus'
@@ -215,18 +215,22 @@ llxHeader('', $langs->trans("Setup"), $wikihelp, '', 0, 0, '', '', '', 'mod-admi
$param = '&mode='.$mode;
$enabledisablehtml = $langs->trans("EnableDefaultValues").' ';
$enabledisablehtml = '<span class="divfilteralone">';
if (!getDolGlobalString('MAIN_ENABLE_DEFAULT_VALUES')) {
// Button off, click to enable
$enabledisablehtml .= '<a class="reposition valignmiddle" href="'.$_SERVER["PHP_SELF"].'?action=setMAIN_ENABLE_DEFAULT_VALUES&token='.newToken().'&value=1'.$param.'">';
$enabledisablehtml .= img_picto($langs->trans("Disabled"), 'switch_off');
$enabledisablehtml .= '</a>';
$enabledisablehtml .= '<a class="reposition valignmiddle nounderlineimp" href="'.$_SERVER["PHP_SELF"].'?action=setMAIN_ENABLE_DEFAULT_VALUES&token='.newToken().'&value=1'.$param.'">';
} else {
// Button on, click to disable
$enabledisablehtml .= '<a class="reposition valignmiddle" href="'.$_SERVER["PHP_SELF"].'?action=setMAIN_ENABLE_DEFAULT_VALUES&token='.newToken().'&value=0'.$param.'">';
$enabledisablehtml .= img_picto($langs->trans("Activated"), 'switch_on');
$enabledisablehtml .= '</a>';
$enabledisablehtml .= '<a class="reposition valignmiddle nounderlineimp" href="'.$_SERVER["PHP_SELF"].'?action=setMAIN_ENABLE_DEFAULT_VALUES&token='.newToken().'&value=0'.$param.'">';
}
$enabledisablehtml .= $langs->trans("EnableDefaultValues");
if (!getDolGlobalString('MAIN_ENABLE_DEFAULT_VALUES')) {
$enabledisablehtml .= img_picto($langs->trans("Disabled"), 'switch_off', 'class="paddingleft valignmiddle"');
} else {
$enabledisablehtml .= img_picto($langs->trans("Activated"), 'switch_on', 'class="paddingleft valignmiddle"');
}
$enabledisablehtml .= '</a>';
$enabledisablehtml .= '</span>';
print load_fiche_titre($langs->trans("DefaultValues"), $enabledisablehtml, 'title_setup');

View File

@@ -805,6 +805,18 @@ if ($conf->use_javascript_ajax) {
print '</td>';
print '</tr>';
// EMAILCOLLECTOR_ISNOTANSWER_USE_REFERENCES: Consider generic References as answer for "isnotanswer" filter.
print '<tr class="oddeven"><td>'.$form->textwithpicto($langs->trans("EmailCollectorIsNotAnswerUseReferences"), $langs->transnoentitiesnoconv("EmailCollectorIsNotAnswerUseReferencesHelp")).'</td>';
print '<td class="left">';
if ($conf->use_javascript_ajax) {
print ajax_constantonoff('EMAILCOLLECTOR_ISNOTANSWER_USE_REFERENCES');
} else {
$arrval = array('0' => $langs->trans("No"), '1' => $langs->trans("Yes"));
print $form->selectarray("EMAILCOLLECTOR_ISNOTANSWER_USE_REFERENCES", $arrval, getDolGlobalInt('EMAILCOLLECTOR_ISNOTANSWER_USE_REFERENCES', 0));
}
print '</td>';
print '</tr>';
print '</table>';
print '</div>';

View File

@@ -156,7 +156,7 @@ if (empty($reshook)) {
}
if (!$error) {
header('Location: ' . $_SERVER['PHP_SELF']);
header('Location: '.DOL_URL_ROOT.'/admin/expensereport_rules.php');
exit;
} else {
$action = '';
@@ -170,7 +170,7 @@ if (empty($reshook)) {
dol_print_error($object->db);
}
header('Location: ' . $_SERVER['PHP_SELF']);
header('Location: ' . DOL_URL_ROOT.'/admin/expensereport_rules.php');
exit;
}

View File

@@ -122,8 +122,8 @@ print '<br><br>';
print $langs->trans("SetupDescription3b");
if (!empty($setupcompanynotcomplete)) {
$langs->load("errors");
$warnpicto = img_warning($langs->trans("WarningMandatorySetupNotComplete"), 'style="padding-right: 6px;"');
print '<br><div class="warning"><a href="'.DOL_URL_ROOT.'/admin/company.php?mainmenu=home&action=edit&token='.newToken().'">'.$warnpicto.' '.$langs->trans("WarningMandatorySetupNotComplete").'</a></div>';
$warnpicto = img_warning($langs->trans("WarningMandatorySetupNotComplete"), 'style="padding-right: 10px;"');
print '<br><div class="warning marginrightonly"><a href="'.DOL_URL_ROOT.'/admin/company.php?mainmenu=home&action=edit&token='.newToken().'">'.$warnpicto.$langs->trans("WarningMandatorySetupNotComplete").'</a></div>';
}
print '</a>';
@@ -132,7 +132,6 @@ print '</section>';
print '<br>';
print '<br>';
// Show info setup modules
print '<section class="setupsection setupmodules cursorpointer">';
@@ -153,8 +152,8 @@ print '<a class="nounderlineimp fontsize-1-1" href="'.DOL_URL_ROOT.'/admin/modul
print '<br><br>'.$langs->trans("SetupDescription4b");
if ($nbmodulesnotautoenabled < getDolGlobalInt('MAIN_MIN_NB_ENABLED_MODULE_FOR_WARNING', 1)) { // If only minimal initial modules enabled
$langs->load("errors");
$warnpicto = img_warning($langs->trans("WarningEnableYourModulesApplications"), 'style="padding-right: 6px;"');
print '<br><div class="warning"><a href="'.DOL_URL_ROOT.'/admin/modules.php?mainmenu=home">'.$warnpicto.$langs->trans("WarningEnableYourModulesApplications").'</a></div>';
$warnpicto = img_warning($langs->trans("WarningEnableYourModulesApplications"), 'style="padding-right: 10px;"');
print '<br><div class="warning marginrightonly"><a href="'.DOL_URL_ROOT.'/admin/modules.php?mainmenu=home">'.$warnpicto.$langs->trans("WarningEnableYourModulesApplications").'</a></div>';
}
print '</section>';

View File

@@ -27,16 +27,16 @@
// Load Dolibarr environment
require '../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
/**
* @var Conf $conf
* @var DoliDB $db
* @var HookManager $hookmanager
* @var Societe $mysoc
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
// Load translation files required by the page
$langs->loadLangs(array('companies', 'products', 'admin'));

View File

@@ -775,7 +775,7 @@ if ($mode == 'common' || $mode == 'commonkanban') {
$moreforfilter .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=commonkanban'.$param, '', ($mode == 'commonkanban' ? 2 : 1), array('morecss' => 'reposition'));
$moreforfilter .= '</li></ul></div>';
$moreforfilter .= '<div class="divfilteralone colorbacktimesheet float valignmiddle">';
$moreforfilter .= '<div class="divfilteralone colorbacktimesheet float valignmiddle nopaddingtopimp nopaddingbottomimp">';
$moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
$moreforfilter .= img_picto($langs->trans("Filter"), 'filter', 'class="paddingright opacityhigh hideonsmartphone"').'<input type="text" id="search_keyword" name="search_keyword" class="maxwidth125" value="'.dol_escape_htmltag($search_keyword).'" spellcheck="false" placeholder="'.dol_escape_htmltag($langs->trans('Keyword')).'">';
$moreforfilter .= '</div>';

View File

@@ -101,7 +101,9 @@ if ($action == 'update') {
}
}
if (GETPOSTISSET($constvalue.'_URL')) {
if (!dolibarr_set_const($db, $newconstvalue.'_URL', GETPOST($constvalue.'_URL'), 'chaine', 0, '', $conf->entity)) {
$cleanurl = GETPOST($constvalue.'_URL');
$cleanurl = preg_replace('/\/$/', '', $cleanurl);
if (!dolibarr_set_const($db, $newconstvalue.'_URL', $cleanurl, 'chaine', 0, '', $conf->entity)) {
$error++;
}
}
@@ -443,7 +445,7 @@ if (count($listinsetup) > 0) {
print '</tr>';
// Tenant
if ($keybeforeprovider == 'MICROSOFT' || $keybeforeprovider == 'MICROSOFT2') {
if ($keybeforeprovider == 'MICROSOFT' || $keybeforeprovider == 'MICROSOFT2' || $keybeforeprovider == 'MICROSOFT3') {
print '<tr class="oddeven value">';
print '<td><label for="'.$key[2].'">'.$langs->trans("OAUTH_TENANT").'</label></td>';
print '<td><input type="text" size="100" id="OAUTH_'.$keybeforeprovider.($keyforprovider ? '-'.$keyforprovider : '').'_TENANT" name="OAUTH_'.$keybeforeprovider.($keyforprovider ? '-'.$keyforprovider : '').'_TENANT" value="'.getDolGlobalString('OAUTH_'.$keybeforeprovider.($keyforprovider ? '-'.$keyforprovider : '').'_TENANT').'">';

View File

@@ -66,7 +66,6 @@ if (!$user->admin) {
accessforbidden();
}
/*
* Action
*/
@@ -433,7 +432,7 @@ if ($mode == 'setup' && $user->admin) {
print '<td>';
if ($keyforprovider != 'Login') {
if (is_object($tokenobj)) {
print $form->textwithpicto(yn(1), $langs->trans("HasAccessToken").' : '.dol_print_date($storage->date_modification, 'dayhour').' state='.dol_escape_htmltag($storage->state));
print $form->textwithpicto(yn(1), $langs->trans("HasAccessToken").' : '.dol_print_date($storage->date_modification, 'dayhour').'<br>Scopes saved into field state='.dol_escape_htmltag($storage->state));
} else {
print '<span class="opacitymedium">'.$langs->trans("NoAccessToken").'</span>';
}

View File

@@ -27,7 +27,6 @@ require '../main.inc.php';
require_once DOL_DOCUMENT_ROOT . '/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT . '/core/lib/openid_connect.lib.php';
require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php';
require_once DOL_DOCUMENT_ROOT . '/core/lib/openid_connect.lib.php';
/**
* @var Conf $conf
@@ -118,6 +117,41 @@ if ($action == 'set') {
$errors[] = $db->lasterror();
$error++;
}
$value = GETPOST('MAIN_AUTHENTICATION_OIDC_DEFAULT_GROUP', 'int');
$res = dolibarr_set_const($db, 'MAIN_AUTHENTICATION_OIDC_DEFAULT_GROUP', $value, 'chaine', 0, '', 0);
if (!$res > 0) {
$errors[] = $db->lasterror();
$error++;
}
$value = GETPOSTINT('MAIN_AUTHENTICATION_OIDC_DEFAULT_CREATOR');
$res = dolibarr_set_const($db, 'MAIN_AUTHENTICATION_OIDC_DEFAULT_CREATOR', $value, 'chaine', 0, '', 0);
if (!$res > 0) {
$errors[] = $db->lasterror();
$error++;
}
$value = GETPOST('MAIN_AUTHENTICATION_OIDC_CLAIM_FIRSTNAME', 'alpha');
$res = dolibarr_set_const($db, 'MAIN_AUTHENTICATION_OIDC_CLAIM_FIRSTNAME', $value, 'chaine', 0, '', 0);
if (!$res > 0) {
$errors[] = $db->lasterror();
$error++;
}
$value = GETPOST('MAIN_AUTHENTICATION_OIDC_CLAIM_LASTNAME', 'alpha');
$res = dolibarr_set_const($db, 'MAIN_AUTHENTICATION_OIDC_CLAIM_LASTNAME', $value, 'chaine', 0, '', 0);
if (!$res > 0) {
$errors[] = $db->lasterror();
$error++;
}
$value = GETPOST('MAIN_AUTHENTICATION_OIDC_CLAIM_EMAIL', 'alpha');
$res = dolibarr_set_const($db, 'MAIN_AUTHENTICATION_OIDC_CLAIM_EMAIL', $value, 'chaine', 0, '', 0);
if (!$res > 0) {
$errors[] = $db->lasterror();
$error++;
}
}
if ($action != '') {
@@ -300,8 +334,70 @@ if (getDolGlobalString('MAIN_AUTHENTICATION_OIDC_ON')) {
print '<input name="MAIN_AUTHENTICATION_OPENID_URL_IMG" id="MAIN_AUTHENTICATION_OPENID_URL_IMG" class="minwidth400 centpercent" value="' . dol_escape_htmltag((GETPOSTISSET('MAIN_AUTHENTICATION_OPENID_URL_IMG') ? GETPOST('MAIN_AUTHENTICATION_OPENID_URL_IMG', 'nohtml') : (getDolGlobalString('MAIN_AUTHENTICATION_OPENID_URL_IMG') ? getDolGlobalString("MAIN_AUTHENTICATION_OPENID_URL_IMG") : ''))) . '">';
print '</td></tr>' . "\n";
print '</table>' . "\n";
print '</div>';
// --- User Auto-Creation Settings ---
print '</table></div>' . "\n";
$langs->load("errors");
global $dolibarr_main_authentication_autocreateuser;
if (empty($dolibarr_main_authentication_autocreateuser)) {
print info_admin($langs->trans("OIDCAutocreateUserDisabled"), 0, 0, 1, 'warning');
} else {
print info_admin($langs->trans("OIDCAutocreateUserEnabled"), 0, 0, 1, 'success');
}
if (!empty($dolibarr_main_authentication_autocreateuser)) {
print '<div class="div-table-responsive-no-min">';
print '<table class="tagtable noborder liste nobottomiftotal">';
print '<tr class="liste_titre">';
print '<th class="liste_titre" colspan="3">' . $langs->trans("MainAuthenticationOidcAutoCreateTitle") . '</th>' . "\n";
print "</tr>\n";
// MAIN_AUTHENTICATION_OIDC_DEFAULT_CREATOR
$form = new Form($db);
print '<tr class="oddeven">' . "\n";
print '<td>' . $langs->trans("MainAuthenticationOidcDefaultCreatorName") . '</td>' . "\n";
print '<td>' . $langs->trans("MainAuthenticationOidcDefaultCreatorDesc") . '</td>' . "\n";
print '<td align="right">' . "\n";
$creator_val = GETPOSTISSET('MAIN_AUTHENTICATION_OIDC_DEFAULT_CREATOR') ? GETPOSTINT('MAIN_AUTHENTICATION_OIDC_DEFAULT_CREATOR') : getDolGlobalInt('MAIN_AUTHENTICATION_OIDC_DEFAULT_CREATOR');
print $form->select_dolusers($creator_val, 'MAIN_AUTHENTICATION_OIDC_DEFAULT_CREATOR', 1, null, 0, '', '', '', 0, 0, '(admin:=:1) AND (statut:=:1)', 0, '', 'minwidth200 maxwidth500');
print '</td></tr>' . "\n";
// MAIN_AUTHENTICATION_OIDC_DEFAULT_GROUP
print '<tr class="oddeven">' . "\n";
print '<td>' . $langs->trans("MainAuthenticationOidcDefaultGroupName") . '</td>' . "\n";
print '<td>' . $langs->trans("MainAuthenticationOidcDefaultGroupDesc") . '</td>' . "\n";
print '<td align="right">' . "\n";
$defaultgroup_val = GETPOSTISSET('MAIN_AUTHENTICATION_OIDC_DEFAULT_GROUP') ? GETPOSTINT('MAIN_AUTHENTICATION_OIDC_DEFAULT_GROUP') : getDolGlobalInt('MAIN_AUTHENTICATION_OIDC_DEFAULT_GROUP');
$form->select_dolgroups($defaultgroup_val, 'MAIN_AUTHENTICATION_OIDC_DEFAULT_GROUP', 1);
print '</td></tr>' . "\n";
// MAIN_AUTHENTICATION_OIDC_CLAIM_FIRSTNAME
print '<tr class="oddeven">' . "\n";
print '<td>' . $langs->trans("MainAuthenticationOidcClaimFirstnameName") . '</td>' . "\n";
print '<td>' . $langs->trans("MainAuthenticationOidcClaimFirstnameDesc") . '</td>' . "\n";
print '<td align="right">' . "\n";
print '<input name="MAIN_AUTHENTICATION_OIDC_CLAIM_FIRSTNAME" id="MAIN_AUTHENTICATION_OIDC_CLAIM_FIRSTNAME" class="minwidth400 centpercent" value="' . dol_escape_htmltag((GETPOSTISSET('MAIN_AUTHENTICATION_OIDC_CLAIM_FIRSTNAME') ? GETPOST('MAIN_AUTHENTICATION_OIDC_CLAIM_FIRSTNAME', 'nohtml') : (getDolGlobalString('MAIN_AUTHENTICATION_OIDC_CLAIM_FIRSTNAME') ? getDolGlobalString("MAIN_AUTHENTICATION_OIDC_CLAIM_FIRSTNAME") : ''))) . '" placeholder="given_name">';
print '</td></tr>' . "\n";
// MAIN_AUTHENTICATION_OIDC_CLAIM_LASTNAME
print '<tr class="oddeven">' . "\n";
print '<td>' . $langs->trans("MainAuthenticationOidcClaimLastnameName") . '</td>' . "\n";
print '<td>' . $langs->trans("MainAuthenticationOidcClaimLastnameDesc") . '</td>' . "\n";
print '<td align="right">' . "\n";
print '<input name="MAIN_AUTHENTICATION_OIDC_CLAIM_LASTNAME" id="MAIN_AUTHENTICATION_OIDC_CLAIM_LASTNAME" class="minwidth400 centpercent" value="' . dol_escape_htmltag((GETPOSTISSET('MAIN_AUTHENTICATION_OIDC_CLAIM_LASTNAME') ? GETPOST('MAIN_AUTHENTICATION_OIDC_CLAIM_LASTNAME', 'nohtml') : (getDolGlobalString('MAIN_AUTHENTICATION_OIDC_CLAIM_LASTNAME') ? getDolGlobalString("MAIN_AUTHENTICATION_OIDC_CLAIM_LASTNAME") : ''))) . '" placeholder="family_name">';
print '</td></tr>' . "\n";
// MAIN_AUTHENTICATION_OIDC_CLAIM_EMAIL
print '<tr class="oddeven">' . "\n";
print '<td>' . $langs->trans("MainAuthenticationOidcClaimEmailName") . '</td>' . "\n";
print '<td>' . $langs->trans("MainAuthenticationOidcClaimEmailDesc") . '</td>' . "\n";
print '<td align="right">' . "\n";
print '<input name="MAIN_AUTHENTICATION_OIDC_CLAIM_EMAIL" id="MAIN_AUTHENTICATION_OIDC_CLAIM_EMAIL" class="minwidth400 centpercent" value="' . dol_escape_htmltag((GETPOSTISSET('MAIN_AUTHENTICATION_OIDC_CLAIM_EMAIL') ? GETPOST('MAIN_AUTHENTICATION_OIDC_CLAIM_EMAIL', 'nohtml') : (getDolGlobalString('MAIN_AUTHENTICATION_OIDC_CLAIM_EMAIL') ? getDolGlobalString("MAIN_AUTHENTICATION_OIDC_CLAIM_EMAIL") : ''))) . '" placeholder="email">';
print '</td></tr>' . "\n";
print '</table>' . "\n";
print '</div>';
} // end if autocreateuser
print '<br>';
print '<div align="center">';

View File

@@ -1,15 +0,0 @@
# PrestaShop-webservice-lib
Source updated from:
https://github.com/PrestaShop/PrestaShop-webservice-lib/blob/master/PSWebServiceLibrary.php
## License compatibility analysis
https://www.gnu.org/licenses/license-list.html#OSL
## Local changes
- Change `executeRequest` to public method because
`htdocs/admin/remotestore/ajax/image.php#` uses it.

View File

@@ -8,6 +8,7 @@
* Copyright (C) 2011-2018 Philippe Grand <philippe.grand@atoo-net.com>
* Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
*
* 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
@@ -499,6 +500,12 @@ print '</td><td class="right">';
print '<input type="submit" class="button button-edit" value="'.$langs->trans("Modify").'">';
print "</td></tr>\n";
// Allow external download
print '<tr class="oddeven">';
print '<td>'.$langs->trans("AllowExternalDownload").'</td>';
print '<td class="left" colspan="2">';
print ajax_constantonoff('SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD', array(), null, 0, 0, 0, 2, 0, 1);
print '</td></tr>';
// Notifications
print '<tr class="oddeven">';

View File

@@ -8,6 +8,7 @@
* Copyright (C) 2011-2018 Philippe Grand <philippe.grand@atoo-net.com>
* Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
*
* 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
@@ -538,6 +539,12 @@ print '<td colspan="2">';
print ajax_constantonoff('SUPPLIER_ORDER_DISABLE_CLASSIFY_BILLED_FROM_SUPPLIER_ORDER');
print '</td></tr>';
// Allow external download
print '<tr class="oddeven">';
print '<td>'.$langs->trans("AllowExternalDownload").'</td>';
print '<td class="left" colspan="2">';
print ajax_constantonoff('SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD', array(), null, 0, 0, 0, 2, 0, 1);
print '</td></tr>';
// Notifications
print '<tr class="oddeven">';

View File

@@ -421,7 +421,7 @@ if (empty($dolibarr_main_restrict_eval_methods)) {
} else {
print $dolibarr_main_restrict_eval_methods;
}
print ' &nbsp; &nbsp; <span class="opacitymedium">('.$langs->trans("RecommendedValueIs", 'getDolGlobalString, getDolGlobalInt, getDolCurrency, getDolEntity, getDolDBType, fetchNoCompute, hasRight, isAdmin, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, dol_concat, preg_match').')</span>';
print ' &nbsp; &nbsp; <span class="opacitymedium">('.$langs->trans("RecommendedValueIs", 'getDolGlobalString, getDolGlobalInt, getDolCurrency, getDolEntity, getDolDBType, fetchNoCompute, hasRight, isAdmin, isExternalUser, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, dol_concat, preg_match').')</span>';
print '<br>';

View File

@@ -708,7 +708,7 @@ print '<tr class="oddeven"><td class="titlefieldmiddle"><label for="TICKET_SEND_
print $form->textwithpicto($langs->trans("TicketSendToInternalCC"), $langs->trans("TicketSendToInternalCCHelp")).'</label></td>';
print '<td>';
print img_picto('', 'email', 'class="pictofixedwidth"');
print '<input class="flat width300" name="TICKET_SEND_INTERNAL_CC" value="'.getDolGlobalString('TICKET_SEND_INTERNAL_CC').'">';
print '<input class="minwidth200" name="TICKET_SEND_INTERNAL_CC" value="'.getDolGlobalString('TICKET_SEND_INTERNAL_CC').'">';
print '</td>';
print '<td></td>';
print '</tr>';

View File

@@ -18,7 +18,7 @@
*/
/**
* \file htdocs/admint/tools/ui/class/uidoc.class.php
* \file htdocs/admint/tools/ui/class/documentation.class.php
* \ingroup ui
* \brief File of class to manage UI documentation
*/
@@ -155,6 +155,7 @@ class Documentation
'summary' => array(
'DocBasicUsage' => '#seteventmessagesection-basicusage',
'DocSetEventMessageContextualVariations' => '#seteventmessagesection-contextvariations',
'DocSetEventMessageJsContext' => '#titlesection-tool-seteventmessage',
)
),
'Inputs' => array(
@@ -251,6 +252,7 @@ class Documentation
'JSDolibarrhooks' => '#titlesection-hooks',
'JSDolibarrhooksReadyVsInit' => '#titlesection-event-init-vs-ready',
'JSDolibarrAwaitHooks' => '#titlesection-await-hooks',
'JSDolibarrhooksAjaxSpecial' => '#titlesection-dom-initnewcontent',
'ExampleOfCreatingNewContextTool' => '#titlesection-create-tool-example',
'SetEventMessageTool' => '#titlesection-tool-seteventmessage',
'SetAndUseContextVars' => '#titlesection-contextvars',

View File

@@ -90,7 +90,7 @@ $documentation->showSidebar(); ?>
'<h5>Example heading <span class="badge badge-secondary">New</span></h5>',
'<h6>Example heading <span class="badge badge-secondary">New</span></h6>'
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
<p class="documentation-text"><?php echo $langs->trans('DocBadgeUseOnLinksOrButtons'); ?></p>
<div class="documentation-example">
@@ -105,7 +105,7 @@ $documentation->showSidebar(); ?>
' Notifications <span class="badge badge-primary">4</span>',
'</button>',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
<div class="warning">
<p class="documentation-text"><?php echo $langs->trans('DocBadgeWarningAriaHidden1'); ?></p>
@@ -127,7 +127,7 @@ $documentation->showSidebar(); ?>
' <span class="sr-only">unread messages</span>',
'</button>',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
<!-- Contextual variations -->
@@ -155,7 +155,7 @@ $documentation->showSidebar(); ?>
'<span class="badge badge-light">Light</span>',
'<span class="badge badge-dark">Dark</span>',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
<div class="warning">
<p class="documentation-text"><strong><?php echo $langs->trans('DocBadgeContextualVariationsWarning1'); ?></strong></p>
<p class="documentation-text"><?php echo $langs->trans('DocBadgeContextualVariationsWarning2'); ?></p>
@@ -176,7 +176,7 @@ $documentation->showSidebar(); ?>
for ($i = 0; $i <= 9; $i++) :
$lines[] = '<span class="badge badge-status'.$i.'">status-'.$i.'</span>';
endfor;
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
<!-- Pill badges -->
@@ -210,7 +210,7 @@ $documentation->showSidebar(); ?>
for ($i = 0; $i <= 9; $i++) :
$lines[] = '<span class="badge badge-pill badge-status'.$i.'">status-'.$i.'</span>';
endfor;
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
<!-- Dot badges -->
@@ -244,7 +244,7 @@ $documentation->showSidebar(); ?>
for ($i = 0; $i <= 9; $i++) :
$lines[] = '<span class="badge badge-dot badge-status'.$i.'"></span>';
endfor;
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
<!-- Links -->
@@ -278,7 +278,7 @@ $documentation->showSidebar(); ?>
for ($i = 0; $i <= 9; $i++) :
$lines[] = '<a href="#" class="badge badge-status'.$i.'" >status'.$i.'</a>';
endfor;
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
<!-- Use badge helper function -->
@@ -308,7 +308,7 @@ $documentation->showSidebar(); ?>
"print dolGetBadge('your label for accessibility', 'your label <u>with</u> <em>html</em>', 'danger', 'pill');",
"print dolGetBadge('your label for accessibility', 'your label', 'warning', 'dot');",
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- -->

View File

@@ -135,7 +135,7 @@ $documentation->showSidebar(); ?>
' */',
'print dolGetButtonAction($label, $html, $actionType, $url, $id, $userRight, $params);',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- Example of modal usage -->
@@ -202,7 +202,7 @@ $documentation->showSidebar(); ?>
'',
'print dolGetButtonAction($label, $html, $actionType, $url, $id, $userRight, $params);',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- Example of subbutton usage -->
@@ -259,7 +259,7 @@ $documentation->showSidebar(); ?>
');',
'print dolGetButtonAction($label, $html, $actionType, $url, $id, $userRight, $params);'
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
@@ -286,7 +286,7 @@ $documentation->showSidebar(); ?>
'print \' <button class="btn-low-emphasis --btn-icon" title="\'.dol_escape_htmltag($btnLabel).\'" aria-label="\'.dol_escape_htmltag($btnLabel).\'" >\'.img_picto($btnLabel, \'eraser\', \'aria-hidden="true"\', 0, 0, 1).\'</button>\';',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- Example of subbutton usage -->
@@ -322,7 +322,7 @@ $documentation->showSidebar(); ?>
'print dolGetButtonTitle($btnLabel, \'\', \'fa fa-file\', \'#\', \'\', $status);',
);
echo $documentation->showCode($lines, 'php'); ?><div class="documentation-example">
$documentation->showCode($lines, 'php'); ?><div class="documentation-example">
<?php
$btnLabel = $langs->trans('Label', 'php');
@@ -350,7 +350,7 @@ $documentation->showSidebar(); ?>
'$status = -2; // Disabled without info',
'print dolGetButtonTitle($btnLabel, \'\', \'fa fa-download\', \'#\', \'\', $status, [\'forcenohideoftext\'=>1]);',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
</div>

View File

@@ -127,7 +127,7 @@ $documentation->showSidebar(); ?>
'setEventMessages("message", null);',
'setEventMessages(null, messages[]);',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- Contextual variations -->
@@ -136,6 +136,10 @@ $documentation->showSidebar(); ?>
<p class="documentation-text"><?php echo $langs->trans('DocSetEventMessageContextualVariationsDescription'); ?></p>
<div class="documentation-example">
<?php
$params = [
'attr' => []
];
$label = 'My action label used for accessibility visually for impaired people';
$user_right = 1;
$html = '<span class="fa fa-comment paddingright"></span>'.$langs->trans('DocSetEventMessageDisplayOKMessage');
@@ -167,9 +171,18 @@ $documentation->showSidebar(); ?>
'setEventMessages("message", null, "warnings");',
'setEventMessages("message", null, "errors");'
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- -->
<?php
$setEventMessageJsContextTitle = $langs->trans('DocSetEventMessageJsContext');
include __DIR__ . '/../dolibarr-context/inc_seteventmessage.php';
?>
</div>
</div>

View File

@@ -84,7 +84,6 @@ if (!GETPOST('hidenavmenu')) {
<div class="documentation-section" id="img-picto-section-list">
<?php
$iconFileName = 'shims.json';
$iconFilePath = DOL_DOCUMENT_ROOT . '/theme/common/fontawesome-5/metadata';
@@ -169,18 +168,27 @@ if (!GETPOST('hidenavmenu')) {
<div class="documentation-section" id="icon-section-list">
<?php
$iconFileName = 'shims.json';
$iconFileFa = 'icons.json';
$iconFilePath = DOL_DOCUMENT_ROOT . '/theme/common/fontawesome-5/metadata';
$fontAwesomeIconRaw = file_get_contents($iconFilePath. '/' .$iconFileName);
if ($fontAwesomeIconRaw === false) {
dol_print_error($db, 'Error missing file '. $iconFilePath . '/' . $iconFileName);
}
// Load the full FontAwesome 5 icons JSON
$allIconsRaw = file_get_contents($iconFilePath . '/' . $iconFileFa);
$fontAwesomeIcons = []; // This will be the output array in shims.json format
$fontAwesomeIcons = json_decode($fontAwesomeIconRaw);
if ($fontAwesomeIcons === null) {
dol_print_error($db, 'Error decoding '. $iconFilePath . '/' . $iconFileName);
if ($allIconsRaw === false) {
dol_print_error($db, 'Error: missing file ' . $iconFilePath . '/' . $iconFileFa);
} else {
$allIcons = json_decode($allIconsRaw, true);
if ($allIcons === null) {
dol_print_error($db, 'Error: cannot decode JSON from ' . $iconFilePath . '/' . $iconFileFa);
} else {
foreach ($allIcons as $iconName => $iconData) {
// Determine prefix: 'fab' for brands, 'fas' or 'far' can be added later if needed
$prefix = in_array('brands', $iconData['styles']) ? 'fab' : null;
// Format: [ "icon-name", "prefix if any", null ]
$fontAwesomeIcons[] = [$iconName, $prefix, null]; // null reserved for future alias
}
}
}
?>
@@ -208,7 +216,7 @@ if (!GETPOST('hidenavmenu')) {
<div class="documentation-fontawesome-icon-list">
<?php
$alreadyDisplay = [];
if ($fontAwesomeIcons && is_array($fontAwesomeIcons)) {
if ($fontAwesomeIcons) {
foreach ($fontAwesomeIcons as $iconData) {
$class= $iconData[1]??'fa';
if (!empty($iconData[2])) {
@@ -223,6 +231,7 @@ if (!GETPOST('hidenavmenu')) {
$alreadyDisplay[] = $class;
$iconCode = '<span class="'.$class.'" ></span>';
$iconLabel = !empty($iconData[2]) ? $iconData[2] : $iconData[0];
if ($displayMode == 'kanban') {
print '<div class="info-box ">
@@ -230,7 +239,7 @@ if (!GETPOST('hidenavmenu')) {
' . $iconCode . '
</span>
<div class="info-box-content">
<div class="info-box-title" >' . ($iconData[2] ?? ($iconData[0] ?? '')) . '</div>
<div class="info-box-title" >' . $iconLabel . '</div>
<div class="info-box-lines">
<div class="info-box-line spanoverflow nowrap">
<div class="inline-block nowraponall">

View File

@@ -90,7 +90,7 @@ $documentation->showSidebar(); ?>
'Disabled Input',
'<input id="label" name="label" class="minwidth200" maxlength="255" value="" disabled>',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
<!-- Checkbox input -->
<p class="documentation-text"><?php echo $langs->trans('DocCheckboxInputsDescription'); ?></p>
@@ -105,7 +105,7 @@ $documentation->showSidebar(); ?>
'<span class="spannature paddinglarge marginrightonly nonature-back valignmiddle"><label for="customerinput" class="valignmiddle"><span class="valignmiddle">Customer</span><input id="customerinput" class="flat checkforselect marginleftonly valignmiddle" type="checkbox" name="customer" value="1" checked></label></span>',
'<span class="spannature paddinglarge marginrightonly nonature-back valignmiddle"><label for="supplierinput" class="valignmiddle"><span class="valignmiddle">Supplier</span><input id="supplierinput" class="flat checkforselect marginleftonly valignmiddle" type="checkbox" name="supplier" value="1" checked></label></span>',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
<!-- Radio input -->
<p class="documentation-text"><?php echo $langs->trans('DocRadioInputsDescription'); ?></p>
@@ -118,7 +118,7 @@ $documentation->showSidebar(); ?>
'<input type="radio" id="idforradioinput1" name="radioinput" value="value1"><label for="idforradioinput1" class="marginrightonly"> Radio Input 1</label>',
'<input type="radio" id="idforradioinput2" name="radioinput" value="value2"><label for="idforradioinput2" class="marginrightonly"> Radio Input 2</label>'
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
<!-- Helper functions -->
@@ -174,7 +174,7 @@ $documentation->showSidebar(); ?>
'print $form->selectarray(\'htmlnameselectwithinemptyvalue\', $values, \'idnameselectwithinemptyvalue\', 0,0, 0, \'\', 0, 0, 0, \'\', \'minwidth200\');',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
<!-- Multiselect input -->
<p class="documentation-text"><?php echo $langs->trans('DocMultiSelectInputsDescription'); ?></p>
@@ -212,7 +212,7 @@ $documentation->showSidebar(); ?>
'// Multiselect',
'print $form->multiselectarray(\'categories\', $values, GETPOST(\'categories\', \'array\'), 0, 0, \'minwidth200\', 0, 0);'
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
<!-- Date input -->
<p class="documentation-text"><?php echo $langs->trans('DocDateSelectInputsDescription'); ?></p>
@@ -269,7 +269,7 @@ $documentation->showSidebar(); ?>
'// Date Select with hours',
'print $form->selectDate(\'\', \'re2\', 1, 1, 1);'
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
<!-- Editor input -->
@@ -306,7 +306,7 @@ $documentation->showSidebar(); ?>
'$doleditor = new DolEditor(\'desc\', GETPOST(\'desc\', \'restricthtml\'), \'\', 160, \'dolibarr_details\', \'\', false, true, getDolGlobalString(\'FCKEDITOR_ENABLE_DETAILS\'), ROWS_4, \'90%\');',
'print $form->multiselectarray(\'categories\', $values, GETPOST(\'categories\', \'array\'), 0, 0, \'minwidth200\', 0, 0);'
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- Search Filter Tool Input -->
@@ -376,7 +376,7 @@ $documentation->showSidebar(); ?>
'</div>',
);
echo $documentation->showCode($lines, 'php');
$documentation->showCode($lines, 'php');
$lines = array(
'<?php',
@@ -403,7 +403,7 @@ $documentation->showSidebar(); ?>
'</div>',
);
echo $documentation->showCode($lines, 'php');
$documentation->showCode($lines, 'php');
?>

View File

@@ -113,7 +113,7 @@ $documentation->showSidebar(); ?>
' <div class="progress-bar" role="progressbar" style="width: 80%" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>',
'</div>',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
<p class="documentation-text"><?php echo $langs->trans('DocProgressCanBeSpaced'); ?></p>
<div class="documentation-example">
@@ -128,7 +128,7 @@ $documentation->showSidebar(); ?>
' <div class="progress-bar" role="progressbar" style="width: 40%" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100"></div>',
'</div>',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
@@ -172,7 +172,7 @@ $documentation->showSidebar(); ?>
'',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
@@ -216,7 +216,7 @@ $documentation->showSidebar(); ?>
'',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
<!-- -->
@@ -258,7 +258,7 @@ $documentation->showSidebar(); ?>
'',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
<!-- -->

View File

@@ -164,7 +164,7 @@ $documentation->showSidebar(); ?>
'',
'</table>',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- Table with filters -->
@@ -283,7 +283,7 @@ $documentation->showSidebar(); ?>
' </table>',
'</form>',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
<!-- Add a row before filters -->
@@ -386,7 +386,7 @@ $documentation->showSidebar(); ?>
' <!-- Total -->',
'</table>',
);
echo $documentation->showCode($lines); ?>
$documentation->showCode($lines); ?>
</div>
<!-- CSS classes for tables -->

View File

@@ -101,7 +101,7 @@ $documentation->showSidebar(); ?>
'// Title with custom image icon, 4th parameter must be 1',
'print load_fiche_titre("'.$langs->trans('DocMyPageTitle').'", "", "IMAGE_URL", 1);',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
<p class="documentation-text"><?php echo $langs->trans('DocTitleMoreContentDescription'); ?></p>
<div class="documentation-example">
@@ -133,7 +133,7 @@ $documentation->showSidebar(); ?>
'$moreContent = \'MyHTMLContent\';',
'print load_fiche_titre("'.$langs->trans('DocMyPageTitle').'", $moreHtmlRight, "fa-rocket_fas_#b0bb39", 0, $tableID, $moreclass, $moreContent);',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- Table with filters -->
@@ -196,7 +196,7 @@ $documentation->showSidebar(); ?>
' */',
'print_barre_liste($title, $page, $file, $options, $sortfield, $sortorder, $morehtmlcenter, $num, $totalnboflines, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limit, $selectlimitsuffix, $hidenavigation, $pagenavastextinput, $morehtmlrightbeforearrow);',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
</div>

View File

@@ -0,0 +1,90 @@
<?php
/**
* @var string $setEventMessageJsContextTitle
* @var Documentation $documentation
*
* @phan-var string|null $setEventMessageJsContextTitle
* @phan-var Documentation $documentation
*/
if (!defined('DOL_VERSION')) {die();}
global $documentation, $setEventMessageJsContextTitle;
if ($documentation === null || !($documentation instanceof Documentation)) { return; }
?>
<div class="documentation-section">
<h2 id="titlesection-tool-seteventmessage" class="documentation-title"><?php print $setEventMessageJsContextTitle ?? 'Set event message tool'; ?></h2>
<p>
Instead of calling JNotify directly in your code, use Dolibarrs setEventMessage tool.
Dolibarr provides the configuration option DISABLE_JQUERY_JNOTIFY, which disables the jQuery JNotify system, usually because another notification library will be used instead.
</p>
<p>
If you rely on Dolibarr.tools.setEventMessage(), your code remains compatible even if the underlying notification system changes.
The setEventMessage tool can be replaced internally without requiring any changes in your modules or custom scripts.
</p>
<p>
This means all developers can write features without worrying about frontend compatibility or future library replacements. Enjoy!
</p>
<?php
$lines = array(
'<script nonce="<?php print getNonce() ?>" >',
' document.addEventListener(\'Dolibarr:Ready\', function(e) {',
'',
' document.getElementById(\'setEventMessage-success\').addEventListener(\'click\', function(e) {',
' Dolibarr.tools.setEventMessage(\'Success Test\');',
' });',
'',
' document.getElementById(\'setEventMessage-error\').addEventListener(\'click\', function(e) {',
' Dolibarr.tools.setEventMessage(\'Error Test\', \'errors\');',
' });',
'',
' document.getElementById(\'setEventMessage-error-sticky\').addEventListener(\'click\', function(e) {',
' Dolibarr.tools.setEventMessage(\'Error Test\', \'errors\', true);',
' });',
'',
' document.getElementById(\'setEventMessage-warning\').addEventListener(\'click\', function(e) {',
' Dolibarr.tools.setEventMessage(\'Warning Test\', \'warnings\');',
' });',
'',
' });',
'</script>',
);
$documentation->showCode($lines, 'php'); ?>
<div class="documentation-example">
<script nonce="<?php print getNonce() ?>" >
document.addEventListener('Dolibarr:Ready', function(e) {
document.getElementById('setEventMessage-success').addEventListener('click', function(e) {
Dolibarr.tools.setEventMessage('Success Test')
});
document.getElementById('setEventMessage-error').addEventListener('click', function(e) {
Dolibarr.tools.setEventMessage('Error Test', 'errors');
});
document.getElementById('setEventMessage-error-sticky').addEventListener('click', function(e) {
Dolibarr.tools.setEventMessage('Error Test', 'errors', true);
});
document.getElementById('setEventMessage-warning').addEventListener('click', function(e) {
Dolibarr.tools.setEventMessage('Warning Test', 'warnings');
});
});
</script>
<button id="setEventMessage-success" class="button">Alert success</button>
<button id="setEventMessage-error" class="button">Alert error</button>
<button id="setEventMessage-error-sticky" class="button">Alert error sticky</button>
<button id="setEventMessage-warning" class="button">Alert warning</button>
</div>
</div>

View File

@@ -40,8 +40,8 @@ $langs->load('uxdocumentation');
//
$documentation = new Documentation($db);
$group = 'ExperimentalUx';
$experimentName = 'UxDolibarrContext';
$group = 'UxDolibarrContext';
$experimentName = 'UxDolibarrContextHowItWork';
$js = [
'/includes/ace/src/ace.js',
@@ -65,7 +65,7 @@ $documentation->showSidebar(); ?>
<div class="doc-content-wrapper">
<h1 class="documentation-title"><?php echo $langs->trans($experimentName); ?> : <?php echo $langs->trans('UxDolibarrContextHowItWork'); ?></h1>
<h1 class="documentation-title"><?php echo $langs->trans($group); ?> : <?php echo $langs->trans('UxDolibarrContextHowItWork'); ?></h1>
<?php $documentation->showSummary(); ?>
@@ -137,7 +137,6 @@ $documentation->showSidebar(); ?>
When enabled, <code>Dolibarr.log()</code> behaves like <code>console.log()</code>.
</p>
<div class="documentation-example">
<?php
$lines = array(
'<script>',
@@ -153,7 +152,6 @@ $documentation->showSidebar(); ?>
);
$documentation->showCode($lines, 'php');
?>
</div>
<h3>Summary</h3>
<ul>
@@ -187,7 +185,6 @@ $documentation->showSidebar(); ?>
<code>e.detail</code> and the event name is prefixed by <code>Dolibarr:</code> so for a hook named A event name is <code>Dolibarr:A</code>
</p>
<div class="documentation-example">
<?php
$lines = array(
'<script>',
@@ -203,7 +200,7 @@ $documentation->showSidebar(); ?>
'</script>',
);
$documentation->showCode($lines, 'php'); ?>
</div>
<h3>Practical usage</h3>
<p>
@@ -212,7 +209,6 @@ $documentation->showSidebar(); ?>
are valid, but <code>Dolibarr.on()</code> is simpler and more convenient because you get the
data directly.
</p>
<div class="documentation-example">
<?php
$lines = array(
'<script>',
@@ -246,6 +242,7 @@ $documentation->showSidebar(); ?>
);
$documentation->showCode($lines, 'php'); ?>
<div class="documentation-example">
Open your console <code>F12</code> and click on <button class="button" id="try-event-yourCustomHookName">try</button>
<script nonce="<?php print getNonce() ?>" >
document.addEventListener('Dolibarr:Ready', function(e) {
@@ -309,7 +306,6 @@ $documentation->showSidebar(); ?>
</p>
<h3>Examples of usage</h3>
<div class="documentation-example">
<?php
$lines = array(
'<script>',
@@ -334,7 +330,7 @@ $documentation->showSidebar(); ?>
'</script>',
);
$documentation->showCode($lines, 'php'); ?>
</div>
<p>
In summary:
@@ -364,7 +360,6 @@ $documentation->showSidebar(); ?>
This means you can <code>await</code> their results in your code, and any asynchronous operations inside a hook (e.g., API calls, timers) will be handled correctly before moving to the next hook.
</p>
<div class="documentation-example">
<?php
$lines = array(
'<script nonce="<?php print getNonce() ?>">',
@@ -402,6 +397,7 @@ $documentation->showSidebar(); ?>
);
$documentation->showCode($lines, 'php'); ?>
<div class="documentation-example">
Open your console <code>F12</code> and click on <button class="button" id="try-event-yourCustomAwaitHookName">try</button>
<script nonce="<?php print getNonce() ?>">
@@ -439,6 +435,232 @@ $documentation->showSidebar(); ?>
</div>
<div class="documentation-section">
<h2 id="titlesection-dom-initnewcontent" class="documentation-title">
initNewContent event system
</h2>
<p>
The <strong>initNewContent</strong> event is a standardized Dolibarr mechanism
to re-initialize UI components on dynamically added content.
Use it whenever you inject new DOM elements via AJAX, templates, or other dynamic updates.
</p>
<h3 id="titlesection-usecase-tooltips" class="documentation-title">
Use Case example: Dynamic Tooltips
</h3>
<p>
In a typical Dolibarr page, tooltips are initialized on page load for all elements
that have the class <code>.classfortooltip</code>. This works perfectly for static content.
</p>
<p>
However, when a section of the page is dynamically recreated or loaded via AJAX,
the new elements with <code>.classfortooltip</code> do not automatically have tooltips,
because the initialization script has already run on the initial DOM and is not rerun for the new elements.
</p>
<p>
The <strong>initNewContent</strong> mechanism solves this problem by providing a standardized hook
to re-initialize all interactive components on newly added DOM elements.
Developers can listen to <code>initNewContent</code> and re-run tooltip initialization
(or any other dynamic behavior) only on the new elements or their children, ensuring consistency and avoiding duplication.
</p>
<p>
This approach guarantees that tooltips, dialogs, and other interactive components
remain functional even when content is injected or updated asynchronously.
</p>
<p>
In addition to <code>document.ready</code> or <code>$(document).ready()</code>,
listen to <strong>initNewContent</strong> to ensure that tooltips, dialogs, or other interactive components
are properly initialized on any new DOM fragment added dynamically.
</p>
<div class="documentation-example">
<p>
<button class="button" id="try-no-initNewContent">Test without event</button>
<button class="button" id="try-initNewContent">Test with initNewContent event</button>
</p>
<div id="initNewContent-test-container"></div>
<style>
/* Animation for highlighting new content */
@keyframes highlightfortest {
from { background-color: #fffa8d; }
to { background-color: transparent; }
}
.highlightfortest {
padding: 10px;
animation: highlightfortest 1s ease-out;
}
</style>
<div id="idfortooltiponclick_doc-event-dialog-test" class="classfortooltiponclicktext" title="The title" style="display: none" >Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nec elit venenatis, bibendum dui in, tristique dolor. In hac habitasse platea dictumst. Vestibulum consectetur quam non felis fringilla mollis pretium vel nibh. Pellentesque congue risus et laoreet blandit. Aliquam orci ipsum, gravida id leo eget, molestie pulvinar sem. Nulla sed felis et lacus tristique finibus. Cras ornare tincidunt. Aenean hendrerit volutpat efficitur. Integer vestibulum dui eget lectus pulvinar, vel mattis odio facilisis. Etiam convallis scelerisque lobortis. Mauris tristique, quam dignissim sollicitudin sodales, elit ligula venenatis neque, sit amet interdum lacus tellus id tellus. Mauris eu pretium turpis. Proin porta sem eget nisl vulputate vehicula.</div>
<script nonce="<?php print getNonce() ?>">
document.addEventListener('Dolibarr:Ready', function(e) {
const container = document.getElementById('initNewContent-test-container');
document.getElementById('try-no-initNewContent').addEventListener('click', function() {
container.innerHTML = `<span class="classfortooltip highlightfortest" title="this is the title">A text with a tooltip but the tooltip isn't load</span>
and <span class="classfortooltiponclick highlightfortest" dolid="doc-event-dialog-test" style="cursor: pointer;" >A text with a tooltip to click but the tooltip isn't load</span>`;
});
document.getElementById('try-initNewContent').addEventListener('click', function() {
container.innerHTML = `<span class="classfortooltip highlightfortest" title="this is the title">A text with a tooltip and the tooltip is loaded</span>
And <span class="classfortooltiponclick highlightfortest" dolid="doc-event-dialog-test" style="cursor: pointer;" >A text with a tooltip to click and the tooltip is loaded</span>`;
Dolibarr.initNewContent(container);
});
});
</script>
</div>
<?php
$lines = array(
'<div id="idfortooltiponclick_doc-event-dialog-test" class="classfortooltiponclicktext" title="The title" >Lorem ipsum .....</div>',
'<script nonce="<?php print getNonce() ?>">',
'document.addEventListener("Dolibarr:Ready", function(e) {',
' const container = document.getElementById("initNewContent-test-container");',
'',
' // Insert content without calling initNewContent',
' document.getElementById("try-no-initNewContent").addEventListener("click", function() {',
' container.innerHTML = `<span class="classfortooltip highlightfortest" title="this is the title">A text with a tooltip but the tooltip isn\'t load</span>',
' and <span class="classfortooltiponclick highlightfortest" dolid="doc-event-dialog-test" style="cursor: pointer;">A text with a tooltip to click but the tooltip isn\'t load</span>`;',
' });',
'',
' // Insert content and trigger initNewContent',
' document.getElementById("try-initNewContent").addEventListener("click", function() {',
' container.innerHTML = `<span class="classfortooltip highlightfortest" title="this is the title">A text with a tooltip and the tooltip is loaded</span>',
' and <span class="classfortooltiponclick highlightfortest" dolid="doc-event-dialog-test" style="cursor: pointer;">A text with a tooltip to click and the tooltip is loaded</span>`;',
' Dolibarr.initNewContent(container);',
' });',
'});',
'</script>',
);
$documentation->showCode($lines, 'php'); ?>
<h3 id="titlesection-usecase-tooltips" class="documentation-title">
How to use it
</h3>
<p>
The event handler receives an object with the property <code>targets</code>,
which is an array of DOM elements or jQuery collections to initialize. Each element can be:
</p>
<ul>
<li>a container with child elements to initialize</li>
<li>or a direct element that needs initialization</li>
</ul>
<h4>Example: Trigger initNewContent manually on a container</h4>
<?php
$lines = array(
'<script nonce="<?php print getNonce() ?>">',
' document.addEventListener(\'Dolibarr:Ready\', function(e) {',
' /* [... code that dynamically reloads part of the DOM ...] */',
'',
' /**',
' * true: only include the children of each target element',
' * false: include the target elements themselves',
' */',
' const applyToChildrenOnly = true;',
'',
' // Trigger initNewContent manually on a jQuery container',
' Dolibarr.initNewContent($("#myContainer"), applyToChildrenOnly);',
'',
' // Trigger initNewContent manually on a vanilla JS element',
' const element = document.getElementById("myContainer");',
' Dolibarr.initNewContent(element, applyToChildrenOnly);',
' });',
'</script>',
);
$documentation->showCode($lines, 'php'); ?>
<p>
Dolibarr provides a custom event system to properly initialize dynamic content.
Always use <strong>initNewContent</strong> when working with AJAX-injected fragments or
dynamically created elements instead of relying solely on jQuery document ready.
</p>
<h4>Example in pure JS style: listening to initNewContent</h4>
<?php
$lines = array(
'<script nonce="<?php print getNonce() ?>">',
' Dolibarr.on("initNewContent", ({ targets }) => {',
' targets.forEach(root => {',
'',
' // Array to store all matching dialog elements',
' const dialogs = [];',
'',
' // Include the root element if it matches the selector',
' if (root.matches(".classfortooltiponclicktext")) {',
' dialogs.push(root);',
' }',
'',
' // Add all descendants matching the selector',
' dialogs.push(...root.querySelectorAll(".classfortooltiponclicktext"));',
'',
' // Initialize each dialog element',
' dialogs.forEach(el => {',
' // Your code to initialize the tooltip/dialog or other stuff',
' });',
' });',
' });',
'</script>',
);
$documentation->showCode($lines, 'php');
?>
<h4>Compact version in pure JS</h4>
<?php
$lines = array(
'<script nonce="<?php print getNonce() ?>">',
' Dolibarr.on("initNewContent", ({ targets }) => {',
' targets.forEach(root => {',
' const dialogs = [',
' ...(root.matches(".classfortooltiponclicktext") ? [root] : []),',
' ...root.querySelectorAll(".classfortooltiponclicktext")',
' ];',
' dialogs.forEach(el => {',
' // Initialize tooltip/dialog or other stuff here',
' });',
' });',
' });',
'</script>',
);
$documentation->showCode($lines, 'php');
?>
<h4>Example in jQuery style</h4>
<?php
$lines = array(
'<script nonce="<?php print getNonce() ?>">',
' Dolibarr.on("initNewContent", ({ targets }) => {',
' targets.forEach($root => {',
' const $dialogs = $root',
' .filter(".classfortooltiponclicktext")',
' .add($root.find(".classfortooltiponclicktext"));',
' $dialogs.each(function () {',
' const $el = $(this);',
' // Initialize tooltip/dialog behavior or other stuff here',
' });',
' });',
' });',
'</script>',
);
$documentation->showCode($lines, 'php');
?>
</div>
<div class="documentation-section">
<h2 id="titlesection-create-tool-example" class="documentation-title">Example of creating a new context tool</h2>
@@ -450,7 +672,6 @@ $documentation->showSidebar(); ?>
<p>See also <code>dolibarr-context.mock.js</code> for defining all standard Dolibarr tools and creating mock implementations to improve code completion and editor support.</p>
<p><b>Note :</b> a tool can be a class not only a function</p>
<div class="documentation-example">
<?php
$lines = array(
'<script>',
@@ -467,14 +688,12 @@ $documentation->showSidebar(); ?>
'</script>',
);
$documentation->showCode($lines, 'php'); ?>
</div>
<h3>Protected Tools</h3>
<p>
Once a tool is defined on overwrite false, it cannot be replaced. Attempting to redefine it without overwrite will throw an error:
</p>
<div class="documentation-example">
<?php
$lines = array(
'<script>',
@@ -486,14 +705,12 @@ $documentation->showSidebar(); ?>
'</script>',
);
$documentation->showCode($lines, 'php'); ?>
</div>
<h3>Reading Tools</h3>
<p>
You can read the list of available tools using <code>Dolibarr.tools</code>. It returns a frozen copy:
</p>
<div class="documentation-example">
<?php
$lines = array(
'<script>',
@@ -502,82 +719,10 @@ $documentation->showSidebar(); ?>
'</script>',
);
$documentation->showCode($lines, 'php'); ?>
</div>
</div>
<div class="documentation-section">
<h2 id="titlesection-tool-seteventmessage" class="documentation-title">Set event message tool</h2>
<p>
Instead of calling JNotify directly in your code, use Dolibarrs setEventMessage tool.
Dolibarr provides the configuration option DISABLE_JQUERY_JNOTIFY, which disables the jQuery JNotify system, usually because another notification library will be used instead.
</p>
<p>
If you rely on Dolibarr.tools.setEventMessage(), your code remains compatible even if the underlying notification system changes.
The setEventMessage tool can be replaced internally without requiring any changes in your modules or custom scripts.
</p>
<p>
This means all developers can write features without worrying about frontend compatibility or future library replacements. Enjoy!
</p>
<div class="documentation-example">
<?php
$lines = array(
'<script nonce="<?php print getNonce() ?>" >',
' document.addEventListener(\'Dolibarr:Ready\', function(e) {',
'',
' document.getElementById(\'setEventMessage-success\').addEventListener(\'click\', function(e) {',
' Dolibarr.tools.setEventMessage(\'Success Test\');',
' });',
'',
' document.getElementById(\'setEventMessage-error\').addEventListener(\'click\', function(e) {',
' Dolibarr.tools.setEventMessage(\'Error Test\', \'errors\');',
' });',
'',
' document.getElementById(\'setEventMessage-error-sticky\').addEventListener(\'click\', function(e) {',
' Dolibarr.tools.setEventMessage(\'Error Test\', \'errors\', true);',
' });',
'',
' document.getElementById(\'setEventMessage-warning\').addEventListener(\'click\', function(e) {',
' Dolibarr.tools.setEventMessage(\'Warning Test\', \'warnings\');',
' });',
'',
' });',
'</script>',
);
$documentation->showCode($lines, 'php'); ?>
<script nonce="<?php print getNonce() ?>" >
document.addEventListener('Dolibarr:Ready', function(e) {
document.getElementById('setEventMessage-success').addEventListener('click', function(e) {
Dolibarr.tools.setEventMessage('Success Test')
});
document.getElementById('setEventMessage-error').addEventListener('click', function(e) {
Dolibarr.tools.setEventMessage('Error Test', 'errors');
});
document.getElementById('setEventMessage-error-sticky').addEventListener('click', function(e) {
Dolibarr.tools.setEventMessage('Error Test', 'errors', true);
});
document.getElementById('setEventMessage-warning').addEventListener('click', function(e) {
Dolibarr.tools.setEventMessage('Warning Test', 'warnings');
});
});
</script>
<button id="setEventMessage-success" class="button">Alert success</button>
<button id="setEventMessage-error" class="button">Alert error</button>
<button id="setEventMessage-error-sticky" class="button">Alert error sticky</button>
<button id="setEventMessage-warning" class="button">Alert warning</button>
</div>
</div>
<?php include __DIR__ . '/inc_seteventmessage.php'; ?>
<div class="documentation-section">
@@ -598,7 +743,6 @@ $documentation->showSidebar(); ?>
</p>
<h3>Add context var (overridable or not)</h3>
<div class="documentation-example">
<?php
$lines = array(
'<script nonce="<?php print getNonce() ?>" >',
@@ -613,11 +757,9 @@ $documentation->showSidebar(); ?>
);
$documentation->showCode($lines, 'php');
?>
</div>
<h3>Add multiple context vars (overridable or not)</h3>
<div class="documentation-example">
<?php
$lines = array(
'<?php',
@@ -644,10 +786,8 @@ $documentation->showSidebar(); ?>
);
$documentation->showCode($lines, 'php');
?>
</div>
<h3>Get context var</h3>
<div class="documentation-example">
<?php
$lines = array(
'<script nonce="<?php print getNonce() ?>" >',
@@ -659,7 +799,6 @@ $documentation->showSidebar(); ?>
);
$documentation->showCode($lines, 'php');
?>
</div>
</div>

View File

@@ -191,7 +191,7 @@ $documentation->showSidebar(); ?>
'$documentation->docFooter();',
'?>',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- Second Step -->
@@ -231,7 +231,7 @@ $documentation->showSidebar(); ?>
' )',
');',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
<!-- Third Step -->
@@ -247,7 +247,7 @@ $documentation->showSidebar(); ?>
'// Set view for menu and breadcrumb',
'$documentation->view = array(\'Components\', \'MyComponent\');',
);
echo $documentation->showCode($lines, 'php'); ?>
$documentation->showCode($lines, 'php'); ?>
</div>
</div>

View File

@@ -25,10 +25,6 @@
// Load Dolibarr environment
require '../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php';
/**
* @var Conf $conf
* @var DoliDB $db
@@ -36,6 +32,9 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php';
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php';
// Load translation files required by the page
$langs->loadLangs(array("companies", "products", "admin", "sms", "other", "errors"));
@@ -231,19 +230,23 @@ llxHeader('', $title, $wikihelp, '', 0, 0, '', '', '', 'mod-admin page-translati
$param = '&mode='.urlencode($mode);
$enabledisablehtml = '';
$enabledisablehtml .= $langs->trans("EnableOverwriteTranslation").' ';
$enabledisablehtml = '<span class="divfilteralone">';
if (!getDolGlobalString('MAIN_ENABLE_OVERWRITE_TRANSLATION')) {
// Button off, click to enable
$enabledisablehtml .= '<a class="reposition valignmiddle" href="'.$_SERVER["PHP_SELF"].'?action=setMAIN_ENABLE_OVERWRITE_TRANSLATION&token='.newToken().'&value=1'.$param.'">';
$enabledisablehtml .= img_picto($langs->trans("Disabled"), 'switch_off');
$enabledisablehtml .= '</a>';
$enabledisablehtml .= '<a class="reposition valignmiddle nounderlineimp" href="'.$_SERVER["PHP_SELF"].'?action=setMAIN_ENABLE_OVERWRITE_TRANSLATION&token='.newToken().'&value=1'.$param.'">';
} else {
$enabledisablehtml .= '<a class="reposition valignmiddle nounderlineimp" href="'.$_SERVER["PHP_SELF"].'?action=setMAIN_ENABLE_OVERWRITE_TRANSLATION&token='.newToken().'&value=0'.$param.'">';
}
$enabledisablehtml .= $langs->trans("EnableOverwriteTranslation");
if (!getDolGlobalString('MAIN_ENABLE_OVERWRITE_TRANSLATION')) {
// Button off, click to enable
$enabledisablehtml .= img_picto($langs->trans("Disabled"), 'switch_off', 'class="paddingleft valignmiddle"');
} else {
// Button on, click to disable
$enabledisablehtml .= '<a class="reposition valignmiddle" href="'.$_SERVER["PHP_SELF"].'?action=setMAIN_ENABLE_OVERWRITE_TRANSLATION&token='.newToken().'&value=0'.$param.'">';
$enabledisablehtml .= img_picto($langs->trans("Activated"), 'switch_on');
$enabledisablehtml .= '</a>';
$enabledisablehtml .= img_picto($langs->trans("Activated"), 'switch_on', 'class="paddingleft valignmiddle"');
}
$enabledisablehtml .= '</a>';
$enabledisablehtml .= '</span>';
$current_language_code = $langs->defaultlang;
$s = picto_from_langcode($current_language_code);
@@ -580,37 +583,46 @@ if ($mode == 'searchkey') {
print '<div class="div-table-responsive-no-min">';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre liste_titre_filter"><td>';
print '<tr class="liste_titre liste_titre_filter">';
// Action column
if ($conf->main_checkbox_left_column) {
print '<td class="center nowraponall">';
$searchpicto = $form->showFilterAndCheckAddButtons(!empty($massactionbutton) ? 1 : 0, 'checkforselect', 1);
print $searchpicto;
print '</td>';
}
print '<td>';
print $formadmin->select_language($langcode, 'langcode', 0, array(), 0, 0, 0, 'minwidth100 maxwidth250', 1);
print '</td>'."\n";
print '<td>';
print '<input type="text" class="flat maxwidthonsmartphone" name="transkey" value="'.dol_escape_htmltag($transkey).'">';
print '</td><td>';
print '<input type="text" class="quatrevingtpercent" name="transvalue" value="'.dol_escape_htmltag($transvalue).'">';
// Limit to superadmin
/*if (isModEnabled('multicompany') && !$user->entity)
{
print '</td><td>';
print '<input type="text" class="flat" size="1" name="entitysearch" value="'.$conf->entity.'">';
}
else
{*/
print '<input type="hidden" name="entitysearch" value="'.$conf->entity.'">';
//}
print '</td>';
print '<td></td>';
// Action column
print '<td class="right nowraponall">';
$searchpicto = $form->showFilterAndCheckAddButtons(!empty($massactionbutton) ? 1 : 0, 'checkforselect', 1);
print $searchpicto;
print '</td>';
if (!$conf->main_checkbox_left_column) {
print '<td class="right nowraponall">';
$searchpicto = $form->showFilterAndCheckAddButtons(!empty($massactionbutton) ? 1 : 0, 'checkforselect', 1);
print $searchpicto;
print '</td>';
}
print '</tr>';
print '<tr class="liste_titre">';
// Action column
if ($conf->main_checkbox_left_column) {
print_liste_field_titre("");
}
print_liste_field_titre("Language_en_US_es_MX_etc", $_SERVER["PHP_SELF"], 'lang,transkey', '', $param, '', $sortfield, $sortorder);
print_liste_field_titre("TranslationKey", $_SERVER["PHP_SELF"], 'transkey', '', $param, '', $sortfield, $sortorder);
print_liste_field_titre("CurrentTranslationString", $_SERVER["PHP_SELF"], 'transvalue', '', $param, '', $sortfield, $sortorder);
//if (isModEnabled('multicompany') && !$user->entity) print_liste_field_titre("Entity", $_SERVER["PHP_SELF"], 'entity,transkey', '', $param, '', $sortfield, $sortorder);
print '<td align="center"></td>';
print_liste_field_titre("");
// Action column
if (!$conf->main_checkbox_left_column) {
print_liste_field_titre("");
}
print "</tr>\n";
@@ -637,7 +649,15 @@ if ($mode == 'searchkey') {
if ($limit && $i > ($offset + $limit)) {
break;
}
print '<tr class="oddeven"><td>'.dolPrintHTML($langcode).'</td>';
print '<tr class="oddeven">';
// Action column
if ($conf->main_checkbox_left_column) {
print '<td class="center nowraponall">';
print '</td>';
}
// Code lang
print '<td>'.dolPrintHTML($langcode).'</td>';
// Key
print '<td class="" title="'.dolPrintHTMLForAttribute($key).'">'.dolPrintHTML($key).'</td>';
print '<td class="tdoverflowmax300 small">';
@@ -656,6 +676,7 @@ if ($mode == 'searchkey') {
}
print '</span>';
print '</td>';
print '<td class="right nowraponall">';
if (!empty($newlangfileonly->tab_translate[$key])) {
if ($val != $newlangfileonly->tab_translate[$key]) {
@@ -713,11 +734,13 @@ if ($mode == 'searchkey') {
print $form->textwithpicto('', $htmltext, 1, 'warning');
}
}
/*if (isModEnabled('multicompany') && !$user->entity)
{
print '<td>'.$val.'</td>';
}*/
print '</td></tr>'."\n";
print '</td>';
// Action column
if (!$conf->main_checkbox_left_column) {
print'<td></td>';
}
print '</tr>'."\n";
}
if (empty($recordtoshow)) {

View File

@@ -57,14 +57,17 @@ $modulepart = GETPOST('modulepart', 'aZ09'); // Used by actions_setmoduleoptions
$type = 'group';
/*
* Action
*/
include DOL_DOCUMENT_ROOT.'/core/actions_setmoduleoptions.inc.php';
$reg = array();
if ($action == 'set_default') {
$ret = addDocumentModel($value, $type, $label, $scandir);
$ret = addDocumentModel($value, $type, '', '');
$res = true;
} elseif ($action == 'del_default') {
$ret = delDocumentModel($value, $type);
@@ -85,7 +88,7 @@ if ($action == 'set_default') {
// On active le modele
$ret = delDocumentModel($value, $type);
if ($ret > 0) {
$ret = addDocumentModel($value, $type, $label, $scandir);
$ret = addDocumentModel($value, $type, '', '');
}
$res = true;
} elseif (preg_match('/set_([a-z0-9_\-]+)/i', $action, $reg)) {

View File

@@ -75,19 +75,24 @@ if (!class_exists('FormSetup')) {
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formsetup.class.php';
}
$form = new Form($db);
$formSetup = new FormSetup($db);
$aiservice = getDolGlobalString('AI_API_SERVICE', 'chatgpt');
// Setup conf for AI model
$formSetup->formHiddenInputs['action'] = "updatefeaturemodel";
foreach ($arrayofaifeatures as $featurekey => $feature) {
$newkey = $featurekey;
$newfeaturekey = $featurekey;
if (preg_match('/^text/', $featurekey)) {
$newkey = 'textgeneration';
$newfeaturekey = 'textgeneration';
}
$item = $formSetup->newItem('AI_API_'.strtoupper($aiservice).'_MODEL_'.$feature["function"]); // Name of constant must end with _KEY so it is encrypted when saved into database.
if ($arrayofai[$aiservice][$newkey] != 'na') {
$item->nameText = $langs->trans("AI_API_MODEL_".$feature["function"]).' <span class="opacitymedium">('.$langs->trans("Default").' = '.$arrayofai[$aiservice][$newkey].')</span>';
if (!empty($arrayofai[$aiservice][$newfeaturekey]['default']) && $arrayofai[$aiservice][$newfeaturekey]['default'] != 'na') {
$item->nameText = '<span class="valignmiddle">'.$langs->trans("AI_API_MODEL_".$feature["function"]).' </span><span class="opacitymedium valignmiddle">('.$langs->trans("Default").' = '.$arrayofai[$aiservice][$newfeaturekey]['default'].')</span>';
if (!empty($arrayofai[$aiservice][$newfeaturekey]['examples'])) {
$htmltooltip = $langs->trans("Example").': '.$arrayofai[$aiservice][$newfeaturekey]['examples'];
$item->nameText .= $form->textwithpicto('', $htmltooltip);
}
} else {
$item->nameText = $langs->trans("AI_API_MODEL_".$feature["function"]).' <span class="opacitymedium">('.$langs->trans("None").')</span>';
}
@@ -205,7 +210,6 @@ if ($action == 'confirm_deleteproperty' && GETPOST('confirm') == 'yes') {
* View
*/
$form = new Form($db);
$formai = new FormAI($db);
$help_url = '';
@@ -245,10 +249,10 @@ if ($action == 'deleteproperty') {
print $formconfirm;
}
print '<br>';
if ($action == 'create') {
$out = '<div class="addcustomprompt">';
$out = '<br>';
$out .= '<div class="addcustomprompt">';
$out .= '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
$out .= '<input type="hidden" name="token" value="'.newToken().'">';
@@ -335,17 +339,23 @@ if ($action == 'create') {
$out .= $form->buttonsSaveCancel("Add", "");
$out .= '</form>';
$out .= '<br><br><br>';
$out .= '</div>';
print $out;
}
if ($action == 'edit' || $action == 'create' || $action == 'deleteproperty') {
$out = '';
if (!empty($currentConfigurations)) {
if (empty($currentConfigurations)) {
print '<span class="opacitymedium">'.$langs->trans("None").'</span>';
print '<br>';
print '<br>';
print '<br>';
} else {
print '<br>';
print '<br>';
print '<br>';
foreach ($currentConfigurations as $confkey => $config) {
if (!empty($confkey) && !preg_match('/^[a-z]+$/i', $confkey)) { // Ignore empty saved setup
continue;

View File

@@ -0,0 +1,529 @@
<?php
/* Copyright (C) 2004-2017 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2026 Nick Fragoulis
*
* 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 <https://www.gnu.org/licenses/>.
*/
/**
* \file htdocs/ai/admin/log_viewer.php
* \ingroup ai
* \brief AI Request Log Viewer with Payload Inspection
*/
/**
* @var Conf $conf
* @var DoliDB $db
* @var HookManager $hookmanager
* @var Translate $langs
* @var User $user
* @var Form $form
*/
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT . '/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php';
// Access Control
if (!$user->admin) {
accessforbidden();
}
// Load translations
$langs->loadLangs(array("admin", "other"));
// Parameters
$action = GETPOST('action', 'aZ09');
$massaction = GETPOST('massaction', 'alpha');
$confirm = GETPOST('confirm', 'alpha');
$toselect = GETPOST('toselect', 'array');
$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'ailoglist';
$optioncss = GETPOST('optioncss', 'alpha');
$mode = GETPOST('mode', 'alpha');
// Search parameters for all columns
$search_date_start = dol_mktime(0, 0, 0, GETPOSTINT('search_date_startmonth'), GETPOSTINT('search_date_startday'), GETPOSTINT('search_date_startyear'));
$search_date_end = dol_mktime(23, 59, 59, GETPOSTINT('search_date_endmonth'), GETPOSTINT('search_date_endday'), GETPOSTINT('search_date_endyear'));
$search_user = GETPOST('search_user', 'alpha');
$search_query = GETPOST('search_query', 'alpha');
$search_tool = GETPOST('search_tool', 'alpha');
$search_provider = GETPOST('search_provider', 'alpha');
$search_time_min = GETPOST('search_time_min', 'alpha');
$search_time_max = GETPOST('search_time_max', 'alpha');
$search_status = GETPOST('search_status', 'alpha');
// Pagination parameters
$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
$sortfield = GETPOST('sortfield', 'alpha');
$sortorder = GETPOST('sortorder', 'alpha');
$page = GETPOSTINT("page");
if (empty($page) || $page == -1) {
$page = 0;
}
$offset = $limit * $page;
if (!$sortfield) $sortfield = "l.date_request";
if (!$sortorder) $sortorder = "DESC";
// Initialize array of search criteria
$search_array = array(
'search_date_start' => $search_date_start,
'search_date_end' => $search_date_end,
'search_user' => $search_user,
'search_query' => $search_query,
'search_tool' => $search_tool,
'search_provider' => $search_provider,
'search_time_min' => $search_time_min,
'search_time_max' => $search_time_max,
'search_status' => $search_status
);
/*
* Actions
*/
$error = '';
if ($action == 'purge' && $confirm == 'yes') {
$db->begin();
$sql = "DELETE FROM " . MAIN_DB_PREFIX . "ai_request_log";
$sql .= " WHERE entity IN (" . getEntity('airequestlog') . ")";
$resql = $db->query($sql);
if ($resql) {
$nbDeleted = $db->affected_rows($resql);
$db->commit();
setEventMessages($langs->trans("LogsCleared") . " (" . $nbDeleted . ")", null, 'mesgs');
} else {
$db->rollback();
setEventMessages($db->lasterror(), null, 'errors');
}
header('Location: ' . $_SERVER["PHP_SELF"]);
exit;
}
// Purge selection
if ($massaction == 'purge' && !empty($toselect) && is_array($toselect)) {
$db->begin();
foreach ($toselect as $id) {
$sql = "DELETE FROM " . MAIN_DB_PREFIX . "ai_request_log";
$sql .= " WHERE rowid = " . ((int) $id);
$sql .= " AND entity IN (" . getEntity('airequestlog') . ")";
$resql = $db->query($sql);
if (!$resql) {
$error++;
$db->rollback();
setEventMessages($db->lasterror(), null, 'errors');
break;
}
}
if (!$error) {
$db->commit();
setEventMessages($langs->trans("SelectedLogsDeleted"), null, 'mesgs');
} else {
$db->rollback();
}
$action = 'list';
$massaction = '';
}
// Clear filter action
if (GETPOST('button_removefilter', 'alpha') || GETPOST('button_removefilter_x', 'alpha')) {
$search_date_start = '';
$search_date_end = '';
$search_user = '';
$search_query = '';
$search_tool = '';
$search_provider = '';
$search_time_min = '';
$search_time_max = '';
$search_status = '';
// Reset page
$page = 0;
}
/*
* View
*/
// Initialize array of search criteria for the view
$param = '';
if ($contextpage != $_SERVER["PHP_SELF"]) {
$param .= '&contextpage='.urlencode($contextpage);
}
if ($limit > 0 && $limit != $conf->liste_limit) {
$param .= '&limit='.urlencode($limit);
}
foreach ($search_array as $key => $val) {
if (!empty($val) || $val === '0') {
$param .= '&' . $key . '=' . urlencode($val);
}
}
llxHeader('', $langs->trans("AIRequestLogs"), '');
// Build WHERE clause
$where = array();
$where[] = "l.entity IN (" . getEntity('airequestlog') . ")";
if ($search_date_start) {
$where[] = "l.date_request >= '" . $db->escape(date('Y-m-d H:i:s', $search_date_start)) . "'";
}
if ($search_date_end) {
$where[] = "l.date_request <= '" . $db->escape(date('Y-m-d H:i:s', $search_date_end)) . "'";
}
if ($search_user) {
$where[] = "u.login LIKE '%" . $db->escape($search_user) . "%'";
}
if ($search_query) {
$where[] = "l.query_text LIKE '%" . $db->escape($search_query) . "%'";
}
if ($search_tool) {
$where[] = "l.tool_name LIKE '%" . $db->escape($search_tool) . "%'";
}
if ($search_provider) {
$where[] = "l.provider LIKE '%" . $db->escape($search_provider) . "%'";
}
if ($search_time_min) {
$where[] = "l.execution_time >= " . floatval($search_time_min);
}
if ($search_time_max) {
$where[] = "l.execution_time <= " . floatval($search_time_max);
}
if ($search_status) {
$where[] = "l.status = '" . $db->escape($search_status) . "'";
}
$whereSQL = '';
if (!empty($where)) {
$whereSQL = ' WHERE ' . implode(' AND ', $where);
}
// Get total count for pagination
$sqlCount = "SELECT COUNT(*) as total
FROM " . MAIN_DB_PREFIX . "ai_request_log as l
LEFT JOIN " . MAIN_DB_PREFIX . "user as u ON l.fk_user = u.rowid
$whereSQL";
$resCount = $db->query($sqlCount);
$totalRecords = $resCount ? $db->fetch_object($resCount)->total : 0;
$sql = "SELECT l.*, u.login
FROM " . MAIN_DB_PREFIX . "ai_request_log as l
LEFT JOIN " . MAIN_DB_PREFIX . "user as u ON l.fk_user = u.rowid
$whereSQL
ORDER BY $sortfield $sortorder
LIMIT " . $offset . ", " . $limit;
$res = $db->query($sql);
$num = $db->num_rows($res);
// Create object for list
$object = new stdClass();
$object->total = $totalRecords;
$title = $langs->trans("AIRequestLogs");
print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $totalRecords, 'title_ai', 0, '', '', $limit, 1, 0, 0, '');
print '<form method="POST" action="' . $_SERVER["PHP_SELF"] . '" name="limitform">';
print '<input type="hidden" name="token" value="' . newToken() . '">';
print '<input type="hidden" name="action" value="list">';
// Add all search parameters to preserve them when changing the limit
foreach ($search_array as $key => $val) {
if (!empty($val) || $val === '0') {
print '<input type="hidden" name="' . $key . '" value="' . dol_escape_htmltag($val) . '">';
}
}
print '<div class="div-table-responsive-no-min">';
print '<table class="noborder" width="100%">';
print '<tr>';
print '<td class="right">';
print $langs->trans("Show") . ': ';
print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
print '<input type="hidden" name="page" value="'.$page.'">';
// Create array of options for limit
$arrayoflimit = array(5, 10, 20, 50, 100, 500, 1000);
print '<select class="flat" name="limit" onchange="this.form.submit()">';
foreach ($arrayoflimit as $val) {
print '<option value="'.$val.'"';
if ($limit == $val) print ' selected';
print '>'.$val.'</option>';
}
print '</select>';
print ' ' . $langs->trans("Entries");
print '</td>';
print '</tr>';
print '</table>';
print '</div>';
print '</form>';
// Display form for filters
print '<form method="POST" action="' . $_SERVER["PHP_SELF"] . '" name="search_form">';
print '<input type="hidden" name="token" value="' . newToken() . '">';
print '<input type="hidden" name="formfilteraction" id="formfilteraction" value="list">';
print '<input type="hidden" name="sortfield" value="' . $sortfield . '">';
print '<input type="hidden" name="sortorder" value="' . $sortorder . '">';
print '<input type="hidden" name="page" value="' . $page . '">';
print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
print '<input type="hidden" name="page_y" value="">';
print '<input type="hidden" name="mode" value="'.$mode.'">';
print '<div class="div-table-responsive">';
print '<table class="tagtable liste listwithfilterbefore">'."\n";
// Fields title
print '<tr class="liste_titre">';
print_liste_field_titre("Date", $_SERVER["PHP_SELF"], "l.date_request", "", $param, '', $sortfield, $sortorder);
print_liste_field_titre("User", $_SERVER["PHP_SELF"], "u.login", "", $param, '', $sortfield, $sortorder);
print_liste_field_titre("Query", $_SERVER["PHP_SELF"], "l.query_text", "", $param, '', $sortfield, $sortorder);
print_liste_field_titre("MCPTool", $_SERVER["PHP_SELF"], "l.tool_name", "", $param, '', $sortfield, $sortorder);
print_liste_field_titre("Provider", $_SERVER["PHP_SELF"], "l.provider", "", $param, '', $sortfield, $sortorder);
print_liste_field_titre("Time", $_SERVER["PHP_SELF"], "l.execution_time", "", $param, 'align="center"', $sortfield, $sortorder);
print_liste_field_titre("Status", $_SERVER["PHP_SELF"], "l.status", "", $param, 'align="center"', $sortfield, $sortorder);
print_liste_field_titre('', $_SERVER["PHP_SELF"], "", "", $param, 'align="center"');
print '</tr>';
// Search row
print '<tr class="liste_titre_filter">';
// Date search
print '<td class="liste_titre">';
print $form->selectDate($search_date_start, 'search_date_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"));
print ' - ';
print $form->selectDate($search_date_end, 'search_date_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("To"));
print '</td>';
// User search
print '<td class="liste_titre"><input type="text" name="search_user" value="' . dol_escape_htmltag($search_user) . '" class="maxwidth100"></td>';
// Query search
print '<td class="liste_titre"><input type="text" name="search_query" value="' . dol_escape_htmltag($search_query) . '" class="maxwidth150"></td>';
// Tool search
print '<td class="liste_titre"><input type="text" name="search_tool" value="' . dol_escape_htmltag($search_tool) . '" class="maxwidth100"></td>';
// Provider search
print '<td class="liste_titre"><input type="text" name="search_provider" value="' . dol_escape_htmltag($search_provider) . '" class="maxwidth100"></td>';
// Time search
print '<td class="liste_titre center">';
print '<input type="text" name="search_time_min" value="' . dol_escape_htmltag($search_time_min) . '" size="3" placeholder="' . dol_escape_htmltag($langs->trans('Min')) . '">';
print '<input type="text" name="search_time_max" value="' . dol_escape_htmltag($search_time_max) . '" size="3" placeholder="' . dol_escape_htmltag($langs->trans('Max')) . '">';
// Status search
print '<td class="liste_titre center">';
$status_options = array('' => $langs->trans("All"), 'success' => $langs->trans("Success"), 'confirm' => $langs->trans("Confirm"), 'error' => $langs->trans("Error"));
print $form->selectarray('search_status', $status_options, $search_status, 0, 0, 0, '', 1); // @phan-suppress-current-line PhanPluginSuspiciousParamOrder
print '</td>';
// Search buttons
print '<td class="liste_titre center">';
$searchpicto = img_picto($langs->trans("Search"), 'search.png', '', 0, 1);
print '<input type="image" class="liste_titre" name="button_search" src="' . $searchpicto . '" value="' . dol_escape_htmltag($langs->trans("Search")) . '" title="' . dol_escape_htmltag($langs->trans("Search")) . '">';
$clearpicto = img_picto($langs->trans("RemoveFilter"), 'searchclear.png', '', 0, 1);
print '<input type="image" class="liste_titre" name="button_removefilter" src="' . $clearpicto . '" value="' . dol_escape_htmltag($langs->trans("RemoveFilter")) . '" title="' . dol_escape_htmltag($langs->trans("RemoveFilter")) . '">';
print '</td>';
print '</tr>';
// Mass action buttons
print '<tr class="liste_titre">';
print '<td class="liste_titre" colspan="8">';
print '<div class="center">';
print '<div class="inline-block divButAction"><a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=purge&token='.newToken().'" onclick="return confirm(\''.$langs->trans("ConfirmDeleteAllLogs").'\');">'.$langs->trans("ClearAllLogs").'</a></div>';
print '</div>';
print '</td>';
print '</tr>';
if ($res && $db->num_rows($res) > 0) {
$i = 0;
while ($obj = $db->fetch_object($res)) {
print '<tr class="oddeven">';
// Date
print '<td>' . dol_print_date($db->jdate($obj->date_request), 'dayhour') . '</td>';
// User
print '<td>' . ($obj->login ? dol_escape_htmltag($obj->login) : $langs->trans("Unknown")) . '</td>';
// Query - properly escaped
$shortQuery = dol_trunc($obj->query_text, 60);
print '<td title="' . dol_escape_htmltag($obj->query_text) . '">' . dol_escape_htmltag($shortQuery) . '</td>';
// Tool
print '<td>' . dol_escape_htmltag($obj->tool_name) . '</td>';
// Provider
print '<td>' . dol_escape_htmltag($obj->provider) . '</td>';
// Time
$timeColor = ($obj->execution_time > 5) ? 'color:red;' : '';
print '<td style="' . $timeColor . '" align="center">' . round($obj->execution_time, 2) . 's</td>';
// Status
$badge = 'badge-status0';
if ($obj->status == $langs->transnoentitiesnoconv("Success")) {
$badge = 'badge-status4'; // Green
}
if ($obj->status == $langs->transnoentitiesnoconv("Confirm")) {
$badge = 'badge-status3'; // Yellow
}
if ($obj->status == $langs->transnoentitiesnoconv('Error')) {
$badge = 'badge-status8'; // Red
}
print '<td align="center"><span class="badge ' . $badge . '">' . dol_escape_htmltag($obj->status) . '</span></td>';
// Details Button (Triggers Modal)
// We embed data attributes securely with proper UTF-8 handling
$reqSafe = base64_encode($obj->raw_request_payload);
$resSafe = base64_encode($obj->raw_response_payload);
$errSafe = base64_encode($obj->error_msg);
print '<td align="center">';
print '<a href="#" class="button button-small" onclick="openLogModal(this)"
data-req="' . dol_escape_htmltag($reqSafe) . '"
data-res="' . dol_escape_htmltag($resSafe) . '"
data-err="' . dol_escape_htmltag($errSafe) . '">';
print '<span class="fa fa-search-plus"></span> ' . $langs->trans("View");
print '</a>';
print '</td>';
print '</tr>';
$i++;
}
} else {
$colspan = 8;
print '<tr><td colspan="' . $colspan . '" class="opacitymedium">' . $langs->trans("NoLogsFound");
if (!empty($where)) {
print ' ' . $langs->trans("MatchingSearchCriteria");
}
print '. ' . $langs->trans("TryAskingAI") . '.</td></tr>';
}
print '</table></div>';
print '</form>';
// --- MODAL HTML & JS ---
?>
<div id="logModal" style="display:none; position:fixed; z-index:9999; left:0; top:0; width:100%; height:100%; overflow:auto; background-color:rgba(0,0,0,0.5);">
<div style="background-color:#fff; margin:5% auto; padding:20px; border:1px solid #888; width:80%; max-width:900px; border-radius:8px; box-shadow:0 4px 8px rgba(0,0,0,0.2);">
<span style="float:right; font-size:28px; font-weight:bold; cursor:pointer;" onclick="document.getElementById('logModal').style.display='none'">&times;</span>
<h2><?php echo $langs->trans("LogDetails"); ?></h2>
<h3><?php echo $langs->trans("ErrorWarning"); ?></h3>
<div id="modalError" style="background:#fff0f0; border:1px solid #ffcdd2; color:#d32f2f; padding:10px; border-radius:4px; display:none;"></div>
<div style="display:flex; gap:20px; margin-top:15px;">
<div style="flex:1;">
<h3><?php echo $langs->trans("RequestPayload"); ?></h3>
<textarea id="modalReq" style="width:100%; height:300px; font-family:monospace; font-size:12px; border:1px solid #ccc;" readonly></textarea>
</div>
<div style="flex:1;">
<h3><?php echo $langs->trans("ResponsePayload"); ?></h3>
<textarea id="modalRes" style="width:100%; height:300px; font-family:monospace; font-size:12px; border:1px solid #ccc;" readonly></textarea>
</div>
</div>
<div style="text-align:right; margin-top:15px;">
<button class="button" onclick="document.getElementById('logModal').style.display='none'"><?php echo $langs->trans("Close"); ?></button>
</div>
</div>
</div>
<script>
// UTF-8 safe base64 decoding function
function base64ToUtf8(str) {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
// Function to decode Unicode escape sequences in JSON strings
function decodeUnicodeEscapes(str) {
// First, try to parse as JSON to handle escaped Unicode properly
try {
// If it's a JSON string, parse and stringify to decode escapes
const parsed = JSON.parse(str);
return JSON.stringify(parsed, null, 2);
} catch (e) {
// If not valid JSON, try to decode Unicode escapes in the string
return str.replace(/\\u([0-9a-fA-F]{4})/g, function(match, p1) {
return String.fromCharCode(parseInt(p1, 16));
});
}
}
// Sanitize HTML to prevent XSS
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function openLogModal(btn) {
// Decode Base64 safely with UTF-8 support
const reqBase64 = btn.getAttribute('data-req') || '';
const resBase64 = btn.getAttribute('data-res') || '';
const errBase64 = btn.getAttribute('data-err') || '';
let req = '';
let res = '';
let err = '';
try {
// Decode Unicode escape sequences
if (reqBase64) {
req = base64ToUtf8(reqBase64);
req = decodeUnicodeEscapes(req);
}
if (resBase64) {
res = base64ToUtf8(resBase64);
res = decodeUnicodeEscapes(res);
}
if (errBase64) {
err = base64ToUtf8(errBase64);
err = decodeUnicodeEscapes(err);
}
} catch (e) {
console.error('Error decoding base64:', e);
// Fallback to regular atob if UTF-8 decoding fails
req = reqBase64 ? atob(reqBase64) : '';
res = resBase64 ? atob(resBase64) : '';
err = errBase64 ? atob(errBase64) : '';
}
// Sanitize content before setting it
document.getElementById('modalReq').value = req || '(<?php echo $langs->trans("NoRequestPayload"); ?>)';
document.getElementById('modalRes').value = res || '(<?php echo $langs->trans("NoResponsePayload"); ?>)';
const errDiv = document.getElementById('modalError');
if (err) {
errDiv.innerText = err;
errDiv.style.display = 'block';
} else {
errDiv.style.display = 'none';
}
document.getElementById('logModal').style.display = 'block';
}
</script>
<?php
llxFooter();

View File

@@ -4,107 +4,48 @@
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
* Coryright (C) 2024 Alexandre Spangaro <alexandre@inovea-conseil.com>
* Copyright (C) 2026 Nick Fragoulis
*
* This program is free software: you can redistribute it and/or modify
* 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
* 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
* 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 <https://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* \file htdocs/ai/admin/server_mcp.php
* \file htdocs/ai/admin/server_mcp.php
* \ingroup ai
* \brief Ai setup page.
* \brief MCP Server & Assistant Configuration Page
*/
// Load Dolibarr environment
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
require_once DOL_DOCUMENT_ROOT."/core/class/doleditor.class.php";
require_once DOL_DOCUMENT_ROOT."/ai/lib/ai.lib.php";
/**
* @var Conf $conf
* @var DoliDB $db
* @var HookManager $hookmanager
* @var Translate $langs
* @var User $user
* @var Form $form
*/
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT . "/core/lib/admin.lib.php";
require_once DOL_DOCUMENT_ROOT . "/core/class/html.form.class.php";
require_once DOL_DOCUMENT_ROOT . "/core/class/doleditor.class.php";
require_once DOL_DOCUMENT_ROOT . "/ai/lib/ai.lib.php";
require_once DOL_DOCUMENT_ROOT . "/core/lib/functions2.lib.php";
require_once DOL_DOCUMENT_ROOT . "/core/lib/security.lib.php";
$langs->loadLangs(array("admin", "website", "other"));
// Parameters
$action = GETPOST('action', 'aZ09');
$backtopage = GETPOST('backtopage', 'alpha');
$modulepart = GETPOST('modulepart', 'aZ09'); // Used by actions_setmoduleoptions.inc.php
if (empty($action)) {
$action = 'edit';
}
$content = GETPOST('content');
$error = 0;
$setupnotempty = 0;
// Set this to 1 to use the factory to manage constants. Warning, the generated module will be compatible with version v15+ only
$useFormSetup = 1;
if (!class_exists('FormSetup')) {
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formsetup.class.php';
}
$formSetup = new FormSetup($db);
// List all available IA
$arrayofai = getListOfAIServices();
// List all available features
$arrayofaifeatures = getListOfAIFeatures();
$item = $formSetup->newItem('AI_API_SERVICE'); // Name of constant must end with _KEY so it is encrypted when saved into database.
$item->setAsSelect($arrayofai);
$item->cssClass = 'minwidth150';
foreach ($arrayofai as $ia => $iarecord) {
$ialabel = $iarecord['label'];
// Setup conf AI_PUBLIC_INTERFACE_TOPIC
/*$item = $formSetup->newItem('AI_API_'.strtoupper($ia).'_ENDPOINT'); // Name of constant must end with _KEY so it is encrypted when saved into database.
$item->defaultFieldValue = '';
$item->cssClass = 'minwidth500';*/
$item = $formSetup->newItem('AI_API_'.strtoupper($ia).'_KEY')->setAsSecureKey(); // Name of constant must end with _KEY so it is encrypted when saved into database.
$item->nameText = $langs->trans("AI_API_KEY").' ('.$ialabel.')';
$item->defaultFieldValue = '';
$item->fieldParams['hideGenerateButton'] = 1;
$item->fieldParams['trClass'] = 'iaservice '.$ia;
$item->cssClass = 'minwidth500 text-security input'.$ia;
$item = $formSetup->newItem('AI_API_'.strtoupper($ia).'_URL'); // Name of constant must end with _KEY so it is encrypted when saved into database.
$item->nameText = $langs->trans("AI_API_URL").' ('.$ialabel.')';
$item->defaultFieldValue = '';
$item->fieldParams['trClass'] = 'iaservice iaurl '.$ia;
$item->cssClass = 'minwidth500 input'.$ia;
if ($ia == 'custom') {
$item->fieldAttr['placeholder'] = 'https://domainofapi.com/v1/';
}
}
$setupnotempty = + count($formSetup->items);
$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
// Access control
if (!$user->admin) {
accessforbidden();
@@ -113,35 +54,349 @@ if (!isModEnabled('ai')) {
accessforbidden('Module AI not activated.');
}
// Parameters
$action = GETPOST('action', 'aZ09');
/*
* Actions
* ACTIONS
*/
include DOL_DOCUMENT_ROOT.'/core/actions_setmoduleoptions.inc.php';
// Main Settings
if ($action == 'update') {
$error = 0;
$action = 'edit';
if (GETPOSTISSET('AI_ASK_FOR_CONFIRMATION')) {
$res = dolibarr_set_const($db, "AI_ASK_FOR_CONFIRMATION", GETPOSTINT("AI_ASK_FOR_CONFIRMATION"), 'int', 0, '', $conf->entity);
if ($res <= 0) $error++;
}
if (GETPOSTISSET('AI_LOG_RETENTION')) {
$res = dolibarr_set_const($db, "AI_LOG_RETENTION", GETPOST("AI_LOG_RETENTION"), 'int', 0, '', $conf->entity);
if ($res <= 0) {
$error++;
}
}
if (GETPOSTISSET('AI_DEFAULT_INPUT_MODE')) {
$res = dolibarr_set_const($db, "AI_DEFAULT_INPUT_MODE", GETPOST("AI_DEFAULT_INPUT_MODE"), 'chaine', 0, '', $conf->entity);
if ($res <= 0) {
$error++;
}
}
if (GETPOSTISSET('AI_INTENT_PROMPT')) {
$res = dolibarr_set_const($db, "AI_INTENT_PROMPT", GETPOST("AI_INTENT_PROMPT"), 'chaine', 0, '', $conf->entity);
if ($res <= 0) {
$error++;
}
}
if ($error) {
setEventMessages($langs->trans("ErrorSavingSettings"), null, 'errors');
} else {
setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
}
header("Location: ".$_SERVER["PHP_SELF"]."?mainmenu=home");
exit;
}
// Test connection
if ($action == 'test_provider') {
$service = GETPOST('service_key', 'aZ09');
if ($service) {
$credential = getDolGlobalString('AI_API_' . strtoupper($service) . '_KEY');
$url = getDolGlobalString('AI_API_' . strtoupper($service) . '_URL');
// Decrypt if needed
if (preg_match('/^crypted:/', $credential)) {
$credential = dol_decode(substr($credential, 8));
} elseif (preg_match('/^dolcrypt:/', $credential)) {
$credential = dolDecrypt($credential, '');
}
// Only proceed if the key is valid (decrypted or not encrypted)
if ($credential !== null) {
$res = testAIConnection($service, $credential, $url);
if ($res['success']) {
setEventMessages($langs->trans("ConnectionSuccessful") . $res['message'], null, 'mesgs');
} else {
setEventMessages($langs->trans("ConnectionFailed") . $res['message'], null, 'errors');
}
}
}
}
// External Access Settings
if ($action == 'update_external') {
$error = 0;
$user_id = GETPOSTINT("AI_MCP_USER_ID");
if (GETPOSTISSET('AI_MCP_USER_ID')) {
$res = dolibarr_set_const($db, "AI_MCP_USER_ID", $user_id, 'int', 0, '', $conf->entity);
if ($res <= 0) {
$error++;
}
}
if ($error) {
setEventMessages($langs->trans("ErrorSavingSettings"), null, 'errors');
} else {
setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
}
header("Location: ".$_SERVER["PHP_SELF"]."?mainmenu=home");
exit;
}
// Generate New API Key
if ($action == 'generate_key') {
$newKey = dolGetRandomBytes(64);
if (empty($newKey)) {
setEventMessages($langs->trans("KeyGenerationFailed"), null, 'errors');
} else {
if (dolibarr_set_const($db, 'AI_MCP_API_KEY', $newKey, 'chaine', 0, '', $conf->entity) > 0) {
setEventMessages($langs->trans("KeyGenerationSuccessfull"), null, 'mesgs');
} else {
setEventMessages($langs->trans("KeySaveFailed"), null, 'errors');
}
}
}
/*
* View
* VIEW
*/
$help_url = '';
$title = "AiSetup";
$title = "AIMCPConfig";
llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-ai page-admin');
// Subheader
$linkback = '<a href="'.($backtopage ? $backtopage : DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1').'">'.img_picto($langs->trans("BackToModuleList"), 'back', 'class="pictofixedwidth"').'<span class="hideonsmartphone">'.$langs->trans("BackToModuleList").'</span></a>';
print load_fiche_titre($langs->trans($title), $linkback, 'title_setup');
$linkback = '<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';
print load_fiche_titre($title, $linkback, 'title_setup');
// Configuration header
$head = aiAdminPrepareHead();
print dol_get_fiche_head($head, 'servermcp', $langs->trans($title), -1, "ai");
print dol_get_fiche_head($head, 'servermcp', "MCP Server", -1, "ai");
print 'There is no MCP server available yet.';
print '<span class="opacitymedium">' . $langs->trans("ConfigHelp") . '</span><br><br>';
$form = new Form($db);
// Load Current Values
$apiKey = getDolGlobalString('AI_MCP_API_KEY');
// Default Prompt Logic
$defaultPromptText = " ROLE: Dolibarr ERP AI.
GOAL: Map user intent to specific JSON commands.
CONSTRAINTS:
1. OUTPUT: Single valid JSON object ONLY. No Markdown. No text.
Format: {\"tool\": \"tool_name\", \"arguments\": {\"argument_name\": \"argument_value\", ...}}
2. TOOLS: Use ONLY provided tools. If no tool or thirdparty fits, you must first use 'respond_to_user' to inform user.
3. ARGS: strict adherence to schema. Do not invent parameters.
4. MISSING INFO: If a required argument (like an ID) is missing, use 'ask_for_clarification'.
5. SAFETY: For DELETE/UPDATE actions, you MUST use 'ask_for_confirmation'.";
$currentPrompt = getDolGlobalString('AI_INTENT_PROMPT', $defaultPromptText);
// Settings
print '<div class="div-table-responsive-no-min">';
print '<table class="noborder centpercent">';
// Enable/Disable
print '<tr class="oddeven">';
print '<td class="titlefield" width="30%">' . $langs->trans('EnableMCPServer') . '</td>';
print '<td>';
print ajax_constantonoff('AI_MCP_ENABLED');
print ' <span class="opacitymedium">' . $langs->trans('DisableMCPAI') . '</span>';
print '</td>';
print '</tr>';
print '</table>';
print '</div>';
print load_fiche_titre($langs->trans("PrivateModeTitle"), '', 'fas fa-lock');
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="update">';
print '<div class="div-table-responsive-no-min">';
print '<table class="noborder centpercent">';
// Input Mode
print '<tr class="oddeven">';
print '<td>Default Interface Mode</td>';
print '<td>';
$input_modes = [
'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 '<br><span class="opacitymedium small">' . $langs->trans("InputMethodHelp") . '</span>';
print '</td>';
print '</tr>';
// Enhanced Privacy control
print '<tr class="oddeven">';
print '<td class="titlefield" width="30%">' . $langs->trans('ObfuscatePIIData') . '</td>';
print '<td>';
print ajax_constantonoff('AI_PRIVACY_REDACTION');
print ' <span class="opacitymedium">' . $langs->trans("RedactionHelp") . '</span>';
print '</td>';
print '</tr>';
// Confirmation Level
print '<tr class="oddeven">';
print '<td class="titlefield" width="30%">' . $langs->trans("AskConfirmation");
print ' <span class="fa fa-info-circle" title="' . $langs->trans("AskConfirmationHelp") . '"></span></td>';
print '<td>';
$confirmation_options = [
'0' => $langs->trans("ConfirmNever"),
'1' => $langs->trans("ConfirmWriteOnly"),
'2' => $langs->trans("ConfirmAlways")
];
print $form->selectarray('AI_ASK_FOR_CONFIRMATION', $confirmation_options, getDolGlobalInt('AI_ASK_FOR_CONFIRMATION', 1), 0, 0, 0);
print '</td>';
print '</tr>';
// Logging
print '<tr class="oddeven">';
print '<td class="titlefield" width="30%">' . $langs->trans('EnableLogging') . '</td>';
print '<td>';
print ajax_constantonoff('AI_LOG_REQUESTS');
print ' <a href="' . DOL_URL_ROOT . '/ai/admin/log_viewer.php" target="_blank" class="button" style="padding-top: 4px; padding-bottom: 4px;">View Logs</a>';
print '</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td>' . $langs->trans("LogRetention") . '</td>';
print '<td><input type="number" name="AI_LOG_RETENTION" value="' . getDolGlobalInt('AI_LOG_RETENTION', 30) . '" size="5"> (0 = Forever)</td>';
print '</tr>';
// System Prompt
print '<tr class="oddeven">';
print '<td colspan="2">';
print '<strong>' . $langs->trans("SystemPrompt") . '</strong><br>';
print '<span class="opacitymedium small">' . $langs->trans("SystemPromptHelp") . '</span><br>';
$doleditor = new DolEditor('AI_INTENT_PROMPT', $currentPrompt, '', 250, 'dolibarr_notes', 'In', false, false, true, ROWS_8, '90%');
$doleditor->Create();
print '</td>';
print '</tr>';
print '</table>';
print '</div>';
print '<div class="center"><input type="submit" class="button" value="'.$langs->trans("Save").'"></div>';
print '</form>';
// AI Provider Config and Connection testing
$services = getListOfAIServices();
$currentService = getDolGlobalString('AI_API_SERVICE');
print load_fiche_titre($langs->trans("AIProviderConfigTitle"), '', 'fa fa-plug');
if ((string) $currentService == '-1') {
print '<div class="warning">'.$langs->trans("NoAIProviderSelected").' <a href="'.dol_buildpath('/ai/admin/setup.php', 1).'">'.$langs->trans("ConfigureHere").'</a></div>';
} else {
print '<div class="div-table-responsive-no-min">';
print '<table class="noborder centpercent">';
print '<tr class="oddeven"><td class="titlefield">'.$langs->trans("AIProvider").'</td><td>'.$services[$currentService]['label'].'</td></tr>';
$prefix = 'AI_API_'.strtoupper($currentService);
$modelVal = getDolGlobalString($prefix.'_MODEL', $services[$currentService]['textgeneration']);
print '<tr class="oddeven"><td>'.$langs->trans("AI_API_MODEL").'</td><td>'.$modelVal.'</td></tr>';
print '</table></div>';
print '<div class="center">';
if ($currentService && $currentService !== '-1') {
print ' <a href="'.$_SERVER["PHP_SELF"].'?action=test_provider&service_key='.$currentService.'" class="button">Test Connection</a>';
}
print '</div>';
}
print '</div>';
print '</form>';
// External Access Configuration
print load_fiche_titre($langs->trans("AiMcpExternalAccess"), '', 'fas fa-lock-open text-danger');
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="update_external">';
print '<div class="div-table-responsive-no-min">';
print '<table class="noborder centpercent">';
// Service User
print '<tr class="oddeven">';
print '<td>Service User <span class="fa fa-info-circle" title="' . $langs->trans("UserPermissionsTooltip") . '"></span></td>';
print '<td>';
print '<div style="display: flex; align-items: center;">';
print $form->select_dolusers(getDolGlobalInt('AI_MCP_USER_ID'), 'AI_MCP_USER_ID', 1);
print ' <input type="submit" class="button" value="'.$langs->trans("Save").'" style="margin-left: 20px;">';
print '</div>';
print '<span class="opacitymedium small">' . $langs->trans("DedicatedUserRecommendation") . '</span>';
print '</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td width="30%">API Key</td>';
print '<td>';
if ($apiKey) {
print '<input type="text" id="apikey" value="'.$apiKey.'" readonly style="width:400px; padding:6px; background:#f4f4f4; border:1px solid #ccc; color:#555;">';
print ' <button type="button" class="button small" onclick="navigator.clipboard.writeText(document.getElementById(\'apikey\').value)">' . $langs->trans("Copy") . '</button>';
print ' <a class="button" href="'.$_SERVER["PHP_SELF"].'?action=generate_key&token='.newToken().'">Generate New Key</a>';
} else {
print '<span class="opacitymedium">' . $langs->trans("NoKeyWarning") . '</span>';
print ' <a class="button" href="' . $_SERVER["PHP_SELF"] . '?action=generate_key&token=' . newToken() . '">' . $langs->trans("GenerateKey") . '</a>';
}
print '</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td>Endpoint URL</td>';
print '<td>';
$endpoint = dol_buildpath('/ai/server/mcp_server.php', 2);
print '<input type="text" value="'.$endpoint.'" readonly style="width:600px; border:none; background:transparent;">';
print '</td>';
print '</tr>';
print '</table>';
print '</div>';
print '</form>';
// Configuration Examples
print '<br>';
print '<div style="background:#fcfcfc; border:1px solid #eee; padding:15px; border-radius:5px;">';
print '<strong>' . $langs->trans("ClaudeDesktopConfig") . '</strong><br>';
print '<pre style="background:#333; color:#fff; padding:10px; border-radius:4px; overflow:auto; margin-top:10px;">';
echo htmlspecialchars('{
"mcpServers": {
"dolibarr": {
"command": "node",
"args": ["/path/to/mcp-bridge.js"],
"env": {
"DOLIBARR_URL": "'.$endpoint.'",
"DOLIBARR_API_KEY": "'.($apiKey ? $apiKey : "YOUR_KEY_HERE").'"
}
}
}
}');
print '</pre>';
print '</div>';
print dol_get_fiche_end();
llxFooter();
$db->close();

View File

@@ -2,8 +2,9 @@
/* Copyright (C) 2004-2017 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2022 Lamrani Abdel
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
* Coryright (C) 2024 Alexandre Spangaro <alexandre@inovea-conseil.com>
* Copyright (C) 2026 Nick Fragoulis
*
* 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
@@ -11,7 +12,7 @@
* (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
* 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.
*
@@ -27,10 +28,6 @@
// Load Dolibarr environment
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
require_once DOL_DOCUMENT_ROOT."/core/class/doleditor.class.php";
require_once DOL_DOCUMENT_ROOT."/ai/lib/ai.lib.php";
/**
* @var Conf $conf
* @var DoliDB $db
@@ -38,6 +35,9 @@ require_once DOL_DOCUMENT_ROOT."/ai/lib/ai.lib.php";
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
require_once DOL_DOCUMENT_ROOT."/core/class/doleditor.class.php";
require_once DOL_DOCUMENT_ROOT."/ai/lib/ai.lib.php";
$langs->loadLangs(array("admin", "website", "other"));
@@ -66,23 +66,29 @@ if (!class_exists('FormSetup')) {
$formSetup = new FormSetup($db);
// List all available IA
// List all available AI
$arrayofai = getListOfAIServices();
// List all available features
$arrayofaifeatures = getListOfAIFeatures();
// Main Service Selection
$item = $formSetup->newItem('AI_API_SERVICE'); // Name of constant must end with _KEY so it is encrypted when saved into database.
$item->setAsSelect($arrayofai);
$item->cssClass = 'minwidth150';
// Loop for Provider Configs
foreach ($arrayofai as $ia => $iarecord) {
if ($ia == '-1') {
continue;
}
$ialabel = $iarecord['label'];
// Setup conf AI_PUBLIC_INTERFACE_TOPIC
/*$item = $formSetup->newItem('AI_API_'.strtoupper($ia).'_ENDPOINT'); // Name of constant must end with _KEY so it is encrypted when saved into database.
$item->defaultFieldValue = '';
$item->cssClass = 'minwidth500';*/
// API Key
$item = $formSetup->newItem('AI_API_'.strtoupper($ia).'_KEY')->setAsSecureKey(); // Name of constant must end with _KEY so it is encrypted when saved into database.
$item->nameText = $langs->trans("AI_API_KEY").' ('.$ialabel.')';
$item->defaultFieldValue = '';
@@ -91,14 +97,23 @@ foreach ($arrayofai as $ia => $iarecord) {
$item->cssClass = 'minwidth500 text-security input'.$ia;
$item->helpText = '<span class="helptoshow">HelpToShow</span>';
// API URL
$item = $formSetup->newItem('AI_API_'.strtoupper($ia).'_URL'); // Name of constant must end with _KEY so it is encrypted when saved into database.
$item->nameText = $langs->trans("AI_API_URL").' ('.$ialabel.')';
$item->defaultFieldValue = '';
$item->defaultFieldValue = $iarecord['url'];
$item->fieldParams['trClass'] = 'iaservice iaurl '.$ia;
$item->cssClass = 'minwidth500 input'.$ia;
if ($ia == 'custom') {
$item->fieldAttr['placeholder'] = 'https://domainofapi.com/v1/';
}
// Generic Model Field
$item = $formSetup->newItem('AI_API_'.strtoupper($ia).'_MODEL');
$item->nameText = $langs->trans("AI_API_MODEL").' ('.$ialabel.')';
$item->defaultFieldValue = $iarecord['textgeneration'];
$item->fieldParams['trClass'] = 'iaservice '.$ia;
$item->cssClass = 'minwidth500 input'.$ia;
$item->helpText = $langs->trans("AI_API_MODEL_HELP");
}
$setupnotempty = + count($formSetup->items);

View File

@@ -153,18 +153,18 @@ class Ai
if (in_array($function, array('file', 'assistant', 'thread'))) {
$model = '';
} elseif ($function == 'imagegeneration') {
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_IMAGE', $arrayofai[$this->apiService][$function]);
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_IMAGE', $arrayofai[$this->apiService][$function]['default']);
} elseif ($function == 'audiogeneration') {
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_AUDIO', $arrayofai[$this->apiService][$function]);
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_AUDIO', $arrayofai[$this->apiService][$function]['default']);
} elseif ($function == 'transcription') {
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_TRANSCRIPT', $arrayofai[$this->apiService][$function]);
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_TRANSCRIPT', $arrayofai[$this->apiService][$function]['default']);
} elseif ($function == 'translation') {
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_TRANSLATE', $arrayofai[$this->apiService][$function]);
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_TRANSLATE', $arrayofai[$this->apiService][$function]['default']);
} elseif ($function == 'docparsing') {
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_DOCPARSING', $arrayofai[$this->apiService][$function]);
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_DOCPARSING', $arrayofai[$this->apiService][$function]['default']);
} else {
// else 'textgenerationemail', 'textgenerationwebpage', 'textgeneration', 'texttranslation', 'textsummarize'
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_TEXT', $arrayofai[$this->apiService]['textgeneration']);
$model = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_MODEL_TEXT', $arrayofai[$this->apiService]['textgeneration']['default']);
}
}
@@ -297,7 +297,7 @@ class Ai
} else {
fwrite($fp, "Call endpoint ".$this->apiEndpoint." with POST and the following message:\n");
fwrite($fp, $fullInstructions."\n");
fwrite($fp, "Prepompt\n");
fwrite($fp, "And prepompt:\n");
fwrite($fp, $prePrompt."\n");
}
fwrite($fp, "HTTP Header\n");

View File

@@ -1,7 +1,8 @@
<?php
/* Copyright (C) 2022 Alice Adminson <aadminson@example.com>
* Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
/* Copyright (C) 2022 Alice Adminson <aadminson@example.com>
* Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2026 Nick Fragoulis
*
* 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
@@ -9,7 +10,7 @@
* (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
* 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.
*
@@ -51,8 +52,7 @@ function getListOfAIFeatures()
'audiogeneration' => array('label' => 'AudioGeneration', 'picto' => '', 'status' => 'notused', 'function' => 'AUDIO'),
'transcription' => array('label' => 'AudioTranscription', 'picto' => '', 'status' => 'notused', 'function' => 'TRANSCRIPT'),
'translation' => array('label' => 'AudioTranslation', 'picto' => '', 'status' => 'notused', 'function' => 'TRANSLATE'),
'docparsing' => array('label' => 'DocumentPArsing', 'picto' => '', 'status' => 'experimental', 'function' => 'DOCPARSING')
'docparsing' => array('label' => 'DocumentParsing', 'picto' => '', 'status' => 'experimental', 'function' => 'DOCPARSING')
);
return $arrayofaifeatures;
@@ -70,63 +70,296 @@ function getListOfAIServices()
$arrayofai = array(
'-1' => array('label' => $langs->trans('SelectAService')),
'chatgpt' => array(
'label' => 'ChatGPT',
'url' => 'https://api.openai.com/v1/',
'setup' => 'https://platform.openai.com/account/api-keys',
'textgeneration' => 'gpt-4.1-turbo', // a lot of text transformation like: 'textgenerationemail', 'textgenerationwebpage', 'textgeneration', 'texttranslation', 'textsummarize'
'imagegeneration' => 'dall-e-3',
'audiogeneration' => 'tts-1',
'videogeneration' => 'na',
'transcription' => 'whisper-1', // audio to text
'translation' => 'whisper-1', // audio to text into another language
'docparsing' => 'na',
'label' => 'ChatGPT (OpenAI)',
'url' => 'https://api.openai.com/v1/',
'setup' => 'https://platform.openai.com/account/api-keys',
'textgeneration' => array('default' => 'gpt-5.2'), // Flagship model released late 2025, updated Feb 2026
'imagegeneration' => array('default' => 'gpt-image-1.5'), // Replaced DALL-E 3; 4x faster and native to GPT-5
'audiogeneration' => array('default' => 'gpt-audio-1.5'), // New Feb 23, 2026 release for high-fidelity audio out
'videogeneration' => array('default' => 'sora-2'), // OpenAI's standard API video model
'transcription' => array('default' => 'whisper-large-v3-turbo'), // The current speed/accuracy benchmark for ASR
'translation' => array('default' => 'whisper-large-v3-turbo'), // Still the best for multi-language audio translation
'docparsing' => array('default' => 'gpt-5.2'), // Uses the new Responses API / Vision capabilities
'adapter_type' => 'openai'
),
'groq' => array(
'label' => 'Groq',
'url' => 'https://api.groq.com/openai/',
'setup' => 'https://platform.groq.com/signup',
'textgeneration' => 'mixtral-8x7b-32768', // 'llama3-8b-8192', 'gemma-7b-it'
'imagegeneration' => 'na',
'audiogeneration' => 'na',
'videogeneration' => 'na',
'transcription' => 'na',
'translation' => 'na',
'docparsing' => 'na',
'label' => 'Groq (LPU Inference)',
'url' => 'https://api.groq.com/openai/v1/',
'setup' => 'https://console.groq.com/keys',
'textgeneration' => array('default' => 'llama-4-8b-instant'), // February 2026 flagship for extreme speed (1,000+ t/s)
'imagegeneration' => array('default' => 'na'),
'audiogeneration' => array('default' => 'na'),
'videogeneration' => array('default' => 'na'),
'transcription' => array('default' => 'whisper-large-v3-turbo'), // Groq's specialized high-speed Whisper implementation
'translation' => array('default' => 'whisper-large-v3-turbo'), // High-speed audio translation to English
'docparsing' => array('default' => 'llama-4-70b-versatile'), // Best for structured data extraction from text
'adapter_type' => 'openai'
),
'mistral' => array(
'label' => 'Mistral',
'label' => 'Mistral AI',
'url' => 'https://api.mistral.ai/v1/',
'setup' => 'https://console.mistral.ai/',
'textgeneration' => 'open-mistral-7b',
'imagegeneration' => 'na',
'audiogeneration' => 'na',
'videogeneration' => 'na',
'transcription' => 'na',
'translation' => 'na',
'docparsing' => 'open-mistral-7b',
'setup' => 'https://console.mistral.ai/api-keys/',
'textgeneration' => array('default' => 'mistral-small-latest', 'examples' => 'mistral-tiny-latest, mistral-small-latest, mistral-medium-latest, mistral-large-latest'), // Points to Mistral Small 3 (updated Feb 2026)
'imagegeneration' => array('default' => 'na'),
'audiogeneration' => array('default' => 'na'),
'videogeneration' => array('default' => 'na'),
'transcription' => array('default' => 'na'),
'translation' => array('default' => 'na'),
'docparsing' => array('default' => 'pixtral-12b-latest'), // Mistral's native vision/doc model
'adapter_type' => 'openai'
),
'deepseek' => array(
'label' => 'DeepSeek',
'url' => 'https://api.deepseek.com',
'setup' => 'https://platform.deepseek.com/api_keys',
'textgeneration' => array('default' => 'deepseek-v4'), // Released Feb 2026, flagship MoE model
'imagegeneration' => array('default' => 'deepseek-janus-2'), // DeepSeek's latest multimodal vision/gen model
'audiogeneration' => array('default' => 'na'),
'videogeneration' => array('default' => 'na'),
'transcription' => array('default' => 'na'),
'translation' => array('default' => 'na'),
'docparsing' => array('default' => 'deepseek-v4'), // Massive 1M context support for parsing
'adapter_type' => 'openai'
),
'perplexity' => array(
'label' => 'Perplexity (Sonar)',
'url' => 'https://api.perplexity.ai',
'setup' => 'https://www.perplexity.ai/settings/api',
'textgeneration' => array('default' => 'sonar-pro'), // Flagship search model as of Feb 2026
'imagegeneration' => array('default' => 'na'),
'audiogeneration' => array('default' => 'na'),
'videogeneration' => array('default' => 'na'),
'transcription' => array('default' => 'na'),
'translation' => array('default' => 'na'),
'docparsing' => array('default' => 'sonar-reasoning'), // Best for analyzing search-grounded docs
'adapter_type' => 'openai'
),
'zai' => array(
'label' => 'Zhipu AI (GLM)',
'url' => 'https://api.z.ai/api/paas/v4',
'setup' => 'https://docs.z.ai/guides/overview/quick-start',
'textgeneration' => array('default' => 'glm-5'), // Flagship released February 11, 2026
'imagegeneration' => array('default' => 'cogview-4'), // Zhipu's latest SOTA image generator
'audiogeneration' => array('default' => 'cogvlm2-audio'), // High-fidelity conversational audio
'videogeneration' => array('default' => 'cogvideox-2'), // Flagship API video model
'transcription' => array('default' => 'na'),
'translation' => array('default' => 'na'),
'docparsing' => array('default' => 'glm-5'), // Top-tier agentic document processing
'adapter_type' => 'openai'
),
'custom' => array(
'label' => 'Custom',
'url' => 'https://domainofapi.com/v1/',
'setup' => 'Ask your AI provider how to get your API key',
'textgeneration' => 'tinyllama-1.1b',
'imagegeneration' => 'mixtral-8x7b-32768',
'audiogeneration' => 'mixtral-8x7b-32768',
'videogeneration' => 'na',
'transcription' => 'mixtral-8x7b-32768',
'translation' => 'mixtral-8x7b-32768',
'docparsing' => 'na',
'textgeneration' => array('default' => 'tinyllama-1.1b'),
'imagegeneration' => array('default' => 'mixtral-8x7b-32768'),
'audiogeneration' => array('default' => 'mixtral-8x7b-32768'),
'videogeneration' => array('default' => 'na'),
'transcription' => array('default' => 'mixtral-8x7b-32768'),
'translation' => array('default' => 'mixtral-8x7b-32768'),
'docparsing' => array('default' => 'na'),
'adapter_type' => 'openai'
),
// --- SPECIALIZED ADAPTERS ---
'anthropic' => array(
'label' => 'Anthropic (Claude)',
'url' => 'https://api.anthropic.com/v1/',
'setup' => 'https://console.anthropic.com/',
'textgeneration' => array('default' => 'claude-opus-4-6'), // Released Feb 2026; features a 1M context window
'imagegeneration' => array('default' => 'na'), // Anthropic remains focused on text/code logic
'audiogeneration' => array('default' => 'na'),
'videogeneration' => array('default' => 'na'),
'transcription' => array('default' => 'na'),
'translation' => array('default' => 'na'),
'docparsing' => array('default' => 'claude-opus-4-6'), // Leading model for "Computer Use" and PDF analysis
'adapter_type' => 'anthropic'
),
'google' => array(
'label' => 'Google Gemini',
'url' => 'https://generativelanguage.googleapis.com/v1beta/',
'setup' => 'https://aistudio.google.com/',
'textgeneration' => array('default' => 'gemini-3.1-pro-preview'), // Flagship reasoning model released Feb 19, 2026
'imagegeneration' => array('default' => 'nano-banana-pro'), // Latest SOTA image model (Gemini 3 Pro Image)
'audiogeneration' => array('default' => 'gemini-2.5-pro-tts'), // High-fidelity native speech synthesis
'videogeneration' => array('default' => 'veo-3.1'), // Google's flagship cinematic video API
'transcription' => array('default' => 'gemini-3.1-pro-preview'), // Native multi-modal audio reasoning
'translation' => array('default' => 'gemini-3.1-pro-preview'), // Native audio-to-text translation
'docparsing' => array('default' => 'gemini-3.1-pro-preview'), // Massive 2M+ context window for full repo parsing
'adapter_type' => 'google'
)
//'gemini' => array(
// 'label' => 'Gemini',
//)
);
return $arrayofai;
}
/**
* Tests the connection to an AI service using its API key and URL.
*
* This function supports multiple AI providers (Google Gemini, Anthropic Claude, and OpenAI-compatible APIs like
* Mistral, Groq, and DeepSeek). It constructs a minimal, provider-specific request payload and sends it
* to the given endpoint to verify that the API key is valid and the service is reachable.
*
* @param string $service The identifier of the AI service (e.g., 'google', 'anthropic', 'openai', 'mistral').
* @param string $key The API key for the service.
* @param string $url The base URL of the AI service's API endpoint.
*
* @return array{success: bool, message: string} An associative array indicating the result of the test.
* - 'success' is true on a successful connection (HTTP 2xx), false otherwise.
* - 'message' provides details, such as "OK (HTTP 200)" or an error description.
*/
function testAIConnection(string $service, string $key, string $url): array
{
if (empty($key)) {
return ['success' => false, 'message' => 'API Key is empty'];
}
// Load Defaults (Ensure this function exists or handle the error)
if (!function_exists('getListOfAIServices')) {
return ['success' => false, 'message' => 'Configuration helper function missing.'];
}
$list = getListOfAIServices();
$defUrl = $list[$service]['url'] ?? '';
// Use model from config, fallback to hardcoded if necessary
$defaultModel = $list[$service]['model'] ?? 'unknown';
// Normalize URL
if (empty($url)) {
$url = $defUrl;
}
$url = rtrim($url, '/');
$data = [];
$headers = ["Content-Type: application/json"];
$model = '';
if (empty($model)) {
$model = getDolGlobalString('AI_API_' . strtoupper($service) . '_MODEL');
}
$data = [];
$headers = ["Content-Type: application/json"];
// GOOGLE
if ($service == 'google' || strpos($url, 'googleapis') !== false) {
if (strpos($url, ':generateContent') === false) {
if (strpos($url, 'models') === false) {
$url .= "/models/$model:generateContent";
} else {
$url .= "/$model:generateContent";
}
}
$url .= "?key=" . $key;
$data = ["contents" => [ ["parts" => [ ["text" => "Hello"] ] ] ], "generationConfig" => ["maxOutputTokens" => 5]];
} elseif ($service == 'anthropic' || strpos($url, 'anthropic') !== false) { // ANTHROPIC
if (strpos($url, 'messages') === false) $url .= '/messages';
$headers[] = "x-api-key: $key";
$headers[] = "anthropic-version: 2023-06-01";
$data = [
"model" => $model, // Uses Configured Model
"messages" => [["role" => "user", "content" => "Hello"]],
"max_tokens" => 5
];
} else {
if (strpos($url, '/chat/completions') === false) $url .= '/chat/completions';
$headers[] = "Authorization: Bearer $key";
$data = [
"model" => $model, // Uses Configured Model (from Priority Chain)
"messages" => [["role" => "user", "content" => "Hello"]],
"max_tokens" => 5
];
}
// Execute cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
// Optional: Add SSL verification if behind a proxy with self-signed certs
// curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
return ['success' => false, 'message' => "Curl Error: $err"];
}
if ($httpCode >= 200 && $httpCode < 300) {
return ['success' => true, 'message' => "OK (HTTP $httpCode)."];
} else {
$json = json_decode($result, true);
// Attempt to find the error message in various common structures
$msg = $json['error']['message'] ?? $json['message'] ?? substr($result, 0, 150);
return ['success' => false, 'message' => "HTTP $httpCode. Error: $msg"];
}
}
/**
* Log AI Request with Raw Payloads
*
* @param DoliDB $db Database object
* @param User $user User object
* @param string $query The query sent to the AI
* @param array<string, mixed> $response The full response from the AI
* @param string $provider The AI provider (e.g., 'OpenAI', 'Anthropic')
* @param float $time Execution time in seconds
* @param float $confidence Confidence score from the AI (if any)
* @param string $status Status of the request (e.g., 'success', 'error')
* @param string $error Error message, if any
* @param string $rawReq Raw request payload
* @param string $rawRes Raw response payload
* @return void
*/
function ai_log_request($db, $user, $query, array $response, $provider, float $time, float $confidence, $status, $error = '', $rawReq = '', $rawRes = ''): void
{
global $conf;
if (!getDolGlobalInt('AI_LOG_REQUESTS')) {
return;
}
$tool = isset($response['tool']) ? (string) $response['tool'] : '';
if (dol_strlen($rawReq) > 60000) {
$rawReq = dol_substr($rawReq, 0, 60000) . '... [Truncated]';
}
$rawResStr = (string) $rawRes;
if (dol_strlen($rawResStr) > 60000) {
$rawResStr = dol_substr($rawResStr, 0, 60000) . '... [Truncated]';
}
$sql = "INSERT INTO " . MAIN_DB_PREFIX . "ai_request_log (";
$sql .= "entity, date_request, fk_user, query_text, tool_name, provider, ";
$sql .= "execution_time, confidence, status, error_msg, raw_request_payload, raw_response_payload";
$sql .= ") VALUES (";
$sql .= ((int) $conf->entity) . ", ";
$sql .= "'" . $db->idate(dol_now()) . "', ";
$sql .= ((int) $user->id) . ", ";
$sql .= "'" . $db->escape($query) . "', ";
$sql .= "'" . $db->escape($tool) . "', ";
$sql .= "'" . $db->escape($provider) . "', ";
$sql .= ((float) $time) . ", ";
$sql .= ((float) $confidence) . ", ";
$sql .= "'" . $db->escape($status) . "', ";
$sql .= "'" . $db->escape($error) . "', ";
$sql .= "'" . $db->escape($rawReq) . "', ";
$sql .= "'" . $db->escape($rawResStr) . "'";
$sql .= ")";
$resql = $db->query($sql);
if (!$resql) {
dol_print_error($db);
}
}
/**
* Get list for AI summarize

View File

@@ -111,6 +111,23 @@ class Documents extends DolibarrApi
throw new RestException(403);
}
if (DolibarrApiAccess::$user->socid > 0) {
if ($sqlprotectagainstexternals) {
$resql = $this->db->query($sqlprotectagainstexternals);
if ($resql) {
$num = $this->db->num_rows($resql);
$i = 0;
while ($i < $num) {
$obj = $this->db->fetch_object($resql);
if (DolibarrApiAccess::$user->socid != $obj->fk_soc) {
throw new RestException(403, 'Not allowed to download documents with such a ref');
}
$i++;
}
}
}
}
$filename = basename($original_file);
$original_file_osencoded = dol_osencode($original_file); // New file name encoded in OS encoding charset
@@ -197,6 +214,23 @@ class Documents extends DolibarrApi
throw new RestException(403);
}
if (DolibarrApiAccess::$user->socid > 0) {
if ($sqlprotectagainstexternals) {
$resql = $this->db->query($sqlprotectagainstexternals);
if ($resql) {
$num = $this->db->num_rows($resql);
$i = 0;
while ($i < $num) {
$obj = $this->db->fetch_object($resql);
if (DolibarrApiAccess::$user->socid != $obj->fk_soc) {
throw new RestException(403, 'Not allowed to download documents with such a ref');
}
$i++;
}
}
}
}
// --- Generates the document
$hidedetails = !getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 0 : 1;
$hidedesc = !getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 0 : 1;
@@ -1283,6 +1317,23 @@ class Documents extends DolibarrApi
throw new RestException(403);
}
if (DolibarrApiAccess::$user->socid > 0) {
if ($sqlprotectagainstexternals) {
$resql = $this->db->query($sqlprotectagainstexternals);
if ($resql) {
$num = $this->db->num_rows($resql);
$i = 0;
while ($i < $num) {
$obj = $this->db->fetch_object($resql);
if (DolibarrApiAccess::$user->socid != $obj->fk_soc) {
throw new RestException(403, 'Not allowed to download documents with such a ref');
}
$i++;
}
}
}
}
$filename = basename($original_file);
$original_file_osencoded = dol_osencode($original_file); // New file name encoded in OS encoding charset

View File

@@ -1473,6 +1473,7 @@ class Setup extends DolibarrApi
$list[$tab->elementtype][$tab->name]['perms'] = $tab->perms;
$list[$tab->elementtype][$tab->name]['list'] = $tab->list;
$list[$tab->elementtype][$tab->name]['printable'] = $tab->printable;
$list[$tab->elementtype][$tab->name]['showintooltip'] = $tab->showintooltip;
$list[$tab->elementtype][$tab->name]['totalizable'] = $tab->totalizable;
$list[$tab->elementtype][$tab->name]['langs'] = $tab->langs;
$list[$tab->elementtype][$tab->name]['help'] = $tab->help;
@@ -1558,7 +1559,7 @@ class Setup extends DolibarrApi
}
$sql = "SELECT t.rowid as id, t.name, t.entity, t.elementtype, t.label, t.type, t.size, t.fieldcomputed, t.fielddefault,";
$sql .= " t.fieldunique, t.fieldrequired, t.perms, t.enabled, t.pos, t.alwayseditable, t.param, t.list, t.printable,";
$sql .= " t.fieldunique, t.fieldrequired, t.perms, t.enabled, t.pos, t.alwayseditable, t.param, t.list, t.printable, t.showintooltip,";
$sql .= " t.totalizable, t.langs, t.help, t.css, t.cssview, t.csslist, t.fk_user_author, t.fk_user_modif, t.datec, t.tms";
$sql .= " FROM ".MAIN_DB_PREFIX."extrafields as t";
$sql .= " WHERE t.entity IN (".getEntity('extrafields').")";
@@ -1585,6 +1586,7 @@ class Setup extends DolibarrApi
$answer[$tab->elementtype][$tab->name]['perms'] = $tab->perms;
$answer[$tab->elementtype][$tab->name]['list'] = $tab->list;
$answer[$tab->elementtype][$tab->name]['printable'] = $tab->printable;
$answer[$tab->elementtype][$tab->name]['showintooltip'] = $tab->showintooltip;
$answer[$tab->elementtype][$tab->name]['totalizable'] = $tab->totalizable;
$answer[$tab->elementtype][$tab->name]['langs'] = $tab->langs;
$answer[$tab->elementtype][$tab->name]['help'] = $tab->help;
@@ -1658,6 +1660,7 @@ class Setup extends DolibarrApi
$default_value = $request_data['default'];
$totalizable = $request_data['totalizable'];
$printable = $request_data['printable'];
$showintooltip = $request_data['showintooltip'];
$required = $request_data['required'];
$langfile = $request_data['langfile'];
$computed = $request_data['computed'];
@@ -1743,6 +1746,7 @@ class Setup extends DolibarrApi
$default_value = $request_data['default'];
$totalizable = $request_data['totalizable'];
$printable = $request_data['printable'];
$showintooltip = $request_data['showintooltip'];
$required = $request_data['required'];
$langfile = $request_data['langfile'];
$computed = $request_data['computed'];

View File

@@ -122,10 +122,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
$conf->global->MAIN_DISALLOW_UNSECURED_SELECT_INTO_EXTRAFIELDS_FILTER = 1;
// In API context, we force the protection to avoid forging of criteria including bind SQL injection
$conf->global->MAIN_DISALLOW_UNSECURED_SELECT_INTO_EXTRAFIELDS_FILTER = 1;
$url = $_SERVER['PHP_SELF'];
if (preg_match('/api\/index\.php$/', $url)) { // sometimes $_SERVER['PHP_SELF'] is 'api\/index\.php' instead of 'api\/index\.php/explorer.php' or 'api\/index\.php/method'
$url = $_SERVER['PHP_SELF'].(empty($_SERVER['PATH_INFO']) ? $_SERVER['ORIG_PATH_INFO'] : $_SERVER['PATH_INFO']);

View File

@@ -25,10 +25,6 @@
// Load Dolibarr environment
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT . '/core/lib/asset.lib.php';
require_once DOL_DOCUMENT_ROOT . '/asset/class/assetmodel.class.php';
require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
/**
* @var Conf $conf
* @var DoliDB $db
@@ -36,6 +32,9 @@ require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
* @var Translate $langs
* @var User $user
*/
require_once DOL_DOCUMENT_ROOT . '/core/lib/asset.lib.php';
require_once DOL_DOCUMENT_ROOT . '/asset/class/assetmodel.class.php';
require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
// Load translation files required by the page
$langs->loadLangs(array("assets", "companies"));
@@ -46,6 +45,7 @@ $ref = GETPOST('ref', 'alpha');
$action = GETPOST('action', 'aZ09');
$cancel = GETPOST('cancel', 'alpha');
$backtopage = GETPOST('backtopage', 'alpha');
$backtopageforcancel = GETPOST('backtopageforcancel', 'alpha');
// Initialize a technical objects
$object = new AssetModel($db);

View File

@@ -115,7 +115,7 @@ $block_static = new BlockedLog($db);
$block_static->loadTrackedEvents();
// Access Control
if ((!$user->admin && !$user->hasRight('blockedlog', 'read')) || !isModEnabled('blockedlog')) {
if (((!$user->admin && !$user->hasRight('blockedlog', 'read')) || !isModEnabled('blockedlog')) && !userIsTaxAuditor()) {
accessforbidden();
}
@@ -415,6 +415,10 @@ if ($action == 'export' && $user->hasRight('blockedlog', 'read')) { // read is
$i++;
}
if ($i == 0) {
fwrite($fh, ";\n");
}
} else {
$error++;
setEventMessages($db->lasterror, null, 'errors');
@@ -524,159 +528,57 @@ if ($action == 'export' && $user->hasRight('blockedlog', 'read')) { // read is
.csvClean($statusofrecord).';'."\n");
// Get lifetime amount of all invoices validated and payments created/deleted.
// We do not use $totalamountalllines because it is only for the period, but we want lifetime amount since the first record to now.
$totalamountlifetime = array('BILL_VALIDATE' => 0, 'PAYMENT_CUSTOMER_CREATE' => 0, 'PAYMENT_CUSTOMER_DELETE' => 0);
$totalhtamountlifetime = array('BILL_VALIDATE' => 0, 'PAYMENT_CUSTOMER_CREATE' => 0, 'PAYMENT_CUSTOMER_DELETE' => 0);
// Calculate lifetime totals (with date of first record)
$sql = "SELECT action, module_source, object_format, MIN(date_creation) as datemin, SUM(amounts_taxexcl) as sumamounts_taxexcl, SUM(amounts) as sumamounts";
$sql .= " FROM ".MAIN_DB_PREFIX."blockedlog";
$sql .= " WHERE entity = ".((int) $conf->entity);
//$sql .= " AND action IN ('BILL_VALIDATE', 'BILL_SENTBYMAIL', 'PAYMENT_CUSTOMER_CREATE', 'CASHCONTROL_CLOSE', 'PAYMENT_CUSTOMER_DELETE', 'DOC_DOWNLOAD', 'DOC_PREVIEW')";
$sql .= " AND action IN ('BILL_VALIDATE', 'PAYMENT_CUSTOMER_CREATE', 'PAYMENT_CUSTOMER_DELETE')"; // Only event into lifetime total
$sql .= " AND date_creation < '".$db->idate(dol_get_last_day(GETPOSTINT('yeartoexport'), GETPOSTINT('monthtoexport') > 0 ? GETPOSTINT('monthtoexport') : 12))."'";
$sql .= " GROUP BY action, module_source, object_format";
$foundoldformat = 0;
$firstrecorddatearray = array();
$firstrecorddate = 0;
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
// First record date per action code and module
if (!empty($firstrecorddatearray[$obj->action][$obj->module_source])) {
$firstrecorddatearray[$obj->action] = min($firstrecorddatearray[$obj->action][$obj->module_source], $db->jdate($obj->datemin, 'gmt'));
} else {
$firstrecorddatearray[$obj->action] = $db->jdate($obj->datemin, 'gmt');
}
// First record for all actions code
if (!empty($firstrecorddate)) {
$firstrecorddate = min($firstrecorddate, $db->jdate($obj->datemin, 'gmt'));
} else {
$firstrecorddate = $obj->datemin;
}
if (!isset($totalamountlifetime[$obj->action])) {
$totalamountlifetime[$obj->action] = 0;
}
// Total per action code and module
$totalamountlifetime[$obj->action] += $obj->sumamounts;
// If format of line is old, the sumamounts_taxexcl was not recorded. So we flag this case.
if (empty($obj->object_format) || $obj->object_format == 'V1') {
$foundoldformat = 1;
} else {
$totalhtamountlifetime[$obj->action] += $obj->sumamounts_taxexcl;
}
}
} else {
$error++;
setEventMessages($db->lasterror, null, 'errors');
}
// Now calculate cumulative total of all invoices validated
if (array_key_exists('BILL_VALIDATE', $totalhtamount)) {
foreach ($totalhtamount['BILL_VALIDATE'] as $val) { // Loop on each module
$totalhtamountalllines['BILL_VALIDATE'] += $val;
}
foreach ($totalvatamount['BILL_VALIDATE'] as $val) {
$totalvatamountalllines['BILL_VALIDATE'] += $val;
}
foreach ($totalamount['BILL_VALIDATE'] as $val) {
$totalamountalllines['BILL_VALIDATE'] += $val;
}
}
if (array_key_exists('PAYMENT_CUSTOMER', $totalhtamount)) {
foreach ($totalhtamount['PAYMENT_CUSTOMER'] as $val) {
$totalhtamountalllines['PAYMENT_CUSTOMER'] += $val;
}
foreach ($totalvatamount['PAYMENT_CUSTOMER'] as $val) {
$totalvatamountalllines['PAYMENT_CUSTOMER'] += $val;
}
foreach ($totalamount['PAYMENT_CUSTOMER'] as $val) {
$totalamountalllines['PAYMENT_CUSTOMER'] += $val;
}
}
include_once DOL_DOCUMENT_ROOT.'/blockedlog/admin/lifetimeamount.inc.php';
// Add a final line with perpetual total for invoice validations
$block_static->id = 0;
$block_static->date_creation = '';
$block_static->action = '';
$block_static->module_source = '*';
$block_static->pos_source = '*';
$block_static->amounts_taxexcl = '';
$block_static->amounts = '';
$block_static->ref_object = '';
$block_static->date_object = 0;
$block_static->user_fullname = '';
$block_static->linktoref = '';
$block_static->linktype = '';
$block_static->object_version = '';
$block_static->object_format = '';
$block_static->signature = '';
$statusofrecord = '';
fwrite($fh, 'SUMMARY LIFETIME BILLED - '.$langs->transnoentitiesnoconv("Invoices").' : '.$totalhtamountalllines['BILL_VALIDATE'].' '.$langs->trans("HT")." - ".($foundoldformat ? '' : ($totalamountalllines['BILL_VALIDATE'] - $totalhtamountalllines['BILL_VALIDATE']).' '.$langs->transnoentitiesnoconv("VAT")).' - '.$totalamountalllines['BILL_VALIDATE'].' '.$langs->trans("TTC").";"
fwrite($fh, 'SUMMARY LIFETIME BILLED - '.$langs->transnoentitiesnoconv("Invoices").' : '.$totalhtamountlifetime['BILL_VALIDATE'].' '.$langs->trans("HT")." - ".($foundoldformat ? '' : ($totalamountlifetime['BILL_VALIDATE'] - $totalhtamountlifetime['BILL_VALIDATE']).' '.$langs->transnoentitiesnoconv("VAT")).' - '.$totalamountlifetime['BILL_VALIDATE'].' '.$langs->trans("TTC").";"
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean($block_static->date_creation).';'
.csvClean($block_static->action).';'
.csvClean($block_static->module_source).';'
.csvClean($block_static->pos_source).';'
.csvClean($block_static->amounts_taxexcl).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1
.csvClean($block_static->amounts).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1
.csvClean($block_static->ref_object).';'
.csvClean('').';'
.csvClean($block_static->user_fullname).';'
.csvClean($block_static->linktoref).';'
.csvClean($block_static->linktype).';'
.csvClean('').';' // We must use the string (so $obj->object_data) and not the array decoded with dolDecodeBlockedData
.csvClean($block_static->object_version).';'
.csvClean($block_static->object_format).';'
.csvClean($block_static->signature).';'
.csvClean($statusofrecord).';'
.csvClean('>= '.dol_print_date($firstrecorddate, 'standard')).";\n");
// Add a final line with perpetual total for customer payments
$block_static->id = 0;
$block_static->date_creation = '';
$block_static->action = '';
$block_static->module_source = '*';
$block_static->pos_source = '*';
$block_static->amounts_taxexcl = '';
$block_static->amounts = '';
$block_static->ref_object = '';
$block_static->date_object = 0;
$block_static->user_fullname = '';
$block_static->linktoref = '';
$block_static->linktype = '';
$block_static->object_version = '';
$block_static->object_format = '';
$block_static->signature = '';
$statusofrecord = '';
fwrite($fh, 'SUMMARY LIFETIME PAID - '.$langs->transnoentitiesnoconv("Payments").' : '.($totalamountlifetime['PAYMENT_CUSTOMER_CREATE'] + $totalamountlifetime['PAYMENT_CUSTOMER_DELETE']).";"
.csvClean('').';'
.csvClean($block_static->date_creation).';'
.csvClean($block_static->action).';'
.csvClean($block_static->module_source).';'
.csvClean($block_static->pos_source).';'
.csvClean($block_static->amounts_taxexcl).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1
.csvClean($block_static->amounts).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1
.csvClean($block_static->ref_object).';'
.csvClean('').';'
.csvClean($block_static->user_fullname).';'
.csvClean($block_static->linktoref).';'
.csvClean($block_static->linktype).';'
.csvClean('').';' // We must use the string (so $obj->object_data) and not the array decoded with dolDecodeBlockedData
.csvClean($block_static->object_version).';'
.csvClean($block_static->object_format).';'
.csvClean($block_static->signature).';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('').';'
.csvClean('>= '.dol_print_date($firstrecorddate, 'standard')).";\n");
// End of file, we will calculate global signature on it now, before adding last line.
fclose($fh);
// Calculate the signature of the file (the last line has a return line)
@@ -687,43 +589,50 @@ if ($action == 'export' && $user->hasRight('blockedlog', 'read')) { // read is
// Now add a signature to check integrity at end of file
file_put_contents($tmpfile, 'END - sha256='.$sha256.' - hmac_sha256='.$hmacsha256, FILE_APPEND);
dolChmod($tmpfile);
}
if (!$error) {
if ($periodnotcomplete) {
setEventMessages($langs->trans("ErrorPeriodMustBePastToAllowExport"), null, "warnings");
} else {
// We record the export as a new line into the unalterable logs
require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php';
$b = new BlockedLog($db);
$object = new stdClass();
$object->id = 0;
$object->element = 'module';
$object->ref = 'systemevent';
$object->entity = $conf->entity;
$object->date = dol_now();
$object->fullname = $user->getFullName($langs);
if (!$error) {
if ($periodnotcomplete) {
setEventMessages($langs->trans("ErrorPeriodMustBePastToAllowExport"), null, "warnings");
} else {
// We record the export as a new line into the unalterable logs
require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php';
$b = new BlockedLog($db);
$object->label = 'Export unalterable logs';
$object->period = 'year='.GETPOSTINT('yeartoexport').(GETPOSTINT('monthtoexport') ? ' month='.GETPOSTINT('monthtoexport') : '');
$object = new stdClass();
$object->id = 0;
$object->element = 'module';
$object->ref = 'systemevent';
$object->entity = $conf->entity;
$object->date = dol_now();
$object->fullname = $user->getFullName($langs);
$action = 'BLOCKEDLOG_EXPORT';
$object->label = 'Export unalterable logs';
$result = $b->setObjectData($object, $action, 0, $user, 0);
$object->total_billed = $totalhtamountalllines['BILL_VALIDATE'].' '.$langs->trans("HT").' - '.$totalvatamountalllines['BILL_VALIDATE'].' '.$langs->trans("VAT").' - '.$totalamountalllines['BILL_VALIDATE'].' '.$langs->trans("HT");
$object->total_collected = $totalamountalllines['PAYMENT_CUSTOMER'];
$object->totallifetime_billed = $totalhtamountlifetime['BILL_VALIDATE'].' '.$langs->trans("HT")." - ".($foundoldformat ? '' : ($totalamountlifetime['BILL_VALIDATE'] - $totalhtamountlifetime['BILL_VALIDATE']).' '.$langs->transnoentitiesnoconv("VAT")).' - '.$totalamountlifetime['BILL_VALIDATE'].' '.$langs->trans("HT");
$object->totallifetime_collected = ($totalamountlifetime['PAYMENT_CUSTOMER_CREATE'] + $totalamountlifetime['PAYMENT_CUSTOMER_DELETE']);
if ($result < 0) {
setEventMessages('Failed to insert the export into the unalterable log. Export canceled: '.$b->error, null, 'errors');
dol_delete_file($tmpfile);
$error++;
}
$object->period = 'year='.GETPOSTINT('yeartoexport').(GETPOSTINT('monthtoexport') ? ' month='.GETPOSTINT('monthtoexport') : '');
$res = $b->create($user);
$action = 'BLOCKEDLOG_EXPORT';
if ($res < 0) {
setEventMessages('Failed to insert the export into the unalterable log. Export canceled: '.$b->error, null, 'errors');
dol_delete_file($tmpfile);
$error++;
$result = $b->setObjectData($object, $action, 0, $user, 0);
if ($result < 0) {
setEventMessages('Failed to insert the export into the unalterable log. Export canceled: '.$b->error, null, 'errors');
dol_delete_file($tmpfile);
$error++;
}
$res = $b->create($user);
if ($res < 0) {
setEventMessages('Failed to insert the export into the unalterable log. Export canceled: '.$b->error, null, 'errors');
dol_delete_file($tmpfile);
$error++;
}
}
}
}
@@ -768,11 +677,14 @@ if ($withtab) {
}
$morehtmlcenter = '';
$texttop = '';
$registrationnumber = getHashUniqueIdOfRegistration();
$texttop = '<small class="opacitymedium">'.$langs->trans("RegistrationNumber").':</small> <small>'.dol_trunc($registrationnumber, 10).'</small>';
if (!isRegistrationDataSavedAndPushed()) {
$texttop = '';
if (!userIsTaxAuditor()) {
$texttop = '<small class="opacitymedium">'.$langs->trans("RegistrationNumber").':</small> <small>'.dol_trunc($registrationnumber, 10).'</small>';
if (!isRegistrationDataSavedAndPushed()) {
$texttop = '';
}
}
print load_fiche_titre($title.'<br>'.$texttop, $linkback, 'blockedlog', 0, '', '', $morehtmlcenter);
@@ -785,9 +697,11 @@ print dol_get_fiche_head($head, 'archives', '', -1);
//print '<br><br>';
print '<div class="opacitymedium hideonsmartphone justify">';
print $langs->trans("ArchivesDesc")."<br>";
if (!userIsTaxAuditor()) {
print $langs->trans("ArchivesDesc")."<br>";
} else {
print $langs->trans("ArchivesAuditorDesc")."<br>";
}
print "</div>\n";
@@ -827,6 +741,7 @@ if ($action == 'check' || $action == 'checkconfirmed') {
$registrationnumber = getHashUniqueIdOfRegistration();
$secretkey = $registrationnumber;
$inputregistrationnumber = '';
// Prepare to create a temporary file
$fullpathtmp = $upload_dir.'/temp/'.GETPOST('urlfile').'.tmp';
@@ -838,15 +753,39 @@ if ($action == 'check' || $action == 'checkconfirmed') {
removeLastLine($fullpathtmp);
print $langs->trans("FileHasBeenEncodedWithASecretKeyStartingWith").' : '.$regnumber.'...<br>';
if (preg_match('/^'.$regnumber.'/', $secretkey)) {
if (preg_match('/^'.$regnumber.'/', $secretkey) && !userIsTaxAuditor()) {
print 'As this matches the 10 first characters of the full registration number of this instance, we will use the full registration number to control the archive file...';
} else {
print 'This archive file was not generated by this instance. The control of authenticity is possible only if you know the full registration number.';
print '<input type="text" name="inputregistrationnumber" placeholder="'.$langs->trans("FullRegistrationNumber").'">';
if (!userIsTaxAuditor() || !isModEnabled('captureserver')) { // @phan-suppress-current-line UnknownModuleName
print 'This archive file was not generated by this instance.';
print 'The control of authenticity is possible only if you know the full registration number.';
$inputregistrationnumber = '';
print $langs->trans("PleaseEnterFullRegistrationNumber").' ';
} else {
// Here module "captureserver" is on. We can search the full registration number and prefill value
$sql = "SELECT registerid from ".MAIN_DB_PREFIX."captureserver_captureserver";
$sql .= " WHERE registerid LIKE '".$db->escape($regnumber)."%'";
$sql .= " AND type = 'dolibarrregistration'";
$sql .= " LIMIT 1";
$resql = $db->query($sql);
if ($resql) {
$obj = $db->fetch_object($resql);
if ($obj) {
$inputregistrationnumber = $obj->registerid;
}
}
print $langs->trans("WeFoundThisFullRegistrationNumberForThisKey").' ';
}
print '<input type="text" name="inputregistrationnumber" class="width300" placeholder="'.$langs->trans("FullRegistrationNumber").'"value="'.$inputregistrationnumber.'">';
}
print '<br><br>';
print '<center><a class="button small nomarginleft" href="'.$_SERVER["PHP_SELF"].'?action=checkconfirmed&urlfile='.urlencode(GETPOST('urlfile')).'">'.$langs->trans("ControlFile").'</a></center>';
//<input type="text" name="inputregistrationnumber" placeholder="'.$langs->trans("RegistrationNumber").'">';
print '</div>';
@@ -868,6 +807,13 @@ if ($action == 'check' || $action == 'checkconfirmed') {
$nbLinesModifiedInExportButKo = 0;
$nbLinesModifiedBeforeExport = 0;
$amounthtlifetime = array();
$amountvatlifetime = array();
$amountttclifetime = array();
$amounthtlifetime['BILL_VALIDATE'] = $amounthtlifetime['PAYMENT_CUSTOMER'] = null;
$amountvatlifetime['BILL_VALIDATE'] = $amountvatlifetime['PAYMENT_CUSTOMER'] = null;
$amountttclifetime['BILL_VALIDATE'] = $amountttclifetime['PAYMENT_CUSTOMER'] = null;
$handle = fopen($fullpath, "r");
if ($handle) {
$numline = 0;
@@ -926,7 +872,7 @@ if ($action == 'check' || $action == 'checkconfirmed') {
$block_static->action = $lineactioncode = (string) $line[3];
$block_static->module_source = (string) $line[4];
$block_static->pos_source = (string) $line[5];
$block_static->amounts_taxexcl = $lineamountht = ($line[6] === '' ? null : (float) $line[5]);
$block_static->amounts_taxexcl = $lineamountht = ($line[6] === '' ? null : (float) $line[6]);
$block_static->amounts = $lineamountttc = (float) $line[7];
$block_static->ref_object = $lineref = (string) $line[8];
$block_static->date_object = (int) $line[9];
@@ -1006,10 +952,35 @@ if ($action == 'check' || $action == 'checkconfirmed') {
}
}
// Test if line is a summary line
if (preg_match('/^SUMMARY /', (string) $line[0])) {
// We are on a line for summary information
$lineanalyzed = 1;
/*
if (preg_match('/^SUMMARY TURNOVER BILLED/', (string) $line[0])) {
// Do nothing, we recalculate amount from previous lines
}
if (preg_match('/^SUMMARY TURNOVER PAID/', (string) $line[0])) {
// Do nothing, we recalculate amount from previous lines
}
*/
if (preg_match('/^SUMMARY LIFETIME BILLED/', (string) $line[0])) {
// We load data from line
$amountstring = (string) $line[0];
if (preg_match('/^SUMMARY LIFETIME BILLED[^\d]*\s:\s([\d\.]+)\s[^\d]+\s([\d\.]+)\s[^\d]+\s([\d\.]+)\s[^\d]+/', $amountstring, $reg)) {
$amounthtlifetime['BILL_VALIDATE'] = (float) $reg[1];
$amountvatlifetime['BILL_VALIDATE'] = (float) $reg[2];
$amountttclifetime['BILL_VALIDATE'] = (float) $reg[3];
}
}
if (preg_match('/^SUMMARY LIFETIME PAID/', (string) $line[0])) {
$amountstring = (string) $line[0];
if (preg_match('/^SUMMARY LIFETIME PAID[^\d]*\s:\s([\d\.]+)/', $amountstring, $reg)) {
$amounthtlifetime['PAYMENT_CUSTOMER'] = (float) $reg[1];
$amountvatlifetime['PAYMENT_CUSTOMER'] = 0.0;
$amountttclifetime['PAYMENT_CUSTOMER'] = (float) $reg[1];
}
}
}
if (preg_match('/END - ([a-z0-9_]+)=([a-z0-9]+) - ([a-z0-9_]+)=([a-z0-9]+)$/', (string) $line[0], $reg)) {
@@ -1040,7 +1011,7 @@ if ($action == 'check' || $action == 'checkconfirmed') {
print '<br><br>';
if ($recalculatedhashsign && $recalculatedhashsign == $hashsign) {
if ($recalculatedhashsign && hash_equals($recalculatedhashsign, $hashsign)) {
print img_picto('', 'tick', 'class="valignmiddle pictofixedwidth"');
print '<b>'.$langs->trans("FileIntegrity").'</b> ';
print ' '.$form->textwithpicto('', $langs->trans("FileContentMatchSignature").'<br><br>'.$algosign.' = '.$recalculatedhashsign);
@@ -1051,12 +1022,12 @@ if ($action == 'check' || $action == 'checkconfirmed') {
}
print '<br><br>';
if ($recalculatedhashauth && $recalculatedhashauth == $hashauth) {
if ($recalculatedhashauth && hash_equals($recalculatedhashauth, $hashauth)) {
print img_picto('', 'tick', 'class="valignmiddle pictofixedwidth"');
print '<b>'.$langs->trans("FileAuthenticity").'</b> ';
print ' - <span class="opacitymedium">'.$langs->trans("FileWasGeneratedByThisInstance").'</span>';
print ' '.$form->textwithpicto('', $langs->trans("FileContentMatchSignature").'<br><br>'.$algoauth.' = '.$recalculatedhashauth);
} elseif ($recalculatedhashsign == $hashsign) {
} elseif ($recalculatedhashsign && hash_equals($recalculatedhashsign, $hashsign)) {
print img_picto('', 'cross', 'class="error valignmiddle pictofixedwidth"');
print '<b>'.$langs->trans("FileAuthenticity").'</b> ';
print ' '.$form->textwithpicto('', $langs->trans("FileNotFromInstance").'<br><br>Recalculated '.$recalculatedhashauth.' != Found in file '.$hashauth);
@@ -1067,17 +1038,19 @@ if ($action == 'check' || $action == 'checkconfirmed') {
}
print '<br><br>';
if ($nbLinesModifiedInExportButKo) {
print img_picto('', 'cross', 'class="error valignmiddle pictofixedwidth"');
print '<b>'.$langs->trans("nbLinesModifiedInExportButKo").'</b>: ';
//print ' '.$form->textwithpicto('', $langs->trans("FileHasBeenCorrupted").'<br>Recalculated '.$recalculatedhashsign.' != Found in file '.$hashsign);
print '<br><br>';
/*
if (!userIsTaxAuditor()) {
if ($nbLinesModifiedInExportButKo) {
print img_picto('', 'cross', 'class="error valignmiddle pictofixedwidth"');
print '<b>'.$langs->trans("nbLinesModifiedInExportButKo").'</b>: ';
print '<br><br>';
}
}
*/
if ($nbLinesModifiedBeforeExport) {
print img_picto('', 'warning', 'class="error valignmiddle pictofixedwidth"');
print '<b>'.$langs->trans("nbLinesModifiedBeforeExport").'</b>';
//print ' '.$form->textwithpicto('', $langs->trans("FileHasBeenCorrupted").'<br>Recalculated '.$recalculatedhashsign.' != Found in file '.$hashsign);
print '<br><br>';
}
@@ -1092,7 +1065,7 @@ if ($action == 'check' || $action == 'checkconfirmed') {
print '<hr>';
$arraykeys = array('BILL_VALIDATE', 'PAYMENT_CUSTOMER_CREATE');
$arraykeys = array('BILL_VALIDATE', 'PAYMENT_CUSTOMER');
foreach ($arraykeys as $key) {
$totalhttoshow = $totalhtamountforaction[$key];
$totalvattoshow = $totalvatamountforaction[$key];
@@ -1101,12 +1074,12 @@ if ($action == 'check' || $action == 'checkconfirmed') {
print '<b>'.dolPrintHTML($langs->trans("TotalForAction").' '.$langs->trans('log'.$key)).'</b>';
if ($key == 'BILL_VALIDATE') {
print ' <span class="opacitymedium">('.$langs->trans("Turnover").')</span>';
} elseif ($key == 'PAYMENT_CUSTOMER_CREATE') {
} elseif ($key == 'PAYMENT_CUSTOMER') {
print ' <span class="opacitymedium">('.$langs->trans("TurnoverCollected").')</span>';
}
print ': ';
if ($key == 'PAYMENT_CUSTOMER_CREATE') {
if ($key == 'PAYMENT_CUSTOMER') {
print '<span class="amount">'.price($totaltoshow, 0, $langs, 1, -1, -1, getDolCurrency()).'</span>';
} else {
print $langs->trans("HT").': ';
@@ -1125,6 +1098,44 @@ if ($action == 'check' || $action == 'checkconfirmed') {
print '<br>';
}
// Now print the value for lifetime amounts.
$arraykeys = array('BILL_VALIDATE', 'PAYMENT_CUSTOMER');
foreach ($arraykeys as $key) {
if (is_null($amounthtlifetime[$key])) { // If not entry found, we discard
continue;
}
$totalhttoshow = $amounthtlifetime[$key];
$totalvattoshow = $amountvatlifetime[$key];
$totaltoshow = $amountttclifetime[$key];
print '<b>'.dolPrintHTML($langs->trans("LifetimeAmountShort").' '.$langs->trans('log'.$key)).'</b>';
if ($key == 'BILL_VALIDATE') {
print ' <span class="opacitymedium">('.$langs->trans("Turnover").')</span>';
} elseif ($key == 'PAYMENT_CUSTOMER') {
print ' <span class="opacitymedium">('.$langs->trans("TurnoverCollected").')</span>';
}
print ': ';
if ($key == 'PAYMENT_CUSTOMER') {
print '<span class="amount">'.price($totaltoshow, 0, $langs, 1, -1, -1, getDolCurrency()).'</span>';
} else {
print $langs->trans("HT").': ';
print '<span class="amount">'.price($totalhttoshow, 0, $langs, 1, -1, -1, getDolCurrency()).'</span>';
print ' - ';
print $langs->trans("VAT").': ';
print '<span class="amount">'.price($totalvattoshow, 0, $langs, 1, -1, -1, getDolCurrency()).'</span>';
print ' - ';
print $langs->trans("TTC").': ';
print '<span class="amount">'.price($totaltoshow, 0, $langs, 1, -1, -1, getDolCurrency()).'</span>';
}
print '<br>';
}
print '<br>';
$text = $langs->trans("IfIntegrityAuthenticityIsOkYouCanGetdetailByOpeningTheFile");
@@ -1142,17 +1153,20 @@ if ($action == 'check' || $action == 'checkconfirmed') {
if ($action != 'check' && $action != 'checkconfirmed') {
$htmltext = '';
$htmltext .= $langs->trans("UnalterableLogTool2", $langs->transnoentities("Archives"))."<br>";
if ($mysoc->country_code == 'FR') {
$htmltext .= '<br>'.$langs->trans("UnalterableLogTool1FR").'<br>';
if (!userIsTaxAuditor()) {
$htmltext .= $langs->trans("UnalterableLogTool2", $langs->transnoentities("Archives"))."<br>";
if ($mysoc->country_code == 'FR') {
$htmltext .= '<br>'.$langs->trans("UnalterableLogTool1FR").'<br>';
}
//$htmltext .= $langs->trans("UnalterableLogTool1");
//$htmltext .= $langs->trans("UnalterableLogTool3")."<br>";
print info_admin($htmltext, 0, 0, 'warning');
print '<br>';
} else {
print '<br>';
}
//$htmltext .= $langs->trans("UnalterableLogTool1");
//$htmltext .= $langs->trans("UnalterableLogTool3")."<br>";
print info_admin($htmltext, 0, 0, 'warning');
print '<br>';
$param = '';
if ($contextpage != getDolDefaultContextPage(__FILE__)) {
@@ -1218,33 +1232,34 @@ if ($action != 'check' && $action != 'checkconfirmed') {
}
print '<form method="POST" id="exportArchives" action="'.$_SERVER["PHP_SELF"].'?output=file">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="export">';
if (!userIsTaxAuditor()) {
print '<form method="POST" id="exportArchives" action="'.$_SERVER["PHP_SELF"].'?output=file">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="export">';
print '<div class="right">';
print '<div class="right">';
print '<span class="hideonsmartphone">'.$langs->trans("RestrictYearToExport").': </span>';
// Month
print $formother->select_month((string) GETPOSTINT('monthtoexport'), 'monthtoexport', $langs->trans("Month"), 0, 'minwidth50 maxwidth75imp valignmiddle', true);
print '<input type="text" name="yeartoexport" class="valignmiddle maxwidth75imp" value="'.GETPOST('yeartoexport').'" placeholder="'.$langs->trans("Year").'">';
print '<span class="hideonsmartphone">'.$langs->trans("RestrictYearToExport").': </span>';
// Month
print $formother->select_month((string) GETPOSTINT('monthtoexport'), 'monthtoexport', $langs->trans("Month"), 0, 'minwidth50 maxwidth75imp valignmiddle', true);
print '<input type="text" name="yeartoexport" class="valignmiddle maxwidth75imp" value="'.GETPOST('yeartoexport').'" placeholder="'.$langs->trans("Year").'">';
print ' ';
print ' ';
// Disabled, we will use the getHashUniqueIdOfRegistration() as secret HMAC
//print '<input type="text" name="hmacexportkey" class="valignmiddle minwidth150imp maxwidth300imp" required value="'.GETPOST('hmacexportkey').'" placeholder="'.$langs->trans("Password").'">';
// Disabled, we will use the getHashUniqueIdOfRegistration() as secret HMAC
//print '<input type="text" name="hmacexportkey" class="valignmiddle minwidth150imp maxwidth300imp" required value="'.GETPOST('hmacexportkey').'" placeholder="'.$langs->trans("Password").'">';
print ' ';
print ' ';
print '<input type="hidden" name="withtab" value="'.GETPOST('withtab', 'alpha').'">';
print '<input type="submit" name="downloadcsv" class="button" value="'.$langs->trans('DownloadLogCSV').'">';
/*if (getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY')) {
print ' | <a href="?action=downloadblockchain'.(GETPOST('withtab', 'alpha') ? '&withtab='.GETPOST('withtab', 'alpha') : '').'">'.$langs->trans('DownloadBlockChain').'</a>';
}*/
print ' </div><br>';
print '</form>';
print '<input type="hidden" name="withtab" value="'.GETPOST('withtab', 'alpha').'">';
print '<input type="submit" name="downloadcsv" class="button" value="'.$langs->trans('DownloadLogCSV').'">';
/*if (getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY')) {
print ' | <a href="?action=downloadblockchain'.(GETPOST('withtab', 'alpha') ? '&withtab='.GETPOST('withtab', 'alpha') : '').'">'.$langs->trans('DownloadBlockChain').'</a>';
}*/
print ' </div><br>';
print '</form>';
}
/*
print '<form method="POST" id="searchFormList" action="'.dolBuildUrl($_SERVER["PHP_SELF"]).'">';

View File

@@ -157,6 +157,12 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x'
$search_array_options = array();
}
if (userIsTaxAuditor()) {
// When this hidden option is on, open another tab as the tab by default
header("Location: ".DOL_URL_ROOT."/blockedlog/admin/blockedlog_archives.php");
exit;
}
/*
* View
@@ -191,11 +197,14 @@ if (GETPOST('withtab', 'alpha')) {
}
$morehtmlcenter = '';
$texttop = '';
$registrationnumber = getHashUniqueIdOfRegistration();
$texttop = '<small class="opacitymedium">'.$langs->trans("RegistrationNumber").':</small> <small>'.dol_trunc($registrationnumber, 10).'</small>';
if (!isRegistrationDataSavedAndPushed()) {
$texttop = '';
if (userIsTaxAuditor()) {
$texttop = '<small class="opacitymedium">'.$langs->trans("RegistrationNumber").':</small> <small>'.dol_trunc($registrationnumber, 10).'</small>';
if (!isRegistrationDataSavedAndPushed()) {
$texttop = '';
}
}
print load_fiche_titre($title.'<br>'.$texttop, $linkback, 'blockedlog', 0, '', '', $morehtmlcenter);
@@ -599,10 +608,11 @@ if (is_array($blocks)) {
}
print '<tr><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
} else {
ksort($totalamount);
foreach ($totalamount as $key => $totalamountperref) {
if ($key == 'BILL_VALIDATE' || $key == 'PAYMENT_CUSTOMER') {
// Total
print '<tr class="liste_total">';
print '<tr class="liste_total totalblockedlog">';
// Action column
if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
@@ -614,31 +624,17 @@ if (is_array($blocks)) {
print '<td colspan="4">';
print dolPrintHTML($langs->trans("TotalForAction").' '.$langs->trans('log'.$key));
if ($key == 'BILL_VALIDATE') {
print ' <span class="opacitymedium">('.$langs->trans("Turnover").')</span>';
print ' <span class="opacitylow">('.$langs->trans("Turnover").')</span>';
} elseif ($key == 'PAYMENT_CUSTOMER') {
print ' <span class="opacitymedium">('.$langs->trans("TurnoverCollected").')</span>';
print ' <span class="opacitylow">('.$langs->trans("TurnoverCollected").')</span>';
}
print '</td>';
// Date
//print '<td class="nowraponall"></td>';
// User
//print '<td class="tdoverflowmax200">';
//print '</td>';
// Module source
//print '<td></td>';
// Action
print '<td></td>';
// Ref
//print '<td class="nowraponall">';
//print '</td>';
// Amount (HT)
print '<td class="right nowraponall" colspan="2">';
print '<td class="right nowraponall" colspan="3">';
$totalhttoshow = 0;
foreach ($totalhtamount[$key] as $value) { // Loop on each module
$totalhttoshow += $value;
@@ -652,21 +648,22 @@ if (is_array($blocks)) {
$totaltoshow += $value;
}
print $langs->trans("HT").': ';
print price($totalhttoshow);
if ($key == 'BILL_VALIDATE') {
print price($totalhttoshow);
print ' '.$langs->trans("HT");
//print '<br>';
print ' &nbsp; ';
print ' - ';
print $langs->trans("VAT").': ';
print price($totalvattoshow);
print price($totalvattoshow);
print ' '.$langs->trans("VAT");
//print '<br>';
print ' &nbsp; ';
print ' - ';
}
print $langs->trans("TTC").': ';
print price($totaltoshow);
if ($key == 'BILL_VALIDATE') {
print ' '.$langs->trans("TTC");
}
print '</td>';
// Details link
@@ -695,6 +692,129 @@ if (is_array($blocks)) {
print '</tr>';
}
}
// TODO Show the lifetime payment only if we click on a link.
$afilterexists = ($search_id || ($search_fk_user > 0) || $search_ref || $search_amount || $search_signature || !empty($search_module_source) || $search_pos_source);
if (! $afilterexists) {
// Get lifetime amount of all invoices validated and payments created/deleted.
// We do not use $totalamountalllines because it is only for the period, but we want lifetime amount since the first record to now.
$totalamountlifetime = array('BILL_VALIDATE' => 0, 'PAYMENT_CUSTOMER_CREATE' => 0, 'PAYMENT_CUSTOMER_DELETE' => 0);
$totalhtamountlifetime = array('BILL_VALIDATE' => 0, 'PAYMENT_CUSTOMER_CREATE' => 0, 'PAYMENT_CUSTOMER_DELETE' => 0);
$foundoldformat = 0;
$firstrecorddate = 0;
if (empty($search_end) || $search_end == -1) {
$search_end = dol_now();
}
include_once DOL_DOCUMENT_ROOT.'/blockedlog/admin/lifetimeamount.inc.php';
if (empty($search_code) || in_array('BILL_VALIDATE', $search_code)) {
// Total
print '<tr class="liste_total totalblockedlog">';
// Action column
if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
print '<td></td>';
}
// ID
print '<td colspan="4">';
print dolPrintHTML($langs->trans("TotalForAction").' '.$langs->trans('logBILL_VALIDATE'));
print ' <span class="opacitylow">('.$langs->trans("Turnover").')';
print '<br>'.$langs->trans("LifetimeAmountShort").': '.dol_print_date($firstrecorddate, 'dayhour', 'tzuserrel');
if ($search_end && $search_end != -1) {
print ' - '.dol_print_date($search_end, 'dayhoursec', 'tzuserrel');
} else {
print ' - '.$langs->trans("Now");
}
print '</span>';
print '</td>';
// Action
print '<td></td>';
// Amount (HT)
print '<td class="right nowraponall" colspan="3">';
print $totalhtamountlifetime['BILL_VALIDATE'].' '.$langs->trans("HT")." - ".($foundoldformat ? '' : ($totalamountlifetime['BILL_VALIDATE'] - $totalhtamountlifetime['BILL_VALIDATE']).' '.$langs->transnoentitiesnoconv("VAT")).' - '.$totalamountlifetime['BILL_VALIDATE'].' '.$langs->trans("TTC");
print '</td>';
// Details link
print '<td class="center"></td>';
// Fingerprint
print '<td class="nowraponall"></td>';
// Status
print '<td class="center"></td>';
// Link to debug information object
if (getDolGlobalString("BLOCKEDLOG_DEBUG")) { // If in experimental or develop mode, we add some debug information. It may help developers to find origin of bugs.
print '<td class="tdoverflowmax150"'.(preg_match('/<a/', $object_link) ? '' : 'title="'.dol_escape_htmltag(dol_string_nohtmltag($object_link.($object_link_title ? ' - '.$object_link_title : ''))).'"').'>';
print '</td>';
}
// Action column
if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
print '<td class="liste_titre"></td>';
}
print '</tr>';
}
if (empty($search_code) || in_array('PAYMENT_CUSTOMER_CREATE', $search_code) || in_array('PAYMENT_CUSTOMER_DELETE', $search_code)) {
// Total
print '<tr class="liste_total totalblockedlog">';
// Action column
if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
print '<td></td>';
}
// ID
print '<td colspan="4">';
print dolPrintHTML($langs->trans("TotalForAction").' '.$langs->trans('logPAYMENT_CUSTOMER'));
print ' <span class="opacitylow">('.$langs->trans("TurnoverCollected").')';
print '<br>'.$langs->trans("LifetimeAmountShort").': '.dol_print_date($firstrecorddate, 'dayhour', 'tzuserrel');
if ($search_end && $search_end != -1) {
print ' - '.dol_print_date($search_end, 'dayhoursec', 'tzuserrel');
} else {
print ' - '.$langs->trans("Now");
}
print '</span>';
print '</td>';
// Action
print '<td></td>';
// Amount (HT)
print '<td class="right nowraponall" colspan="3">';
print ($totalamountlifetime['PAYMENT_CUSTOMER_CREATE'] + $totalamountlifetime['PAYMENT_CUSTOMER_DELETE']);
print '</td>';
// Details link
print '<td class="center"></td>';
// Fingerprint
print '<td class="nowraponall"></td>';
// Status
print '<td class="center"></td>';
// Link to debug information object
if (getDolGlobalString("BLOCKEDLOG_DEBUG")) { // If in experimental or develop mode, we add some debug information. It may help developers to find origin of bugs.
print '<td class="tdoverflowmax150"'.(preg_match('/<a/', $object_link) ? '' : 'title="'.dol_escape_htmltag(dol_string_nohtmltag($object_link.($object_link_title ? ' - '.$object_link_title : ''))).'"').'>';
print '</td>';
}
// Action column
if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
print '<td class="liste_titre"></td>';
}
print '</tr>';
}
}
}
}

View File

@@ -54,7 +54,7 @@ $origin = GETPOST('origin');
$mode = GETPOST('mode');
// Access Control
if (!$user->admin) {
if (!$user->admin && !userIsTaxAuditor()) {
accessforbidden();
}
@@ -93,11 +93,14 @@ if (GETPOST('withtab', 'alpha')) {
}
$morehtmlcenter = '';
$texttop = '';
$registrationnumber = getHashUniqueIdOfRegistration();
$texttop = '<small class="opacitymedium">'.$langs->trans("RegistrationNumber").':</small> <small>'.dol_trunc($registrationnumber, 10).'</small>';
if ((!isRegistrationDataSavedAndPushed() || !isModEnabled('blockedlog')) && $mode != "forceregistration") {
$texttop = '';
if (!userIsTaxAuditor()) {
$texttop = '<small class="opacitymedium">'.$langs->trans("RegistrationNumber").':</small> <small>'.dol_trunc($registrationnumber, 10).'</small>';
if ((!isRegistrationDataSavedAndPushed() || !isModEnabled('blockedlog')) && $mode != "forceregistration") {
$texttop = '';
}
}
print load_fiche_titre($title.'<br>'.$texttop, $linkback, 'blockedlog', 0, '', '', $morehtmlcenter);
@@ -109,8 +112,9 @@ if ($withtab) {
print '<br>';
}
print '<span class="opacitymedium">'.$langs->trans("BlockedLogDesc")."</span><br>\n";
if (!userIsTaxAuditor()) {
print '<span class="opacitymedium">'.$langs->trans("BlockedLogDesc")."</span><br>\n";
}
// Version
$versionbadge = '<span class="badge-text badge-secondary">'.getBlockedLogVersionToShow().'</span>';
@@ -133,7 +137,7 @@ if ($mysoc->country_code == 'FR') {
}
// Show generic message (for countries that need registration) to explain we need registration to collect data and why
if (in_array($mysoc->country_code, array('FR'))) {
if (in_array($mysoc->country_code, array('FR')) && !userIsTaxAuditor()) {
$organization_for_ping = getDolGlobalString('MAIN_ORGANIZATION_FOR_PING', "Association Dolibarr");
$dataprivacy_url = getDolGlobalString('MAIN_ORGANIZATION_URL_PRIVACY', "https://www.dolibarr.org/legal-privacy-gdpr.php");

View File

@@ -0,0 +1,95 @@
<?php
/* Copyright (C) 2026 Laurent Destailleur <eldy@destailleur.fr>
*
* 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 <https://www.gnu.org/licenses/>.
*/
/**
* \file htdocs/blockedlog/admin/lifetimeamount.inc.php
* \ingroup blockedlog
* \brief Code to calculate lifetime amount
*/
/**
* @var DoliDB $db
* @var Conf $conf
* @var CommonObject $object
*
* @var array<string,float> $totalamountlifetime
* @var array<string,float> $totalhtamountlifetime
* @var int $foundoldformat
* @var int $firstrecorddate
* @var int $error
* @var ?int $search_end
*/
'@phan-var-force array<string,float> $totalamountlifetime';
'@phan-var-force array<string,float> $totalhtamountlifetime';
// Protection to avoid direct call of template
if (empty($conf) || !is_object($conf)) {
print "Error, template page ".basename(__FILE__)." can't be called with no conf defined.";
exit;
}
$firstrecorddatearray = array();
if (!empty($search_end) && $search_end > 0) {
$dateend = $search_end;
} else {
$dateend = dol_get_last_day(GETPOSTINT('yeartoexport'), GETPOSTINT('monthtoexport') > 0 ? GETPOSTINT('monthtoexport') : 12);
}
// Calculate lifetime totals (with date of first record)
$sql = "SELECT action, module_source, object_format, MIN(date_creation) as datemin, SUM(amounts_taxexcl) as sumamounts_taxexcl, SUM(amounts) as sumamounts";
$sql .= " FROM ".MAIN_DB_PREFIX."blockedlog";
$sql .= " WHERE entity = ".((int) $conf->entity);
//$sql .= " AND action IN ('BILL_VALIDATE', 'BILL_SENTBYMAIL', 'PAYMENT_CUSTOMER_CREATE', 'CASHCONTROL_CLOSE', 'PAYMENT_CUSTOMER_DELETE', 'DOC_DOWNLOAD', 'DOC_PREVIEW')";
$sql .= " AND action IN ('BILL_VALIDATE', 'PAYMENT_CUSTOMER_CREATE', 'PAYMENT_CUSTOMER_DELETE')"; // Only event into lifetime total
$sql .= " AND date_creation < '".$db->idate($dateend)."'";
$sql .= " GROUP BY action, module_source, object_format";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
// First record date per action code and module
if (!empty($firstrecorddatearray[$obj->action][$obj->module_source])) {
$firstrecorddatearray[$obj->action] = min($firstrecorddatearray[$obj->action][$obj->module_source], $db->jdate($obj->datemin, 'gmt'));
} else {
$firstrecorddatearray[$obj->action] = $db->jdate($obj->datemin, 'gmt');
}
// First record for all actions code
if (!empty($firstrecorddate)) {
$firstrecorddate = min($firstrecorddate, $db->jdate($obj->datemin, 'gmt'));
} else {
$firstrecorddate = $obj->datemin;
}
if (!isset($totalamountlifetime[$obj->action])) {
$totalamountlifetime[$obj->action] = 0;
}
// Total per action code and module
$totalamountlifetime[$obj->action] += $obj->sumamounts;
// If format of line is old, the sumamounts_taxexcl was not recorded. So we flag this case.
if (empty($obj->object_format) || $obj->object_format === 'V1') {
$foundoldformat = 1;
} else {
$totalhtamountlifetime[$obj->action] += $obj->sumamounts_taxexcl;
}
}
} else {
$error++;
setEventMessages($db->lasterror, null, 'errors');
}

View File

@@ -224,11 +224,14 @@ if (GETPOST('withtab', 'alpha')) {
}
$morehtmlcenter = '';
$texttop = '';
$registrationnumber = getHashUniqueIdOfRegistration();
$texttop = '<small class="opacitymedium">'.$langs->trans("RegistrationNumber").':</small> <small>'.dol_trunc($registrationnumber, 10).'</small>';
if ((!isRegistrationDataSavedAndPushed() || !isModEnabled('blockedlog')) && $mode != "forceregistration") {
$texttop = '';
if (!userIsTaxAuditor()) {
$texttop = '<small class="opacitymedium">'.$langs->trans("RegistrationNumber").':</small> <small>'.dol_trunc($registrationnumber, 10).'</small>';
if ((!isRegistrationDataSavedAndPushed() || !isModEnabled('blockedlog')) && $mode != "forceregistration") {
$texttop = '';
}
}
print load_fiche_titre($title.'<br>'.$texttop, $linkback, 'blockedlog', 0, '', '', $morehtmlcenter);

View File

@@ -1,7 +1,7 @@
<?php
/* Copyright (C) 2017 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2017 ATM Consulting <contact@atm-consulting.fr>
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
/* Copyright (C) 2017-2026 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2017 ATM Consulting <contact@atm-consulting.fr>
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
*
* 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
@@ -75,6 +75,7 @@ if ($element === 'facture') { // Test on permission done in top of page
if ($facture->fetch($id) > 0) {
//print 'Object '.$element.' logged with action code = '.$action." pos_print_counter is currently ".$facture->pos_print_counter;
/* Increase of counter is now managed inside the document.php file
if ($facture->status > Facture::STATUS_DRAFT) {
// Increase counter by 1
$sql = "UPDATE ".MAIN_DB_PREFIX."facture SET pos_print_counter = pos_print_counter + 1";
@@ -84,6 +85,7 @@ if ($element === 'facture') { // Test on permission done in top of page
$facture->pos_print_counter += 1;
// $facture->update($user, 1); // disabled update, we did a direct sql update before. We disable trigger here because we already call the trigger $action = DOC_PREVIEW or DOC_DOWNLOAD just after.
}
*/
$facture->call_trigger($action, $user);
}

View File

@@ -58,7 +58,7 @@ if ((!$user->admin && !$user->hasRight('blockedlog', 'read')) || empty($conf->bl
accessforbidden();
}
$langs->loadLangs(array("admin", "bills", "blockedlog", "cashdesk", "companies", "mails", "members", "products"));
$langs->loadLangs(array("admin", "bills", "blockedlog", "cashdesk", "companies", "compta", "mails", "members", "products"));
/*
@@ -163,7 +163,7 @@ function formatObject($objtoshow, $prefix, $parentelement = '')
'vat_src_code' => 'VATCode',
'multicurrency_code' => 'Currency',
'qty' => 'Quantity',
'remise_percent' => 'DiscountInPercent',
'remise_percent' => $langs->transnoentitiesnoconv('Discount').' (%)',
'nom' => 'Name',
'name' => 'Name',
'email' => 'Email',
@@ -190,15 +190,19 @@ function formatObject($objtoshow, $prefix, $parentelement = '')
'special_code' => 'Special line (WEEE line, option, id of module...)',
'status' => 'Status',
'cash' => 'PaymentTypeLIQ',
'cash_declared' => $langs->transnoentities('PaymentTypeLIQ').' - '.$langs->transnoentities("AmuntCountedByUserShort"),
'cash_lifetime' => $langs->transnoentities('LifetimeAmount', $langs->transnoentities('PaymentTypeLIQ')),
'cash_declared' => $langs->transnoentitiesnoconv('PaymentTypeLIQ').' - '.$langs->transnoentitiesnoconv("AmuntCountedByUserShort"),
'cash_lifetime' => $langs->transnoentitiesnoconv('LifetimeAmount', $langs->transnoentitiesnoconv('PaymentTypeLIQ')),
'card' => 'PaymentTypeCB',
'card_declared' => $langs->transnoentities('PaymentTypeCB').' - '.$langs->transnoentities("AmuntCountedByUserShort"),
'card_lifetime' => $langs->transnoentities('LifetimeAmount', $langs->transnoentities('PaymentTypeCB')),
'card_declared' => $langs->transnoentitiesnoconv('PaymentTypeCB').' - '.$langs->transnoentitiesnoconv("AmuntCountedByUserShort"),
'card_lifetime' => $langs->transnoentitiesnoconv('LifetimeAmount', $langs->transnoentitiesnoconv('PaymentTypeCB')),
'cheque' => 'PaymentTypeCHQ',
'cheque_declared' => $langs->transnoentities('PaymentTypeCHQ').' - '.$langs->transnoentities("AmuntCountedByUserShort"),
'cheque_lifetime' => $langs->transnoentities('LifetimeAmount', $langs->transnoentities('PaymentTypeCHQ')),
'cheque_declared' => $langs->transnoentitiesnoconv('PaymentTypeCHQ').' - '.$langs->transnoentitiesnoconv("AmuntCountedByUserShort"),
'cheque_lifetime' => $langs->transnoentitiesnoconv('LifetimeAmount', $langs->transnoentitiesnoconv('PaymentTypeCHQ')),
'lifetime_start' => 'LifetimeStatDate',
'total_billed' => 'Turnover',
'total_collected' => 'TurnoverCollected',
'totallifetime_billed' => $langs->transnoentitiesnoconv('Turnover').' - '.$langs->transnoentitiesnoconv('LifetimeAmountShort'),
'totallifetime_collected' => $langs->transnoentitiesnoconv('TurnoverCollected').' - '.$langs->transnoentitiesnoconv('LifetimeAmountShort'),
'email_from' => 'MailFrom',
'email_to' => 'MailTo',
'email_msgid' => 'EmailMsgID',
@@ -281,6 +285,8 @@ function formatObject($objtoshow, $prefix, $parentelement = '')
'cash_declared', 'cheque_declared', 'card_declared', 'cash_lifetime', 'cheque_lifetime', 'card_lifetime'
)) || (isset($arrayoffields[$key]['type']) && in_array($arrayoffields[$key]['type'], array('price')))) {
$s .= '<span class="amount">'.price($val, 0, $langs, 1, 0, -2).'</span>';
} elseif (in_array($key, array('total_billed', 'total_collected', 'totallifetime_billed', 'totallifetime_collected'))) {
$s .= '<span class="amount">'.$val.'</span>';
} else {
$s .= $val;
}

View File

@@ -58,19 +58,23 @@ function blockedlogadmin_prepare_head($withtabsetup)
$h = 0;
$head = array();
$head[$h][0] = DOL_URL_ROOT."/blockedlog/admin/registration.php".$param;
$head[$h][1] = $langs->trans("UserRegistration");
$head[$h][2] = 'registration';
$h++;
$b = new BlockedLog($db);
$head[$h][0] = DOL_URL_ROOT."/blockedlog/admin/blockedlog_list.php".$param;
$head[$h][1] = $langs->trans("BrowseBlockedLog");
if ($b->alreadyUsed()) {
$head[$h][1] .= (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') ? '<span class="badge marginleftonlyshort">...</span>' : '');
if (!userIsTaxAuditor()) {
$head[$h][0] = DOL_URL_ROOT."/blockedlog/admin/registration.php".$param;
$head[$h][1] = $langs->trans("UserRegistration");
$head[$h][2] = 'registration';
$h++;
}
if (!userIsTaxAuditor()) {
$b = new BlockedLog($db);
$head[$h][0] = DOL_URL_ROOT."/blockedlog/admin/blockedlog_list.php".$param;
$head[$h][1] = $langs->trans("BrowseBlockedLog");
if ($b->alreadyUsed()) {
$head[$h][1] .= (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') ? '<span class="badge marginleftonlyshort">...</span>' : '');
}
$head[$h][2] = 'fingerprints';
$h++;
}
$head[$h][2] = 'fingerprints';
$h++;
$head[$h][0] = DOL_URL_ROOT."/blockedlog/admin/blockedlog_archives.php".$param;
$head[$h][1] = $langs->trans("Archives");
@@ -85,7 +89,7 @@ function blockedlogadmin_prepare_head($withtabsetup)
$h++;
}
if ($withtabsetup) {
if ($withtabsetup && !userIsTaxAuditor()) {
$head[$h][0] = DOL_URL_ROOT."/blockedlog/admin/blockedlog.php".$param;
$head[$h][1] = $langs->trans("TechnicalInformation");
$head[$h][2] = 'technicalinfo';
@@ -384,13 +388,16 @@ function callApiToPushCounter($id, $signature, $test, $previousid, $previoussign
$url_for_ping = getDolGlobalString('MAIN_URL_FOR_PING', "https://ping.dolibarr.org/");
$algo = 'sha256';
$hash_unique_id = getHashUniqueIdOfRegistration($algo);
$hash_unique_id = getHashUniqueIdOfRegistration($algo); // The hash of the unique IDof instance
$t = microtime(true);
$micro = sprintf("%06d", (int) ($t - floor($t)) * 1000000);
$data = '';
$data .= 'hash_algo=dol_hash-'.urlencode($algo);
$data .= '&hash_unique_id='.urlencode($hash_unique_id);
$data .= '&action=dolibarrpushcounter';
$data .= '&datesys='.urlencode(dol_print_date(dol_now(), 'standard', 'gmt'));
$data .= '&datesys='.urlencode(dol_print_date(dol_now('gmt'), 'standard', 'gmt').'.'.$micro);
$data .= '&version='.(float) DOL_VERSION;
$data .= '&version_full='.urlencode(DOL_VERSION);
$data .= '&versionblockedlog='.(float) getBlockedLogVersionToShow();
@@ -445,3 +452,15 @@ function callApiToPushCounter($id, $signature, $test, $previousid, $previoussign
return 0;
}
/**
* Return if user is a ta auditor
*
* @return int Return > 0 if user is an external user so must be restricted to archive control feature
*/
function userIsTaxAuditor()
{
global $user;
return ((getDolGlobalString('BLOCKEDLOG_FOR_TAX_AUDITOR') && $user->socid) ? 1 : 0);
}

View File

@@ -258,39 +258,57 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
print '</thead>';
print '<tbody>';
$tablerows = array();
$position = '';
$levelposition = '';
$lineposition = 0;
if (count($TChildBom) > 0) {
if ($action == 'treeview') {
foreach ($TChildBom as $fk_bom => $TProduct) {
$repeatChar = '&emsp;';
if (!empty($TProduct['bom'])) {
// we make position string in format 'lineposition.bomlevel.childposition'
if (!empty($TProduct['position']) && $TProduct['parentid'] == $object->id) {
// define lineposition
$lineposition = $TProduct['position'];
}
// define lineposition.bomlevel
$position = sprintf('%d.%d', $lineposition, $TProduct['level']);
// memorize level position for products in bom
$levelposition = $position;
if (!empty($TProduct['parentid']) && $TProduct['parentid'] != $object->id && empty($TProduct['product'])) {
// define lineposition.bomlevel.childposition
$position = sprintf('%s.%d', $position, $TProduct['position']);
}
$prod = new Product($db);
$prod->fetch($TProduct['bom']->fk_product);
if ($TProduct['parentid'] != $object->id) {
print '<tr class="sub_bom_lines oddeven" parentid="'.$TProduct['parentid'].'">';
$tablerows[$position] = '<tr class="sub_bom_lines oddeven" parentid="'.$TProduct['parentid'].'">';
} else {
print '<tr class="oddeven">';
$tablerows[$position] = '<tr class="oddeven">';
}
if ($action == 'treeview') {
print '<td class="linecoldescription">'.str_repeat($repeatChar, $TProduct['level']).$prod->getNomUrl(1);
} else {
print '<td class="linecoldescription">'.str_repeat($repeatChar, $TProduct['level']).$TProduct['bom']->getNomUrl(1);
}
print ' <a class="collapse_bom" id="collapse-'.$fk_bom.'" href="#">';
print img_picto('', 'folder-open');
print '</a>';
print '</td>';
if ($action == 'treeview') {
print '<td class="left">'.$TProduct['bom']->getNomUrl(1).'</td>';
}
print '<td class="linecolqty right">'.price(price2num($TProduct['qty'], 'MS')).'</td>';
print '<td>';
print '</td>';
print '<td class="linecolstock right"></td>';
print '<td class="linecoltheoricalstock right"></td>';
print '</tr>';
$tablerows[$position] .= '<td class="linecoldescription">'.str_repeat($repeatChar, $TProduct['level']).$prod->getNomUrl(1);
$tablerows[$position] .= ' <a class="collapse_bom" id="collapse-'.$fk_bom.'" href="#">';
$tablerows[$position] .= img_picto('', 'folder-open');
$tablerows[$position] .= '</a>';
$tablerows[$position] .= '</td>';
$tablerows[$position] .= '<td class="left">'.$TProduct['bom']->getNomUrl(1).'</td>';
$tablerows[$position] .= '<td class="linecolqty right">'.price(price2num($TProduct['qty'], 'MS')).'</td>';
$tablerows[$position] .= '<td>';
$tablerows[$position] .= '</td>';
$tablerows[$position] .= '<td class="linecolstock right"></td>';
$tablerows[$position] .= '<td class="linecoltheoricalstock right"></td>';
$tablerows[$position] .= '</tr>';
}
if (!empty($TProduct['product'])) {
foreach ($TProduct['product'] as $fk_product => $TInfos) {
if (!empty($TInfos['position']) && $fk_bom == $object->id) {
// top level position
$position = $TInfos['position'];
} else {
// sublevel position
$position = sprintf('%s.%d', $levelposition, $TInfos['position']);
}
$prod = new Product($db);
$prod->fetch($fk_product);
$prod->load_virtual_stock();
@@ -298,23 +316,27 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
$prod->stock_reel = 0;
}
if ($fk_bom != $object->id) {
print '<tr class="sub_bom_lines oddeven" parentid="'.$fk_bom.'">';
$tablerows[$position] = '<tr class="sub_bom_lines oddeven" parentid="'.$fk_bom.'">'; // sub bom on same position
} else {
print '<tr class="oddeven">';
$tablerows[$position] = '<tr class="oddeven">';
}
print '<td class="linecoldescription">'.str_repeat($repeatChar, $TInfos['level']).$prod->getNomUrl(1).'</td>';
if ($action == 'treeview') {
print '<td></td>';
}
print '<td class="linecolqty right">'.price(price2num($TInfos['qty'], 'MS')).'</td>';
print '<td>';
print '</td>';
print '<td class="linecolstock right">'.price2num($prod->stock_reel, 'MS').'</td>';
print '<td class="linecoltheoricalstock right">'.$prod->stock_theorique.'</td>';
print '</tr>';
$tablerows[$position] .= '<td class="linecoldescription">'.str_repeat($repeatChar, (int) $TInfos['level']).$prod->getNomUrl(1).'</td>';
$tablerows[$position] .= '<td></td>';
$tablerows[$position] .= '<td class="linecolqty right">'.price(price2num((float) $TInfos['qty'], 'MS')).'</td>';
$tablerows[$position] .= '<td>';
$tablerows[$position] .= '</td>';
$tablerows[$position] .= '<td class="linecolstock right">'.price2num($prod->stock_reel, 'MS').'</td>';
$tablerows[$position] .= '<td class="linecoltheoricalstock right">'.$prod->stock_theorique.'</td>';
$tablerows[$position] .= '</tr>';
}
}
}
// Sort the rows numeric by position string
ksort($tablerows, SORT_NUMERIC);
// Print the rows
foreach ($tablerows as $position => $row) {
print $row;
}
} else {
foreach ($TChildBom as $fk_product => $elem) {
$prod = new Product($db);

View File

@@ -1558,7 +1558,7 @@ class BOM extends CommonObject
/**
* Get/add Net needs Tree by product or bom
*
* @param array<int,array{product:array,bom:BOM,parentid:int,qty:float,level:int,fk_unit:?int}> $TNetNeeds Array of ChildBom and infos linked to
* @param array<int,array{product:array,bom:BOM,parentid:int,qty:float,level:int,position:int,fk_unit:?int}> $TNetNeeds Array of ChildBom and infos linked to
* @param float $qty qty needed (used as a factor to produce 1 unit)
* @param int<0,1000> $level level of recursivity
* @return void
@@ -1576,6 +1576,7 @@ class BOM extends CommonObject
//$TNetNeeds[$childBom->id]['fk_unit'] = $line->fk_unit;
$TNetNeeds[$childBom->id]['qty'] = $line->qty * $qty;
$TNetNeeds[$childBom->id]['level'] = $level;
$TNetNeeds[$childBom->id]['position'] = $line->position;
$childBom->getNetNeedsTree($TNetNeeds, $line->qty * $qty / $childBom->qty, $level + 1);
}
} else {
@@ -1593,6 +1594,7 @@ class BOM extends CommonObject
}
$TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
$TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
$TNetNeeds[$this->id]['product'][$line->fk_product]['position'] = $line->position;
}
}
}

View File

@@ -425,7 +425,6 @@ while ($i < $imaxinloop) {
print img_picto('', 'url', 'class="pictofixedwidth"');
print '<a class="" href="'.$obj->url.'"'.($obj->target ? ' target="newlink" rel="noopener"' : '').'>';
} else {
//print img_picto('', 'rightarrow', 'class="pictofixedwidth"');
print '<a class="" href="'.$obj->url.'">';
}
print $link;

Some files were not shown because too many files have changed in this diff Show More