Ver código fonte

Merge branch '19.0' into 19.0-mmi

Mathieu Moulin 1 ano atrás
pai
commit
b35e5cc7e4
100 arquivos alterados com 3392 adições e 1208 exclusões
  1. 24 18
      .github/CONTRIBUTING.md
  2. 1 1
      .github/ISSUE_TEMPLATE/bug_report.yml
  3. 10 1
      .github/PULL_REQUEST_TEMPLATE.md
  4. 1 1
      .github/workflows/code_quality_qodana.yml.disabled
  5. 7 4
      .github/workflows/exakat.yml
  6. 1 1
      .github/workflows/github_ci_php71_pgsql.yml.disabled
  7. 1 1
      .github/workflows/github_ci_php81_mysql.yml.disabled
  8. 1 1
      .github/workflows/phpcs.yml
  9. 24 5
      .github/workflows/phpcsfixer.yml.disabled
  10. 7 6
      .gitignore
  11. 3 3
      .gitmodules
  12. 63 0
      .php-cs-fixer.dist.php
  13. 13 0
      .phpcs.xml.dist
  14. 0 236
      .scrutinizer.yml
  15. 0 14
      .stickler.yml
  16. 74 21
      .travis.yml
  17. 0 6
      .tx/config
  18. 7 5
      COPYRIGHT
  19. 360 61
      ChangeLog
  20. 12 12
      README-FR.md
  21. 26 23
      README.md
  22. 27 26
      SECURITY.md
  23. 16 15
      build/dmg/dolimamp/install.forced.php
  24. 1 1
      build/docker/README.md
  25. 1 1
      build/exe/doliwamp/Makefile
  26. 16 1
      build/exe/doliwamp/UsedPort.cpp
  27. 1 1
      build/exe/doliwamp/php.ini.install
  28. 37 37
      build/generate_filelist_xml.php
  29. 14 5
      build/makepack-dolibarr.pl
  30. 2 0
      build/makepack-howto.txt
  31. 4 6
      build/patch/README
  32. 18 5
      build/phpstan/README
  33. 7 2
      build/phpstan/bootstrap.php
  34. 1 0
      build/rpm/conf.php
  35. 1 0
      dev/.gitignore
  36. 70 35
      dev/dolibarr_changes.txt
  37. 17 17
      dev/examples/code/create_invoice.php
  38. 14 14
      dev/examples/code/create_order.php
  39. 8 8
      dev/examples/code/create_product.php
  40. 10 10
      dev/examples/code/create_user.php
  41. 9 9
      dev/examples/code/get_contracts.php
  42. 124 0
      dev/examples/stripe/webhook_ipn_paymentintent_failed_sepa.txt
  43. 1 1
      dev/examples/zapier/package.json
  44. 3 1
      dev/initdata/README
  45. 4 3
      dev/initdata/generate-invoice.php
  46. 2 2
      dev/initdata/generate-order.php
  47. 17 8
      dev/initdata/generate-product.php
  48. 4 4
      dev/initdata/generate-proposal.php
  49. 10 6
      dev/initdata/generate-thirdparty.php
  50. 3 3
      dev/initdata/import-products.php
  51. 4 4
      dev/initdata/import-thirdparties.php
  52. 2 2
      dev/initdata/import-users.php
  53. 59 53
      dev/initdemo/initdemopassword.sh
  54. 5 3
      dev/initdemo/mysqldump_dolibarr_19.0.0.sql
  55. 0 69
      dev/initdemo/mysqldump_dolibarr_3.5.0.sql
  56. 12 0
      dev/initdemo/savedemo.sh
  57. 41 40
      dev/initdemo/sftpget_and_loaddump.php
  58. 44 44
      dev/initdemo/updatedemo.php
  59. 1 1
      dev/setup/fail2ban/filter.d/web-accesslog-limit403.conf
  60. 1 1
      dev/setup/fail2ban/filter.d/web-dolibarr-limitpublic.conf
  61. 1 1
      dev/setup/fail2ban/filter.d/web-dolibarr-rulesbruteforce.conf
  62. 1 1
      dev/setup/fail2ban/filter.d/web-dolibarr-rulespassforgotten.conf
  63. 3 3
      dev/setup/qodana/README.md
  64. 15 0
      dev/setup/sonarqube/README.md
  65. 1 1
      dev/skeletons/README.md
  66. 1 0
      dev/tools/.gitignore
  67. 438 0
      dev/tools/apstats.php
  68. 142 0
      dev/tools/codespell/codespell-lines-ignore.txt
  69. 18 18
      dev/tools/dolibarr-postgres2mysql.php
  70. 3 3
      dev/tools/fixdosfiles.sh
  71. 12 6
      dev/tools/github_authors_and_commits_peryear.sh
  72. 38 7
      dev/tools/github_commits_perversion.sh
  73. 57 0
      dev/tools/make_sprite.sh
  74. 3 0
      dev/tools/php-cs-fixer/.gitignore
  75. 80 0
      dev/tools/php-cs-fixer/run-php-cs-fixer.sh
  76. 2 0
      dev/tools/rector/.gitignore
  77. 28 0
      dev/tools/rector/README.md
  78. 19 0
      dev/tools/rector/composer.json
  79. 137 0
      dev/tools/rector/composer.lock
  80. 62 0
      dev/tools/rector/rector.php
  81. 216 0
      dev/tools/rector/src/Renaming/EmptyGlobalToFunction.php
  82. 391 0
      dev/tools/rector/src/Renaming/GlobalToFunction.php
  83. 153 0
      dev/tools/rector/src/Renaming/UserRightsToFunction.php
  84. 0 146
      dev/tools/spider.php
  85. 0 0
      dev/tools/test/facturex-pdfextractxml.py
  86. 1 0
      dev/tools/test/testtcpdf.php
  87. 1 0
      dev/tools/test/testutf.php
  88. 4 5
      dev/translation/autotranslator.class.php
  89. 8 7
      dev/translation/sanity_check_en_langfiles.php
  90. 14 5
      dev/translation/strip_language_file.php
  91. 0 1
      htdocs/.gitignore
  92. 134 46
      htdocs/accountancy/admin/account.php
  93. 45 47
      htdocs/accountancy/admin/accountmodel.php
  94. 4 4
      htdocs/accountancy/admin/card.php
  95. 5 2
      htdocs/accountancy/admin/categories.php
  96. 36 21
      htdocs/accountancy/admin/categories_list.php
  97. 40 4
      htdocs/accountancy/admin/closure.php
  98. 7 6
      htdocs/accountancy/admin/defaultaccounts.php
  99. 6 6
      htdocs/accountancy/admin/export.php
  100. 20 10
      htdocs/accountancy/admin/fiscalyear.php

+ 24 - 18
.github/CONTRIBUTING.md

@@ -7,18 +7,18 @@ Bug reports and feature requests
 <a name="not-a-support-forum"></a>*Note*: **GitHub Issues is not a support forum.** If you have questions about Dolibarr / need help using the software, please use [the forums](https://www.dolibarr.org/forum.php). Forums exist in different languages.
 
 Issues are managed on [GitHub](https://github.com/Dolibarr/dolibarr/issues). 
-Default **language here is english**. So please prepare your contributions in english.
+Default **language here is English**. So please prepare your contributions in English.
 
 1. Please [use the search engine](https://help.github.com/articles/searching-issues) to check if nobody's already reported your problem.
 2. [Create an issue](https://help.github.com/articles/creating-an-issue). Choose an appropriate title. Prepend appropriately with Bug or Feature Request.
 3. Tell us the version you are using!   (look at  /htdocs/admin/system/dolibarr.php?  and check if you are using the latest version) 
-4. Write a report with as much detail as possible (Use [screenshots](https://help.github.com/articles/issue-attachments) or even screencasts and provide logging and debugging informations whenever possible).
+4. Write a report with as much detail as possible (Use [screenshots](https://help.github.com/articles/issue-attachments) or even screencasts and provide logging and debugging information whenever possible).
 5. Delete unnecessary submissions.
 6. **Check your Message at Preview before sending.**
 
 
 
-<a name="code"></a>Code
+<a name="code"></a>Submit code
 ---------------------
 
 ### Basic workflow
@@ -35,20 +35,22 @@ Default **language here is english**. So please prepare your contributions in en
 
 Unless you're fixing a bug, all pull requests should be made against the *develop* branch.
 
-If you're fixing a bug, it is preferred that you cook your fix and pull request it
-against the oldest version affected that's still supported.
+If you're fixing a bug, it is preferred that you cook your fix and pull request it against an oldest version affected.
 
-We officially support versions N, N − 1 and N − 2 for N the latest version available.
+We recommend to push it into N - 2 for N the latest version available, if not possible into version N - 1, and finally into develop.
+This is just a recommendation, currently, if you push a bug fix on a very old version, it is still merged and propagated into
+higher versions. 
 
-Choose your base branch accordingly.
+The rule N - 2 is just a tip if you don't know which version to choose to get the best the best compromise between ease of correction 
+and number of potential beneficiaries of the correction.
 
 ### General rules
-Please don't edit the ChangeLog file. File will be generated from all commit messages during release process by the project manager.
+Please don't edit the ChangeLog file. This file is generated from all commit messages during release process by the project manager.
 
 ### <a name="commits"></a>Commits
 Use clear commit messages with the following structure:
 
-```
+```plaintext
 [KEYWORD] [ISSUENUM] DESC
 
 LONGDESC
@@ -66,10 +68,13 @@ where
 #### Keyword
 In uppercase if you want to have the log comment appears into the generated ChangeLog file.
 
-The keyword can be ommitted if your commit does not fit in any of the following categories:
+The keyword can be omitted if your commit does not fit in any of the following categories:
+
 - Fix/FIX: for a bug fix
-- New/NEW: for an unreferenced new feature (Opening a feature request and using close is prefered)
 - Close/CLOSE: for closing a referenced feature request
+- New/NEW: for an unreferenced new feature (Opening a feature request and using close is preferred)
+- Perf/PERF: for a performance enhancement
+- Qual/QUAL: for quality code enhancement or re-engineering
 
 #### Issuenum
 If your commit fixes a referenced bug or feature request.
@@ -90,19 +95,19 @@ Feel free to express technical details, use cases or anything relevant to the cu
 
 This section can span multiple lines.
 
-Try to keep lines under 120 characters.
+If your PR is a change on interface, you must also paste a screenshot showing the new screen.
 
 #### Examples
 <pre>
 FIX|Fix #456 Short description (where #456 is number of bug fix, if it exists. In upper case to appear into ChangeLog)
 or
-NEW|New Short description (In upper case to appear into ChangeLog, use this if you add a feature not tracked, otherwise use CLOSE #456)
-or
 CLOSE|Close #456 Short description (where #456 is number of feature request, if it exists. In upper case to appear into ChangeLog)
 or
+NEW|New|QUAL|Qual|PERF|Perf Short description (In upper case to appear into ChangeLog, use this if you add a feature not tracked, otherwise use CLOSE #456)
+or
 Short description (when the commit is not introducing feature nor closing a bug)
 
-Long description (Can span accross multiple lines).
+Long description (Can span across multiple lines).
 </pre>
 
 ### Pull Requests
@@ -119,7 +124,7 @@ Also, some code changes need a prior approbation:
 
 * if you want to include a new external library (into htdocs/includes directory), please ask before to the core project manager (mention @dolibarr-jedi in your issue) to see if such a library can be accepted.
 
-* if you add a new tables or fields, you MUST first submit a standalone PR with the data structure changes you plan to add/modify (and only data structure changes). Start development only once this data structure has been accepted.
+* if you add new tables or fields, you MUST first submit a standalone PR with the data structure changes you plan to add/modify (and only data structure changes). Start development only once this data structure has been accepted.
 
 Once a PR has been submitted, you may need to wait for its integration. It is common that the project leader let the PR open for a long delay to allow every developer discuss about the PR (A label is added in such a case).
 
@@ -130,7 +135,8 @@ If your PR has errors reported by the Continuous Integration Platform, it means
 If the PR is valid, and is kept open for a long time, a tag will also be added on the PR to describe the status of your PR and why the PR is kept open. By putting your mouse on the tag, you will get a full explanation of the tag/status that explain why your PR has not been integrated yet.
 In most cases, it gives you information of things you have to do to have the PR taken into consideration (for example a change is requested, a conflict is expected to be solved, some questions were asked). If you have a yellow, red flag of purple flag, don't expect to have your PR validated. You must first provide the answer the tag ask you. The majority of open PR are waiting an action of the author of the PR.
 
-Statistics on Dolibarr project shows that 95% of submitted PR are reviewed and tagged. Average answer delay is also one of the best among Open source projects (just few days before having the Answer Tag set). This is one of the most important ratio of answered PR in Open Source world for a major project. Don't expect the core team to reach the 100%. A so high ratio is very rare on a so popular project and with the increasing popularity of Dolibarr, this ratio will probably decrease in future to a more common level.
+Statistics on Dolibarr project shows that 95% of submitted PR are reviewed and tagged. Average answer delay is also one of the best among Open source projects (just few days before having the Answer Tag set). This is one of the most important ratio of answered PR in Open Source world for a major project. Don't expect the core team to reach the 100%. 
+A so high ratio is very rare on a so popular project and with the increasing popularity of Dolibarr, this ratio will probably decrease in future to a more common level.
 
 
 ### Resources
@@ -144,7 +150,7 @@ All other translations are managed online at [Transifex](https://www.transifex.c
 
 Translations done on transifex are available in the next major release.
 
-Note: Sometimes, the source text (english) is modified. In such a case, the translation is reset. Transifex assume that if the original source
+Note: Sometimes, the source text (English) is modified. In such a case, the translation is reset. Transifex assume that if the original source
 has changed, the translation is surely no more correct so must be done again. But old translation is not lost and you can use the tab "History"
 to retrieve all old translation of a source text, and restore the translation in one click with no need to retranslate it if there is no need to.
 

+ 1 - 1
.github/ISSUE_TEMPLATE/bug_report.yml

@@ -68,4 +68,4 @@ body:
     id: files
     attributes:
       label: Attached files
-      description: Screenshots, screencasts, dolibarr.log, debugging informations
+      description: Screenshots, screencasts, dolibarr.log, debugging information

+ 10 - 1
.github/PULL_REQUEST_TEMPLATE.md

@@ -1,7 +1,7 @@
 # Instructions
 *This is a template to help you make good pull requests. You may use [Github Markdown](https://help.github.com/articles/getting-started-with-writing-and-formatting-on-github/) syntax to format your issue report.*
 *Please:*
-- *only keep the "FIX", "CLOSE" or "NEW" section* (use uppercase to have the PR appears into the ChangeLog, lowercase will not appears)
+- *only keep the "FIX", "CLOSE", "NEW", "PERF" or "QUAL" section* (use uppercase to have the PR appears into the ChangeLog, lowercase will not appears)
 - *follow the project [contributing guidelines](/.github/CONTRIBUTING.md)*
 - *replace the bracket enclosed texts with meaningful information*
 
@@ -16,3 +16,12 @@
 
 # NEW|New [*Short description*]
 [*Long description*]
+
+
+# PERF|Perf #[*issue_number Short description*]
+[*Long description*]
+
+
+# QUAL|Qual #[*issue_number Short description*]
+[*Long description*]
+

+ 1 - 1
.github/workflows/code_quality_qodana.yml → .github/workflows/code_quality_qodana.yml.disabled

@@ -21,7 +21,7 @@ jobs:
           fetch-depth: 1
           #php-version: '7.1'
       - name: 'Qodana Scan'
-        uses: JetBrains/qodana-action@v2023.1.5
+        uses: JetBrains/qodana-action@v2023.2.1
         #with:
         #  php-version: '7.1'
         env:

+ 7 - 4
.github/workflows/exakat.yml.disabled → .github/workflows/exakat.yml

@@ -2,8 +2,9 @@
 name: "Exakat analysis"
 
 on:
+  # execute once a month, the 1st
   schedule:
-  - cron: "0 20 * * *"
+  - cron: "0 20 1 * *"
   workflow_dispatch:
     branches:
       - develop
@@ -15,11 +16,13 @@ jobs:
   exakat:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         fetch-depth: 1
     - name: Exakat
       uses: docker://exakat/exakat-ga
       with:
-        ignore_rules: 'Classes/UseInstanceof,Performances/PrePostIncrement,Functions/UndefinedFunctions,Functions/WrongNumberOfArguments,Functions/WrongTypeWithCall,Variables/UndefinedVariable,Classes/DontUnsetProperties,Classes/NonPpp,Classes/StaticMethodsCalledFromObject,Classes/UseClassOperator,Functions/UsesDefaultArguments,Php/NoClassInGlobal,Php/ShouldUseCoalesce,Php/WrongTypeForNativeFunction,Structures/AddZero,Structures/DropElseAfterReturn,Structures/IfWithSameConditions,Structures/MergeIfThen,Structures/ElseIfElseif,Structures/ExitUsage,Structures/RepeatedPrint,Structures/RepeatedRegex,Structures/SameConditions,Structures/SwitchWithoutDefault,Structures/ShouldMakeTernary,Structures/UselessParenthesis,Structures/UseConstant' 
-        ignore_dirs: '/htdocs/includes,/build,/dev,/doc,/scripts,/test'        
+        ignore_rules: 'Classes/UseInstanceof,Performances/PrePostIncrement,Functions/UndefinedFunctions,Functions/WrongNumberOfArguments,Functions/WrongTypeWithCall,Variables/UndefinedVariable,Classes/DontUnsetProperties,Classes/NonPpp,Classes/StaticMethodsCalledFromObject,Classes/UseClassOperator,Functions/UsesDefaultArguments,Php/NoClassInGlobal,Php/ShouldUseCoalesce,Php/WrongTypeForNativeFunction,Structures/AddZero,Structures/DropElseAfterReturn,Structures/IfWithSameConditions,Structures/MergeIfThen,Structures/NestedTernary,Structures/ElseIfElseif,Structures/ExitUsage,Structures/RepeatedPrint,Structures/RepeatedRegex,Structures/SameConditions,Structures/SwitchWithoutDefault,Structures/ShouldMakeTernary,Structures/UselessParenthesis,Structures/UseConstant' 
+        ignore_dirs: '/htdocs/includes/,/htdocs/install/doctemplates/,/build/,/dev/,/doc/,/scripts/,/test/'
+        file_extensions: php   
+        project_reports: Perfile     

+ 1 - 1
.github/workflows/github_ci_php71_pgsql.yml.disabled

@@ -235,7 +235,7 @@ jobs:
         sudo cat /etc/apache2/sites-enabled/000-default.conf
         sudo service apache2 restart
         curl -I localhost
-    - name: Chech Apache availability
+    - name: Check Apache availability
       run: |
         echo "Checking webserver availability by a wget -O - --debug http://127.0.0.1"
         # Ensure we stop on error with set -e

+ 1 - 1
.github/workflows/github_ci_php81_mysql.yml.disabled

@@ -190,7 +190,7 @@ jobs:
         sudo cat /etc/apache2/sites-enabled/000-default.conf
         sudo service apache2 restart
         curl -I localhost
-    - name: Chech Apache availability
+    - name: Check Apache availability
       run: |
         echo "Checking webserver availability by a wget -O - --debug http://127.0.0.1"
         # Ensure we stop on error with set -e

+ 1 - 1
.github/workflows/phpcs.yml

@@ -11,7 +11,7 @@ jobs:
   phpcs:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 50 # important!
 

+ 24 - 5
.github/workflows/phpcsfixer.yml.disabled

@@ -1,6 +1,8 @@
 name: GitHub CI PHPCS and PHPCBF
 
-on: push
+on: 
+  pull_request:
+    types: [opened]
 #on: 
 #  push:
 #    paths:
@@ -11,7 +13,7 @@ jobs:
   #  uses: ./.github/workflows/files_changed.yaml
   #  with:
   #    folder_path: .*
-      
+
   linter_name:
     name: Run & fix PHP Code Sniffer
     runs-on: ubuntu-latest
@@ -19,7 +21,9 @@ jobs:
     steps:
       - uses: actions/checkout@v3
         with:
-          fetch-depth: 10
+          repository: ${{ github.event.pull_request.head.repo.full_name }}
+          ref: ${{ github.event.pull_request.head.ref }}      
+          #  fetch-depth: 10
 
       - name: echo Get list of all changed files
         run: |
@@ -39,6 +43,21 @@ jobs:
           phpcs_ref_name: ${{github.ref_name}}
           phpcs_github_event_name: ${{github.event_name}}
           phpcs_files: ${{ needs.filesChanged.outputs.all_changed_files }}
-      - uses: stefanzweifel/git-auto-commit-action@v4 # auto commit the fixes action for GitHub
+
+      #- uses: stefanzweifel/git-auto-commit-action@v4 # auto commit the fixes action for GitHub
+      #  with:
+      #    commit_message: Fix PHPCS errors by GitHub PHPCSfixer action
+
+      - name: Commit changes
+        uses: EndBug/add-and-commit@v9.1.3
         with:
-          commit_message: Fix PHPCS errors by GitHub PHPCSfixer action
+          default_author: github_actions
+          committer_name: GitHub Actions
+          committer_email: actions@github.com
+          #author_name: PHP CS fixer
+          #author_email: eldy@destailleur.fr
+          #committer_name: PHP CS fixer
+          #committer_email: eldy@destailleur.fr
+          message: 'PHP CS fixer github action'
+          add: '*.php'
+

+ 7 - 6
.gitignore

@@ -62,11 +62,12 @@ package-lock.json
 doc/install.lock
 /.asciidoctorconfig.adoc
 
-# Qodana
-.idea/vcs.xml
-.idea/modules.xml
-.idea/workspace.xml
-.idea/inspectionProfiles/Project_Default.xml
-.idea/jsLinters/jshint.xml
+.idea
 /composer.json
 /composer.lock
+
+/build/phpstan/phpstan
+/build/phpstan/bootstrap_custom.php
+phpstan_custom.neon
+/.php-cs-fixer.cache
+/.php_cs.cache

+ 3 - 3
.gitmodules

@@ -1,7 +1,7 @@
 [submodule "htdocs/custom/mmicommon"]
 	path = htdocs/custom/mmicommon
 	url = git@gogs.iprospective.fr:iProspective/dolibarr-module-mmicommon.git
-	branch = 16.0
+	branch = 19.0
 [submodule "htdocs/custom/mmicompta"]
 	path = htdocs/custom/mmicompta
 	url = git@gogs.iprospective.fr:iProspective/dolibarr-module-mmicompta.git
@@ -49,7 +49,7 @@
 [submodule "htdocs/custom/mmiprestasync"]
 	path = htdocs/custom/mmiprestasync
 	url = git@gogs.iprospective.fr:iProspective/dolibarr-module-mmiprestasync.git
-	branch = 16.0
+	branch = 19.0
 [submodule "htdocs/custom/mmiproduct"]
 	path = htdocs/custom/mmiproduct
 	url = git@gogs.iprospective.fr:iProspective/dolibarr-module-mmiproduct.git
@@ -73,7 +73,7 @@
 [submodule "htdocs/custom/mmiworkflow"]
 	path = htdocs/custom/mmiworkflow
 	url = git@gogs.iprospective.fr:iProspective/dolibarr-module-mmiworkflow.git
-	branch = 16.0
+	branch = 19.0
 [submodule "htdocs/custom/mmishipping"]
 	path = htdocs/custom/mmishipping
 	url = git@gogs.iprospective.fr:iProspective/dolibarr-module-mmishipping.git

+ 63 - 0
.php-cs-fixer.dist.php

@@ -0,0 +1,63 @@
+<?php
+
+/* PHP 7.0 */
+$finder = (new PhpCsFixer\Finder())
+->in(__DIR__)
+->exclude([
+	'core/includes',
+	'custom',
+	'documents',
+	'doctemplates',
+	'vendor',
+	'install/doctemplates',
+	'htdocs/custom',
+	'htdocs/includes',
+	'htdocs/install/doctemplates',
+])
+->notPath('vendor');
+
+
+/* PHP 7.4+ */
+/*
+$finder = (new PhpCsFixer\Finder())
+	->in(__DIR__)
+	->exclude([
+		'custom',
+		'documents',
+		'htdocs/custom',
+		'htdocs/includes',
+	])
+	->notPath([
+		'vendor',
+	]);
+*/
+
+return (new PhpCsFixer\Config())
+	->setRules([
+		// Apply PSR-12 as per https://wiki.dolibarr.org/index.php?title=Langages_et_normes#PHP:~:text=utiliser%20est%20le-,PSR%2D12,-(https%3A//www
+		'@PSR12' => true,  // Disabled for now to limit number of changes
+
+		// Minimum version Dolibarr v18.0.0
+		// Compatibility with min 7.1 is announced with Dolibarr18.0 but
+		// app is still working with 7.0 so no reason to abandon compatiblity with this target for the moment.
+		// So we use target PHP70 for the moment.
+		'@PHP70Migration' => true,
+		//'@PHP71Migration' => true,
+                // Avoid adding public to const (incompatible with PHP 7.0):
+                'visibility_required' => ['elements'=>['property', 'method']],
+
+		//'strict_param' => true,
+		//'array_syntax' => ['syntax' => 'short'],
+		//'list_syntax' => false,
+		//'visibility_required' => false,
+		'array_syntax' => false,
+		'ternary_to_null_coalescing' => false
+	])
+	->setFinder($finder)
+	// TAB Indent violates PSR-12 "must" rule, but used in code
+	// (See https://www.php-fig.org/psr/psr-12/#24-indenting )
+	->setIndent("\t")
+	// All files MUST use the Unix LF line ending only
+	// https://www.php-fig.org/psr/psr-12/#22-files
+	->setLineEnding("\n")
+;

+ 13 - 0
.phpcs.xml.dist

@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PHP_CodeSniffer" xsi:noNamespaceSchemaLocation="phpcs.xsd">
+    <!--
+      Documentation:
+      https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-Ruleset
+      -->
+
+    <rule ref="dev/setup/codesniffer/ruleset.xml"/>
+
+    <arg value="p"/>
+    <arg name="colors"/>
+    <arg name="extensions" value="php,inc" />
+</ruleset>

+ 0 - 236
.scrutinizer.yml

@@ -1,236 +0,0 @@
-# .scrutinizer.yml
-#build:
-#    - php-scrutinizer-run
-build:
-    nodes:
-        analysis:
-            tests:
-                override:
-                    - command: php-scrutinizer-run
-                      idle_timeout: 8000
-                    #- php-scrutinizer-run --sub-project-dir=htdocs/admin
-
-imports:
-    - php
-
-filter:
-    excluded_paths:
-        - build/
-        - dev/
-        - doc/
-        - documents/
-        - node_modules/
-        - test/
-        - htdocs/custom/
-        - htdocs/includes/
-        - htdocs/install/doctemplates/
-    #dependency_paths:
-    #    - htdocs/includes/
-    paths: 
-        - htdocs/*
-        - scripts/*
-    
-tools:
-    # php_analyzer. Doc on https://scrutinizer-ci.com/docs/tools/php/php-analyzer/
-    php_analyzer:
-        enabled: true
-        extensions:
-            - php
-        #dependency_paths: 
-        #    - htdocs/includes/
-        filter:
-            excluded_paths:
-                - build/
-                - dev/
-                - doc/
-                - documents/
-                - htdocs/custom/
-                - htdocs/includes/
-                - htdocs/install/doctemplates/
-                - htdocs/core/class/lessc.class.php
-                - node_modules/
-                - test/
-            paths:
-                - htdocs/*
-                - scripts/*
-        config:
-            parameter_reference_check:
-                enabled:              true
-            checkstyle:
-                enabled:              false
-                no_trailing_whitespace:  true
-                naming:
-                    enabled:              true
-                    local_variable:       ^[a-z][a-zA-Z0-9]*$
-                    abstract_class_name:  ^Abstract|Factory$
-                    utility_class_name:   Utils?$
-                    constant_name:        ^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$
-                    property_name:        ^[a-z][a-zA-Z0-9]*$
-                    method_name:          ^(?:[a-z]|__)[a-zA-Z0-9]*$
-                    parameter_name:       ^[a-z][a-zA-Z0-9]*$
-                    interface_name:       ^[A-Z][a-zA-Z0-9]*Interface$
-                    type_name:            ^[A-Z][a-zA-Z0-9]*$
-                    exception_name:       ^[A-Z][a-zA-Z0-9]*Exception$
-                    isser_method_name:    ^(?:is|has|should|may|supports)
-            unreachable_code:
-                enabled:              true
-            check_access_control:
-                enabled:              true
-            typo_checks:
-                enabled:              true
-            check_variables:
-                enabled:              true
-            check_calls:
-                enabled:              true
-                too_many_arguments:   true
-                missing_argument:     true
-                argument_type_checks:  lenient # Allowed Values: "disabled", "lenient", "strict"
-            suspicious_code:
-                enabled:              true
-                overriding_parameter:  false
-                overriding_closure_use:  true
-                parameter_closure_use_conflict:  true
-                parameter_multiple_times:  true
-                non_existent_class_in_instanceof_check:  true
-                non_existent_class_in_catch_clause:  true
-                assignment_of_null_return:  true
-                non_commented_switch_fallthrough:  true
-                non_commented_empty_catch_block:  true
-                overriding_private_members:  true
-                use_statement_alias_conflict:  true
-                precedence_in_condition_assignment:  true
-            dead_assignments:
-                enabled:              true
-            verify_php_doc_comments:
-                enabled:              false
-                parameters:           true
-                return:               true
-                suggest_more_specific_types:  true
-                ask_for_return_if_not_inferrable:  true
-                ask_for_param_type_annotation:  true
-            loops_must_use_braces:
-                enabled:              true
-            check_usage_context:
-                enabled:              true
-            simplify_boolean_return:
-                enabled:              false
-            phpunit_checks:
-                enabled:              false
-            reflection_checks:
-                enabled:              true
-
-            # Checks Common Precedence Mistakes
-            precedence_checks:
-                enabled:              true
-                assignment_in_condition:  true
-                comparison_of_bit_result:  true
-            basic_semantic_checks:
-                enabled:              true
-            # Disabled unused code. In most cases, we want to keep it.
-            unused_code:
-                enabled:              false		
-            deprecation_checks:
-                enabled:              true
-            useless_function_calls:
-                enabled:              true
-            metrics_lack_of_cohesion_methods:
-                enabled:              true
-            metrics_coupling:
-                enabled:              true
-                stable_code:
-                    namespace_prefixes:   []
-                    classes:              []
-            doctrine_parameter_binding:
-                enabled:              false
-            doctrine_entity_manager_injection:
-                enabled:              false
-            symfony_request_injection:
-                enabled:              false
-            doc_comment_fixes:
-                enabled:              true
-            reflection_fixes:
-                enabled:              false
-            use_statement_fixes:
-                enabled:              true
-                remove_unused:        true
-                # Whether you would like multiple imports in one USE statement to be preserved, e.g. ``use A, B;``.
-                preserve_multiple:    false
-                # Whether you would like to preserve blank lines between use statements.
-                preserve_blanklines:  false
-                order_alphabetically:  false
-         # To use specific config for a specific path, use path_configs: (see example on page https://scrutinizer-ci.com/docs/configuration/tool_config_structure)
-                        
-    # php_depend
-    #php_pdepend:
-    #    enabled: false
-    #    configuration_file: null
-    #    suffixes:
-    #        - php
-    #    excluded_dirs: {  }
-    #    filter:
-    #        excluded_paths:
-    #            - 'build/*'
-    #            - 'dev/*'
-    #            - 'doc/*'
-    #            - 'test/*'
-    #            - 'htdocs/includes/*'
-    #        paths: {  }
-
-    # change tracking
-    #php_changetracking:
-    #    enabled: false
-    #    bug_patterns:
-    #        - '\bfix(?:es|ed)?\b'
-    #    feature_patterns:
-    #        - '\badd(?:s|ed)?\b'
-    #        - '\bimplement(?:s|ed)?\b'
-    #    filter:
-    #        excluded_paths:
-    #            - 'build/*'
-    #            - 'dev/*'
-    #            - 'doc/*'
-    #            - 'documents/*'
-    #            - 'htdocs/includes/*'
-    #            - 'node_modules/*'
-    #            - 'test/*'
-    #        paths: {  }
-
-    # Similar code detection
-    #php_sim:
-    #    enabled: false
-    #    min_mass: 30
-    #    filter:
-    #        excluded_paths:
-    #            - 'build/*'
-    #            - 'dev/*'
-    #            - 'doc/*'
-    #            - 'documents/*'
-    #            - 'htdocs/includes/*'
-    #            - 'node_modules/*'
-    #            - 'test/*'
-    #        paths: {  }
-            
-    # Coding-Style / Bug Detection
-    #js_hint:
-    #    enabled: false
-    #    use_native_config: true
-    #    extensions:
-    #        - js
-    #    filter:
-    #        excluded_paths:
-    #            - 'build/*'
-    #            - 'dev/*'
-    #            - 'doc/*'
-    #            - 'documents/*'
-    #            - 'htdocs/includes/*'
-    #            - 'node_modules/*'
-    #            - 'test/*'
-    #        paths: {  }
-    #    config: {  }
-    #    path_configs: {  }
-
-
-before_commands: {  }
-after_commands: {  }
-artifacts: {  }
-build_failure_conditions: {  }

+ 0 - 14
.stickler.yml

@@ -1,14 +0,0 @@
----
-linters:
-  phpcs:
-      standard: 'dev/setup/codesniffer/ruleset.xml'
-      extensions: 'php'
-      tab_width: 4
-      fixer: true
-
-fixers:
-    enable: true
-
-files:
-    ignore:
-        - 'htdocs/includes/*'

+ 74 - 21
.travis.yml

@@ -8,7 +8,7 @@ dist: focal
 
 language: generic
 
-scan_logs: false
+#scan_logs: false
 
 git:
   depth: 1
@@ -31,25 +31,27 @@ env:
 
 jobs:
   fast_finish: true
-  #allow_failures:
-  #- php: nightly
+  allow_failures:
+  - php: '8.3'
   include:
-    - stage: PHP 7.0-8.1
+    - stage: PHP min and max
       if: type = push
       php: '7.1'
       env: 
       - DB=postgresql
       - TRAVIS_PHP_VERSION=7.1
-    - stage: PHP 7.0-8.1
+    - stage: PHP min and max
       if: type = pull_request OR type = push
-      php: '8.1'
+      php: '8.2'
+      env:
+      - DB=mysql
+      - TRAVIS_PHP_VERSION=8.2
+    - stage: PHP 8.3
+      if: type = push AND branch = develop
+      php: '8.3'
       env: 
       - DB=mysql
-      - TRAVIS_PHP_VERSION=8.1
-    #- stage: PHP Dev
-    #  if: type = push AND branch = developdisabled
-    #  php: '8.2'
-    #  env: DB=mysql
+      - TRAVIS_PHP_VERSION=8.3
 
 notifications:
   email:
@@ -81,7 +83,13 @@ before_install:
   if [ "$TRAVIS_PHP_VERSION" = '8.1' ]; then
   	sudo apt install unzip apache2 php8.1 php8.1-cli php8.1-curl php8.1-mysql php8.1-pgsql php8.1-gd php8.1-imap php8.1-intl php8.1-ldap php8.1-xml php8.1-mbstring php8.1-xml php8.1-zip libapache2-mod-php8.1
   fi
-
+  if [ "$TRAVIS_PHP_VERSION" = '8.2' ]; then
+  	sudo apt install unzip apache2 php8.2 php8.2-cli php8.2-curl php8.2-mysql php8.2-pgsql php8.2-gd php8.2-imap php8.2-intl php8.2-ldap php8.2-xml php8.2-mbstring php8.2-xml php8.2-zip libapache2-mod-php8.2
+  fi
+  if [ "$TRAVIS_PHP_VERSION" = '8.3' ]; then
+  	sudo apt install unzip apache2 php8.3 php8.3-cli php8.3-curl php8.3-mysql php8.3-pgsql php8.3-gd php8.3-imap php8.3-intl php8.3-ldap php8.3-xml php8.3-mbstring php8.3-xml php8.3-zip libapache2-mod-php8.3
+  fi
+  
 - |
   echo Install pgsql if run is for pgsql
   if [ "$DB" = 'postgresql' ]; then
@@ -112,6 +120,9 @@ install:
   if [ "$TRAVIS_PHP_VERSION" = '8.1' ]; then
     sudo update-alternatives --set php /usr/bin/php8.1
   fi 
+  if [ "$TRAVIS_PHP_VERSION" = '8.2' ]; then
+    sudo update-alternatives --set php /usr/bin/php8.2
+  fi 
   php -i | head -
   
 - |
@@ -122,6 +133,8 @@ install:
   php -r "if (hash_file('SHA384', '/tmp/composer-setup.php') === '$HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
   sudo php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
   sudo chmod -R a+rwx /usr/local/bin/composer
+  
+  #sudo apt install composer
   composer -V
   composer -n config -g vendor-dir htdocs/includes
   echo
@@ -146,7 +159,7 @@ install:
                         squizlabs/php_codesniffer ^3
   fi
   # phpunit 9 is required for php 8
-  if [ "$TRAVIS_PHP_VERSION" = '8.0' ] || [ "$TRAVIS_PHP_VERSION" = '8.1' ] || [ "$TRAVIS_PHP_VERSION" = '8.2' ] || [ "$TRAVIS_PHP_VERSION" = 'nightly' ]; then
+  if [ "$TRAVIS_PHP_VERSION" = '8.0' ] || [ "$TRAVIS_PHP_VERSION" = '8.1' ] || [ "$TRAVIS_PHP_VERSION" = '8.2' ] || [ "$TRAVIS_PHP_VERSION" = '8.3' ] || [ "$TRAVIS_PHP_VERSION" = 'nightly' ]; then
       sudo composer self-update 2.4.4
       composer -n require --ignore-platform-reqs phpunit/phpunit ^8 \
                                                  php-parallel-lint/php-parallel-lint ^1.2 \
@@ -255,8 +268,9 @@ before_script:
 
   - |
     export CONF_FILE=htdocs/conf/conf.php
-    echo "Setting up Dolibarr $CONF_FILE"
+    echo "Setting up Dolibarr '$CONF_FILE'"
     echo '<?php' > $CONF_FILE
+    echo 'error_reporting(E_ALL);' >> $CONF_FILE
     echo '$'dolibarr_main_url_root=\'http://127.0.0.1\'';' >> $CONF_FILE
     echo '$'dolibarr_main_document_root=\'$TRAVIS_BUILD_DIR/htdocs\'';' >> $CONF_FILE
     echo '$'dolibarr_main_data_root=\'$TRAVIS_BUILD_DIR/documents\'';' >> $CONF_FILE
@@ -321,26 +335,54 @@ script:
         --exclude htdocs/includes/mike42/escpos-php/example --exclude htdocs/includes/maximebf \
         --exclude htdocs/includes/phpunit/ --exclude htdocs/includes/tecnickcom/tcpdf/include/barcodes --exclude htdocs/includes/webmozart --exclude htdocs/includes/webklex --blame .
     fi
+    if [ "$TRAVIS_PHP_VERSION" = "8.2" ]; then
+      parallel-lint -e php --exclude dev/tools/test/namespacemig --exclude htdocs/includes/composer --exclude htdocs/includes/myclabs --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes \
+        --exclude htdocs/includes/sabre --exclude htdocs/includes/phpoffice/PhpSpreadsheet --exclude htdocs/includes/sebastian \
+        --exclude htdocs/includes/squizlabs/php_codesniffer --exclude htdocs/includes/jakub-onderka --exclude htdocs/includes/php-parallel-lint --exclude htdocs/includes/symfony \
+        --exclude htdocs/includes/mike42/escpos-php/example --exclude htdocs/includes/maximebf \
+        --exclude htdocs/includes/phpunit/ --exclude htdocs/includes/tecnickcom/tcpdf/include/barcodes --exclude htdocs/includes/webmozart --exclude htdocs/includes/webklex --blame .
+    fi
+    if [ "$TRAVIS_PHP_VERSION" = "8.3" ]; then
+      parallel-lint -e php --exclude dev/tools/test/namespacemig --exclude htdocs/includes/composer --exclude htdocs/includes/myclabs --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes \
+        --exclude htdocs/includes/sabre --exclude htdocs/includes/phpoffice/PhpSpreadsheet --exclude htdocs/includes/sebastian \
+        --exclude htdocs/includes/squizlabs/php_codesniffer --exclude htdocs/includes/jakub-onderka --exclude htdocs/includes/php-parallel-lint --exclude htdocs/includes/symfony \
+        --exclude htdocs/includes/mike42/escpos-php/example --exclude htdocs/includes/maximebf \
+        --exclude htdocs/includes/phpunit/ --exclude htdocs/includes/tecnickcom/tcpdf/include/barcodes --exclude htdocs/includes/webmozart --exclude htdocs/includes/webklex --blame .
+    fi
     set +e
     echo
 
   - |
-    echo "Checking coding style (only for Pull Requests builds and 1 version to not overload travis and avoid duplicate tests)"
+    echo "Checking coding style (only for 1 version to not overload travis and avoid duplicate tests)"
     # Ensure we catch errors
     set -e
     # Exclusions are defined in the ruleset.xml file
-    if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_PHP_VERSION" = "8.1" ]; then
+    #if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_PHP_VERSION" = "8.1" ]; then
+    if [ "$TRAVIS_PHP_VERSION" = "8.1" ]; then
+      phpcs -s -p -d memory_limit=-1 --extensions=php --colors --tab-width=4 --standard=dev/setup/codesniffer/ruleset.xml --encoding=utf-8 --runtime-set ignore_warnings_on_exit true .;
+    fi
+    if [ "$TRAVIS_PHP_VERSION" = "8.2" ]; then
+      phpcs -s -p -d memory_limit=-1 --extensions=php --colors --tab-width=4 --standard=dev/setup/codesniffer/ruleset.xml --encoding=utf-8 --runtime-set ignore_warnings_on_exit true .;
+    fi
+    if [ "$TRAVIS_PHP_VERSION" = "8.3" ]; then
       phpcs -s -p -d memory_limit=-1 --extensions=php --colors --tab-width=4 --standard=dev/setup/codesniffer/ruleset.xml --encoding=utf-8 --runtime-set ignore_warnings_on_exit true .;
     fi
     set +e
     echo
 
   - |
-    echo "Checking missing debug"
+    echo "Checking missing debug (only for 1 version to not overload travis and avoid duplicate tests)"
     # Ensure we catch errors
     set -e
     # Exclusions are defined in the ruleset.xml file
-    if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_PHP_VERSION" = "8.1" ]; then
+    #if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_PHP_VERSION" = "8.1" ]; then
+    if [ "$TRAVIS_PHP_VERSION" = "8.1" ]; then
+       var-dump-check --extensions php --tracy --exclude htdocs/includes --exclude test/ --exclude htdocs/public/test/ --exclude htdocs/core/lib/functions.lib.php .
+    fi
+    if [ "$TRAVIS_PHP_VERSION" = "8.2" ]; then
+       var-dump-check --extensions php --tracy --exclude htdocs/includes --exclude test/ --exclude htdocs/public/test/ --exclude htdocs/core/lib/functions.lib.php .
+    fi
+    if [ "$TRAVIS_PHP_VERSION" = "8.3" ]; then
        var-dump-check --extensions php --tracy --exclude htdocs/includes --exclude test/ --exclude htdocs/public/test/ --exclude htdocs/core/lib/functions.lib.php .
     fi
     set +e
@@ -352,6 +394,7 @@ script:
     # Ensure we catch errors
     set +e
     echo '<?php ' > $INSTALL_FORCED_FILE
+    echo 'error_reporting(E_ALL);' >> $INSTALL_FORCED_FILE
     echo '$'force_install_noedit=2';' >> $INSTALL_FORCED_FILE
     if [ "$DB" = 'mysql' ] || [ "$DB" = 'mariadb' ]; then
       echo '$'force_install_type=\'mysqli\'';' >> $INSTALL_FORCED_FILE
@@ -371,6 +414,7 @@ script:
     echo '$'force_install_mainforcehttps=false';' >> $INSTALL_FORCED_FILE
     echo '$'force_install_main_data_root=\'$TRAVIS_BUILD_DIR/htdocs\'';' >> $INSTALL_FORCED_FILE
     #cat $INSTALL_FORCED_FILE
+    echo
   
   - |
     echo "Upgrading Dolibarr"
@@ -436,15 +480,22 @@ script:
     php upgrade.php 17.0.0 18.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade17001800.log
     php upgrade2.php 17.0.0 18.0.0 > $TRAVIS_BUILD_DIR/upgrade17001800-2.log
     php step5.php 17.0.0 18.0.0 > $TRAVIS_BUILD_DIR/upgrade17001800-3.log
+    php upgrade.php 18.0.0 19.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade18001900.log || cat $TRAVIS_BUILD_DIR/upgrade18001900.log
+    php upgrade2.php 18.0.0 19.0.0 > $TRAVIS_BUILD_DIR/upgrade18001900-2.log || cat $TRAVIS_BUILD_DIR/upgrade18001900-2.log
+    php step5.php 18.0.0 19.0.0 > $TRAVIS_BUILD_DIR/upgrade18001900-3.log || cat $TRAVIS_BUILD_DIR/upgrade18001900-3.log
+    set +e
+    echo
 
-    #show table content and log
+  - |
+    echo Show some debug info like table content or log
     #echo '\d llx_adherent' | psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis'
-    #cat $TRAVIS_BUILD_DIR/upgrade500600.log
+    cat $TRAVIS_BUILD_DIR/upgrade18001900.log
+    echo
 
   - |
     echo Result of migration scripts
     ls -alrt $TRAVIS_BUILD_DIR/
-        
+
     echo Show content of last file
     cat $TRAVIS_BUILD_DIR/"$(ls -rt $TRAVIS_BUILD_DIR/ | tail -n1)"
 
@@ -463,6 +514,7 @@ script:
     echo
     #cat /tmp/dolibarr_install.log
     cat $TRAVIS_BUILD_DIR/enablemodule.log
+    echo
 
   - |
     echo "Unit testing"
@@ -472,6 +524,7 @@ script:
     phpunitresult=$?
     echo "Phpunit return code = $phpunitresult"
     set +e
+    echo
 
 after_script:
   - |

+ 0 - 6
.tx/config

@@ -200,12 +200,6 @@ source_file = htdocs/langs/en_US/ldap.lang
 source_lang = en_US
 type        = MOZILLAPROPERTIES
 
-[o:dolibarr-association:p:dolibarr:r:link]
-file_filter = htdocs/langs/<lang>/link.lang
-source_file = htdocs/langs/en_US/link.lang
-source_lang = en_US
-type        = MOZILLAPROPERTIES
-
 [o:dolibarr-association:p:dolibarr:r:loan]
 file_filter = htdocs/langs/<lang>/loan.lang
 source_file = htdocs/langs/en_US/loan.lang

+ 7 - 5
COPYRIGHT

@@ -23,20 +23,22 @@ Licence of dependencies of third-party components used by Dolibarr (all compatib
 Component              Version       License                     GPL Compatible  Usage
 -------------------------------------------------------------------------------------
 PHP libraries:
-EvalMath               1.0           BSD                         Yes             Safe math expressions evaluation
+EvalMath               1.0           BSD                         Yes             Safe math expressions evaluation. Used by dynamic price only. TODO Replace with dol_eval ?
 Escpos-php             3.0           MIT License                 Yes             Thermal receipt printer library, for use with ESC/POS compatible printers
 GeoIP2                 0.2.0         Apache License 2.0          Yes             Lib to make geoip convert
+MathPHP                2.8.1         MIT License                 Yes             Modern math library for PHP (only few files)
 Mobiledetect           2.8.41        MIT License                 Yes             Detect mobile devices browsers
-NuSoap                 0.9.5         LGPL 2.1+                   Yes             Library to develop SOAP Web services (not into rpm and deb package)
+NuSoap                 0.9.16        LGPL 2.1+                   Yes             Library to develop SOAP Web services. From https://github.com/f00b4r/nusoap/tree/v0.9.16
 PEAR Mail_MIME         1.8.9         BSD                         Yes             NuSoap dependency
 ParseDown              1.7.4         MIT License                 Yes             Markdown parser
 PCLZip                 2.8.4         LGPL-3+                     Yes             Library to zip/unzip files
 PHPDebugBar            1.18.2        MIT License                 Yes             Used only by the module "debugbar" for developers
-PHP-Imap               2.7.2         MIT License                 Yes             Library to use IMAP with OAuth
 PHPSpreadSheet         1.12.0        LGPL-2.1+                   Yes             Read/Write XLS files, read ODS files
-php-iban               4.1.1         LGPL-3+                     Yes             Parse and validate IBAN (and IIBAN) bank account information in PHP
+PHP-Iban               4.1.1         LGPL-3+                     Yes             Parse and validate IBAN (and IIBAN) bank account information in PHP
+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)
@@ -52,7 +54,7 @@ bacon, dasprid, swiss-qr-bill, kmukku, symfony/validator
 JS libraries:
 Ace                    1.4.14        BSD                         Yes             JS library to get code syntaxique coloration in a textarea.
 ChartJS                3.7.1         MIT License                 Yes             JS library for graph
-CKEditor               4.18          LGPL-2.1+                   Yes             Editor WYSIWYG
+CKEditor               4.22.1        LGPL-2.1+                   Yes             Editor WYSIWYG
 jQuery                 3.6.4         MIT License                 Yes             JS library
 jQuery UI              1.13.2        GPL and MIT License         Yes             JS library plugin UI
 jQuery select2         4.0.13        GPL and Apache License      Yes             JS library plugin for sexier multiselect. Warning: 4.0.6+ create troubles without patching css

+ 360 - 61
ChangeLog

@@ -2,6 +2,304 @@
 English Dolibarr ChangeLog
 --------------------------------------------------------------
 
+
+***** ChangeLog for 19.0.1 compared to 19.0.0 *****
+
+FIX: 16.0 - parent company gets emptied when updating a third party from the card in edit mode (#28269)
+FIX: #22948
+FIX: #28205
+FIX: 28251 Fixing subpermission name on api_multicurrencies.class.php (#28252)
+FIX: #28369
+FIX: #28429
+FIX: #28491 (#28522)
+FIX: #28518 (#28520)
+FIX: #28533 Mo::deleteLine removes the "main" MoLine if consumed line is delete (#28535)
+FIX: #28564
+FIX: Adding the dependencies list feature for extrafields "select" (#28549)
+FIX: Add new hidden conf "DISABLE_QTY_OVERWRITTEN" (#28523)
+FIX: avoid error "Column 'entity' in where clause is ambiguous" (#28270)
+FIX: avoid Unknown column 'pfp.ref_fourn' (#28145)
+FIX: avoid warning "error parsing attribute name in Entity" (#28543)
+FIX: Bad column for total in bom list
+FIX: Bad condition on button back to draft on recruitment job.
+FIX: Bad CRLF when sending text only content. Fix dol_htmlwithnojs()
+FIX: Bad picto on list of permission of a user when user not admin
+FIX: bad timezone for the start/end date of an event
+FIX: Better test on validity of compute field syntax with parenthesis
+FIX: close #28279
+FIX: disabled pito of menu must be greyed.
+FIX: Don't display column when it's out of date (#28271)
+FIX: duplicate with lines: 414-416 (#28358)
+FIX: Error When cloning fourn price no default value for tva_tx (#28368)
+FIX: migration missing 2 columns in llx_resource and 1 in llx_user
+FIX: missing trans
+FIX: notification module: for supplier orders (any of the 3 triggers), user can choose an e-mail template in conf, but the conf is not used when sending the notification (#28216)
+FIX: Not truncate the multicurrency rate shown on cards (even if the global MAIN_MAX_DECIMALS_SHOWN is set to 0) (#28211)
+FIX: Payment on customer invoice - Remove accountid in url if empty for apply default value (#28156)
+FIX: Pb in redirect of a website page in USEDOLIBARRSERVER mode
+FIX: PHP Warning: Undefined variable $lib (#28342)
+FIX: Picto for mime
+FIX: position of field in list of field in shipment list
+FIX: postgresql error (#28542)
+FIX: quote in sql request
+FIX: Responsive on admin project
+FIX: Shipment closing action has wrong value (#28174)
+FIX: some tooltips has disappeared on invoice action button
+FIX: Special code is now transmitted by args only in order supplier (#28546)
+FIX: subscription must be editable when accounting isn't reconciled (#28469)
+FIX: Value of field int = 0 from modulebuilder must not be set to null
+
+
+***** ChangeLog for 19.0.0 compared to 18.0.0 *****
+
+For users:
+----------
+NEW: Compatibility with PHP 8.2
+NEW: Module Workstation (used to enhance the module BOM and Manufacturing Order) is now stable
+NEW: Add a confirmation popup when deleting extrafields
+NEW: Add type 'icon' type for extrafields
+NEW: Close #20930 Use ajax for state loading after country change
+NEW: #23331 Add support for parent projects (#24856)
+NEW: #22531 Expense report - Add two fields into export : Qty & Unit price (excl.) (#26309)
+NEW: #22626 date filter thirdparties contracts projects (#22707)
+NEW: #24085 Add the Project filter
+NEW: #25312 Support extrafields in selectForForms
+NEW: #26312 Manage intermediate BIC - SQL Part (#26325)
+NEW: Accountancy - Add quick navigation with keyboard shortcut on ledger (#26221)
+NEW: Accountancy - FEC/FEC2 format export with attachments (#26192)
+NEW: Accountancy - Option to choose length of lettering code
+NEW: Add a button to create a product or a service from an order or an invoice (#26173)
+NEW: Add a button to re-encrypt data of a dolcrypt extrafield password
+NEW: Add a CLI tool to regenerate all documents
+NEW: Add a goto url from smartphone search page
+NEW: Add all id prof checker on thirdparty for code compta customer and supplier
+NEW: add and list external contributors on ticket public interface
+NEW: Add a protection on purgeFiles
+NEW: Add a public page to list all open surveys
+NEW: Add chart of accounts PCG08-PYME-CAT for ES in catalan language
+NEW: add constant to check if qty shipped not greater than qty ordered
+NEW: Add context for the movement stock (role toconsume/toproduce) on mrp
+NEW: Add contract link on ticket
+NEW: Add culum Technical ID in list of details lines of an order (#26164)
+NEW: ADD: custom compute for exports
+NEW: Add custom Text on footer total (#26334)
+NEW: Add different picto for each type of extrafields (date, string, ...)
+NEW: Add edit line on MO (#26122)
+NEW: Added notification on closed intervention
+NEW: Added of a popup on validation instead of a database fielld to know if the user wants to include subwarehouse
+NEW: Added of the field "include_sub_warehouse" in the table "llx_inventory"
+NEW: Added total line to third-parties list (#26148)
+NEW: Added VAT free & VAT amounts on payment input
+NEW: Add ext payment system ID in the payment page with link to Stripe
+NEW: Add field TechnicalID in list of users
+NEW: Add filter on status of line of a dictionary
+NEW: Add get_substitutionarray_other() on shipping odt (#25080)
+NEW: add IdProfCheck on thirdparty for BE (xxxx.xxx.xxx)
+NEW: Add index on prelevement_demande
+NEW: Add invoice subtype in customer invoice (#26543) and template invoice (SQL part) (#26535)
+NEW: Add label to price level when changing price (#26240)
+NEW: Add modifications of template invoices into agenda
+NEW: Add more company informations (ProfId7 to 10) (#25266)
+NEW: Add more information to holiday mailings (#25461)
+NEW: Add more param on fetch() to prepare perf optimization
+NEW: Add more tables activated by module activation only
+NEW: Add new field into $fields array + Creation of the function getChildWarehouse()
+NEW: Add option for cancel consumed and produced lines (delete lines and rollback stocks) when delete or cancel an manufacturing order (#26254)
+NEW: Add option in PDF for purchase order and quotation to hide prices
+NEW: Add option TAKEPOS_HIDE_PRODUCT_PRICES to hide prices in TakePOS
+NEW: add order supplier submit notif
+NEW: add parent product column on list
+NEW: Add picto in product/service list in object lines (#25511)
+NEW: Add possibility to choose separator #21426
+NEW: Add preselected update keys attribute to import class and select it by default if filled
+NEW: add product barcode on stock exports
+NEW: Add recurring behaviour
+NEW: add recursive deletion option for child m os (#26102)
+NEW: Add refactoring user permission (#26162)
+NEW: add sorting of product price list by customer (#26483)
+NEW: Add tab Events/Agenda on recurring invoices
+NEW: Add the formEditObjectLine hook on commande card and invoice card
+NEW: Add the picto phone of thirdparty on the kanban view of projects
+NEW: Add the status of partnership to select partnership for emailing
+NEW: add Ticket tab on contract
+NEW: agenda per user use quarter hour split instead of half hour split
+NEW: Allow generation of delivery note through REST-API (#26226)
+NEW: Allow sync of currency rates with currency layer by default.
+NEW: Allow to have products not managed in stocks - SQL Part (#26190)
+NEW: Assign contact to a ticket message (#24735)
+NEW: Better protection against reserved words
+NEW: billing on shipment+reception. Can be done before or after delivery.
+NEW: can edit bomline workstation
+NEW: Can edit both the Test and Live stripe customer account on payment
+NEW: Can include product variants in list of products
+NEW: Can manage ODT documents for groups of users.
+NEW: Can modify the picto into modulebuilder
+NEW: Can restore product in stock when deleting the supplier invoice
+NEW: Can see the favicon file into setup of properties of a website
+NEW: Can switch product batch management to no management. (#21691)
+NEW: Can upload/delete ODT template for project and tasks
+NEW: chart of accounts ES PCG08-PYME-CAT in catalan language
+NEW: clone skill object (#26526)
+NEW: close notification for interventions
+NEW: column in  table prelevement_lignes for fk_user (#26196)
+NEW: CONF allow modify ticket classification even if closed
+NEW: conf to display date entry stock exped and sort in date order (#22625)
+NEW: create a product from a free line in a document (#22324)
+NEW: customize position in complete_head_from_modules (#26406)
+NEW: Date d'entree en stock sur les exped au moment de la création
+NEW: Date field for shipment export (#25574)
+NEW: Date format dayhoursec is using year on 2 char on smartphones
+NEW: Default customer, category and product when enable TakePOS (#25031)
+NEW: do not add default value in list
+NEW: drop down for action button show a simple button if only 1 action
+NEW: Enhance github_commits_perversion to get more stats on git commits
+NEW: Enhance IPN to support payment_intent.succeeded for both card/ban
+NEW: extrafields password accepts 'dolcrypt' algorithm (reversible algo)
+NEW: Factorize a lot of code for numbering modules
+NEW: filter on from/to period rather than month/year (#26378)
+NEW: FontAwesome - Add possibility to select another version
+NEW: Form for add object's property on moduleBuilder
+NEW: Can generate SEPA files for salaries (#26541)
+NEW: massactions to delete projects
+NEW: Generic doc template for donations (#26338)
+NEW: Get list evaluation with skills details in user fiche (#26510)
+NEW: hidden conf to disable use of dns_get_record (which can become unresponsive) (#26339)
+NEW: improved resource data structure
+NEW: Include sub warehouse in inventory
+NEW: inventory without virtual products (kits)
+NEW: Invoice subtypes for customers and vendors (#26233)
+NEW: Invoice time from task, make task note better display in invoice line
+NEW: lazy load to substitute project variables (#26451)
+NEW: LDAP Active Directory UserAccountControl (#25507)
+NEW: Library including math and financial functions (#25035)
+NEW: Loan - Can upload a file with drag and drop
+NEW: Manage rate indirect. (#26449)
+NEW: memorize model name for pdf hooks
+NEW: Menu editor is reponsive
+NEW: Merge the "Create ..." buttons on contract into one.
+NEW: More accurate tooltip on what admin permissions are
+NEW: (#24834) new option for hide the footer (#25464)
+NEW: (#25044) new option for choose project visibility
+NEW: new option for hide the footer of tickets on the public interface
+NEW: no need to create invoice supplier object on supplier card for standalone credit note
+NEW: Option to show label, ref+label or only ref of product in TakePOS
+NEW: payment full amount detail tooltip
+NEW: possibility to deselect line when create a recurring invoice + missing to use fk_parent_line
+NEW: Project - List - use select2 multiselect for status
+NEW: Propagate invoice extrafields into template invoice (#26529)
+NEW: remove include_subwarehouse form llx_inventory database table
+NEW: resource improvements - data structure (#26285)
+NEW: Retrieve vat details from the Greek Ministry of Finance GSIS SOAP web service and autocomplete third party fields
+NEW: Right for stats orders (#24607)
+NEW: rights and check access to create portal accounts
+NEW: Row in list higher height (#26177)
+NEW: Save date of RUM creation when creating a Stripe SEPA mandate
+NEW: shipment can include service (for information and invoicing) (#26407)
+NEW: Show id of module on the tooltip module help page
+NEW: show VAT free amount on payment input close #26208 (#26209)
+NEW: start and end date for due date filter on invoice list
+NEW: Sub total in list (#26165)
+NEW: Suport html content for combo list of email recipient
+NEW: Website: Support of js into the Dolibarr server preview
+NEW: TakePOS - add constant to check qty asked is available (#24820)
+NEW: TakePOS - add constant to choose contact instead of customer (#24807)
+NEW: TakePOS - amount label with or without tax in free product (#24829)
+NEW: TakePOS compatibility with lots and serials (#26426)
+NEW: Top menu support picto of modules that are font awesome picto.
+NEW: updating by adding massactions for delete projects in societe tab
+NEW: updating by adding tooltip for api section in Modulebuilder
+NEW: updating by adjust request Sql for Salary invoice (#26279)
+NEW: updating for display Help title when try to delete Don (issue  #25314)
+NEW: Upgrade in module builder in menu section
+NEW: use account address in sepa mandate (#23642)
+NEW: VAT rate - Add entity
+NEW: When an user unset the batch management of products, transformation of each batch stock mouvement in global stock mouvement
+NEW: PDF Generation for each Human Resource Evaluations. 
+
+SEC: #25512 applicative anti bruteforce - security on too many login attempts (#25520)
+SEC: Add action confirm_... as sensitive to need a CSRF token
+SEC: Disable not used PHP streams
+SEC: Add option MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY
+
+
+For developers or integrators:
+------------------------------
+
+QUAL Reduce very seriously the technical debt (using PHPStan, Psalm and Rector)
+NEW Tool in dev/tools/rector to autofix code using style coding practice rules
+
+PERF: Removed a useless fetch_thirdparty
+PERF: Perf avoid 2 useless fetch into the triggers of agenda.
+PERF: performance & code quality enhancements template sections
+
+QUAL: Remove hardcoded code for OVH sms. Generic method is ok now.
+QUAL: Code fix using rector
+QUAL: Force subclass MINVERSION (#26314)
+QUAL: group all flag images into 1 sprite file. (#26459)
+QUAL: Move conf->global into getDolGlobal...
+QUAL: Refactor merging companies and fix #26272 with Reception objects (#26320)
+QUAL: Removed deprecated field remise, remise_percent, remise_absolute
+QUAL: Standardize code and look and feel for dictionaries
+QUAL: Standardize code. Renamed ID of user properties into ->user_xxx_id
+QUAL: Use dol_clone with parameter 2 for ->oldcopy
+QUAL: use switch case instead of if elseif statements for actions
+QUAL: Better respect of REST API RFC.
+
+NEW: [Add hook in user bank page]
+NEW: #19501 Add two hooks in dolreceiptprinter.php (#26439)
+NEW: Accountancy - Add hooks on PrintFieldList for expensereportsjournal, purchasesjournal, sellsjournal
+NEW: add a $notrigger param to Product::updatePrice() method (#26404)
+NEW: Add a rule to fix empty($conf->global->...) into !getDolGlobal...
+NEW: Add column extraparams on societe_rib
+NEW: Add hook on selectLotDataList() function (#25368)
+NEW: add Hooks and prepare extrafields management for product invoices consumptions (#26280)
+NEW: Add hooks on import, step 5 - 6 (#24915)
+NEW: add hook tabContentCreateSupplierOrder (#26418)
+NEW: add hook tabContentViewSupplierInvoice (#26431)
+NEW: add new hook AfterImportInsert
+NEW: add new hook OrderCard (#26380)
+NEW: add new hook tabContentCreateOrder (#26408)
+NEW: Add phpunit for REST API of contacts
+NEW: Add triggers COMPANY_RIB_XXX already present in companybankaccount.class.php
+NEW: Add triggers on import success
+NEW: Add trigger when deleting a bank account line
+NEW: subproduct triggers in product class (#25142)
+NEW: ModuleBuilder: add section changeLog to Doc in MB
+NEW: ModuleBuilder: Add api url to documentation in ModuleBuilder
+NEW: ModuleBuilder: Checkin comments begin and end before each actions
+NEW: ModuleBuilder: edit properties in description tab for ModuleBuilder
+NEW: ModuleBuilder: remove dictionary in ModuleBuilder
+NEW: ModuleBuilder: add page for create dictionary
+NEW: ModuleBuilder: add badge for each tabs
+NEW: ModuleBuilder: for edit name of dictionnary and delete it in MB
+NEW: add barcode function to check if EAN13 is valid (
+
+WARNING:
+--------
+
+The following changes may create regressions for some external modules, but were necessary to make Dolibarr better:
+* Recheck the setup of your module Workflow to see if you need to enable the new setting to have shipment set to billed automatically when
+  an invoice from a shipment is validated (and if your process is to make invoice on shipment and not on order), because this setup has changed.
+* The hook changeHelpURL is replaced by llxHeader
+* The property ->brouillon has been removed from all classes. It was not reliable and was a duplicate of ->status == self::STATUS_DRAFT.
+* The duplicated and deprecated property ->date_livraison that was renamed into ->delivery_date has been completely removed.
+* The property ->user_close to store ID of closing user has been renamed into ->user_closing_id.
+* The property ->user_validation to store ID of user validating has been renamed into ->user_validation_id.
+* The property ->user_creation to store ID of user of creation has been renamed into ->user_creation_id.
+* The property ->user_modification to store ID of user of modification has been renamed into ->user_modification_id.
+* The private array ->status_short, ->statuts and ->status_long are now array ->labelStatusShort and ->labelStatus everywhere.
+* The duplicate property ->user_creat, ->date_creat, ->date_valid have been removed (use instead user_creation, date_creation, date_validation). 
+* The method get_substitutionarray_shipment_lines() has been removed. Use the generic get_substitutionarray_lines() instead.
+* The method ProductcustomerPrice->fetch_all_log() has been renamed into camel case ->fetchAllLog()
+* It was possible to use a variable $soc or $right inside a PHP code condition of some extrafields properties, this is no more true (this 2 variables are no more global variables).
+* New hook files of modules actions_mymodule.class.php should now "extends CommonHookActions"
+* Endpoint for API /partnershipapi and /recruitment has been renamed into /partnerships and /recruitments to follow name conventions.
+* Hidden option ACCOUNTANCY_AUTOFIX_MISSING_LINK_TO_USER_ON_SALARY_BANK_PAYMENT has been renamed into ACCOUNTANCY_AUTOFIX_MISSING_LINK_TO_USER_ON_SALARY_BANK_PAYMENT
+* The delete() method of AdherentType, Contact, Delivery, MultiCurrency, CurrencyRate now need $user as first parameter.
+* A very high number of class properties (with old name in french) are now deprecated in favor of the property name in english.
+* The load of hook context productdao has been removed before calling loadvirtualstock. Modules must use the context of main parent page or 'all' for all cases.
+
+
 ***** ChangeLog for 18.0.5 compared to 18.0.4 *****
 FIX: 17.0: deprecated field should only be a fallback
 FIX: 17.0 - php8 warnings: test for $field existence before checking if it is null or empty
@@ -259,8 +557,8 @@ FIX: when adding new times on a survey, all hours would be erased.
 
 ***** ChangeLog for 18.0.0 compared to 17.0.0 *****
 
-For uses:
----------
+For users:
+----------
 
 NEW: PHP 8.2 compatibility (not yet complete).
 NEW: Module Workstations Management upgraded to stable status.
@@ -273,66 +571,56 @@ NEW: Accountancy - Manage intra-community VAT on supplier invoices - FPC22
 NEW: Accountancy - iSuiteExpert export model
 NEW: Accountancy - Quadratus export with attachments in accountancy export
 NEW: Accountancy - Can filter on a custom group of accounts. Perf or ledger list.
-NEW: Can upload a file with drag and drop on purchase invoice, vats, salaries and social contributions
+NEW: Accountancy - Can select the export format during export of journals
+NEW: Accountancy - sort of column of custom group of account
+NEW: Can upload a file with drag and drop on purchase invoice, vats, salaries and social contributions 
 NEW: Authentication: #22740 add OpenID Connect impl
 NEW: Authentication: add experimental support for Google OAuth2 connexion
 NEW: Authentication: can now edit service name for OAuth token
-NEW: Add a public virtual card page for each user
-NEW: Add a status on supplier price ref (WIP to close a supplier ref)
 NEW: add bookmarks in selectable landing pages for users
 NEW: Add column ext_payment_site into societe_rib to allow multiple payment mode
 NEW: add convertion of images to webp for a single image in website media editor
 NEW: Add CRC for currency symbol before amount
-NEW: add customer code to invoices listing
 NEW: Add filter on nb of generation done in list of recurring invoices
 NEW: Add filters and sort on product unit column
 NEW: Add link to edit VAT list from error message of missing VAT
 NEW: add margins in paiement/card.php
 NEW: Add mass action delete on VAT
-NEW: Add origin info when create a product batch when created from a movement stock
 NEW: Add possibility to choose format #21426
-NEW: Add SQL contraint on product_stock table to allow only exsting product and warehouse #23543
-NEW: Add STRIPE_DEBUG, a way to log Stripe IPN
 NEW: An external module can modify the quick search fields
-NEW: Auto activate some modules on install (Export/Import/Wysiwyg editor)
-NEW: Autofill email form with the email template with status "Default" on
-NEW: Bank name no more mandatory on creation. Can be generated if empty.
-NEW: Bank: Add fields zip, town, country for owner of a bank account
+NEW: Bank:  Bank name no more mandatory on creation. Can be generated if empty.
+NEW: Bank:  Add fields zip, town, country for owner of a bank account
 NEW: batch referential objets
 NEW: Can add the add now link on date into addfieldvalue()
+NEW: Can add an array of several links in date selector
 NEW: Can bin accounting line for a given month
-NEW: Can edit account on miscellaneous payment (if not transfered)
 NEW: Can edit inline the VAT number from supplier tab
-NEW: Can fill date of salary payment with date of start of salary
 NEW: Can go back to draft on shipment when stock change not on validate
 NEW: Can modify bank account of sepa payment (if file not sent yet)
-NEW: Customers: add date due and labels into customer comm card
-NEW: Can select the export format during export of journals
 NEW: Can set a checkbox in formconfirm by clicking on the label
-NEW: Can set flag default value on email templates
 NEW: Can set the page "List of opportunities" as landing page
-NEW: Can show the sql request used on emailing selection
+NEW: Can show the SQL request used on emailing selection
 NEW: can stay on edit field when errors occurs
 NEW: comment in api_mymodule for seperate methods
-NEW: constant PROPALE_ADDON_NOTE_PUBLIC_DEFAULT
 NEW: create email substitution variable for intervention signature URL
+NEW: Contacts: presend mass action in contact list
+NEW: Contacts: hook printFieldListFrom in contact list
+NEW: Customers: add date due and labels into customer comm card
 NEW: Debug the custom CSS feature to avoid a directory search/scan at
 NEW: dev name
 NEW: Disable bad reputation product price
-NEW: dolExplodeIntoArray can accept regex
-NEW: dol_sort_array can sort on alphabetical order even if val is num
-NEW: element time integration code + SQL
+NEW: Email: autofill email form with the email template with status "Default" on
 NEW: Email: don't have closed contact proposed as receiver for the mails
+NEW: Email: can set flag default value on email templates
 NEW: Email-Collector: add field reply-to in email collector as possible filter
 NEW: Email-Collector: substitute date now in email collector
 NEW: Email-Collector: operation type in email collector to load or create contact
 NEW: Email-Collector: easier setup - can also use ! for negative search
+NEW: Email-Templates: show module into list of email templates
 NEW: Events: can add any contact on events if global MAIN_ACTIONCOM_CAN_ADD_ANY_CONTACT is set at 1
 NEW: Events: list with color
-NEW: expend/collapse list of social networks
-NEW: Filter on amount and qty on list of service's contracts
+NEW: Events: remove default percentage for event creation url
 NEW: formconfirm can support field with format datetime
-NEW: getCommonSubstitutionArray to have more substitute keys
 NEW: GeoIP: Can test a geoip conversion from the geoip setup page
 NEW: GUI: add a CSS editor into the admin GUI
 NEW: GUI: add dropdown button actions (example on Create button on project)
@@ -343,16 +631,17 @@ NEW: GUI: can set background style with MAIN_LOGIN_BACKGROUND_STYLE
 NEW: Help: Tooltip to explain how to add a photo on a product
 NEW: Help: Possibility to link to German pages in help
 NEW: helper functions for dates + small demo case
-NEW: hook printFieldListFrom in contact list
+NEW: HR - Salary: can fill date of salary payment with date of start of salary
 NEW: HR - Salary: can modify the date of payment of a salary (if not reconciled)
 NEW: HR - Salary: date for salary payment includes the hour/min
 NEW: HR - Salary: adding button Send Email on the salary file
 NEW: Import: filter on entity in import
 NEW: Import: map table to element for get entity in import
 NEW: inc.php: handle parameters from argv
-NEW: Invoice - show category of operations
+NEW: Installation: Auto activate some modules on install (Export/Import/Wysiwyg editor)
+NEW: Invoice: show category of operations
+NEW: Invoice: add customer code to invoices listing
 NEW: Keep a link between user created from recruitment and application
-NEW: List product in orders
 NEW: Mass Actions: Better responsive for mass actions
 NEW: Members: add numbering modules for members
 NEW: Members: add widget box_members_by_tags.php
@@ -366,75 +655,75 @@ NEW: No overwrite of optionals during put() contact
 NEW: Notifications: add Customer Order delivered (ORDER_NEW) in module Notification
 NEW: Notifications: for Sign or Refused Propal from Online Page
 NEW: Now we can edit amount on VAT and salaries clone action
-NEW: only get openned contact from liste_contact function, to not have acces to closed contact as mail receiver
+NEW: only get opened contact from liste_contact function, to not have access to closed contact as mail receiver
+NEW: Option: MAIN_SECURITY_MAXFILESIZE_DOWNLOADED #yogosha10660
 NEW: Option to manage deposit slips for more payment modes (not only
 NEW: Option to show column for field and line selection on the left
 NEW: Orders: add sub total in order list det
+NEW: Orders: list product in orders
 NEW: Orders export: allow to export field 'shipment method'
 NEW: payment default values when supplier order created from reception
-NEW: Payment : manage contracts
-NEW: presend mass action in contact list
+NEW: Payment: manage contracts
+NEW: Payment: sepaStripe now creates the payment mode with type pm_ using new API
+NEW: Payment: add partial payment reason "withholding tax"
+NEW: Payment: Can edit account on miscellaneous payment (if not transfered)
 NEW: Print PDF: category of operation for crabe PDF model
 NEW: Print PDF: Name and date to print on PDF Sign
 NEW: Print PDF: Use the more recent PDF templates for documents by default on a fresh install
+NEW: Print PDF: Option PDF_SHOW_PHONE_AFTER_USER_CONTACT to show phone after specific assigned contact on PDF
+NEW: Print PDF: Option PDF_SHOW_EMAIL_AFTER_USER_CONTACT to show email after specific assigned contact on PDF
 NEW: product images on popup are cached
+NEW: Products: Add origin info when create a product batch when created from a movement stock
 NEW: Products: Add statistics by amount on statistics of products.
+NEW: Products: Add SQL contraint on product_stock table to allow only existing product and warehouse #23543
 NEW: Proposals: filter for Signed+Billed in proposals
 NEW: Proposals: can modify margin rates in offers like VAT rates
 NEW: Proposals: option filter for NoSalesRepresentativeAffected in proposals list
-NEW: Provide the oldcopy value when calling setValueFrom() function with a trigger key
+NEW: Proposals: constant PROPALE_ADDON_NOTE_PUBLIC_DEFAULT
 NEW: Reception: can receive more than qty ordered on reception
 NEW: referential objects of batch
-NEW: remove default percentage for event creation url
 NEW: remove keys whose table element is the same as element in map list
 NEW: repair script skip views
+NEW: search on time spent duration range
 NEW: Security: Save date to invalidate other session into user table
 NEW: Security: Invalidate all sessions of a user when password is modified.
-NEW: search on time spent duration range
-NEW: sepaStripe now creates the payment mode with type pm_ using new API
-NEW: set payment default values when supplier order created from reception
+NEW: Service Contracts: Filter on amount and qty on list of service's contracts
 NEW: set today start time at beginning
-NEW: Show counter of access of website in website list
 NEW: Show main currency in company info user tooltip
-NEW: Show module into list of emails templates
-NEW: Show picto into the combobox of widgets
 NEW: Show supplier invoice ref of direct debit transfer tab invoices
-NEW: show supplier name in getNomUrl of supplier order
-NEW: sort of column of custom group of account
-NEW: Supplier Invoices: add ability of ODT support to supplier invoices
+NEW: Social Networks: expend/collapse list of social networks
 NEW: Stock limit for alert and desired optimal stock by product and warehouse import
 NEW: Stock: Add warehouse create and modify triggers.
 NEW: Stock: Can select several warehouses into the view stock at date in past
+NEW: Stripe: add STRIPE_DEBUG, a way to log Stripe IPN
+NEW: Supplier Invoices: add ability of ODT support to supplier invoices
+NEW: Supplier Order:  show supplier name in getNomUrl of supplier order
+NEW: Supplier Order:  set payment default values when supplier order created from reception
+NEW: Supplier Price:  Add a status on supplier price ref (WIP to close a supplier ref)
 NEW: Support different bank account for several direct debit payments
 NEW: Support multiselect in the warehouse selection combo box
-NEW: Option: MAIN_SECURITY_MAXFILESIZE_DOWNLOADED #yogosha10660
 NEW: Survey: Comment on survey is possible only after vote.
-NEW: tables: llx_element_time to store time spent on several elements (mo, ticket...)
 NEW: TakePOS: adapt category and product pictures sizes on TakePOS
 NEW: TakePOS: limit load products in TakePOS
 NEW: The batch for remind on due date can be setup for using validation date
-NEW: The refresh link for imap collector is always visible
-NEW: The upgrade process can be done by creating a file upgrade.unlock
-NEW: Tickets: --Send an email when ticket assigned--
+NEW: The refresh link for IMAP collector is always visible
+NEW: Third-Party: use an ajax component to switch prospection status on thirdparty list
 NEW: Tickets: Send a notification email when ticket assigned
 NEW: Tickets: set ticket status to answered if the client has answered from the public interface
 NEW: Tickets: added an option to display the progress of tickets on the public interface
 NEW: Tickets: add link to thirdparty tickets history
 NEW: Tickets: notify also the contributor affected to a ticket if a new message public is post (add global TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_ALSO_CONTRIBUTOR)
+NEW: Upgrades: The upgrade process can be done by creating a file upgrade.unlock
 NEW: Use a cache file for external RSS in calendar
 NEW: Use by default the domain $dolibarr_main_url_root for SMTP HELO
-NEW: use more recent model by default
+NEW: Users: add a public virtual card page for each user
 NEW: VAT can be modified during add of line
 NEW: Website Module: Increment website counter on each page access in website module
-NEW: write all fields and their properties in asciidoc format
-NEW: Can add an array of several links in date selector
-NEW: Option PDF_SHOW_PHONE_AFTER_USER_CONTACT to show phone after specific assigned contact on PDF
-NEW: Option PDF_SHOW_EMAIL_AFTER_USER_CONTACT to show email after specific assigned contact on PDF
+NEW: Website Module: Show counter of access of website in website list
+NEW: Widgets: Show picto into the combobox of widgets
 NEW: Widgets: Implement MAIN_ACTIVATE_FILECACHE on birthday widget
 NEW: Widgets: Add widget "The next upcoming events"
 NEW: Widgets: Add widget of open opportunities
-NEW: use an ajax component to switch prospection status on thirdparty list
-NEW: Add partial payment reason "withholding tax"
 
 
 
@@ -444,18 +733,28 @@ For developers or integrators:
 NEW: Make it possible to select hours and minutes in form_confirm
 NEW: add triggers on mailing
 NEW: Add a trigger when create a shipping line batch and fix propagate missing errors
-NEW: add function for listiong objects from directory
+NEW: add function for listing objects from directory
 NEW: add helplist property to describe fields of objects
-NEW: add hook in loadLotStock() in html.formproduct.class.php file, add hook 'llxFooter', Add hook online sign
-NEW: Update lib parsedownto 1.7.4, phpspreadsheet lib to v1.12, ESCPOS v3.0, jquery, Stripe.
-NEW: Support contact in post() document API
-NEW: More APIs (update currency rate, upload of supplier documents, ...)
+NEW: API: Support contact in post() document API
+NEW: API: more APIs (update currency rate, upload of supplier documents, ...)
+NEW: Hooks: printFieldListFrom in contact list
+NEW: Hooks: add hook in loadLotStock() in html.formproduct.class.php file
+NEW: Hooks: add hook 'llxFooter'
+NEW: Hooks: add hook online sign
+NEW: Hooks: add sent info in the parameters provided to the hook sendMailAfter
+NEW: Libraries: Update libs parsedownto 1.7.4, phpspreadsheet lib to v1.12, ESCPOS v3.0, jquery, Stripe.
 NEW: ModuleBuilder: updating in modulbuilder on tab Menu when adding object
 NEW: ModuleBuilder: add/edit permissions
 NEW: ModuleBuilder: better generated documentation
-NEW: add sent info in the parameters provided to the hook sendMailAfter
 NEW: add setAsSelectUser into factory for generic setup page
 NEW: add option keepspace into dol_string_nospecialchar()
+NEW: dol_sort_array can sort on alphabetical order even if val is num
+NEW: dolExplodeIntoArray can accept regex
+NEW: element time integration code + SQL
+NEW: tables: llx_element_time to store time spent on several elements (mo, ticket...)
+NEW: Provide the oldcopy value when calling setValueFrom() function with a trigger key
+NEW: getCommonSubstitutionArray to have more substitute keys
+NEW: write all fields and their properties in asciidoc format
 
 
 WARNING:

+ 12 - 12
README-FR.md

@@ -70,7 +70,7 @@ Note: *Le processus de migration peut être lancé manuellement et plusieurs foi
 
 ## CE QUI EST NOUVEAU
 
-See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog) file.
+Voir le fichier [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog).
 
 ## CE QUE DOLIBARR PEUT FAIRE
 
@@ -88,11 +88,11 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 - Gestion de contrats de services
 - Gestion de stock et inventaires
 - Gestion des expéditions
-- Gestion des demandes de congès
+- Gestion des demandes de congés
 - Gestion des notes de frais
 - Gestion de recrutement
 - GED (Gestion Electronique de Documents)
-- EMailings de masse
+- E-Mailings de masse
 - Réalisation de sondages
 - Gestion d'adhérents
 - Point de vente/Caisse enregistreuse
@@ -107,17 +107,17 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 - Support des codes barres
 - Calcul des marges
 - Connectivité LDAP
-- Intégratn de ClickToDial
+- Intégration de ClickToDial
 - Intégration RSS
-- Intégation Skype
-- Intégration de système de paiements (Paypal, Stripe, Paybox...)
+- Intégration Skype
+- Intégration de système de paiements (PayPal, Stripe, Paybox...)
 - …
 
 ### Divers
 
 - Multi-langue.
 - Multi-utilisateurs avec différents niveaux de permissions par module.
-- Multi-devise.
+- Multidevise.
 - Peux être multi-société par ajout du module externe multi-société.
 - Plusieurs thèmes visuels.
 - Application simple à utiliser.
@@ -127,7 +127,7 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 - Génération PDF et ODT des éléments (factures, propositions commerciales, commandes, bons expéditions, etc...)
 - Code simple et facilement personnalisable (pas de framework lourd; mécanisme de hook et triggers).
 - Support natif de nombreuses fonctions spécifiques aux pays comme:
-  - La tax espagnole TE et ISPF
+  - La taxe espagnole TE et ISPF
   - Gestion de la TVA NPR (non perçue récupérable - pour les utilisateurs français des DOM-TOM)
   - La loi française Finance 2016 et logiciels de caisse
   - La double taxe canadienne
@@ -139,7 +139,7 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 
 ### Extension
 
-Dolibarr peut aussi être étendu à volonté avec l'ajout de module/applications externes développées par des développeus tiers, disponible sur [DoliStore](https://www.dolistore.com).
+Dolibarr peut aussi être étendu à volonté avec l'ajout de modules/applications externes développées par des développeurs tiers, disponible sur [DoliStore](https://www.dolistore.com).
 
 ## CE QUE DOLIBARR NE PEUT PAS (ENCORE) FAIRE
 
@@ -168,15 +168,15 @@ Voir le fichier [COPYRIGHT](https://github.com/Dolibarr/dolibarr/blob/develop/CO
 
 ## ACTUALITES ET RESEAUX SOCIAUX
 
-Suivez le projet Dolibarr project sur les réseaux francophones
+Suivez le projet Dolibarr sur les réseaux francophones
 
 - [Facebook](https://www.facebook.com/dolibarr.fr)
-- [Twitter](https://www.twitter.com/dolibarr_france)
+- [X](https://www.twitter.com/dolibarr_france)
 
 ou sur les réseaux anglophones
 
 - [Facebook](https://www.facebook.com/dolibarr)
-- [Twitter](https://www.twitter.com/dolibarr)
+- [X](https://www.twitter.com/dolibarr)
 - [LinkedIn](https://www.linkedin.com/company/association-dolibarr)
 - [YouTube](https://www.youtube.com/user/DolibarrERPCRM)
 - [GitHub](https://github.com/Dolibarr/dolibarr)

+ 26 - 23
README.md

@@ -9,7 +9,7 @@ Dolibarr ERP & CRM is a modern software package that helps manage your organizat
 
 It's an Open Source Software suite (written in PHP with optional JavaScript enhancements) designed for small, medium or large companies, foundations and freelancers.
 
-You can freely use, study, modify or distribute it according to its licence.
+You can freely use, study, modify or distribute it according to its license.
 
 You can use it as a standalone application or as a web application to access it from the Internet or a LAN.
 
@@ -47,9 +47,9 @@ On GNU/Linux, first check if your distribution has already packaged Dolibarr.
 
 - Check that your installed PHP version is supported [see PHP support](https://wiki.dolibarr.org/index.php/Releases).
 
-- Uncompress the downloaded .zip archive to copy the "dolibarr/htdocs" directory and all its files inside your web server root or get the files directly from GitHub (recommanded if you know git as it makes it easier if you want to upgrade later):
+- Uncompress the downloaded .zip archive to copy the "dolibarr/htdocs" directory and all its files inside your web server root or get the files directly from GitHub (recommended if you know git as it makes it easier if you want to upgrade later):
 
-  `git clone https://github.com/dolibarr/dolibarr -b x.y`     (where x.y is main version like 3.6, 9.0, ...)
+  `git clone https://github.com/dolibarr/dolibarr -b x.y`     (where x.y is the main version like 3.6, 9.0, ...)
 
 - Set up your web server to use "*dolibarr/htdocs*" as root if your web server does not have an already defined directory to point to.
 
@@ -57,7 +57,7 @@ On GNU/Linux, first check if your distribution has already packaged Dolibarr.
 
 - From your browser, go to the dolibarr "install/" page
 
-  The URL will depends on how you web setup was setup to point to your dolibarr installation. It may looks like:
+  The URL will depends on how your web setup was set up to point to your dolibarr installation. It may look like:
 
   `http://localhost/dolibarr/htdocs/install/`
 
@@ -71,7 +71,7 @@ On GNU/Linux, first check if your distribution has already packaged Dolibarr.
 
 - Follow the installer instructions
 
-### Saas/Cloud setup
+### SaaS/Cloud setup
 
 If you don't have time to install it yourself, you can try some commercial 'ready to use' Cloud offers (See [https://saas.dolibarr.org](https://saas.dolibarr.org)). However, this third solution is not free.
 
@@ -81,7 +81,7 @@ Dolibarr supports upgrading, usually without the need for any (commercial) suppo
 
 - At first make a backup of your Dolibarr files & then [see](https://wiki.dolibarr.org/index.php/Installation_-_Upgrade#Upgrade_Dolibarr)
 - Check that your installed PHP version is supported by the new version [see PHP support](https://wiki.dolibarr.org/index.php/Releases).
-- Overwrite all old files from 'dolibarr' directory with files provided into the new version's package.
+- Overwrite all old files from the 'dolibarr' directory with files provided into the new version's package.
 - At first next access, Dolibarr will redirect you to the "install/" page to follow the upgrade process.
   If an `install.lock` file exists to lock any other upgrade process, the application will ask you to remove the file manually (you should find the `install.lock` file in the directory used to store generated and uploaded documents, in most cases, it is the directory called "*documents*").
 
@@ -98,13 +98,13 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 
  Product Management
 
-- Products and/or Services catalog
+- Products and/or Services catalogue
 - Stock / Warehouse management + Inventory
 - Barcodes
 - Batches / Lots / Serials
 - Product Variants
 - Bill of Materials (BOM)
-- Manufacturing Orders
+- Manufacturing Orders (MO)
 
  Customer/Sales Management
 
@@ -114,7 +114,8 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 - Customer Orders management
 - Contracts/Subscription management
 - Interventions management
-- Ticket System
+- Ticket System  (+ Knowledge management)
+- Partnership management
 - Shipping management
 - Customer Invoices/Credit notes and payment management
 - Point of Sale (POS)
@@ -124,7 +125,7 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 - Suppliers/Vendors + Contacts
 - Supplier (price) requests
 - Purchase Orders management
-- Delivery/Receiption
+- Delivery/Reception
 - Supplier Invoices/credit notes and payment management
 - INCOTERMS
 
@@ -141,16 +142,18 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 
  Collaboration
 
-- Shared calendar/agenda (with ical and vcal import/export for third party tools integration)
+- Shared calendar/agenda (with ical and vcal import/export for third-party tools integration)
 - Projects & Tasks management
+- Event organization
 - Ticket System
 - Surveys
 
- HR
+ HR - Human Resources Management
 
 - Employee's leaves management
 - Expense reports
 - Recruitment management
+- Employee/staff management
 - Timesheets
 
 ### Other application/modules
@@ -168,20 +171,20 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 - Payment platforms integration (PayPal, Stripe, Paybox...)
 - Email-Collector
 
-(around 100 modules available by default, 1000+ on the addon market place)
+(around 100 modules available by default, 1000+ addons at the official marketplace Dolistore.com)
 
 ### Other general features
 
 - Multi-Language Support (Localization in most major languages)
-- Multi-Users and groups with finely grained rights
+- Multi-users and groups with finely-grained rights
 - Multi-Currency
 - Multi-Company (by adding of an external module)
-- Very user friendly and easy to use
+- Very user-friendly and easy to use
 - Customizable dashboards
 - Highly customizable: enable only the modules you need, add user personalized fields, choose your skin, several menu managers (can be used by internal users as a back-office with a particular menu, or by external users as a front-office with another one)
 - APIs (REST, SOAP)
 - Code that is easy to understand, maintain and develop (PHP with no heavy framework; trigger and hook architecture)
-- Support a lot of country specific features:
+- Support a lot of country-specific features:
   - Spanish Tax RE and ISPF
   - French NPR VAT rate (VAT called "Non Perçue Récupérable" for DOM-TOM)
   - Canadian double taxes (federal/province) and other countries using cumulative VAT
@@ -189,7 +192,7 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
   - Argentina invoice numbering using A,B,C...
   - ZATCA e-invoicing QR-Code
   - Compatible with [European directives](https://europa.eu/legislation_summaries/taxation/l31057_en.htm) (2006/112/CE ... 2010/45/UE)
-  - Compatible with data privacy rules (europe GDPR, ...)
+  - Compatible with data privacy rules (Europe GDPR, ...)
   - ...
 - Flexible PDF & ODT generation for invoices, proposals, orders...
 - ...
@@ -204,7 +207,7 @@ See exact requirements on the [Wiki](https://wiki.dolibarr.org/index.php/Prerequ
 
 ### Extending
 
-Dolibarr can be extended with a lot of other external application or modules from third party developers available at the [DoliStore](https://www.dolistore.com).
+Dolibarr can be extended with a lot of other external applications or modules from third-party developers available at the [DoliStore](https://www.dolistore.com).
 
 ## WHAT DOLIBARR CAN'T DO YET
 
@@ -212,19 +215,19 @@ These are features that Dolibarr does **not** yet fully support:
 
 - Tasks dependencies in projects
 - Payroll module
-- No native embedded Webmail, but you can send email to contacts in Dolibarr with e.g. offers, invoices, etc.
+- No native embedded Webmail, but you can send emails to contacts in Dolibarr with e.g. offers, invoices, etc.
 - Dolibarr can't do coffee (yet)
 
 ## DOCUMENTATION
 
-Administrator, user, developer and translator's documentations are available along with other community resources in the [Wiki](https://wiki.dolibarr.org).
+Administrator, user, developer and translator's documentation are available along with other community resources in the [Wiki](https://wiki.dolibarr.org).
 
 ## CONTRIBUTING
 
 This project exists thanks to all the people who contribute.
-Please read the instructions how to contribute (report a bug/error, a feature request, send code ...)  [[Contribute](https://github.com/Dolibarr/dolibarr/blob/develop/.github/CONTRIBUTING.md)]
+Please read the instructions on how to contribute (report a bug/error, a feature request, send code ...)  [[Contribute](https://github.com/Dolibarr/dolibarr/blob/develop/.github/CONTRIBUTING.md)]
 
-A view on Contributors:
+A View on Contributors:
 
 [![Dolibarr](https://opencollective.com/dolibarr/contributors.svg?width=890&button=false)](https://github.com/Dolibarr/dolibarr/graphs/contributors)
 
@@ -239,7 +242,7 @@ See [COPYRIGHT](https://github.com/Dolibarr/dolibarr/blob/develop/COPYRIGHT) fil
 Follow Dolibarr project on:
 
 - [Facebook](https://www.facebook.com/dolibarr)
-- [Twitter](https://www.twitter.com/dolibarr)
+- [X](https://x.com/dolibarr)
 - [LinkedIn](https://www.linkedin.com/company/association-dolibarr)
 - [Reddit](https://www.reddit.com/r/Dolibarr_ERP_CRM/)
 - [YouTube](https://www.youtube.com/user/DolibarrERPCRM)

+ 27 - 26
SECURITY.md

@@ -20,9 +20,9 @@ We believe that the future of software is online SaaS. This means software are m
 
 If you believe you've found a security bug in our service, we are happy to work with you to resolve the issue promptly and ensure you are fairly rewarded for your discovery.
 
-Any type of denial of service attacks is strictly forbidden, as well as any interference with network equipment and Dolibarr infrastructure.
+Any type of denial-of-service attack is strictly forbidden, as well as any interference with network equipment and Dolibarr infrastructure.
 
-We recommand to install Dolibarr ERP CRM on your own server (as most Open Source software, download and use is free: [https://www.dolibarr.org/download](https://www.dolibarr.org/download)) to get access on every side of application.
+We recommend to install Dolibarr ERP CRM on your own server (as most Open Source software, download and use is free: [https://www.dolibarr.org/download](https://www.dolibarr.org/download)) to get access on every side of application.
 
 ### User Agent
 
@@ -30,7 +30,7 @@ If you try to find bug on Dolibarr, we recommend to append to your user-agent he
 
 ### Account access
 
-You can install the web application yourself on your own platform/server so you get full access to application and sources. Download the zip of the files to put into your own web server virtual host from [https://www.dolibarr.org/download](https://www.dolibarr.org/download)
+You can install the web application yourself on your own platform/server so you get full access to application and sources. Download the zip of the files to put in your own web server virtual host from [https://www.dolibarr.org/download](https://www.dolibarr.org/download)
 
 ## Eligibility and Responsible Disclosure
 
@@ -38,7 +38,7 @@ We are happy to thank everyone who submits valid reports which help us improve t
 
 You must be the first reporter of the vulnerability (duplicate reports are closed).
 
-You must avoid tests that could cause degradation or interruption of our service (refrain from using automated tools, and limit yourself about requests per second), that's why we recommand to install software on your own platform.
+You must avoid tests that could cause degradation or interruption of our service (refrain from using automated tools, and limit yourself about requests per second), that's why we recommend to install software on your own platform.
 
 You must not leak, manipulate, or destroy any user data of third parties to find your vulnerability.
 
@@ -48,27 +48,31 @@ Reports are processed around once a month.
 
 ONLY vulnerabilities discovered, when the following setup on test platform is used, are "valid":
 
-* The version to analyze must be the last version available into "develop" branch or into last stable "vX.Y" released version.  
-* $dolibarr_main_prod must be set to 1 into conf.php
-* $dolibarr_nocsrfcheck must be kept to the value 0 into conf.php (this is the default value)
+* The version to analyze must be the last version available in the "develop" branch. Reports on vulnerabilities already fixed (so already reported) in the develop branch will not be validated.   
+* $dolibarr_main_prod must be set to 1 in conf.php
+* $dolibarr_nocsrfcheck must be kept to the value 0 in conf.php (this is the default value)
 * $dolibarr_main_force_https must be set to something else than 0.
-* The constant MAIN_SECURITY_CSRF_WITH_TOKEN must be set to 3 into backoffice menu Home - Setup - Other (this protection should be set to 3 soon by default). CSRF attacks are accepted but
- double check that you have set MAIN_SECURITY_CSRF_WITH_TOKEN to value 3.
-* ONLY security reports on modules provided by default and with the "stable" status are valid (troubles into "experimental", "developement" or external modules are not valid vulnerabilities).
+* Some constant must be set in the backoffice menu Home - Setup - Other
+  - MAIN_SECURITY_CSRF_WITH_TOKEN must be set to 3 
+  - MAIN_RESTRICTHTML_ONLY_VALID_HTML = 1
+  - MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY = 1
+  - MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES = 1 
+  CSRF attacks and HTML injections are accepted but double check this setup that is experimental setup that already fix a lot of case and soon enabled by default.
+* ONLY security reports on modules provided by default and with the "stable" status are valid (troubles in "experimental", "development" or external modules are not valid vulnerabilities).
 * The root of web server must link to htdocs and the documents directory must be outside of the web server root (this is the default when using the default installer but may differs with external installer).
 * The web server setup must be done so that only the documents directory is in write mode. The root directory called htdocs must be read-only.
 * The modules DebugBar and ModuleBuilder must NOT be enabled. (by default, these modules are not enabled. They are developer tools)
-* Ability for a high level user to edit web site pages into the CMS by including HTML or Javascript is an expected feature. Vulnerabilities into the website module are validated only if HTML or Javascript injection can be done by a non allowed user.
-* Fail2ban rules for rate limit on the login page,password forgotten page, api calls and all public pages (/public/*) must be installed as recommendend into the section "About - Admin tools - Section Access limits and mitigation".
+* Ability for a high-level user to edit web site pages in the CMS by including HTML or JavaScript is an expected feature. Vulnerabilities in the website module are validated only if HTML or JavaScript injection can be done by a non-allowed user.
+* Fail2ban rules for rate limit on the login page, forgotten password page, API calls and all public pages (/public/*) must be installed as recommended in the section "About - Admin tools - Section Access limits and mitigation".
 
-Scope is the web application (back office) and the APIs.
+Scope is the web application (backoffice) and the APIs.
 
 ## Examples of vulnerabilities that are Qualified for reporting.
 
 * Remote code execution (RCE)
 * Local files access and manipulation (LFI, RFI, XXE, SSRF, XSPA)
 * Code injections (JS, SQL, PHP). HTML are covered only for fields that are not description, notes or comments fields (where rich content is allowed on purpose).
-* Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose) and except into module "Web site" when permission to edit website content is allowed (injecting any data in this case is allowed too).
+* Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose) and except in the module "Web site" when permission to edit website content is allowed (injecting any data in this case is allowed too).
 * Cross-Site Requests Forgery (CSRF) with real security impact (when using GET URLs, CSRF are qualified only for creating, updating or deleting data from pages restricted to admin users)
 * Open redirect
 * Broken authentication & session management
@@ -76,24 +80,21 @@ Scope is the web application (back office) and the APIs.
 * Cross-Origin Resource Sharing (CORS) with real security impact
 * Horizontal and vertical privilege escalation
 * "HTTP Host Header" XSS
-* Software version disclosure (for non admin users only)
-* Stack traces or path disclosure (for non admin users only)
+* Software version disclosure (for non-admin users only)
+* Stack traces or path disclosure (for non-admin users only)
 
 ## Examples of vulnerabilities that are Non-qualified for reporting.
 
 * "Self" XSS
-* SSL/TLS best practices
-* Denial of Service attacks
 * Clickjacking/UI redressing
-* Physical or social engineering attempts or issues that require physical access to a victim’s computer/device
 * Presence of autocomplete attribute on web forms
-* Vulnerabilities affecting outdated browsers or platforms, or vulnerabilities inside browsers themself.
 * Logout and other instances of low-severity Cross-Site Request Forgery
-* Missing security-related HTTP headers which do not lead directly to a vulnerability
 * Reports from automated web vulnerability scanners (Acunetix, Vega, etc.) that have not been validated
+* Reports on features on modules flagged as "deprecated", "experimental" or "development" if the module needs to be enabled for that (this is not the case on production).
+* Software or libraries versions, private IP disclosure, Stack traces or path disclosure when logged-in user is admin.
+* Any vulnerabilities due to a configuration different than the one defined in chapter "Scope for qualified vulnerabilities".
+* Vulnerabilities affecting outdated browsers or platforms, or vulnerabilities inside browsers themself.
+* Brute force attacks on login page, password forgotten page or any public pages (/public/*) are not qualified if the recommended fail2ban rules were not installed.  
+* SSL/TLS best practices
 * Invalid or missing SPF (Sender Policy Framework) records (Incomplete or missing SPF/DKIM/DMARC)
-* Reports on features flagged as "experimental" or "development"
-* Software version or private IP disclosure when logged-in user is admin
-* Stack traces or path disclosure when logged-in user is admin
-* Any vulnerabilities due to a configuration different than the one defined into chapter "Scope for qualified vulnerabilities".
-* Brute force attacks on login page, password forgotten page or any public pages (/public/*) are not qualified if the fail2ban recommended fail2ban rules were not installed.  
+* Physical or social engineering attempts or issues that require physical access to a victim’s computer/device

+ 16 - 15
build/dmg/dolimamp/install.forced.php

@@ -1,16 +1,17 @@
 <?php
-$force_install_message='KeepDefaultValuesMamp';
-$force_install_main_data_root='/Applications/MAMP/dolibarr_documents';
-$force_install_type='mysqli';
-$force_install_dbserver='localhost';
-$force_install_port='8889';
-$force_install_database='dolibarr';
-$force_install_createdatabase='1';
-$force_install_databaselogin='dolibarrmysql';
-$force_install_databasepass='root';
-$force_install_createuser='1';
-$force_install_databaserootlogin='root';
-$force_install_databaserootpass='root';
-$force_install_dolibarrlogin='admin';
-$force_install_nophpinfo='1';
-$force_install_lockinstall='444';
+
+$force_install_message = 'KeepDefaultValuesMamp';
+$force_install_main_data_root = '/Applications/MAMP/dolibarr_documents';
+$force_install_type = 'mysqli';
+$force_install_dbserver = 'localhost';
+$force_install_port = '8889';
+$force_install_database = 'dolibarr';
+$force_install_createdatabase = '1';
+$force_install_databaselogin = 'dolibarrmysql';
+$force_install_databasepass = 'root';
+$force_install_createuser = '1';
+$force_install_databaserootlogin = 'root';
+$force_install_databaserootpass = 'root';
+$force_install_dolibarrlogin = 'admin';
+$force_install_nophpinfo = '1';
+$force_install_lockinstall = '444';

+ 1 - 1
build/docker/README.md

@@ -1,7 +1,7 @@
 # How to use it ?
 
 The docker-compose.yml file is a sample of a config file to use to build and run Dolibarr in the current workspace with Docker.
-This docker image is intended for developpement usage.
+This docker image is intended for development usage.
 For production usage you should consider other contributor reference like https://hub.docker.com/r/tuxgasy/dolibarr 
 
 Before build/run, define the variable HOST_USER_ID as following:

+ 1 - 1
build/exe/doliwamp/Makefile

@@ -1,5 +1,5 @@
 #-------------------------------------------------------------------#
-# Makefile
+# Makefile to build UsedPort exe
 #-------------------------------------------------------------------#
 # 1.0     Laurent Destailleur     Creation 
 #-------------------------------------------------------------------#

+ 16 - 1
build/exe/doliwamp/UsedPort.cpp

@@ -253,6 +253,7 @@ int main(int argc, char **argv)
 //----------------
 int noarg,curseurarg,help=0,invalide=0;
 char option;
+char *endptr;
 
 for (noarg=1;noarg<argc;noarg++) {
 	if (((argv[noarg][0])=='/') || ((argv[noarg][0])=='-')) {
@@ -261,7 +262,7 @@ for (noarg=1;noarg<argc;noarg++) {
 		if (strlen(argv[noarg]) < 3) { ++noarg; curseurarg=0; }
 		switch (option) {
 			case 's': strncpy(Host,argv[noarg]+curseurarg,sizeof(Host)); break;
-			case 'p': Port=atoi(argv[noarg]+curseurarg); break;
+			case 'p': Port=strtol(argv[noarg] + curseurarg, &endptr, 10); break;					// Get port from "-p80" (curseurarg = 2) or "-p 80" (curseurarg = 0)
 			case '?': help=-1;break;											// Help
 			case 'h': help=-1;break;											// Help
 			case 'v': help=-1;break;											// Help
@@ -270,6 +271,20 @@ for (noarg=1;noarg<argc;noarg++) {
 	}
 }
 
+// Check for conversion errors
+if (*endptr != '\0') {
+    // Handle error: Invalid input format
+    printf("Invalid port number format\n");
+    exit(-1);
+}
+
+// Check for overflow
+if (Port < 0 || Port > INT_MAX) {
+    // Handle error: Port number out of range
+    printf("Port number out of range\n");
+    exit(-1);
+}
+
 help=!(Port > 0);
 
 // Show usage

+ 1 - 1
build/exe/doliwamp/php.ini.install

@@ -251,7 +251,7 @@ safe_mode_protected_env_vars = LD_LIBRARY_PATH
 ; and below.  This directive makes most sense if used in a per-directory
 ; or per-virtualhost web server configuration file. This directive is
 ; *NOT* affected by whether Safe Mode is turned On or Off.
-open_basedir = "WAMPROOT"
+open_basedir = "WAMPROOT;C:\WINDOWS\TEMP"
 
 ; This directive allows you to disable certain functions for security reasons.
 ; It receives a comma-delimited list of function names. This directive is

+ 37 - 37
build/generate_filelist_xml.php

@@ -22,13 +22,13 @@
  * 		\brief      This script create a xml checksum file
  */
 
-if (! defined('NOREQUIREDB')) {
+if (!defined('NOREQUIREDB')) {
 	define('NOREQUIREDB', '1');	// Do not create database handler $db
 }
 
 $sapi_type = php_sapi_name();
 $script_file = basename(__FILE__);
-$path=dirname(__FILE__).'/';
+$path = dirname(__FILE__).'/';
 
 // Test if batch mode
 if (substr($sapi_type, 0, 3) == 'cgi') {
@@ -44,9 +44,9 @@ require_once DOL_DOCUMENT_ROOT."/core/lib/files.lib.php";
  * Main
  */
 
-$includecustom=0;
-$includeconstants=array();
-$buildzip=0;
+$includecustom = 0;
+$includeconstants = array();
+$buildzip = 0;
 
 if (empty($argv[1])) {
 	print "Usage:   ".$script_file." release=autostable|auto[-mybuild]|x.y.z[-mybuild] [includecustom=1] [includeconstant=CC:MY_CONF_NAME:value] [buildzip=1]\n";
@@ -55,7 +55,7 @@ if (empty($argv[1])) {
 }
 
 
-$i=0;
+$i = 0;
 $result = array();
 while ($i < $argc) {
 	if (!empty($argv[$i])) {
@@ -71,10 +71,10 @@ while ($i < $argc) {
 		$includeconstants[$i] = $result["includeconstant"];
 	}
 	if (!empty($result["buildzip"])) {
-		$buildzip=1;
+		$buildzip = 1;
 	}
 	if (preg_match('/includeconstant=/', strval($argv[$i]))) {
-		$tmp=explode(':', $result['includeconstant'], 3);			// $includeconstant has been set with previous parse_str()
+		$tmp = explode(':', $result['includeconstant'], 3);			// $includeconstant has been set with previous parse_str()
 		if (count($tmp) != 3) {
 			print "Error: Bad parameter includeconstant=".$result['includeconstant'] ."\n";
 			exit -1;
@@ -93,16 +93,16 @@ if (empty($release)) {
 $savrelease = $release;
 
 // If release is auto, we take current version
-$tmpver=explode('-', $release, 2);
+$tmpver = explode('-', $release, 2);
 if ($tmpver[0] == 'auto' || $tmpver[0] == 'autostable') {
-	$release=DOL_VERSION;
+	$release = DOL_VERSION;
 	if ($tmpver[1] && $tmpver[0] == 'auto') {
-		$release.='-'.$tmpver[1];
+		$release .= '-'.$tmpver[1];
 	}
 }
 
 if (empty($includecustom)) {
-	$tmpverbis=explode('-', $release, 2);
+	$tmpverbis = explode('-', $release, 2);
 	if (empty($tmpverbis[1]) || $tmpver[0] == 'autostable') {
 		if (DOL_VERSION != $tmpverbis[0] && $savrelease != 'auto') {
 			print 'Error: When parameter "includecustom" is not set and there is no suffix in release parameter, version declared into filefunc.in.php ('.DOL_VERSION.') must be exact same value than "release" parameter ('.$tmpverbis[0].')'."\n";
@@ -110,7 +110,7 @@ if (empty($includecustom)) {
 			exit -1;
 		}
 	} else {
-		$tmpverter=explode('-', DOL_VERSION, 2);
+		$tmpverter = explode('-', DOL_VERSION, 2);
 		if ($tmpverter[0] != $tmpverbis[0]) {
 			print 'Error: When parameter "includecustom" is not set, version declared into filefunc.in.php ('.DOL_VERSION.') must have value without prefix ('.$tmpverter[0].') that is exact same value than "release" parameter ('.$tmpverbis[0].')'."\n";
 			print "Usage:   ".$script_file." release=autostable|auto[-mybuild]|x.y.z[-mybuild] [includecustom=1] [includeconstant=CC:MY_CONF_NAME:value]\n";
@@ -118,7 +118,7 @@ if (empty($includecustom)) {
 		}
 	}
 } else {
-	if (! preg_match('/'.preg_quote(DOL_VERSION, '/').'-/', $release)) {
+	if (!preg_match('/'.preg_quote(DOL_VERSION, '/').'-/', $release)) {
 		print 'Error: When parameter "includecustom" is set, version declared into filefunc.inc.php ('.DOL_VERSION.') must be used with a suffix into "release" parameter (ex: '.DOL_VERSION.'-mydistrib).'."\n";
 		print "Usage:   ".$script_file." release=autostable|auto[-mybuild]|x.y.z[-mybuild] [includecustom=1] [includeconstant=CC:MY_CONF_NAME:value]\n";
 		exit -1;
@@ -137,13 +137,13 @@ foreach ($includeconstants as $countrycode => $tmp) {
 print "\n";
 
 //$outputfile=dirname(__FILE__).'/../htdocs/install/filelist-'.$release.'.xml';
-$outputdir=dirname(dirname(__FILE__)).'/htdocs/install';
+$outputdir = dirname(dirname(__FILE__)).'/htdocs/install';
 print 'Delete current files '.$outputdir.'/filelist*.xml*'."\n";
 dol_delete_file($outputdir.'/filelist*.xml*', 0, 1, 1);
 
-$checksumconcat=array();
+$checksumconcat = array();
 
-$outputfile=$outputdir.'/filelist-'.$release.'.xml';
+$outputfile = $outputdir.'/filelist-'.$release.'.xml';
 $fp = fopen($outputfile, 'w');
 if (empty($fp)) {
 	print 'Failed to open file '.$outputfile."\n";
@@ -156,8 +156,8 @@ fputs($fp, '<checksum_list version="'.$release.'" date="'.dol_print_date(dol_now
 foreach ($includeconstants as $countrycode => $tmp) {
 	fputs($fp, '<dolibarr_constants country="'.$countrycode.'">'."\n");
 	foreach ($tmp as $constname => $constvalue) {
-		$valueforchecksum=(empty($constvalue)?'0':$constvalue);
-		$checksumconcat[]=$valueforchecksum;
+		$valueforchecksum = (empty($constvalue) ? '0' : $constvalue);
+		$checksumconcat[] = $valueforchecksum;
 		fputs($fp, '    <constant name="'.$constname.'">'.$valueforchecksum.'</constant>'."\n");
 	}
 	fputs($fp, '</dolibarr_constants>'."\n");
@@ -172,26 +172,26 @@ $files = new RegexIterator($iterator1, '#^(?:[A-Z]:)?(?:/(?!(?:'.($includecustom
 */
 // Define qualified files (must be same than into generate_filelist_xml.php and in api_setup.class.php)
 $regextoinclude = '\.(php|php3|php4|php5|phtml|phps|phar|inc|css|scss|html|xml|js|json|tpl|jpg|jpeg|png|gif|ico|sql|lang|txt|yml|bak|md|mp3|mp4|wav|mkv|z|gz|zip|rar|tar|less|svg|eot|woff|woff2|ttf|manifest)$';
-$regextoexclude = '('.($includecustom?'':'custom|').'documents|conf|install|dejavu-fonts-ttf-.*|public\/test|sabre\/sabre\/.*\/tests|Shared\/PCLZip|nusoap\/lib\/Mail|php\/example|php\/test|geoip\/sample.*\.php|ckeditor\/samples|ckeditor\/adapters)$';  // Exclude dirs
+$regextoexclude = '('.($includecustom ? '' : 'custom|').'documents|conf|install|dejavu-fonts-ttf-.*|public\/test|sabre\/sabre\/.*\/tests|Shared\/PCLZip|nusoap\/lib\/Mail|php\/example|php\/test|geoip\/sample.*\.php|ckeditor\/samples|ckeditor\/adapters)$';  // Exclude dirs
 $files = dol_dir_list(DOL_DOCUMENT_ROOT, 'files', 1, $regextoinclude, $regextoexclude, 'fullname');
 
-$dir='';
-$needtoclose=0;
+$dir = '';
+$needtoclose = 0;
 foreach ($files as $filetmp) {
 	$file = $filetmp['fullname'];
 	//$newdir = str_replace(dirname(__FILE__).'/../htdocs', '', dirname($file));
 	$newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file));
-	if ($newdir!=$dir) {
+	if ($newdir != $dir) {
 		if ($needtoclose) {
 			fputs($fp, '  </dir>'."\n");
 		}
 		fputs($fp, '  <dir name="'.$newdir.'">'."\n");
 		$dir = $newdir;
-		$needtoclose=1;
+		$needtoclose = 1;
 	}
-	if (filetype($file)=="file") {
-		$md5=md5_file($file);
-		$checksumconcat[]=$md5;
+	if (filetype($file) == "file") {
+		$md5 = md5_file($file);
+		$checksumconcat[] = $md5;
 		fputs($fp, '    <md5file name="'.basename($file).'" size="'.filesize($file).'">'.$md5.'</md5file>'."\n");
 	}
 }
@@ -205,7 +205,7 @@ fputs($fp, md5(join(',', $checksumconcat))."\n");
 fputs($fp, '</dolibarr_htdocs_dir_checksum>'."\n");
 
 
-$checksumconcat=array();
+$checksumconcat = array();
 
 fputs($fp, '<dolibarr_script_dir version="'.$release.'">'."\n");
 
@@ -215,27 +215,27 @@ $iterator2 = new RecursiveIteratorIterator($dir_iterator2);
 // Need to ignore document custom etc. Note: this also ignore natively symbolic links.
 $files = new RegexIterator($iterator2, '#^(?:[A-Z]:)?(?:/(?!(?:custom|documents|conf|install))[^/]+)+/[^/]+\.(?:php|css|html|js|json|tpl|jpg|png|gif|sql|lang)$#i');
 */
-$regextoinclude='\.(php|css|html|js|json|tpl|jpg|png|gif|sql|lang)$';
-$regextoexclude='(custom|documents|conf|install)$';  // Exclude dirs
+$regextoinclude = '\.(php|css|html|js|json|tpl|jpg|png|gif|sql|lang)$';
+$regextoexclude = '(custom|documents|conf|install)$';  // Exclude dirs
 $files = dol_dir_list(dirname(__FILE__).'/../scripts/', 'files', 1, $regextoinclude, $regextoexclude, 'fullname');
-$dir='';
-$needtoclose=0;
+$dir = '';
+$needtoclose = 0;
 foreach ($files as $filetmp) {
 	$file = $filetmp['fullname'];
 	//$newdir = str_replace(dirname(__FILE__).'/../scripts', '', dirname($file));
 	$newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file));
 	$newdir = str_replace(dirname(__FILE__).'/../scripts', '', dirname($file));
-	if ($newdir!=$dir) {
+	if ($newdir != $dir) {
 		if ($needtoclose) {
 			fputs($fp, '  </dir>'."\n");
 		}
 		fputs($fp, '  <dir name="'.$newdir.'" >'."\n");
 		$dir = $newdir;
-		$needtoclose=1;
+		$needtoclose = 1;
 	}
-	if (filetype($file)=="file") {
-		$md5=md5_file($file);
-		$checksumconcat[]=$md5;
+	if (filetype($file) == "file") {
+		$md5 = md5_file($file);
+		$checksumconcat[] = $md5;
 		fputs($fp, '    <md5file name="'.basename($file).'" size="'.filesize($file).'">'.$md5.'</md5file>'."\n");
 	}
 }

+ 14 - 5
build/makepack-dolibarr.pl

@@ -2,7 +2,7 @@
 #----------------------------------------------------------------------------
 # \file         build/makepack-dolibarr.pl
 # \brief        Dolibarr package builder (tgz, zip, rpm, deb, exe, aps)
-# \author       (c)2004-2020 Laurent Destailleur  <eldy@users.sourceforge.net>
+# \author       (c)2004-2023 Laurent Destailleur  <eldy@users.sourceforge.net>
 #
 # This is list of constant you can set to have generated packages moved into a specific dir: 
 #DESTIBETARC='/media/HDDATA1_LD/Mes Sites/Web/Dolibarr/dolibarr.org/files/lastbuild'
@@ -369,12 +369,12 @@ if ($nboftargetok) {
 		}
 		if (! $BUILD || $BUILD eq '0-rc')	# For a major version
 		{
-			print 'cd ~/git/dolibarr_'.$MAJOR.'.'.$MINOR.'; git log `git rev-list --boundary '.$MAJOR.'.'.$MINOR.'..origin/develop | grep ^- | cut -c2- | head -n 1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa';
+			print 'cd ~/git/dolibarr_'.$MAJOR.'.'.$MINOR.'; git log `git rev-list --boundary '.$MAJOR.'.'.$MINOR.'..origin/develop | grep ^- | cut -c2- | head -n 1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\|PERF\|SEC\|QUAL\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa';
 		}
 		else			# For a maintenance release
 		{
-			#print 'cd ~/git/dolibarr_'.$MAJOR.'.'.$MINOR.'; git log '.$MAJOR.'.'.$MINOR.'.'.($BUILD-1).'.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\'| sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa';
-			print 'cd ~/git/dolibarr_'.$MAJOR.'.'.$MINOR.'; git log '.$MAJOR.'.'.$MINOR.'.'.($BUILD-1).'.. | grep -v "Merge branch" | grep -v "Merge pull" | grep "^ " | sed -e "s/^[0-9a-z]* *//" | grep -e \'^FIX\|NEW\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa';
+			#print 'cd ~/git/dolibarr_'.$MAJOR.'.'.$MINOR.'; git log '.$MAJOR.'.'.$MINOR.'.'.($BUILD-1).'.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\|PERF\|SEC\|QUAL\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\'| sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa';
+			print 'cd ~/git/dolibarr_'.$MAJOR.'.'.$MINOR.'; git log '.$MAJOR.'.'.$MINOR.'.'.($BUILD-1).'.. | grep -v "Merge branch" | grep -v "Merge pull" | grep "^ " | sed -e "s/^[0-9a-z]* *//" | grep -e \'^FIX\|NEW\|PERF\|SEC\|QUAL\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa';
 		}
 		print "\n";
 		if (! $ret)
@@ -538,6 +538,8 @@ if ($nboftargetok) {
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/security`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/spec`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/test`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/tools/php-cs-fixer/vendor`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/tools/rector/vendor`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/uml`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/vagrant`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/xdebug`;
@@ -581,6 +583,7 @@ if ($nboftargetok) {
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/factory*`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/forceproject*`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/lead*`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/langs/*/README.md`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/management*`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/multicompany*`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/ndf*`;
@@ -627,15 +630,21 @@ if ($nboftargetok) {
         $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/tests`;
         $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/stripe/tests`;
         $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/stripe/LICENSE`;
-        $ret=`rm -f  $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/examples`;
         $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/dejavu-fonts-ttf-*`;
         $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/freefont-*`;
         $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/ae_fonts_*`;
         $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/utils`;
+        $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/examples`;
         $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/tools`;
         $ret=`rm -f  $BUILDROOT/$PROJECT/htdocs/includes/vendor`;
         $ret=`rm -f  $BUILDROOT/$PROJECT/htdocs/includes/webmozart`;
         $ret=`rm -f  $BUILDROOT/$PROJECT/htdocs/includes/autoload.php`;
+        
+		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/bin`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/bin`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/*/bin`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/*/*/bin`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/*/*/*/bin`;
 	}
 
 	# Build package for each target

+ 2 - 0
build/makepack-howto.txt

@@ -43,6 +43,7 @@ Prerequisites to build autoexe DoliWamp package from Windows:
 
 
 ***** Actions to do a BETA *****
+
 This files describe steps made by Dolibarr packaging team to make a 
 beta version of Dolibarr, step by step.
 
@@ -67,6 +68,7 @@ Recopy the content of the output file into the file ChangeLog.
 
 
 ***** Actions to do a RELEASE *****
+
 This files describe steps made by Dolibarr packaging team to make a 
 complete release of Dolibarr, step by step.
 

+ 4 - 6
build/patch/README

@@ -3,9 +3,7 @@ README (English)
 Building a Patch file
 ##################################################
 
-This directory contains tools to build a patch 
-after a developer has made changes on files in its
-Dolibarr tree. 
-The output patch file can then be submited on Dolibarr
-dev mailing-list, with explanation on its goal, for
-inclusion in main branch.
+This directory contains tools to build a patch after a developer has made changes on files in its Dolibarr tree. 
+The output patch file can then be submited on Dolibarr dev mailing-list, with explanation on its goal, for inclusion in main branch.
+
+Using this tool is now deprecated. You must use git pull requests to submit contributions.

+ 18 - 5
build/phpstan/README

@@ -1,9 +1,22 @@
-PHPStan 
-
-https://phpstan.org 
+PHPStan (https://phpstan.org) 
+-----------------------------
 
 PHPStan requires PHP >= 7.1
 
-Install:  composer require --dev phpstan/phpstan
+Config File is: ./phpstan.neon
+
+In dolibarr/build/phpstan
+mkdir phpstan
+cd phpstan
+composer require --dev phpstan/phpstan
+
+Build report from CLI:
+In dolibarr/
+mv htdocs/custom /tmp/
+php build/phpstan/phpstan/vendor/bin/phpstan -v analyze -c ./phpstan.neon -a build/phpstan/bootstrap.php --memory-limit 4G --error-format=table htdocs/commande/class
+php build/phpstan/phpstan/vendor/bin/phpstan -v analyze -c ./phpstan.neon -a build/phpstan/bootstrap.php --memory-limit 4G --error-format=table htdocs/
+mv /tmp/custom htdocs
 
-Config File:  phpstan.neon
+Build HTML report from Cron:
+Example of line to add into a cron to generate a HTML report:
+0 1 5 * * cd /home/dolibarr/preview.dolibarr.org/dolibarr; chmod -R u+w /home/dolibarr/preview.dolibarr.org/dolibarr; git pull; /home/dolibarr/phpstan/vendor/bin/phpstan -v analyze -a build/phpstan/bootstrap.php --memory-limit 4G --error-format=github | awk ' BEGIN{ print "Date "strftime("%Y-%m-%d")"<br>" } { print $0"<br>" } END{ print NR } ' > /home/dolibarr/doxygen.dolibarr.org/phpstan/index.html

+ 7 - 2
build/phpstan/bootstrap.php

@@ -1,11 +1,16 @@
 <?php
+
 // Defined some constants and load Dolibarr env to reduce PHPStan bootstrap that fails to load a lot of things.
 //define('DOL_DOCUMENT_ROOT', __DIR__ . '/../../htdocs');
 //define('DOL_DATA_ROOT', __DIR__ . '/../../documents');
 //define('DOL_URL_ROOT', '/');
 
 // Load the main.inc.php file to have functions env defined
-if (! defined("NOLOGIN")) define("NOLOGIN", '1');
-if (! defined("NOHTTPSREDIRECT")) define("NOHTTPSREDIRECT", '1');
+if (!defined("NOLOGIN")) {
+	define("NOLOGIN", '1');
+}
+if (!defined("NOHTTPSREDIRECT")) {
+	define("NOHTTPSREDIRECT", '1');
+}
 global $conf, $langs, $user, $db;
 include_once __DIR__ . '/../../htdocs/main.inc.php';

+ 1 - 0
build/rpm/conf.php

@@ -1,4 +1,5 @@
 <?php
+
 //
 // Take a look at conf.php.example file for an example of conf.php file
 // and explanations for all possibles parameters.

+ 1 - 0
dev/.gitignore

@@ -1 +1,2 @@
 /spec
+/tools/rector/vendor/

+ 70 - 35
dev/dolibarr_changes.txt

@@ -18,7 +18,7 @@ With
 
 
 
-CKEDITOR (4.6.2):
+CKEDITOR (4.22.1):
 -----------------
 * In ckeditor/ckeditor/contents.css
 Replace:
@@ -26,7 +26,12 @@ Replace:
 With
 	body { ... margin: 5px;
 
-
+* In ckeditor/ckeditor/ckeditor.js
+Replace:
+    d.items&&
+With
+    d&&d.items&&
+    
 
 ESCPOS:
 -------
@@ -36,40 +41,59 @@ With
 	protected $connector;
 
 
+SABRE:
+------
+rm -fr ./htdocs/includes/sabre/sabre/bin;
+rm -fr ./htdocs/includes/sabre/sabre/*/bin;
+rm -fr ./htdocs/includes/sabre/sabre/*/*/bin;
+rm -fr ./htdocs/includes/sabre/sabre/*/*/*/bin;
+rm -fr ./htdocs/includes/sabre/sabre/*/*/*/*/bin;
+
 
 NUSOAP:
 -------
-* In file nusoap.php, to avoid a warning,
-Replace
-	if (isset($this->methodreturn) && ((get_class($this->methodreturn) == 'soap_fault') || (get_class($this->methodreturn) == 'nusoap_fault'))) {
-By
-	if (! is_array($this->methodreturn) && isset($this->methodreturn) && ((get_class($this->methodreturn) == 'soap_fault') || (get_class($this->methodreturn) == 'nusoap_fault'))) {
-
-* In file nusoap.php, to avoid a warning,
-Replace call to serialize_val with no bugged value
+* Line 1257 of file nusoap.php. Add:
 
-* In all files, replace constructor names into __construct. Replace also parent::constructor_name with parent::__construct
+	libxml_disable_entity_loader(true);	// Avoid load of external entities (security problem). Required only for libxml < 2.
+	
+	
+* Line 4346 of file nusoap.php
 
-* Line 4222 of file nusoap.php
+	$rev = array();
+	preg_match('/\$Revision: ([^ ]+)/', $this->revision, $rev);
+	$this->outgoing_headers[] = "X-SOAP-Server: $this->title/$this->version (".(isset($rev[1]) ? $rev[1] : '').")";
 
-		$rev = array();
-		preg_match('/\$Revision: ([^ ]+)/', $this->revision, $rev);
-		$this->outgoing_headers[] = "X-SOAP-Server: $this->title/$this->version (".(isset($rev[1]) ? $rev[1] : '').")";
+* Line 6566 of file nusoap.php, replace
 
+	if (count($attrs) > 0) {
+	with
+	if (is_array($attrs) && count($attrs) > 0) {
 
 
 
 TCPDF:
 ------
-* Replace in tcpdf.php:
+* Modify in tcpdf.php: make TCPDF::_out public.
+  (htdocs/core/lib/pdf.lib.php uses it as a public method)
+
+  Change:
+      protected function _out($s)
+  to
+      public function _out($s)
+
+  Change in method's _out phpdoc:
 
+      * @protected
+  to
+      * @public
+
+* Replace in tcpdf.php:
             if (isset($this->imagekeys)) {
                 foreach($this->imagekeys as $file) {
                     unlink($file);
                 }
             }
 with
-
             if (isset($this->imagekeys)) {
                 foreach($this->imagekeys as $file) {
 				// DOL CHANGE If we keep this, source image files are physically destroyed
@@ -77,8 +101,7 @@ with
                 }
             }
 
-* Replace in tcpdf.php
-
+* Replace in tcpdf.php:
 		$preserve = array(
 			'file_id',
 			'internal_encoding',
@@ -86,9 +109,7 @@ with
 			'bufferlen',
 			'buffer',
 			'cached_files',
-
 with
-
 		$preserve = array(
 			'file_id',
 			'internal_encoding',
@@ -99,14 +120,11 @@ with
 			// @CHANGE DOL
 			'imagekeys',
 
-* Replace in tcpdf.php
-
+* Replace in tcpdf.php:
 			if (!@TCPDF_STATIC::file_exists($file)) {
 				return false;
 			}
-
 with
-
 			if (!@TCPDF_STATIC::file_exists($file)) {
 				// DOL CHANGE If we keep this, the image is not visible on pages after the first one.
 				//var_dump($file.' '.(!@TCPDF_STATIC::file_exists($file)));
@@ -116,13 +134,10 @@ with
 					$file = $tfile;
 				}
 			}
-
-* Replace in tcpdf.php
-
+			
+* Replace in tcpdf.php:
 		if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
-
 with
-
 		// @CHANGE LDR Add support for src="file://..." links
 		if (strpos($imgsrc, 'file://') === 0) {
 			$imgsrc = str_replace('file://', '/', $imgsrc);
@@ -138,12 +153,10 @@ with
 		}
 		elseif (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
 
-* In tecnickcom/tcpdf/include/tcpdf_static.php, in function fopenLocal, replace
 
+* In tecnickcom/tcpdf/include/tcpdf_static.php, in function fopenLocal, replace:
 		if (strpos($filename, '://') === false) {
-
 with
-
         if (strpos($filename, '//') === 0) {
             // Share folder on a (windows) server
             // e.g.: "//[MyServerName]/[MySharedFolder]/"
@@ -152,11 +165,16 @@ with
         }
         elseif (strpos($filename, '://') === false)
 
-* To avoid to have QRcode changed because generated with a random mask, replace
+* To avoid to have QRcode changed because generated with a random mask, replace:
 	define('QR_FIND_FROM_RANDOM', 2);
-	with:
+with
 	define('QR_FIND_FROM_RANDOM', false);
 
+* Change line:
+	imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
+into
+	imagesetpixel($imgalpha, $xpx, $ypx, (int) $alpha);
+	
 * Removed useless directories ("examples", "tools")
 
 * Optionnaly, removed all fonts except
@@ -184,6 +202,23 @@ In htdocs/includes/tecnickcom/tcpdf/tcpdf.php
 		return strval($number);
 	}
 
+* Add this at begin of tcpdf_autoconfig.php
+
+	// @CHANGE LDR DOCUMENT_ROOT fix for IIS Webserver
+	if ((!isset($_SERVER['DOCUMENT_ROOT'])) OR (empty($_SERVER['DOCUMENT_ROOT']))) {
+		if (isset($_SERVER['SCRIPT_FILENAME']) && isset($_SERVER['PHP_SELF'])) {
+			$_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0-strlen($_SERVER['PHP_SELF'])));
+		} elseif(isset($_SERVER['PATH_TRANSLATED'])) {
+			$_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0-strlen($_SERVER['PHP_SELF'])));
+		} else {
+			// define here your DOCUMENT_ROOT path if the previous fails (e.g. '/var/www')
+			$_SERVER['DOCUMENT_ROOT'] = '/';
+		}
+	}
+	$_SERVER['DOCUMENT_ROOT'] = str_replace('//', '/', $_SERVER['DOCUMENT_ROOT']);
+	if (substr($_SERVER['DOCUMENT_ROOT'], -1) != '/') {
+		$_SERVER['DOCUMENT_ROOT'] .= '/';
+	}
 
 
 TCPDI:

+ 17 - 17
dev/examples/code/create_invoice.php

@@ -25,7 +25,7 @@
 
 $sapi_type = php_sapi_name();
 $script_file = basename(__FILE__);
-$path=dirname(__FILE__).'/';
+$path = dirname(__FILE__).'/';
 
 // Test if batch mode
 if (substr($sapi_type, 0, 3) == 'cgi') {
@@ -34,13 +34,13 @@ if (substr($sapi_type, 0, 3) == 'cgi') {
 }
 
 // Global variables
-$version='1.7';
-$error=0;
+$version = '1.7';
+$error = 0;
 
 
 // -------------------- START OF YOUR CODE HERE --------------------
 // Include Dolibarr environment
-require_once $path."../../htdocs/master.inc.php";
+require_once $path."../../../htdocs/master.inc.php";
 // After this $db, $mysoc, $langs and $conf->entity are defined. Opened handler to database will be closed at end of file.
 
 //$langs->setDefaultLang('en_US'); 	// To change default language of $langs
@@ -48,8 +48,8 @@ $langs->load("main");				// To load language file for default language
 @set_time_limit(0);
 
 // Load user and its permissions
-$result=$user->fetch('', 'admin');	// Load user for login 'admin'. Comment line to run as anonymous user.
-if (! $result > 0) {
+$result = $user->fetch('', 'admin');	// Load user for login 'admin'. Comment line to run as anonymous user.
+if (!$result > 0) {
 	dol_print_error('', $user->error);
 	exit;
 }
@@ -74,20 +74,20 @@ $obj->note_public    = 'A public comment';
 $obj->note_private   = 'A private comment';
 $obj->cond_reglement_id = 1;
 
-$line1=new FactureLigne($db);
-$line1->tva_tx=10.0;
-$line1->remise_percent=0;
-$line1->qty=1;
-$line1->total_ht=100;
-$line1->total_tva=10;
-$line1->total_ttc=110;
-$obj->lines[]=$line1;
+$line1 = new FactureLigne($db);
+$line1->tva_tx = 10.0;
+$line1->remise_percent = 0;
+$line1->qty = 1;
+$line1->total_ht = 100;
+$line1->total_tva = 10;
+$line1->total_ttc = 110;
+$obj->lines[] = $line1;
 
 // Create invoice
-$idobject=$obj->create($user);
+$idobject = $obj->create($user);
 if ($idobject > 0) {
 	// Change status to validated
-	$result=$obj->validate($user);
+	$result = $obj->validate($user);
 	if ($result > 0) {
 		print "OK Object created with id ".$idobject."\n";
 	} else {
@@ -102,7 +102,7 @@ if ($idobject > 0) {
 
 // -------------------- END OF YOUR CODE --------------------
 
-if (! $error) {
+if (!$error) {
 	$db->commit();
 	print '--- end ok'."\n";
 } else {

+ 14 - 14
dev/examples/code/create_order.php

@@ -25,7 +25,7 @@
 
 $sapi_type = php_sapi_name();
 $script_file = basename(__FILE__);
-$path=dirname(__FILE__).'/';
+$path = dirname(__FILE__).'/';
 
 // Test if batch mode
 if (substr($sapi_type, 0, 3) == 'cgi') {
@@ -34,13 +34,13 @@ if (substr($sapi_type, 0, 3) == 'cgi') {
 }
 
 // Global variables
-$version='1.11';
-$error=0;
+$version = '1.11';
+$error = 0;
 
 
 // -------------------- START OF YOUR CODE HERE --------------------
 // Include Dolibarr environment
-require_once $path."../../htdocs/master.inc.php";
+require_once $path."../../../htdocs/master.inc.php";
 // After this $db, $mysoc, $langs and $conf->entity are defined. Opened handler to database will be closed at end of file.
 
 //$langs->setDefaultLang('en_US'); 	// To change default language of $langs
@@ -48,8 +48,8 @@ $langs->load("main");				// To load language file for default language
 @set_time_limit(0);
 
 // Load user and its permissions
-$result=$user->fetch('', 'admin');	// Load user for login 'admin'. Comment line to run as anonymous user.
-if (! $result > 0) {
+$result = $user->fetch('', 'admin');	// Load user for login 'admin'. Comment line to run as anonymous user.
+if (!$result > 0) {
 	dol_print_error('', $user->error);
 	exit;
 }
@@ -75,17 +75,17 @@ $com->note_private   = 'A private comment';
 $com->source         = 1;
 $com->remise_percent = 0;
 
-$orderline1=new OrderLine($db);
-$orderline1->tva_tx=10.0;
-$orderline1->remise_percent=0;
-$orderline1->qty=1;
-$com->lines[]=$orderline1;
+$orderline1 = new OrderLine($db);
+$orderline1->tva_tx = 10.0;
+$orderline1->remise_percent = 0;
+$orderline1->qty = 1;
+$com->lines[] = $orderline1;
 
 // Create order
-$idobject=$com->create($user);
+$idobject = $com->create($user);
 if ($idobject > 0) {
 	// Change status to validated
-	$result=$com->valid($user);
+	$result = $com->valid($user);
 	if ($result > 0) {
 		print "OK Object created with id ".$idobject."\n";
 	} else {
@@ -100,7 +100,7 @@ if ($idobject > 0) {
 
 // -------------------- END OF YOUR CODE --------------------
 
-if (! $error) {
+if (!$error) {
 	$db->commit();
 	print '--- end ok'."\n";
 } else {

+ 8 - 8
dev/examples/code/create_product.php

@@ -25,7 +25,7 @@
 
 $sapi_type = php_sapi_name();
 $script_file = basename(__FILE__);
-$path=dirname(__FILE__).'/';
+$path = dirname(__FILE__).'/';
 
 // Test if batch mode
 if (substr($sapi_type, 0, 3) == 'cgi') {
@@ -34,13 +34,13 @@ if (substr($sapi_type, 0, 3) == 'cgi') {
 }
 
 // Global variables
-$version='1.10';
-$error=0;
+$version = '1.10';
+$error = 0;
 
 
 // -------------------- START OF YOUR CODE HERE --------------------
 // Include Dolibarr environment
-require_once $path."../../htdocs/master.inc.php";
+require_once $path."../../../htdocs/master.inc.php";
 // After this $db, $mysoc, $langs and $conf->entity are defined. Opened handler to database will be closed at end of file.
 
 //$langs->setDefaultLang('en_US'); 	// To change default language of $langs
@@ -48,8 +48,8 @@ $langs->load("main");				// To load language file for default language
 @set_time_limit(0);
 
 // Load user and its permissions
-$result=$user->fetch('', 'admin');	// Load user for login 'admin'. Comment line to run as anonymous user.
-if (! $result > 0) {
+$result = $user->fetch('', 'admin');	// Load user for login 'admin'. Comment line to run as anonymous user.
+if (!$result > 0) {
 	dol_print_error('', $user->error);
 	exit;
 }
@@ -65,7 +65,7 @@ $db->begin();
 require_once DOL_DOCUMENT_ROOT."/product/class/product.class.php";
 
 // Create instance of object
-$myproduct=new Product($db);
+$myproduct = new Product($db);
 
 // Definition of product instance properties
 $myproduct->ref                = '1234';
@@ -91,7 +91,7 @@ if ($idobject > 0) {
 
 // -------------------- END OF YOUR CODE --------------------
 
-if (! $error) {
+if (!$error) {
 	$db->commit();
 	print '--- end ok'."\n";
 } else {

+ 10 - 10
dev/examples/code/create_user.php

@@ -25,7 +25,7 @@
 
 $sapi_type = php_sapi_name();
 $script_file = basename(__FILE__);
-$path=dirname(__FILE__).'/';
+$path = dirname(__FILE__).'/';
 
 // Test if batch mode
 if (substr($sapi_type, 0, 3) == 'cgi') {
@@ -34,13 +34,13 @@ if (substr($sapi_type, 0, 3) == 'cgi') {
 }
 
 // Global variables
-$version='1.7';
-$error=0;
+$version = '1.7';
+$error = 0;
 
 
 // -------------------- START OF YOUR CODE HERE --------------------
 // Include Dolibarr environment
-require_once $path."../../htdocs/master.inc.php";
+require_once $path."../../../htdocs/master.inc.php";
 // After this $db, $mysoc, $langs and $conf->entity are defined. Opened handler to database will be closed at end of file.
 
 //$langs->setDefaultLang('en_US'); 	// To change default language of $langs
@@ -48,8 +48,8 @@ $langs->load("main");				// To load language file for default language
 @set_time_limit(0);
 
 // Load user and its permissions
-$result=$user->fetch('', 'admin');	// Load user for login 'admin'. Comment line to run as anonymous user.
-if (! $result > 0) {
+$result = $user->fetch('', 'admin');	// Load user for login 'admin'. Comment line to run as anonymous user.
+if (!$result > 0) {
 	dol_print_error('', $user->error);
 	exit;
 }
@@ -71,10 +71,10 @@ $obj->login = 'ABCDEF';
 $obj->nom   = 'ABCDEF';
 
 // Create user
-$idobject=$obj->create($user);
+$idobject = $obj->create($user);
 if ($idobject > 0) {
 	// Change status to validated
-	$result=$obj->setStatut(1);
+	$result = $obj->setStatut(1);
 	if ($result > 0) {
 		print "OK Object created with id ".$idobject."\n";
 	} else {
@@ -82,7 +82,7 @@ if ($idobject > 0) {
 		dol_print_error($db, $obj->error);
 	}
 } elseif ($obj->error == 'ErrorLoginAlreadyExists') {
-	 print "User with login ".$obj->login." already exists\n";
+	print "User with login ".$obj->login." already exists\n";
 } else {
 	$error++;
 	dol_print_error($db, $obj->error);
@@ -91,7 +91,7 @@ if ($idobject > 0) {
 
 // -------------------- END OF YOUR CODE --------------------
 
-if (! $error) {
+if (!$error) {
 	$db->commit();
 	print '--- end ok'."\n";
 } else {

+ 9 - 9
dev/examples/code/get_contracts.php

@@ -25,7 +25,7 @@
 
 $sapi_type = php_sapi_name();
 $script_file = basename(__FILE__);
-$path=dirname(__FILE__).'/';
+$path = dirname(__FILE__).'/';
 
 // Test if batch mode
 if (substr($sapi_type, 0, 3) == 'cgi') {
@@ -34,13 +34,13 @@ if (substr($sapi_type, 0, 3) == 'cgi') {
 }
 
 // Global variables
-$version='1.7';
-$error=0;
+$version = '1.7';
+$error = 0;
 
 
 // -------------------- START OF YOUR CODE HERE --------------------
 // Include Dolibarr environment
-require_once $path."../../htdocs/master.inc.php";
+require_once $path."../../../htdocs/master.inc.php";
 // After this $db, $mysoc, $langs and $conf->entity are defined. Opened handler to database will be closed at end of file.
 
 //$langs->setDefaultLang('en_US'); 	// To change default language of $langs
@@ -48,8 +48,8 @@ $langs->load("main");				// To load language file for default language
 @set_time_limit(0);
 
 // Load user and its permissions
-$result=$user->fetch('', 'admin');	// Load user for login 'admin'. Comment line to run as anonymous user.
-if (! $result > 0) {
+$result = $user->fetch('', 'admin');	// Load user for login 'admin'. Comment line to run as anonymous user.
+if (!$result > 0) {
 	dol_print_error('', $user->error);
 	exit;
 }
@@ -57,7 +57,7 @@ $user->getrights();
 
 
 print "***** ".$script_file." (".$version.") *****\n";
-if (! isset($argv[1])) {	// Check parameters
+if (!isset($argv[1])) {	// Check parameters
 	print "Usage: ".$script_file." id_thirdparty ...\n";
 	exit;
 }
@@ -72,9 +72,9 @@ require_once DOL_DOCUMENT_ROOT."/contrat/class/contrat.class.php";
 
 // Create contract object
 $obj = new Contrat($db);
-$obj->socid=$argv[1];
+$obj->socid = $argv[1];
 
-$listofcontractsforcompany=$obj->getListOfContracts('all');
+$listofcontractsforcompany = $obj->getListOfContracts('all');
 
 print $listofcontractsforcompany;
 

+ 124 - 0
dev/examples/stripe/webhook_ipn_paymentintent_failed_sepa.txt

@@ -0,0 +1,124 @@
+{
+  "id": "evt_123456789",
+  "object": "event",
+  "api_version": "2023-10-16",
+  "created": 1702053463,
+  "data": {
+    "object": {
+      "id": "pi_123456789",
+      "object": "payment_intent",
+      "amount": 60,
+      "amount_capturable": 0,
+      "amount_details": {
+        "tip": {
+        }
+      },
+      "amount_received": 0,
+      "application": null,
+      "application_fee_amount": null,
+      "automatic_payment_methods": null,
+      "canceled_at": null,
+      "cancellation_reason": null,
+      "capture_method": "automatic",
+      "client_secret": "pi_123456789_secret_123456789",
+      "confirmation_method": "automatic",
+      "created": 1702053448,
+      "currency": "eur",
+      "customer": "cus_123456789",
+      "description": "Stripe payment from makeStripeSepaRequest: DID=31262-INV=123-CUS=123 did=123 ref=FA2312-123",
+      "invoice": null,
+      "last_payment_error": {
+        "code": "",
+        "decline_code": "generic_decline",
+        "message": "The transaction can't be processed because your customer's account information is missing or incorrect. Collect a new mandate and ask your customer to provide their name and address exactly as it appears on their bank account. After this, you can attempt the transaction again.",
+        "payment_method": {
+          "id": "pm_123456789",
+          "object": "payment_method",
+          "billing_details": {
+            "address": {
+              "city": null,
+              "country": "FR",
+              "line1": null,
+              "line2": null,
+              "postal_code": null,
+              "state": null
+            },
+            "email": "email@example.com",
+			"name": "Test example",
+            "phone": null
+          },
+          "created": 1692688898,
+          "customer": "cus_123456789",
+          "livemode": false,
+          "metadata": {
+            "dol_version": "19.0.0-dev",
+            "dol_thirdparty_id": "123",
+            "ipaddress": "1.2.3.4",
+            "dol_id": "123",
+            "dol_type": "companypaymentmode",
+            "dol_entity": "1"
+          },
+          "sepa_debit": {
+            "bank_code": "123",
+            "branch_code": "",
+            "country": "AT",
+            "fingerprint": "123456789",
+            "generated_from": {
+              "charge": null,
+              "setup_attempt": null
+            },
+            "last4": "3202"
+          },
+          "type": "sepa_debit"
+        },
+        "type": "card_error"
+      },
+      "latest_charge": "py_123456789",
+      "livemode": false,
+      "metadata": {
+        "dol_version": "19.0.0-beta",
+        "dol_thirdparty_id": "123",
+        "ipaddress": "1.2.3.4",
+        "dol_id": "123",
+        "dol_type": "facture",
+        "dol_entity": "1"
+      },
+      "next_action": null,
+      "on_behalf_of": null,
+      "payment_method": null,
+      "payment_method_configuration_details": null,
+      "payment_method_options": {
+        "card": {
+          "installments": null,
+          "mandate_options": null,
+          "network": null,
+		  "request_three_d_secure": "automatic"
+        },
+        "sepa_debit": {
+        }
+      },
+      "payment_method_types": [
+        "card",
+        "sepa_debit"
+      ],
+      "processing": null,
+      "receipt_email": null,
+      "review": null,
+      "setup_future_usage": null,
+      "shipping": null,
+      "source": null,
+      "statement_descriptor": "DID=123-",
+      "statement_descriptor_suffix": "DID=123-",
+      "status": "requires_payment_method",
+      "transfer_data": null,
+      "transfer_group": null
+    }
+  },
+  "livemode": false,
+  "pending_webhooks": 1,
+  "request": {
+    "id": null,
+    "idempotency_key": null
+  },
+  "type": "payment_intent.payment_failed"
+}

+ 1 - 1
dev/examples/zapier/package.json

@@ -15,7 +15,7 @@
     "npm": ">=5.6.0"
   },
   "dependencies": {
-    "zapier-platform-core": "11.3.1"
+    "zapier-platform-core": "15.0.1"
   },
   "devDependencies": {
     "mocha": "^5.2.0",

+ 3 - 1
dev/initdata/README

@@ -1,5 +1,7 @@
 README
 ------
 
-Scripts in this directory can be used to load or purge data of a database instance.
+Scripts in this directory can be used to load massive data or purge data of a database instance.
 WARNING: Some of this script may delete definitely data.
+
+To init data for a demo, use instead the tool into dev/initdemo

+ 4 - 3
dev/initdata/generate-invoice.php

@@ -45,9 +45,9 @@ require_once DOL_DOCUMENT_ROOT."/societe/class/societe.class.php";
  * Parameters
  */
 
-define(GEN_NUMBER_FACTURE, 1);
+define('GEN_NUMBER_FACTURE', $argv[1] ?? 1);
 $year = 2016;
-$dates = array (mktime(12, 0, 0, 1, 3, $year),
+$dates = array(mktime(12, 0, 0, 1, 3, $year),
 	mktime(12, 0, 0, 1, 9, $year),
 	mktime(12, 0, 0, 2, 13, $year),
 	mktime(12, 0, 0, 2, 23, $year),
@@ -168,7 +168,8 @@ while ($i < GEN_NUMBER_FACTURE && $result >= 0) {
 
 		$result=$object->validate($fuser);
 		if ($result) {
-			print " OK with ref ".$object->ref."\n";;
+			print " OK with ref ".$object->ref."\n";
+			;
 		} else {
 			dol_print_error($db, $object->error);
 		}

+ 2 - 2
dev/initdata/generate-order.php

@@ -51,9 +51,9 @@ require_once DOL_DOCUMENT_ROOT."/commande/class/commande.class.php";
  * Parametre
  */
 
-define(GEN_NUMBER_COMMANDE, 10);
+define('GEN_NUMBER_COMMANDE', $argv[1] ?? 10);
 $year = 2016;
-$dates = array (mktime(12, 0, 0, 1, 3, $year),
+$dates = array(mktime(12, 0, 0, 1, 3, $year),
 	mktime(12, 0, 0, 1, 9, $year),
 	mktime(12, 0, 0, 2, 13, $year),
 	mktime(12, 0, 0, 2, 23, $year),

+ 17 - 8
dev/initdata/generate-product.php

@@ -50,7 +50,7 @@ include_once DOL_DOCUMENT_ROOT.'/contrat/class/contrat.class.php';
  * Parameters
  */
 
-define(GEN_NUMBER_PRODUIT, 100000);
+define('GEN_NUMBER_PRODUIT', $argv[1] ?? 100);
 
 
 $ret=$user->fetch('', 'admin');
@@ -64,18 +64,24 @@ $user->getrights();
 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."product"; $productsid = array();
 $resql=$db->query($sql);
 if ($resql) {
-	$num = $db->num_rows($resql); $i = 0;
+	$num = $db->num_rows($resql);
+	$i = 0;
 	while ($i < $num) {
-		$row = $db->fetch_row($resql);      $productsid[$i] = $row[0];      $i++;
+		$row = $db->fetch_row($resql);
+		$productsid[$i] = $row[0];
+		$i++;
 	}
 }
 
 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."societe"; $societesid = array();
 $resql=$db->query($sql);
 if ($resql) {
-	$num = $db->num_rows($resql); $i = 0;
+	$num = $db->num_rows($resql);
+	$i = 0;
 	while ($i < $num) {
-		$row = $db->fetch_row($resql);      $societesid[$i] = $row[0];      $i++;
+		$row = $db->fetch_row($resql);
+		$societesid[$i] = $row[0];
+		$i++;
 	}
 } else {
 	print "err";
@@ -84,9 +90,12 @@ if ($resql) {
 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."commande"; $commandesid = array();
 $resql=$db->query($sql);
 if ($resql) {
-	$num = $db->num_rows($resql); $i = 0;
+	$num = $db->num_rows($resql);
+	$i = 0;
 	while ($i < $num) {
-		$row = $db->fetch_row($resql);      $commandesid[$i] = $row[0];      $i++;
+		$row = $db->fetch_row($resql);
+		$commandesid[$i] = $row[0];
+		$i++;
 	}
 } else {
 	print "err";
@@ -99,7 +108,7 @@ for ($s = 0; $s < GEN_NUMBER_PRODUIT; $s++) {
 	$produit = new Product($db);
 	$produit->type = mt_rand(0, 1);
 	$produit->status = 1;
-	$produit->ref = ($produit->type?'S':'P').time().$s;
+	$produit->ref = ($produit->type ? 'S' : 'P').time().$s;
 	$produit->label = 'Label '.time().$s;
 	$produit->description = 'Description '.time().$s;
 	$produit->price = mt_rand(1, 999.99);

+ 4 - 4
dev/initdata/generate-proposal.php

@@ -47,9 +47,9 @@ require_once DOL_DOCUMENT_ROOT."/societe/class/societe.class.php";
  * Parameters
  */
 
-define(GEN_NUMBER_PROPAL, 10);
+define('GEN_NUMBER_PROPAL', $argv[1] ?? 10);
 $year = 2016;
-$dates = array (mktime(12, 0, 0, 1, 3, $year),
+$dates = array(mktime(12, 0, 0, 1, 3, $year),
 	mktime(12, 0, 0, 1, 9, $year),
 	mktime(12, 0, 0, 2, 13, $year),
 	mktime(12, 0, 0, 2, 23, $year),
@@ -152,8 +152,8 @@ $user->rights->propal->creer=1;
 $user->rights->propal->propal_advance->validate=1;
 
 
-if (!empty($conf->global->PROPALE_ADDON) && is_readable(DOL_DOCUMENT_ROOT ."/core/modules/propale/".$conf->global->PROPALE_ADDON.".php")) {
-	require_once DOL_DOCUMENT_ROOT ."/core/modules/propale/".$conf->global->PROPALE_ADDON.".php";
+if (getDolGlobalString('PROPALE_ADDON') && is_readable(DOL_DOCUMENT_ROOT ."/core/modules/propale/" . getDolGlobalString('PROPALE_ADDON').".php")) {
+	require_once DOL_DOCUMENT_ROOT ."/core/modules/propale/" . getDolGlobalString('PROPALE_ADDON').".php";
 }
 
 $i=0;

+ 10 - 6
dev/initdata/generate-thirdparty.php

@@ -53,7 +53,7 @@ $listoflastname = array("Joe","Marc","Steve","Laurent","Nico","Isabelle","Doroth
  * Parametre
  */
 
-define(GEN_NUMBER_SOCIETE, 10);
+define('GEN_NUMBER_SOCIETE', $argv[1] ?? 10);
 
 
 $ret=$user->fetch('', 'admin');
@@ -67,7 +67,8 @@ $user->getrights();
 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."product"; $productsid = array();
 $resql=$db->query($sql);
 if ($resql) {
-	$num = $db->num_rows($resql); $i = 0;
+	$num = $db->num_rows($resql);
+	$i = 0;
 	while ($i < $num) {
 		$row = $db->fetch_row($resql);
 		$productsid[$i] = $row[0];
@@ -78,7 +79,8 @@ if ($resql) {
 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."societe"; $societesid = array();
 $resql=$db->query($sql);
 if ($resql) {
-	$num = $db->num_rows($resql); $i = 0;
+	$num = $db->num_rows($resql);
+	$i = 0;
 	while ($i < $num) {
 		$row = $db->fetch_row($resql);
 		$societesid[$i] = $row[0];
@@ -91,7 +93,8 @@ if ($resql) {
 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."commande"; $commandesid = array();
 $resql=$db->query($sql);
 if ($resql) {
-	$num = $db->num_rows($resql); $i = 0;
+	$num = $db->num_rows($resql);
+	$i = 0;
 	while ($i < $num) {
 		$row = $db->fetch_row($resql);
 		$commandesid[$i] = $row[0];
@@ -117,12 +120,13 @@ for ($s = 0; $s < GEN_NUMBER_SOCIETE; $s++) {
 	$soc->country_id=1;
 	$soc->country_code='FR';
 	// Un client sur 3 a une remise de 5%
-	$user_remise=mt_rand(1, 3); if ($user_remise==3) {
+	$user_remise=mt_rand(1, 3);
+	if ($user_remise==3) {
 		$soc->remise_percent=5;
 	}
 	print "> client=".$soc->client.", fournisseur=".$soc->fournisseur.", remise=".$soc->remise_percent."\n";
 	$soc->note_private = 'Company created by the script generate-societe.php';
-	$socid = $soc->create();
+	$socid = $soc->create($user);
 
 	if ($socid >= 0) {
 		$rand = mt_rand(1, 4);

+ 3 - 3
dev/initdata/import-products.php

@@ -62,9 +62,9 @@ dol_syslog($script_file." launched with arg ".implode(',', $argv));
 $mode = $argv[1];
 $filepath = $argv[2];
 $filepatherr = $filepath.'.err';
-$defaultlang = empty($argv[3])?'en_US':$argv[3];
-$startlinenb = empty($argv[4])?1:$argv[4];
-$endlinenb = empty($argv[5])?0:$argv[5];
+$defaultlang = empty($argv[3]) ? 'en_US' : $argv[3];
+$startlinenb = empty($argv[4]) ? 1 : $argv[4];
+$endlinenb = empty($argv[5]) ? 0 : $argv[5];
 
 if (empty($mode) || ! in_array($mode, array('test','confirm','confirmforced')) || empty($filepath)) {
 	print "Usage:  $script_file (test|confirm|confirmforced) filepath.csv [defaultlang] [startlinenb] [endlinenb]\n";

+ 4 - 4
dev/initdata/import-thirdparties.php

@@ -63,8 +63,8 @@ $mode = $argv[1];
 $filepath = $argv[2];
 $filepatherr = $filepath.'.err';
 //$defaultlang = empty($argv[3])?'en_US':$argv[3];
-$startlinenb = empty($argv[3])?1:$argv[3];
-$endlinenb = empty($argv[4])?0:$argv[4];
+$startlinenb = empty($argv[3]) ? 1 : $argv[3];
+$endlinenb = empty($argv[4]) ? 0 : $argv[4];
 
 if (empty($mode) || ! in_array($mode, array('test','confirm','confirmforced')) || empty($filepath)) {
 	print "Usage:  $script_file (test|confirm|confirmforced) filepath.csv [startlinenb] [endlinenb]\n";
@@ -128,8 +128,8 @@ while ($fields=fgetcsv($fhandle, $linelength, $delimiter, $enclosure, $escape))
 	$object->client = $fields[7];
 	$object->fournisseur = $fields[8];
 
-	$object->name = $fields[13]?trim($fields[13]):$fields[0];
-	$object->name_alias = $fields[0]!=$fields[13]?trim($fields[0]):'';
+	$object->name = $fields[13] ? trim($fields[13]) : $fields[0];
+	$object->name_alias = $fields[0] != $fields[13] ? trim($fields[0]) : '';
 
 	$object->address = trim($fields[14]);
 	$object->zip = trim($fields[15]);

+ 2 - 2
dev/initdata/import-users.php

@@ -63,8 +63,8 @@ $mode = $argv[1];
 $filepath = $argv[2];
 $filepatherr = $filepath.'.err';
 //$defaultlang = empty($argv[3])?'en_US':$argv[3];
-$startlinenb = empty($argv[3])?1:$argv[3];
-$endlinenb = empty($argv[4])?0:$argv[4];
+$startlinenb = empty($argv[3]) ? 1 : $argv[3];
+$endlinenb = empty($argv[4]) ? 0 : $argv[4];
 
 if (empty($mode) || ! in_array($mode, array('test','confirm','confirmforced')) || empty($filepath)) {
 	print "Usage:  $script_file (test|confirm|confirmforced) filepath.csv [startlinenb] [endlinenb]\n";

+ 59 - 53
dev/initdemo/initdemopassword.sh

@@ -5,15 +5,17 @@
 #
 # Laurent Destailleur - eldy@users.sourceforge.net
 #------------------------------------------------------
-# Usage: initdemopassword.sh confirm 
+# Usage: initdemopassword.sh confirm
 # usage: initdemopassword.sh confirm base port login pass
 #------------------------------------------------------
+# shellcheck disable=2006,2034,2046,2064,2068,2086,2155,2166,2186,2172,2268
+# shellcheck disable=2012,2016,2154
 
 
 export mydir=`echo "$0" | sed -e 's/initdemopassword.sh//'`;
 if [ "x$mydir" = 'x' -o "x$mydir" = 'x./' ]
 then
-    export mydir="."
+	export mydir="."
 fi
 export id=`id -u`;
 
@@ -56,103 +58,107 @@ then
 	fichtemp=`tempfile 2>/dev/null` || fichtemp=/tmp/test$$
 	trap "rm -f $fichtemp" 0 1 2 5 15
 	$DIALOG --title "Reset login password" --clear \
-	        --inputbox "Mysql database name :" 16 55 dolibarrdemo 2> $fichtemp
+		--inputbox "Mysql database name :" 16 55 dolibarrdemo 2> $fichtemp
 	valret=$?
 	case $valret in
-	  0)
-	base=`cat $fichtemp`;;
-	  1)
-	exit;;
-	  255)
-	exit;;
+		0)
+			base=`cat $fichtemp` ;;
+		1)
+			exit ;;
+		255)
+			exit ;;
 	esac
 	rm $fichtemp
-	
+
 	# ---------------------------- database port
 	DIALOG=${DIALOG=dialog}
 	fichtemp=`tempfile 2>/dev/null` || fichtemp=/tmp/test$$
 	trap "rm -f $fichtemp" 0 1 2 5 15
 	$DIALOG --title "Reset login password" --clear \
-	        --inputbox "Mysql port (ex: 3306):" 16 55 3306 2> $fichtemp
-	
+		--inputbox "Mysql port (ex: 3306):" 16 55 3306 2> $fichtemp
+
 	valret=$?
-	
+
 	case $valret in
-	  0)
-	port=`cat $fichtemp`;;
-	  1)
-	exit;;
-	  255)
-	exit;;
+		0)
+			port=`cat $fichtemp` ;;
+		1)
+			exit ;;
+		255)
+			exit ;;
 	esac
 	rm $fichtemp
-	
-	
+
+
 	# ----------------------------- demo login
 	DIALOG=${DIALOG=dialog}
 	DIALOG="$DIALOG --ascii-lines"
 	fichtemp=`tempfile 2>/dev/null` || fichtemp=/tmp/test$$
 	trap "rm -f $fichtemp" 0 1 2 5 15
 	$DIALOG --title "Reset login password" --clear \
-	        --inputbox "Login to reset :" 16 55 dolibarrdemologin 2> $fichtemp
+		--inputbox "Login to reset :" 16 55 dolibarrdemologin 2> $fichtemp
 	valret=$?
 	case $valret in
-	  0)
-	demologin=`cat $fichtemp`;;
-	  1)
-	exit;;
-	  255)
-	exit;;
+		0)
+			demologin=`cat $fichtemp` ;;
+		1)
+			exit ;;
+		255)
+			exit ;;
 	esac
 	rm fichtemp
-	
+
 	# ----------------------------- demo pass
 	DIALOG=${DIALOG=dialog}
 	DIALOG="$DIALOG --ascii-lines"
 	fichtemp=`tempfile 2>/dev/null` || fichtemp=/tmp/test$$
 	trap "rm -f $fichtemp" 0 1 2 5 15
 	$DIALOG --title "Reset login password" --clear \
-	        --inputbox "Pass to set :" 16 55 dolibarrdemopass 2> $fichtemp
+		--inputbox "Pass to set :" 16 55 dolibarrdemopass 2> $fichtemp
 	valret=$?
 	case $valret in
-	  0)
-	demopass=`cat $fichtemp`;;
-	  1)
-	exit;;
-	  255)
-	exit;;
+		0)
+			demopass=`cat $fichtemp` ;;
+		1)
+			exit ;;
+		255)
+			exit ;;
 	esac
 	rm fichtemp
-	
-	
+
+
 	export documentdir=`cat $mydir/../../htdocs/conf/conf.php | grep '^\$dolibarr_main_data_root' | sed -e 's/$dolibarr_main_data_root=//' | sed -e 's/;//' | sed -e "s/'//g" | sed -e 's/"//g' `
 
 
 	# ---------------------------- confirmation
 	DIALOG=${DIALOG=dialog}
 	$DIALOG --title "Reset login password" --clear \
-	        --yesno "Do you confirm ? \n Mysql database : '$base' \n Mysql port : '$port' \n Demo login: '$demologin' \n Demo password : '$demopass'" 15 55
-	
+		--yesno "Do you confirm ? \n Mysql database : '$base' \n Mysql port : '$port' \n Demo login: '$demologin' \n Demo password : '$demopass'" 15 55
+
 	case $? in
-	        0)      echo "Ok, start process...";;
-	        1)      exit;;
-	        255)    exit;;
+		0)      echo "Ok, start process..." ;;
+		1)      exit ;;
+		255)    exit ;;
 	esac
 
 fi
 
 
 # ---------------------------- run sql file
+if [ "x$port" != "x0" ]
+then
+	export Pport="-P$port"
+fi
 if [ "x$passwd" != "x" ]
 then
 	export passwd="-p$passwd"
 fi
-#echo "mysql -P$port -u$admin $passwd $base < $mydir/$dumpfile"
-#mysql -P$port -u$admin $passwd $base < $mydir/$dumpfile
+#echo "mysql $Pport -u$admin $passwd $base < $mydir/$dumpfile"
+#mysql $Pport -u$admin $passwd $base < $mydir/$dumpfile
 
 if [ "x${demopasshash}" != "xpassword_hash" ]
 then
-	echo '<?php echo MD5("'$demopass'"); ?>' > /tmp/tmp.php 
+	echo '<?php echo MD5("'$demopass'"); ?>' > /tmp/tmp.php
 	newpass=`php -f /tmp/tmp.php`
 else
 	echo '<?php echo password_hash("'$demopass'", PASSWORD_DEFAULT); ?>' > /tmp/tmp.php
@@ -160,22 +166,22 @@ else
 fi
 #rm /tmp/tmp.php
 
-echo "echo \"UPDATE llx_user SET pass_crypted = '$newpass' WHERE login = '$demologin';\" | mysql -P$port $base"
-echo "UPDATE llx_user SET pass_crypted = '$newpass' WHERE login = '$demologin';" | mysql -P$port $base
+echo "echo \"UPDATE llx_user SET pass_crypted = '$newpass' WHERE login = '$demologin';\" | mysql $Pport $base"
+echo "UPDATE llx_user SET pass_crypted = '$newpass' WHERE login = '$demologin';" | mysql $Pport $base
 export res=$?
 
 if [ $res -ne 0 ]; then
-	echo "Error to execute sql with mysql -P$port -u$admin -p***** $base"
+	echo "Error to execute sql with mysql $Pport -u$admin -p***** $base"
 	exit
-fi 
+fi
 
 if [ -s "$mydir/initdemopostsql.sql" ]; then
 	echo A file initdemopostsql.sql was found, we execute it.
-	echo "mysql -P$port $base < \"$mydir/initdemopostsql.sql\""
-	mysql -P$port $base < "$mydir/initdemopostsql.sql"
+	echo "mysql $Pport $base < \"$mydir/initdemopostsql.sql\""
+	mysql $Pport $base < "$mydir/initdemopostsql.sql"
 else
 	echo No file initdemopostsql.sql found, so no extra sql action done.
-fi 
+fi
 
 
 if [ "x$res" = "x0" ]

Diferenças do arquivo suprimidas por serem muito extensas
+ 5 - 3
dev/initdemo/mysqldump_dolibarr_19.0.0.sql


+ 0 - 69
dev/initdemo/mysqldump_dolibarr_3.5.0.sql

@@ -4655,75 +4655,6 @@ INSERT INTO `llx_menu` VALUES (19289,'all',1,'cashdesk','top','cashdesk',0,NULL,
 /*!40000 ALTER TABLE `llx_menu` ENABLE KEYS */;
 UNLOCK TABLES;
 
---
--- Table structure for table `llx_milestone`
---
-
-DROP TABLE IF EXISTS `llx_milestone`;
-/*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!40101 SET character_set_client = utf8 */;
-CREATE TABLE `llx_milestone` (
-  `rowid` int(11) NOT NULL AUTO_INCREMENT,
-  `fk_element` int(11) NOT NULL,
-  `elementtype` varchar(16) NOT NULL,
-  `label` varchar(255) NOT NULL,
-  `options` varchar(255) DEFAULT NULL,
-  `priority` int(11) DEFAULT '0',
-  `tms` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-  `fk_user_modif` int(11) DEFAULT NULL,
-  PRIMARY KEY (`rowid`),
-  UNIQUE KEY `uk_milestone_fk_element` (`fk_element`,`elementtype`),
-  KEY `idx_milestone_fk_user_modif` (`fk_user_modif`),
-  CONSTRAINT `fk_milestone_fk_user_modif` FOREIGN KEY (`fk_user_modif`) REFERENCES `llx_user` (`rowid`)
-) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-/*!40101 SET character_set_client = @saved_cs_client */;
-
---
--- Dumping data for table `llx_milestone`
---
-
-LOCK TABLES `llx_milestone` WRITE;
-/*!40000 ALTER TABLE `llx_milestone` DISABLE KEYS */;
-INSERT INTO `llx_milestone` VALUES (2,779,'facture','azerty',NULL,0,'2013-03-09 12:19:30',NULL),(3,780,'facture','fsdf',NULL,0,'2013-03-09 13:01:08',NULL),(4,781,'facture','hhh',NULL,0,'2013-03-09 14:06:37',NULL);
-/*!40000 ALTER TABLE `llx_milestone` ENABLE KEYS */;
-UNLOCK TABLES;
-
---
--- Table structure for table `llx_monitoring_probes`
---
-
-DROP TABLE IF EXISTS `llx_monitoring_probes`;
-/*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!40101 SET character_set_client = utf8 */;
-CREATE TABLE `llx_monitoring_probes` (
-  `rowid` int(11) NOT NULL AUTO_INCREMENT,
-  `title` varchar(64) NOT NULL,
-  `groupname` varchar(64) DEFAULT NULL,
-  `url` varchar(250) NOT NULL,
-  `useproxy` int(11) DEFAULT '0',
-  `checkkey` varchar(250) DEFAULT NULL,
-  `maxval` int(11) DEFAULT NULL,
-  `frequency` int(11) DEFAULT '60',
-  `active` int(11) DEFAULT '1',
-  `status` int(11) DEFAULT '0',
-  `lastreset` datetime DEFAULT NULL,
-  `oldesterrortext` text,
-  `oldesterrordate` datetime DEFAULT NULL,
-  PRIMARY KEY (`rowid`),
-  UNIQUE KEY `uk_monitoring_probes` (`title`)
-) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-/*!40101 SET character_set_client = @saved_cs_client */;
-
---
--- Dumping data for table `llx_monitoring_probes`
---
-
-LOCK TABLES `llx_monitoring_probes` WRITE;
-/*!40000 ALTER TABLE `llx_monitoring_probes` DISABLE KEYS */;
-INSERT INTO `llx_monitoring_probes` VALUES (1,'aaa',NULL,'http://www.chiensderace.com',0,'chiens',1000,10,1,1,'2011-04-20 23:46:41',NULL,NULL),(2,'ChatsDeRace',NULL,'http://www.chatsderace.com',0,'chats',1000,5,1,1,'2011-04-20 23:46:41',NULL,NULL);
-/*!40000 ALTER TABLE `llx_monitoring_probes` ENABLE KEYS */;
-UNLOCK TABLES;
-
 --
 -- Table structure for table `llx_notify`
 --

+ 12 - 0
dev/initdemo/savedemo.sh

@@ -245,6 +245,16 @@ export list="
 	--ignore-table=$base.llx_c_ticketsup_category
 	--ignore-table=$base.llx_c_ticketsup_severity
 	--ignore-table=$base.llx_c_ticketsup_type
+	--ignore-table=$base.llx_cabinetmed_c_banques
+	--ignore-table=$base.llx_cabinetmed_c_examconclusion
+	--ignore-table=$base.llx_cabinetmed_cons_extrafields
+	--ignore-table=$base.llx_cabinetmed_diaglec
+	--ignore-table=$base.llx_cabinetmed_examaut
+	--ignore-table=$base.llx_cabinetmed_exambio
+	--ignore-table=$base.llx_cabinetmed_examenprescrit
+	--ignore-table=$base.llx_cabinetmed_motifcons
+	--ignore-table=$base.llx_cabinetmed_patient
+	--ignore-table=$base.llx_cabinetmed_societe
 	--ignore-table=$base.llx_congespayes
 	--ignore-table=$base.llx_congespayes_config
 	--ignore-table=$base.llx_congespayes_events
@@ -328,6 +338,8 @@ export list="
     --ignore-table=$base.tmp_llx_accouting_account
     --ignore-table=$base.tmp_llx_product_batch
     --ignore-table=$base.tmp_llx_product_batch2
+    --ignore-table=$base.tmp_tmp
+    --ignore-table=$base.tmp_user
 	" 
 echo "mysqldump -P$port -u$admin -p***** $list $base > $mydir/$dumpfile"
 mysqldump -P$port -u$admin $passwd $list $base > $mydir/$dumpfile

+ 41 - 40
dev/initdemo/sftpget_and_loaddump.php

@@ -21,7 +21,7 @@
 
 $sapi_type = php_sapi_name();
 $script_file = basename(__FILE__);
-$path=dirname(__FILE__).'/';
+$path = dirname(__FILE__).'/';
 
 // Test if batch mode
 if (substr($sapi_type, 0, 3) == 'cgi') {
@@ -30,39 +30,39 @@ if (substr($sapi_type, 0, 3) == 'cgi') {
 }
 
 // Global variables
-$error=0;
+$error = 0;
 
-$sourceserver=isset($argv[1])?$argv[1]:'';		// user@server:/src/file
-$password=isset($argv[2])?$argv[2]:'';
-$dataserver=isset($argv[3])?$argv[3]:'';
-$database=isset($argv[4])?$argv[4]:'';
-$loginbase=isset($argv[5])?$argv[5]:'';
-$passwordbase=isset($argv[6])?$argv[6]:'';
+$sourceserver = isset($argv[1]) ? $argv[1] : '';		// user@server:/src/file
+$password = isset($argv[2]) ? $argv[2] : '';
+$dataserver = isset($argv[3]) ? $argv[3] : '';
+$database = isset($argv[4]) ? $argv[4] : '';
+$loginbase = isset($argv[5]) ? $argv[5] : '';
+$passwordbase = isset($argv[6]) ? $argv[6] : '';
 
 // Include Dolibarr environment
-$res=0;
-if (! $res && file_exists($path."../../master.inc.php")) {
-	$res=@include $path."../../master.inc.php";
+$res = 0;
+if (!$res && file_exists($path."../../master.inc.php")) {
+	$res = @include $path."../../master.inc.php";
 }
-if (! $res && file_exists($path."../../htdocs/master.inc.php")) {
-	$res=@include $path."../../htdocs/master.inc.php";
+if (!$res && file_exists($path."../../htdocs/master.inc.php")) {
+	$res = @include $path."../../htdocs/master.inc.php";
 }
-if (! $res && file_exists("../master.inc.php")) {
-	$res=@include "../master.inc.php";
+if (!$res && file_exists("../master.inc.php")) {
+	$res = @include "../master.inc.php";
 }
-if (! $res && file_exists("../../master.inc.php")) {
-	$res=@include "../../master.inc.php";
+if (!$res && file_exists("../../master.inc.php")) {
+	$res = @include "../../master.inc.php";
 }
-if (! $res && file_exists("../../../master.inc.php")) {
-	$res=@include "../../../master.inc.php";
+if (!$res && file_exists("../../../master.inc.php")) {
+	$res = @include "../../../master.inc.php";
 }
-if (! $res && preg_match('/\/nltechno([^\/]*)\//', $_SERVER["PHP_SELF"], $reg)) {
-	$res=@include $path."../../../dolibarr".$reg[1]."/htdocs/master.inc.php"; // Used on dev env only
+if (!$res && preg_match('/\/nltechno([^\/]*)\//', $_SERVER["PHP_SELF"], $reg)) {
+	$res = @include $path."../../../dolibarr".$reg[1]."/htdocs/master.inc.php"; // Used on dev env only
 }
-if (! $res && preg_match('/\/nltechno([^\/]*)\//', $_SERVER["PHP_SELF"], $reg)) {
-	$res=@include "../../../dolibarr".$reg[1]."/htdocs/master.inc.php"; // Used on dev env only
+if (!$res && preg_match('/\/nltechno([^\/]*)\//', $_SERVER["PHP_SELF"], $reg)) {
+	$res = @include "../../../dolibarr".$reg[1]."/htdocs/master.inc.php"; // Used on dev env only
 }
-if (! $res) {
+if (!$res) {
 	die("Failed to include master.inc.php file\n");
 }
 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
@@ -72,13 +72,13 @@ include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  *	Main
  */
 
-$login='';
-$server='';
+$login = '';
+$server = '';
 if (preg_match('/^(.*)@(.*):(.*)$/', $sourceserver, $reg)) {
-	$login=$reg[1];
-	$server=$reg[2];
-	$sourcefile=$reg[3];
-	$targetfile=basename($sourcefile);
+	$login = $reg[1];
+	$server = $reg[2];
+	$sourcefile = $reg[3];
+	$targetfile = basename($sourcefile);
 }
 if (empty($sourceserver) || empty($server) || empty($login) || empty($sourcefile) || empty($password) || empty($database) || empty($loginbase) || empty($passwordbase)) {
 	print "Usage: $script_file login@server:/src/file.(sql|gz|bz2) passssh databaseserver databasename loginbase passbase\n";
@@ -88,22 +88,23 @@ if (empty($sourceserver) || empty($server) || empty($login) || empty($sourcefile
 }
 
 
-$targetdir='/tmp';
+$targetdir = '/tmp';
 print "Get dump file from server ".$server.", path ".$sourcefile.", connect with login ".$login." loaded into localhost\n";
 
-$sftpconnectstring=$sourceserver;
+$sftpconnectstring = $sourceserver;
 print 'SFTP connect string : '.$sftpconnectstring."\n";
 //print 'SFTP password '.$password."\n";
 
 
 // SFTP connect
-if (! function_exists("ssh2_connect")) {
-	dol_print_error('', 'ssh2_connect function does not exists'); exit(1);
+if (!function_exists("ssh2_connect")) {
+	dol_print_error('', 'ssh2_connect function does not exists');
+	exit(1);
 }
 
 $connection = ssh2_connect($server, 22);
 if ($connection) {
-	if (! @ssh2_auth_password($connection, $login, $password)) {
+	if (!@ssh2_auth_password($connection, $login, $password)) {
 		dol_syslog("Could not authenticate with username ".$login." . and password ".preg_replace('/./', '*', $password), LOG_ERR);
 		exit(-5);
 	} else {
@@ -121,16 +122,16 @@ if ($connection) {
 		print 'Get file '.$sourcefile.' into '.$targetdir.$targetfile."\n";
 		ssh2_scp_recv($connection, $sourcefile, $targetdir.$targetfile);
 
-		$fullcommand="cat ".$targetdir.$targetfile." | mysql -h".$databaseserver." -u".$loginbase." -p".$passwordbase." -D ".$database;
+		$fullcommand = "cat ".$targetdir.$targetfile." | mysql -h".$databaseserver." -u".$loginbase." -p".$passwordbase." -D ".$database;
 		if (preg_match('/\.bz2$/', $targetfile)) {
-			$fullcommand="bzip2 -c -d ".$targetdir.$targetfile." | mysql -h".$databaseserver." -u".$loginbase." -p".$passwordbase." -D ".$database;
+			$fullcommand = "bzip2 -c -d ".$targetdir.$targetfile." | mysql -h".$databaseserver." -u".$loginbase." -p".$passwordbase." -D ".$database;
 		}
 		if (preg_match('/\.gz$/', $targetfile)) {
-			$fullcommand="gzip -d ".$targetdir.$targetfile." | mysql -h".$databaseserver." -u".$loginbase." -p".$passwordbase." -D ".$database;
+			$fullcommand = "gzip -d ".$targetdir.$targetfile." | mysql -h".$databaseserver." -u".$loginbase." -p".$passwordbase." -D ".$database;
 		}
 		print "Load dump with ".$fullcommand."\n";
-		$output=array();
-		$return_var=0;
+		$output = array();
+		$return_var = 0;
 		print strftime("%Y%m%d-%H%M%S").' '.$fullcommand."\n";
 		exec($fullcommand, $output, $return_var);
 		foreach ($output as $line) {

+ 44 - 44
dev/initdemo/updatedemo.php

@@ -21,7 +21,7 @@
 
 $sapi_type = php_sapi_name();
 $script_file = basename(__FILE__);
-$path=dirname(__FILE__).'/';
+$path = dirname(__FILE__).'/';
 
 // Test if batch mode
 if (substr($sapi_type, 0, 3) == 'cgi') {
@@ -30,34 +30,34 @@ if (substr($sapi_type, 0, 3) == 'cgi') {
 }
 
 // Global variables
-$error=0;
+$error = 0;
 
-$confirm=isset($argv[1])?$argv[1]:'';
+$confirm = isset($argv[1]) ? $argv[1] : '';
 
 // Include Dolibarr environment
-$res=0;
-if (! $res && file_exists($path."../../master.inc.php")) {
-	$res=@include $path."../../master.inc.php";
+$res = 0;
+if (!$res && file_exists($path."../../master.inc.php")) {
+	$res = @include $path."../../master.inc.php";
 }
-if (! $res && file_exists($path."../../htdocs/master.inc.php")) {
-	$res=@include $path."../../htdocs/master.inc.php";
+if (!$res && file_exists($path."../../htdocs/master.inc.php")) {
+	$res = @include $path."../../htdocs/master.inc.php";
 }
-if (! $res && file_exists("../master.inc.php")) {
-	$res=@include "../master.inc.php";
+if (!$res && file_exists("../master.inc.php")) {
+	$res = @include "../master.inc.php";
 }
-if (! $res && file_exists("../../master.inc.php")) {
-	$res=@include "../../master.inc.php";
+if (!$res && file_exists("../../master.inc.php")) {
+	$res = @include "../../master.inc.php";
 }
-if (! $res && file_exists("../../../master.inc.php")) {
-	$res=@include "../../../master.inc.php";
+if (!$res && file_exists("../../../master.inc.php")) {
+	$res = @include "../../../master.inc.php";
 }
-if (! $res && preg_match('/\/nltechno([^\/]*)\//', $_SERVER["PHP_SELF"], $reg)) {
-	$res=@include $path."../../../dolibarr".$reg[1]."/htdocs/master.inc.php"; // Used on dev env only
+if (!$res && preg_match('/\/nltechno([^\/]*)\//', $_SERVER["PHP_SELF"], $reg)) {
+	$res = @include $path."../../../dolibarr".$reg[1]."/htdocs/master.inc.php"; // Used on dev env only
 }
-if (! $res && preg_match('/\/nltechno([^\/]*)\//', $_SERVER["PHP_SELF"], $reg)) {
-	$res=@include "../../../dolibarr".$reg[1]."/htdocs/master.inc.php"; // Used on dev env only
+if (!$res && preg_match('/\/nltechno([^\/]*)\//', $_SERVER["PHP_SELF"], $reg)) {
+	$res = @include "../../../dolibarr".$reg[1]."/htdocs/master.inc.php"; // Used on dev env only
 }
-if (! $res) {
+if (!$res) {
 	die("Failed to include master.inc.php file\n");
 }
 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
@@ -76,56 +76,56 @@ if (empty($confirm)) {
 }
 
 
-$tmp=dol_getdate(dol_now());
+$tmp = dol_getdate(dol_now());
 
 
-$tables=array(
-	'propal'=>array(0=>'datep', 1=>'fin_validite', 2=>'date_valid', 3=>'date_cloture'),
-	'commande'=>array(0=>'date_commande', 1=>'date_valid', 2=>'date_cloture'),
-	'facture'=>array(0=>'datec', 0=>'datef', 1=>'date_valid', 2=>'date_lim_reglement'),
-	'paiement'=>array(0=>'datep'),
-	'bank'=>array(0=>'datev', 1=>'dateo'),
-	'commande_fournisseur'=>array(0=>'date_commande', 1=>'date_valid', 3=>'date_creation', 4=>'date_approve', 5=>'date_approve2', 6=>'date_livraison'),
-	'supplier_proposal'=>array(0=>'datec', 1=>'date_valid', 2=>'date_cloture'),
-	'expensereport'=>array(0=>'date_debut', 1=>'date_fin', 2=>'date_create', 3=>'date_valid', 4=>'date_approve', 5=>'date_refuse', 6=>'date_cancel'),
-	'holiday'=>array(0=>'date_debut', 1=>'date_fin', 2=>'date_create', 3=>'date_valid', 5=>'date_refuse', 6=>'date_cancel'),
-	'ticket'=>array(0=>'datec', 1=>'date_read', 2=>'date_close')
+$tables = array(
+	'propal' => array(0 => 'datep', 1 => 'fin_validite', 2 => 'date_valid', 3 => 'date_cloture'),
+	'commande' => array(0 => 'date_commande', 1 => 'date_valid', 2 => 'date_cloture'),
+	'facture' => array(0 => 'datec', 1 => 'datef', 2 => 'date_valid', 3 => 'date_lim_reglement'),
+	'paiement' => array(0 => 'datep'),
+	'bank' => array(0 => 'datev', 1 => 'dateo'),
+	'commande_fournisseur' => array(0 => 'date_commande', 1 => 'date_valid', 3 => 'date_creation', 4 => 'date_approve', 5 => 'date_approve2', 6 => 'date_livraison'),
+	'supplier_proposal' => array(0 => 'datec', 1 => 'date_valid', 2 => 'date_cloture'),
+	'expensereport' => array(0 => 'date_debut', 1 => 'date_fin', 2 => 'date_create', 3 => 'date_valid', 4 => 'date_approve', 5 => 'date_refuse', 6 => 'date_cancel'),
+	'holiday' => array(0 => 'date_debut', 1 => 'date_fin', 2 => 'date_create', 3 => 'date_valid', 5 => 'date_refuse', 6 => 'date_cancel'),
+	'ticket' => array(0 => 'datec', 1 => 'date_read', 2 => 'date_close')
 );
 
-$year=2010;
-$currentyear=$tmp['year'];
+$year = 2010;
+$currentyear = $tmp['year'];
 while ($year <= $currentyear) {
 	//$year=2021;
-	$delta1=($currentyear - $year);
-	$delta2=($currentyear - $year - 1);
+	$delta1 = ($currentyear - $year);
+	$delta2 = ($currentyear - $year - 1);
 	//$delta=-1;
 
 	if ($delta1) {
 		foreach ($tables as $tablekey => $tableval) {
 			print "Correct ".$tablekey." for year ".$year." and move them to current year ".$currentyear." ";
-			$sql="select rowid from ".MAIN_DB_PREFIX.$tablekey." where ".$tableval[0]." between '".$year."-01-01' and '".$year."-12-31' and ".$tableval[0]." < DATE_ADD(NOW(), INTERVAL -1 YEAR)";
+			$sql = "select rowid from ".MAIN_DB_PREFIX.$tablekey." where ".$tableval[0]." between '".$year."-01-01' and '".$year."-12-31' and ".$tableval[0]." < DATE_ADD(NOW(), INTERVAL -1 YEAR)";
 			//$sql="select rowid from ".MAIN_DB_PREFIX.$tablekey." where ".$tableval[0]." between '".$year."-01-01' and '".$year."-12-31' and ".$tableval[0]." > NOW()";
 			$resql = $db->query($sql);
 			if ($resql) {
 				$num = $db->num_rows($resql);
-				$i=0;
+				$i = 0;
 				while ($i < $num) {
-					$obj=$db->fetch_object($resql);
+					$obj = $db->fetch_object($resql);
 					if ($obj) {
 						print ".";
-						$sql2="UPDATE ".MAIN_DB_PREFIX.$tablekey." set ";
-						$j=0;
+						$sql2 = "UPDATE ".MAIN_DB_PREFIX.$tablekey." set ";
+						$j = 0;
 						foreach ($tableval as $field) {
 							if ($j) {
-								$sql2.=", ";
+								$sql2 .= ", ";
 							}
-							$sql2.= $field." = ".$db->ifsql("DATE_ADD(".$field.", INTERVAL ".$delta1." YEAR) > NOW()", "DATE_ADD(".$field.", INTERVAL ".$delta2." YEAR)", "DATE_ADD(".$field.", INTERVAL ".$delta1." YEAR)");
+							$sql2 .= $field." = ".$db->ifsql("DATE_ADD(".$field.", INTERVAL ".$delta1." YEAR) > NOW()", "DATE_ADD(".$field.", INTERVAL ".$delta2." YEAR)", "DATE_ADD(".$field.", INTERVAL ".$delta1." YEAR)");
 							$j++;
 						}
-						$sql2.=" WHERE rowid = ".$obj->rowid;
+						$sql2 .= " WHERE rowid = ".$obj->rowid;
 						//print $sql2."\n";
 						$resql2 = $db->query($sql2);
-						if (! $resql2) {
+						if (!$resql2) {
 							dol_print_error($db);
 						}
 					}

+ 1 - 1
dev/setup/fail2ban/filter.d/web-accesslog-limit403.conf

@@ -13,7 +13,7 @@
 # fail2ban-client status web-accesslog-limit403 
 #
 # To test rule file on a existing log file
-# fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/web-accesslog-limit403.conf
+# fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/web-accesslog-limit403.conf --print-all-matched
 
 failregex = <HOST> - - .*HTTP/[0-9]+(.[0-9]+)?" 403
 ignoreregex =

+ 1 - 1
dev/setup/fail2ban/filter.d/web-dolibarr-limitpublic.conf

@@ -13,7 +13,7 @@
 # fail2ban-client status web-dolibarr-limitpublic 
 #
 # To test rule file on a existing log file
-# fail2ban-regex /mypath/documents/dolibarr.log /etc/fail2ban/filter.d/web-dolibarr-limitpublic.conf
+# fail2ban-regex /mypath/documents/dolibarr.log /etc/fail2ban/filter.d/web-dolibarr-limitpublic.conf --print-all-matched
 
 failregex = ^ [A-Z\s]+ <HOST>\s+--- Access to .*/public/
 ignoreregex =

+ 1 - 1
dev/setup/fail2ban/filter.d/web-dolibarr-rulesbruteforce.conf

@@ -13,7 +13,7 @@
 # fail2ban-client status web-dolibarr-rulesbruteforce 
 #
 # To test rule file on a existing log file
-# fail2ban-regex /mypath/documents/dolibarr.log /etc/fail2ban/filter.d/web-dolibarr-rulesbruteforce.conf
+# fail2ban-regex /mypath/documents/dolibarr.log /etc/fail2ban/filter.d/web-dolibarr-rulesbruteforce.conf --print-all-matched
 
 failregex = ^ [A-Z\s]+ <HOST>\s+functions_.*::check_user_.* Authentication KO
 ignoreregex =

+ 1 - 1
dev/setup/fail2ban/filter.d/web-dolibarr-rulespassforgotten.conf

@@ -13,7 +13,7 @@
 # fail2ban-client status web-dolibarr-rulespassforgotten 
 #
 # To test rule file on a existing log file
-# fail2ban-regex /mypath/documents/dolibarr.log /etc/fail2ban/filter.d/web-dolibarr-rulespassforgotten.conf
+# fail2ban-regex /mypath/documents/dolibarr.log /etc/fail2ban/filter.d/web-dolibarr-rulespassforgotten.conf --print-all-matched
 
 failregex = ^ [A-Z\s]+ <HOST>\s+--- Access to .*/passwordforgotten.php - action=buildnewpassword
 ignoreregex =

+ 3 - 3
dev/setup/qodana/README.md

@@ -3,10 +3,10 @@ QODANA TUTO
 This README explains how to use qodana to generate static analytics reports on the code
  
 Install docker
+ apt install docker
  
- 
-Install qodana
- 
+Install qodana into ~/.loca/bin/qodana
+ curl -fsSL https://jb.gg/qodana-cli/install | bash
  
 To run inspection on CLI
  cd ~/git/dirtoscan

+ 15 - 0
dev/setup/sonarqube/README.md

@@ -0,0 +1,15 @@
+= Install SonarQube locally
+
+Check you are using Install Java SDK 17
+java --version must show 61
+
+To install java sdk 17 on ubuntu:
+sudo apt update
+sudo apt install -y openjdk-17-jdk
+
+Unzip the sonar package into a directory
+
+Edit the file conf/sonar.properties to modify port 9000 and 9001 (already used by Eclipse or xdebug) into 9080 and 9081
+
+Launch sonar with
+bin/linux*/sonar.sh console

+ 1 - 1
dev/skeletons/README.md

@@ -1 +1 @@
-Files and tools were moved into htdocs/modulebuilder/template
+Files and tools of a skeleton to build a module were moved into htdocs/modulebuilder/template

+ 1 - 0
dev/tools/.gitignore

@@ -0,0 +1 @@
+/vendor/

+ 438 - 0
dev/tools/apstats.php

@@ -0,0 +1,438 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Copyright (C) 2023 	   Laurent Destailleur 	<eldy@users.sourceforge.net>
+ *
+ * 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    dev/tools/apstats.php
+ * \brief   Script to report Advanced Statistics on a coding project
+ */
+
+
+$sapi_type = php_sapi_name();
+$script_file = basename(__FILE__);
+$path = dirname(__FILE__) . '/';
+
+// Test si mode batch
+$sapi_type = php_sapi_name();
+if (substr($sapi_type, 0, 3) == 'cgi') {
+	echo "Error: You are using PHP for CGI. To execute " . $script_file . " from command line, you must use PHP for CLI mode.\n";
+	exit();
+}
+
+error_reporting(E_ALL & ~E_DEPRECATED);
+define('PRODUCT', "apstats");
+define('VERSION', "1.0");
+
+$phpstanlevel = 2;
+
+
+print '***** '.constant('PRODUCT').' - '.constant('VERSION').' *****'."\n";
+if (empty($argv[1])) {
+	print 'You must run this tool being into the root of the project.'."\n";
+	print 'Usage:   '.constant('PRODUCT').'.php  pathto/outputfile.html  [--dir-scc=pathtoscc] [--dir-phpstan=pathtophpstan]'."\n";
+	print 'Example: '.constant('PRODUCT').'.php  documents/apstats/index.html --dir-scc=/snap/bin --dir-phpstan=~/git/phpstan/htdocs/includes/bin';
+	exit(0);
+}
+
+$outputpath = $argv[1];
+$outputdir = dirname($outputpath);
+$outputfile = basename($outputpath);
+
+if (!is_dir($outputdir)) {
+	print 'Error: dir '.$outputdir.' does not exists or is not writable'."\n";
+	exit(1);
+}
+
+$dirscc = '';
+$dirphpstan = '';
+
+$i = 0;
+while ($i < $argc) {
+	$reg = array();
+	if (preg_match('/--dir-scc=(.*)$/', $argv[$i], $reg)) {
+		$dirscc = $reg[1];
+	}
+	if (preg_match('/--dir-phpstan=(.*)$/', $argv[$i], $reg)) {
+		$dirphpstan = $reg[1];
+	}
+	$i++;
+}
+
+$timestart = time();
+
+// Count lines of code of Dolibarr itself
+/*
+$commandcheck = 'cloc . --exclude-dir=includes --exclude-dir=custom --ignore-whitespace --vcs=git';
+$resexec = shell_exec($commandcheck);
+$resexec = (int) (empty($resexec) ? 0 : trim($resexec));
+
+
+// Count lines of code of external dependencies
+$commandcheck = 'cloc htdocs/includes --ignore-whitespace --vcs=git';
+$resexec = shell_exec($commandcheck);
+$resexec = (int) (empty($resexec) ? 0 : trim($resexec));
+*/
+
+// Count lines of code of application
+$commandcheck = ($dirscc ? $dirscc.'/' : '').'scc . --exclude-dir=htdocs/includes,htdocs/custom,htdocs/theme/common/fontawesome-5,htdocs/theme/common/octicons';
+print 'Execute SCC to count lines of code in project: '.$commandcheck."\n";
+$output_arrproj = array();
+$resexecproj = 0;
+exec($commandcheck, $output_arrproj, $resexecproj);
+
+
+// Count lines of code of dependencies
+$commandcheck = ($dirscc ? $dirscc.'/' : '').'scc htdocs/includes htdocs/theme/common/fontawesome-5 htdocs/theme/common/octicons';
+print 'Execute SCC to count lines of code in dependencies: '.$commandcheck."\n";
+$output_arrdep = array();
+$resexecdep = 0;
+exec($commandcheck, $output_arrdep, $resexecdep);
+
+
+// Get technical debt
+$commandcheck = ($dirphpstan ? $dirphpstan.'/' : '').'phpstan --level='.$phpstanlevel.' -v analyze -a build/phpstan/bootstrap.php --memory-limit 5G --error-format=github';
+print 'Execute PHPStan to get the technical debt: '.$commandcheck."\n";
+$output_arrtd = array();
+$resexectd = 0;
+exec($commandcheck, $output_arrtd, $resexectd);
+
+$arrayoflineofcode = array();
+$arraycocomo = array();
+$arrayofmetrics = array(
+	'proj' => array('Bytes' => 0, 'Files' => 0, 'Lines' => 0, 'Blanks' => 0, 'Comments' => 0, 'Code' => 0, 'Complexity' => 0),
+	'dep' => array('Bytes' => 0, 'Files' => 0, 'Lines' => 0, 'Blanks' => 0, 'Comments' => 0, 'Code' => 0, 'Complexity' => 0)
+);
+
+// Analyse $output_arrproj
+foreach (array('proj', 'dep') as $source) {
+	print 'Analyze SCC result for lines of code for '.$source."\n";
+	if ($source == 'proj') {
+		$output_arr = &$output_arrproj;
+	} elseif ($source == 'dep') {
+		$output_arr = &$output_arrdep;
+	} else {
+		print 'Bad value for $source';
+		die();
+	}
+
+	foreach ($output_arr as $line) {
+		if (preg_match('/^(───|Language|Total)/', $line)) {
+			continue;
+		}
+
+		//print $line."<br>\n";
+
+		if (preg_match('/^Estimated Cost.*\$(.*)/i', $line, $reg)) {
+			$arraycocomo[$source]['currency'] = preg_replace('/[^\d\.]/', '', str_replace(array(',', ' '), array('', ''), $reg[1]));
+		}
+		if (preg_match('/^Estimated Schedule Effort.*\s([\d\s,]+)/i', $line, $reg)) {
+			$arraycocomo[$source]['effort'] = str_replace(array(',', ' '), array('.', ''), $reg[1]);
+		}
+		if (preg_match('/^Estimated People.*\s([\d\s,]+)/i', $line, $reg)) {
+			$arraycocomo[$source]['people'] = str_replace(array(',', ' '), array('.', ''), $reg[1]);
+		}
+		if (preg_match('/^Processed\s(\d+)\s/i', $line, $reg)) {
+			$arrayofmetrics[$source]['Bytes'] = $reg[1];
+		}
+
+		if (preg_match('/^(.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/', $line, $reg)) {
+			$arrayoflineofcode[$source][$reg[1]]['Files'] = $reg[2];
+			$arrayoflineofcode[$source][$reg[1]]['Lines'] = $reg[3];
+			$arrayoflineofcode[$source][$reg[1]]['Blanks'] = $reg[4];
+			$arrayoflineofcode[$source][$reg[1]]['Comments'] = $reg[5];
+			$arrayoflineofcode[$source][$reg[1]]['Code'] = $reg[6];
+			$arrayoflineofcode[$source][$reg[1]]['Complexity'] = $reg[7];
+		}
+	}
+
+	if (!empty($arrayoflineofcode[$source])) {
+		foreach ($arrayoflineofcode[$source] as $key => $val) {
+			$arrayofmetrics[$source]['Files'] += $val['Files'];
+			$arrayofmetrics[$source]['Lines'] += $val['Lines'];
+			$arrayofmetrics[$source]['Blanks'] += $val['Blanks'];
+			$arrayofmetrics[$source]['Comments'] += $val['Comments'];
+			$arrayofmetrics[$source]['Code'] += $val['Code'];
+			$arrayofmetrics[$source]['Complexity'] += $val['Complexity'];
+		}
+	}
+}
+
+$timeend = time();
+
+
+/*
+ * View
+ */
+
+$html = '<html>'."\n";
+$html .= '<meta charset="utf-8">'."\n";
+$html .= '<meta http-equiv="refresh" content="300">'."\n";
+$html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">'."\n";
+$html .= '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha512-q3eWabyZPc1XTCmF+8/LuE1ozpg5xxn7iO89yfSOd5/oKvyqLngoNGsx8jq92Y8eXJ/IRxQbEC+FGSYxtk2oiw==" crossorigin="anonymous" referrerpolicy="no-referrer" />'."\n";
+$html .= '<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js" integrity="sha512-3gJwYpMe3QewGELv8k/BX9vcqhryRdzRMxVfq6ngyWXwo03GFEzjsUm8Q7RZcHPHksttq7/GFoxjCVUjkjvPdw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>'."\n";
+$html .= '
+<style>
+body {
+	margin: 10px;
+	margin-left: 50px;
+	margin-right: 50px;
+}
+
+h1 {
+	font-size: 1.5em;
+	font-weight: bold;
+	padding-top: 5px;
+	padding-bottom: 5px;
+	margin-top: 5px;
+	margin-bottom: 5px;
+}
+
+header {
+	text-align: center;
+}
+header, section.chapter {
+	margin-top: 10px;
+	margin-bottom: 10px;
+	padding: 10px;
+}
+
+table {
+	border-collapse: collapse;
+}
+th,td {
+	padding-top: 5px;
+	padding-bottom: 5px;
+	padding-left: 10px;
+	padding-right: 10px;
+}
+.left {
+	text-align: left;
+}
+.right {
+	text-align: right;
+}
+.nowrap {
+	white-space: nowrap;
+}
+.opacitymedium {
+	opacity: 0.5;
+}
+.centpercent {
+	width: 100%;
+}
+.hidden {
+	display: none;
+}
+.trgroup {
+	background-color: #EEE;
+}
+.seedetail {
+	color: #000088;
+	cursor: pointer;
+}
+.box {
+	padding: 20px;
+	font-size: 1.2em;
+	margin-top: 10px;
+	margin-bottom: 10px;
+	width: 200px;
+}
+.box.inline-box {
+    display: inline-block;
+	text-align: center;
+	margin-left: 10px;
+}
+.back1 {
+	background-color: #884466;
+	color: #FFF;
+}
+.back2 {
+	background-color: #664488;
+	color: #FFF;
+}
+
+div.fiche>form>div.div-table-responsive {
+    min-height: 392px;
+}
+div.fiche>form>div.div-table-responsive, div.fiche>form>div.div-table-responsive-no-min {
+    overflow-x: auto;
+}
+.div-table-responsive {
+    line-height: 120%;
+}
+.div-table-responsive, .div-table-responsive-no-min {
+    overflow-x: auto;
+    min-height: 0.01%;
+}
+
+
+/* Force values for small screen 767 */
+@media only screen and (max-width: 767px)
+{
+	body {
+		margin: 5px;
+		margin-left: 5px;
+		margin-right: 5px;
+	}
+}
+
+
+</style>'."\n";
+
+$html .= '<body>'."\n";
+
+$html .= '<header>'."\n";
+$html .= '<h1>Advanced Project Statistics</h1>'."\n";
+$currentDate = date("Y-m-d H:i:s"); // Format: Year-Month-Day Hour:Minute:Second
+$html .= '<span class="opacitymedium">Generated on '.$currentDate.' in '.($timeend - $timestart).' seconds</span>'."\n";
+$html .= '</header>'."\n";
+
+$html .= '<section class="chapter" id="linesofcode">'."\n";
+$html .= '<h2>Lines of code</h2>'."\n";
+
+$html .= '<div class="div-table-responsive">'."\n";
+$html .= '<table class="centpercent">';
+$html .= '<tr class="loc">';
+$html .= '<th class="left">Language</th>';
+$html .= '<th class="right">Bytes</th>';
+$html .= '<th class="right">Files</th>';
+$html .= '<th class="right">Lines</th>';
+$html .= '<th class="right">Blanks</th>';
+$html .= '<th class="right">Comments</th>';
+$html .= '<th class="right">Code</th>';
+//$html .= '<td class="right">'.$val['Complexity'].'</td>';
+$html .= '</tr>';
+foreach (array('proj', 'dep') as $source) {
+	$html .= '<tr class="trgroup" id="source'.$source.'">';
+	if ($source == 'proj') {
+		$html .= '<td>All files without dependencies';
+	} elseif ($source == 'dep') {
+		$html .= '<td>All files of dependencies only';
+	}
+	$html .= ' &nbsp; &nbsp; <span class="seedetail" data-source="'.$source.'">(See detail per file type...)</span>';
+	$html .= '<td class="right">'.formatNumber($arrayofmetrics[$source]['Bytes']).'</td>';
+	$html .= '<td class="right">'.formatNumber($arrayofmetrics[$source]['Files']).'</td>';
+	$html .= '<td class="right">'.formatNumber($arrayofmetrics[$source]['Lines']).'</td>';
+	$html .= '<td class="right">'.formatNumber($arrayofmetrics[$source]['Blanks']).'</td>';
+	$html .= '<td class="right">'.formatNumber($arrayofmetrics[$source]['Comments']).'</td>';
+	$html .= '<td class="right">'.formatNumber($arrayofmetrics[$source]['Code']).'</td>';
+	$html .= '<td></td>';
+	$html .= '</tr>';
+	if (!empty($arrayoflineofcode[$source])) {
+		foreach ($arrayoflineofcode[$source] as $key => $val) {
+			$html .= '<tr class="loc hidden source'.$source.' language'.str_replace(' ', '', $key).'">';
+			$html .= '<td>'.$key.'</td>';
+			$html .= '<td class="right"></td>';
+			$html .= '<td class="right nowrap">'.(empty($val['Files']) ? '' : formatNumber($val['Files'])).'</td>';
+			$html .= '<td class="right nowrap">'.(empty($val['Lines']) ? '' : formatNumber($val['Lines'])).'</td>';
+			$html .= '<td class="right nowrap">'.(empty($val['Blanks']) ? '' : formatNumber($val['Blanks'])).'</td>';
+			$html .= '<td class="right nowrap">'.(empty($val['Comments']) ? '' : formatNumber($val['Comments'])).'</td>';
+			$html .= '<td class="right nowrap">'.(empty($val['Code']) ? '' : formatNumber($val['Code'])).'</td>';
+			//$html .= '<td class="right">'.(empty($val['Complexity']) ? '' : $val['Complexity']).'</td>';
+			$html .= '<td class="nowrap">TODO graph here...</td>';
+			$html .= '</tr>';
+		}
+	}
+}
+
+$html .= '<tr class="trgroup">';
+$html .= '<td class="left">Total</td>';
+$html .= '<td class="right nowrap">'.formatNumber($arrayofmetrics['proj']['Bytes'] + $arrayofmetrics['dep']['Bytes']).'</td>';
+$html .= '<td class="right nowrap">'.formatNumber($arrayofmetrics['proj']['Files'] + $arrayofmetrics['dep']['Files']).'</td>';
+$html .= '<td class="right nowrap">'.formatNumber($arrayofmetrics['proj']['Lines'] + $arrayofmetrics['dep']['Lines']).'</td>';
+$html .= '<td class="right nowrap">'.formatNumber($arrayofmetrics['proj']['Blanks'] + $arrayofmetrics['dep']['Blanks']).'</td>';
+$html .= '<td class="right nowrap">'.formatNumber($arrayofmetrics['proj']['Comments'] + $arrayofmetrics['dep']['Comments']).'</td>';
+$html .= '<td class="right nowrap">'.formatNumber($arrayofmetrics['proj']['Code'] + $arrayofmetrics['dep']['Code']).'</td>';
+//$html .= '<td>'.$arrayofmetrics['Complexity'].'</td>';
+$html .= '<td></td>';
+$html .= '</tr>';
+$html .= '</table>';
+$html .= '</div>';
+
+$html .= '</section>'."\n";
+
+$html .= '<section class="chapter" id="projectvalue">'."\n";
+$html .= '<h2>Project value</h2><br>'."\n";
+$html .= '<div class="box inline-box back1">';
+$html .= 'COCOMO (Basic organic model) value:<br>';
+$html .= '<b>$'.formatNumber((empty($arraycocomo['proj']['currency']) ? 0 : $arraycocomo['proj']['currency']) + (empty($arraycocomo['dep']['currency']) ? 0 : $arraycocomo['dep']['currency']), 2).'</b>';
+$html .= '</div>';
+$html .= '<div class="box inline-box back2">';
+$html .= 'COCOMO (Basic organic model) effort<br>';
+$html .= '<b>'.formatNumber($arraycocomo['proj']['people'] * $arraycocomo['proj']['effort'] + $arraycocomo['dep']['people'] * $arraycocomo['dep']['effort']);
+$html .= ' monthes people</b><br>';
+$html .= '</section>'."\n";
+
+$tmp = '';
+$nblines = 0;
+foreach ($output_arrtd as $line) {
+	$reg = array();
+	//print $line."\n";
+	preg_match('/^::error file=(.*),line=(\d+),col=(\d+)::(.*)$/', $line, $reg);
+	if (!empty($reg[1])) {
+		$tmp .= '<tr><td>'.$reg[1].'</td><td>'.$reg[2].'</td><td>'.$reg[4].'</td></tr>'."\n";
+		$nblines++;
+	}
+}
+
+$html .= '<section class="chapter" id="technicaldebt">'."\n";
+$html .= '<h2>Technical debt <span class="opacitymedium">(PHPStan level '.$phpstanlevel.' -> '.$nblines.' warnings)</span></h2><br>'."\n";
+$html .= '<div class="div-table-responsive">'."\n";
+$html .= '<table class="list_technical_debt">'."\n";
+$html .= '<tr><td>File</td><td>Line</td><td>Type</td></tr>'."\n";
+$html .= $tmp;
+$html .= '</table>';
+$html .= '</div>';
+$html .= '</section>'."\n";
+
+$html .= '
+<script>
+$(document).ready(function() {
+$( ".seedetail" ).on( "click", function() {
+	var source = $(this).attr("data-source");
+  	console.log("Click on "+source);
+	jQuery(".source"+source).toggle();
+} );
+});
+</script>
+';
+$html .= '</body>';
+$html .= '</html>';
+
+$fh = fopen($outputpath, 'w');
+if ($fh) {
+	fwrite($fh, $html);
+	fclose($fh);
+
+	print 'Generation of output file '.$outputfile.' done.'."\n";
+} else {
+	print 'Failed to open '.$outputfile.' for ouput.'."\n";
+}
+
+
+/**
+ * function to format a number
+ *
+ * @param	string|int		$number			Number to format
+ * @param	int				$nbdec			Number of decimal digits
+ * @return	string							Formated string
+ */
+function formatNumber($number, $nbdec = 0)
+{
+	return number_format($number, 0, '.', ' ');
+}

+ 142 - 0
dev/tools/codespell/codespell-lines-ignore.txt

@@ -4,6 +4,7 @@
 											// Conversion du PDF en image png si fichier png non existant
 											// Save a stripe payment was done in realy life so later we will be able to force a commit on recorded payments
 											// To make a Stripe SEPA payment request, we must have the payment mode source already saved into societe_rib and retreived with ->sepaStripe
+										// If old value crypted in database is same than submited new value, it means we don't change it, so we don't update.
 										//break;	// No break for sortfield and sortorder so we can cumulate fields (is it realy usefull ?)
 									$errmsg = 'Failed to retreive paymentintent or charge from id';
 									$minifile = getImageFileNameForSize($fileinfo['basename'], '_mini'); // For new thumbs using same ext (in lower case howerver) than original
@@ -35,15 +36,19 @@
 								// remove invalid value, as it didnt match anything
 								// we dont use the rank from orderline because we may have lines from several orders
 								dol_syslog("makeStripeSepaRequest get stripe connet account", LOG_DEBUG);
+								if (isset($this->oldcopy->array_options["options_".$key]) && $this->array_options["options_".$key] == $this->oldcopy->array_options["options_".$key]) {	// If old value crypted in database is same than submited new value, it means we don't change it, so we don't update.
 								print '<input type="hidden" class="amount" name="'.$namef.'" value="'.dol_escape_htmltag(GETPOST($namef)).'">'; // class is requied to be used by javascript callForResult();
+								print '<input type="text" size="8" class="amount" name="'.$namef.'" value="'.dol_escape_htmltag(GETPOST($namef)).'">'; // class is requied to be used by javascript callForResult();
 								print 'jQuery("select[name=\''.$paramkey.'\']").focus();'."\n"; // Not really usefull, but we keep it in case of.
 							$filles[$obj->fk_categorie_fille] = 1; // Set record for this child
 							$i2++; // a criteria for 1 more field was added to string (we can add several citeria for the same field as it is a multiselect search criteria)
 							$invoiceid = -1; // There is more than one invoice payed by this payment
 							$line->pa_ht = $line->pa_ht; // we choosed to have buy/cost price always positive, so no revert of sign here
 							$mesg .= '<br>Unkown Error, please refers to your administrator';
+							$result = $adh->setPassword($user, $this->pass, (!getDolGlobalString('DATABASE_PWD_ENCRYPTED') ? 0 : 1), 1); // Cryptage non gere dans module adherent
 							$result = $adh->setPassword($user, $this->pass, (empty($conf->global->DATABASE_PWD_ENCRYPTED) ? 0 : 1), 1); // Cryptage non gere dans module adherent
 							$result = $line->insert(0, 1); // When creating credit note with same lines than source, we must ignore error if discount alreayd linked
+							$s = array();    // Array with size of each page. Exemple array(w'=>210, 'h'=>297);
 							$value = ((!empty($this->array_options) && array_key_exists("options_".$key.$keysuffix, $this->array_options)) ? $this->array_options["options_".$key.$keysuffix] : null); // Value may be cleaned or formated later
 							$ways = $c->print_all_ways(' &gt;&gt; ', 'none', 0, 1); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formated text
 							'capture'  => true, // Charge immediatly
@@ -66,10 +71,13 @@
 							// Show subproducts of product (not recommanded)
 							// TODO Que faire si update echoue car on update avec un login deja existant pour un autre compte.
 							// dont try to send email if no recipient
+							// note: $cs->mandate contians ID of mandate on Stripe side
 							// nous avons au moins une reponse
 							// nous n'avons pas de reponse => n'existe pas
 							//'visible'=>$fille->visible,
 							break; // break for loop incase of error
+							dol_syslog("functions_dolibarr::check_user_password_dolibarr Authentification ok - found old pass in database", LOG_WARNING);
+							dol_syslog("functions_dolibarr::check_user_password_dolibarr Authentification ok - hash ".$cryptType." of pass is ok");
 							hide: { delay: 50 }, 	/* If I enable effect:\'toggle\' here, a bug appears: the tooltip is shown when collpasing a new dir if it was shown before */
 							if ($reg[1] == 'thi') {   // Third-party
 							if (empty($objMod->dictionaries) && !empty($objMod->dictionnaries)) {
@@ -87,6 +95,7 @@
 						$repid[$obj->id] = $label;
 						$reponsesadd = str_split($obj->reponses);
 						$s = array(); 	// Array with size of each page. Exemple array(w'=>210, 'h'=>297);
+						$s = array();    // Array with size of each page. Exemple array(w'=>210, 'h'=>297);
 						$serie[$i] .= 'd' . $i . '.push([' . $x . ', ' . $y . ']);' . "\n";
 						$serie[$i] .= 'd' . $i . '.push({"label":"' . dol_escape_js($legends[$x]) . '", "data":' . $y . '});' . "\n";
 						$sql .= " SET reponses = '".$db->escape($reponsesadd)."'";
@@ -107,6 +116,7 @@
 						// Label mouvement
 						// Login is successfull with this method
 						// Lot/serie
+						// Message-ID=A, In-Reply-To=B, References=B and message can BE an answer but may be NOT (for example a transfer of an email rewriten)
 						// Message-ID=A, In-Reply-To=B, References=B and message can BE an answer or NOT (a transfer rewriten)
 						// Not a recongized record
 						// Note: This suppose that "pass_indatabase_crypted" is a md5 (guaranted by the previous test if "(empty($conf->global->MAIN_SECURITY_HASH_ALGO))"
@@ -153,6 +163,9 @@
 					$heigth = $tmp[3];
 					$info[$conf->global->LDAP_FIELD_PASSWORD] = $this->pass_indatabase; // $this->pass_indatabase = mot de passe non crypte
 					$info[$conf->global->LDAP_MEMBER_FIELD_PASSWORD] = $this->pass_indatabase; // $this->pass_indatabase = mot de passe non crypte
+					$info[getDolGlobalString('LDAP_FIELD_PASSWORD')] = $this->pass_indatabase; // $this->pass_indatabase = mot de passe non crypte
+					$info[getDolGlobalString('LDAP_MEMBER_FIELD_PASSWORD')] = $this->pass_indatabase; // $this->pass_indatabase = mot de passe non crypte
+					$objectoffield = $object; //For compatibily with the computed formula
 					$optstart .= ' data-tvatx-formated="' . dol_escape_htmltag(price($objp->tva_tx, 0, $langs, 1, -1, 2)) . '"';
 					$optstart .= ' data-tvatx-formated="'.dol_escape_htmltag(price($objp->tva_tx, 0, $langs, 1, -1, 2)).'"';
 					$outprice_ht = price($objp->price);			// formated for langage user because is inserted into input field
@@ -167,6 +180,7 @@
 					$this->user_closing = $cluser;
 					$this->user_cloture = $cluser;
 					$usertime = 0; // We dont modify date because we want to have date into memory datep and datef stored as GMT date. Compensation will be done during output.
+					$valuetoshow = ucfirst($value); // Par defaut
 					$ways = $c->print_all_ways(' &gt;&gt; ', 'none', 0, 1); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formated text
 					&& $obj->status != $tmpobject::STATUS_ABANDONED	    // Not abandonned
 					'adress'=>$obj->adress,
@@ -174,6 +188,7 @@
 					'transparency'=>$object->transparency, // Force transparency on onwer from preoperty of event
 					/* Remove selected id as soon as we type or delete a char (it means old selection is wrong). Use keyup/down instead of change to avoid loosing the product id. This is needed only for select of predefined product */
 					/*case 'select':	// Not required, we chosed value='0' for undefined values
+					// Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again whith the caller
 					// Batch number managment
 					// Calculcate number of days consumed
 					// Cas des factures liees par un autre objet (ex: commande)
@@ -183,6 +198,7 @@
 					// For exemple, we may have error: 'No such customer: cus_XXXXX; a similar object exists in live mode, but a test mode key was used to make this request.'
 					// For full day events, date are also GMT but they wont but converted using tz during output
 					// If translation exists, we use it, otherwise, we use tha had coded label
+					// If we want to closed payed invoices
 					// Line dates planed
 					// Message-ID=A, In-Reply-To=B, References=B and message can BE an answer or NOT (a transfer rewriten)
 					// Note: $obj->halfday is  0:Full days, 2:Sart afternoon end morning, -1:Start afternoon, 1:End morning
@@ -191,8 +207,10 @@
 					// On selectionne les groupes auquel fait parti le user
 					// On verifie l'emplacement du modele
 					// Onwer
+					// Option to reload page to retrieve customer informations. Note, this clear other input
 					// Produit non deja existant
 					// Search submenu fot this mainmenu entry
+					// Selection of all product stock mouvements that contains batchs
 					// Si safe_mode on et command hors du parametre exec, on a un fichier out vide donc errormsg vide
 					// Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
 					// Strip off the beggining '<'
@@ -213,6 +231,7 @@
 					// at a time, and thats just stupid, so lets just hope this doesnt appear anywhere in the actual data
 					// de ceux du produit par defaut (par exemple si pays different entre vendeur et acheteur).
 					// if we have a PROJECTLEADER contact and we dont use it as recipient we store the contact object for later use
+					// multiselect array convert html entities into options tags, even if we dont want this, so we encode them a second time
 					// save curent cell padding
 					//$sql .= " AND (status <> 3 OR close_code <> 'abandon')";		// Not abandonned for undefined reason
 					//Esle it's separated key/value and coma list
@@ -231,6 +250,7 @@
 					error="Database $dbname NOT successfully droped. You have to do it manually."
 					foreach ($cats as $fille) {
 					if (!empty($childrens)) {
+					if (!getDolGlobalString('PROJECT_DISABLE_UNLINK_FROM_OVERVIEW') || $user->admin) {		// PROJECT_DISABLE_UNLINK_FROM_OVERVIEW is empty by defaut, so this test true
 					if (!isset($filles[$obj->fk_categorie_fille])) {	// Only one record as child (a child has only on parent).
 					if ($conf->file->mailing_limit_sendbyweb != '-1') {  // MAILING_LIMIT_SENDBYWEB was set to -1 in database, but it is allowed ot increase it.
 					if ($login && $login != '--bad-login-validity--') {	// Login is successfull
@@ -246,20 +266,29 @@
 					jQuery("#mouvement").trigger("change");
 					print "ERROR: Failed to include file '".$filephp."'. Try to edit and re-save page ith this ID.";
 					print '<div class="div-table-responsive-no-min">'; // You can use div-table-responsive-no-min if you dont need reserved height for your table
+					setEventMessages($langs->trans('Bad value for email, emai lwas not verified by Google'), null, 'errors');
 					} else { // Pour les autres schémas, les membres sont listés sous forme de DN complets
 				 $newmenu->add("/compta/stats/comp.php?leftmenu=report","Transforme",2,$user->hasRight('compta',  'resultat', 'lire'));
 				 * Section Creditor (sepa Crediteurs bloc lines)
 				 * Section Debitor (sepa Debiteurs bloc lines)
 				"confirm" => $confirmnow, // Do not confirm immediatly during creation of intent
+				"confirm" => $confirmnow, // try to confirm immediatly after create (if conditions are ok)
 				$ErrorLongMsg = "Session expired. Can't retreive PaymentType. Payment has not been validated.";
 				$accountparent->account_number = $obj->account_number2; // Sotre an account number for output
 				$action = 'transfert';
 				$allways = $parent->get_all_ways();
 				$bugbaseurl .= urlencode("## [Attached files](https://help.github.com/articles/issue-attachments) (Screenshots, screencasts, dolibarr.log, debugging informations…)\n");
 				$childs[] = array_combine($keys, $values);
+				$curent = getDolGlobalString($thisTypeConfName, getDolGlobalString('FACTURE_ADDON_PDF'));
+				$filterabsolutediscount = "fk_facture_source IS NULL"; // If we want deposit to be substracted to payments only and not to total of final invoice
+				$filtercreditnote = "fk_facture_source IS NOT NULL"; // If we want deposit to be substracted to payments only and not to total of final invoice
 				$info[$conf->global->LDAP_FIELD_PASSWORD] = $this->pass; // this->pass = mot de passe non crypte
 				$info[$conf->global->LDAP_MEMBER_FIELD_PASSWORD] = $this->pass; // this->pass = mot de passe non crypte
+				$info[getDolGlobalString('LDAP_FIELD_PASSWORD')] = $this->pass; // this->pass = mot de passe non crypte
+				$info[getDolGlobalString('LDAP_MEMBER_FIELD_PASSWORD')] = $this->pass; // this->pass = mot de passe non crypte
 				$initialY = $tab_top + 7;
+				$invoicestatic->statut = $obj->fk_statut;	// For backward comaptibility
+				$jsListType .= (!empty($jsListType) ? ',' : '').'"'.$type.'":"'.$curent.'"';
 				$newmenu->add("/projet/list.php?leftmenu=projets".($search_project_user ? '&search_project_user='.$search_project_user : ''), $langs->trans("List"), 1, $showmode, '', 'project', 'list');
 				$newmenu->add("/projet/list.php?leftmenu=projets".($search_project_user ? '&search_project_user='.$search_project_user : '').'&search_status=99', $langs->trans("List"), 1, $showmode, '', 'project', 'list');
 				$object->actionmsg = dol_concatdesc($object->actionmsg, "\n".$langs->transnoentities("AttachedFiles").': '.$attachs);
@@ -268,13 +297,16 @@
 				$paramfortooltiptd .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)) . '"'; // Attribut to put on td tag to store tooltip
 				$paramfortooltiptd .= ' title="'.($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)).'"'; // Attribut to put on td tag to store tooltip
 				$showfield = 1; // Par defaut
+				$societe->note_private = "Default customer automaticaly created by Point Of Sale module activation. Can be used as the default generic customer in the Point Of Sale setup. Can also be edited or removed if you don't need a generic customer.";
 				$tagdatabase = true; // We don't know what it was before, so now we consider we are version choosed.
 				$this->category->childs[] = $this->_cleanObjectDatas($cat);
 				$this->civility_id = $obj->civility_code; // Bad. Kept for backard compatibility
 				$this->date_delivery        = $this->db->jdate($obj->date_delivery); // Date planed
 				$this->liste_array = $repid;
+				$this->statut = self::STATUS_DRAFT;	// dperecated
 				$this->stringtoshow .= $serie[$i] . "\n";
 				$this->stringtoshow .= $this->mirrorGraphValues ? '[' . -$serie[$i] . ',' . $serie[$i] . ']' : $serie[$i];
+				$this->stringtoshow .= $this->mirrorGraphValues ? '[-' . $serie[$i] . ',' . $serie[$i] . ']' : $serie[$i];
 				$this->stringtoshow .= '  data: [' . $serie[$i] . ']';
 				$this->stringtoshow .= '<!-- Serie ' . $i . ' -->' . "\n";
 				$tmp = array('id_users'=>$obj->id_users, 'nom'=>$obj->name, 'reponses'=>$obj->reponses);
@@ -287,8 +319,10 @@
 				/* Removed due to awful harcoded values
 				/*case 'select':	// Not required, we chosed value='0' for undefined values
 				// $pdf->GetY() here can't be used. It is bottom of the second addresse box but first one may be higher
+				// Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again whith the caller
 				// Add entry into bank accoun
 				// Add field of attribut
+				// Add link to the Direct Debit if invoice redused ('InvoiceRefused') in bank_url
 				// Ajout de l'utilisateur dans le groupe
 				// Batch number managment
 				// By default, electronic transfert from bank to bank
@@ -307,6 +341,7 @@
 				// Fonctions de conversion non presente dans ce PHP
 				// Get lines of sources alread delivered
 				// Get next free nuber for the ref of bon
+				// Get next free nunber for the ref of bon prelevement
 				// If create form is coming from same page, it means that post was sent but an error occured
 				// If not abandonned
 				// If option "one bill per third" is set, and an invoice for this thirdparty was already created, we re-use it.
@@ -323,10 +358,12 @@
 				// No temp directory provided, so we are not able to support convertion of data:image into physical images.
 				// On verifie l'emplacement du modele
 				// Option to reload page to retrieve customer informations. Note, this clear other input
+				// Or set status to "In porgress" if the client has answered and if the ticket has started
 				// Ordre SQL ne necessitant pas de connexion a une base (exemple: CREATE DATABASE)
 				// Payment informations
 				// Proprietes particulieres a facture de remplacement
 				// Recursive call if there is childs to child
+				// Retreive lines
 				// See example with selectsearchbox.php. This case is reserverd for the selectesearchbox.php so we can
 				// Seperate "Real Name" from eMail address
 				// Should not happend. Entries are added
@@ -336,11 +373,13 @@
 				// Subscription informations
 				// TODO : if base exists in unit dictionary table, remove this convertion exception and update convertion infos in database.
 				// TODO Can we set it to submited ?
+				// TODO Replace this with a checkbox for each payment mode: "Send request to XXX immediatly..."
 				// TODO We can't, we dont' have full path of file, only last_main_doc and ->element, so we must first rebuild full path $destfull
 				// This convert an embedd file with src="/viewimage.php?modulepart... into a cid link
 				// This make 12 calls for each accountancy account (12 monthes M)
 				// Validate immediatly the order
 				// Warning, the function may add a LF so we are forced to trim to compare with old $out without having always a difference and an infinit loop.
+				// We chack if file exists
 				// We check if lines of invoice are not already transfered into accountancy
 				// We dont want on all entities, we delete all and current
 				// We must filter on assignement table
@@ -369,8 +408,10 @@
 				continue; // The field was not submited to be saved
 				dol_syslog("The user login has a validity between [".$user->datestartvalidity." and ".$user->dateendvalidity."], curren date is ".dol_now());
 				dol_syslog("functions_isallowed::check_user_api_key Authentication KO for '".$login."': The user login has a validity between [".$fuser->datestartvalidity." and ".$fuser->dateendvalidity."], curren date is ".dol_now());
+				dol_syslog('Bad password, connexion refused (see a previous notice message for more info)', LOG_NOTICE);
 				dol_syslog('Bad password, connexion refused', LOG_DEBUG);
 				dol_syslog('Bad value for code, connexion refused');
+				dol_syslog('Bad value for code, connexion refused', LOG_NOTICE);
 				dol_syslog('Call fetch_barcode with barcode_type not defined and cant be guessed', LOG_WARNING);
 				foreach ($allways as $way) {
 				foreach ($legends as $val) {	// Loop on each serie
@@ -392,7 +433,11 @@
 				print '<div class="inline-block marginrightonly">';	// Button include dynamic contant
 				return $this->trigger(9, "an unexpected error occured");
 				} // don't wast resourses if we don't need them...
+				} elseif ($links[$key]['type'] == 'banktransfert') {	// transfert between 1 local account and another local account
 			 *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
+			 *  @param  array	$menu_array_before 	       	Table of menu entries to show before entries of menu handler. This param is deprectaed and must be provided to ''.
+			 * @param 	string 	$head			 Optionnal head lines
+			 * Ouput html header of a page. It calls also top_httphead()
 			$action = ''; // Do not show form post if there was at least one successfull sent
 			$action = 'transfert';
 			$alreadyfound = array($id=>1); // We init array of found object to start of tree, so if we found it later (should not happened), we stop immediatly
@@ -424,6 +469,7 @@
 			$paramfortooltiptd = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribut to put on td text tag
 			$paramfortooltiptd = ($extracss ? ' class="'.$extracss.'"' : '').($extrastyle ? ' style="'.$extrastyle.'"' : ''); // Attribut to put on td text tag
 			$pdf->MultiCell($this->posxdiscount - $this->posxunit, 2, $outputlangs->transnoentities("Label Mouvement"), '', 'C');
+			$pdf->MultiCell(190, 5, $outputlangs->transnoentities("Informations"), '', 'L');*/
 			$pdf->SetXY($this->getColumnContentXStart($colKey), $curY); // Set curent position
 			$result -= $amountToBreakdown; // And canceled substraction has been replaced by breakdown
 			$result = $ldap->add($dn, $info, $user); // Wil fail if already exists
@@ -444,6 +490,9 @@
 			$sql = 'SELECT s.reponses';
 			$sql2 .= " SET reponses = '".$db->escape($newcar)."'";
 			$this->category->childs = array();
+			$this->db->query('INSERT INTO '.MAIN_DB_PREFIX.'c_type_contact(rowid, element, source, code, libelle, active, module, position) VALUES('.((int) $nextid).', "StockTransfer", "external", "STDEST", "Contact destinataire transfert de stocks", 1, NULL, 0)');
+			$this->db->query('INSERT INTO '.MAIN_DB_PREFIX.'c_type_contact(rowid, element, source, code, libelle, active, module, position) VALUES('.((int) $nextid).', "StockTransfer", "external", "STFROM", "Contact expéditeur transfert de stocks", 1, NULL, 0)');
+			$this->db->query('INSERT INTO '.MAIN_DB_PREFIX.'c_type_contact(rowid, element, source, code, libelle, active, module, position) VALUES('.((int) $nextid).', "StockTransfer", "internal", "STRESP", "Responsable du transfert de stocks", 1, NULL, 0)');
 			$this->emetteur->country_code = substr($langs->defaultlang, -2); // Par defaut, si n'etait pas defini
 			$this->error = 'update_note was called on objet with property table_element not defined';
 			$this->modelpdf = $modelpdf; // For bakward compatibility
@@ -464,11 +513,13 @@
 			'sr.type' => "Type ban is defaut",
 			/* Disabled because bcc must remain by defintion not visible
 			// $_POST contains fk_commandefourndet_X_Y    where Y is num of product line and X is number of splitted line
+			// $new_array_options will be used for direct update, so must contains formated data for the UPDATE.
 			// $opt['filter[id]'] contais list of product id that are result of search
 			// 1 - Association des utilisateurs du groupe LDAP au groupe Dolibarr
 			// 2 - Suppression des utilisateurs du groupe Dolibarr qui ne sont plus dans le groupe LDAP
 			// A redirect is added if API call successfull
 			// Action according to choosed sending method
+			// Action according to the choosed sending method
 			// Add entry into bank accoun
 			// Add personnal information
 			// Adding <b> may convert the original string into a HTML string. Sowe have to first
@@ -486,6 +537,7 @@
 			// Chercher un contact existant avec cette adresse email
 			// Color of earch arc
 			// Defaut
+			// Delivery date planed
 			// Discard check of mandatory fiedls for other fields
 			// Documents are stored above the web pages root to prevent being downloaded without authentification
 			// Dont't use entity if you use rowid
@@ -523,11 +575,13 @@
 			// On verifie si aucun paiement n'a ete effectue
 			// On verifie si la balise prefix est utilisee
 			// On verifie si la facture a des paiements
+			// Option to reload page to retrieve customer informations.
 			// Option to reload page to retrieve customer informations. Note, this clear other input
 			// Parameteres execution
 			// Programm next run
 			// Replace espacing \' by ''.
 			// Replace protected special codes with matching number of _ as wild card caracter
+			// Retained warranty : usualy use on construction industry
 			// Select des informations du projet
 			// Set default encryption to yes, generate a salt and set default encryption algorythm (but only if there is no user yet into database)
 			// Show var initialized by include fo paypal lib at begin of this file
@@ -562,6 +616,7 @@
 			// add variables subtitutions ticket
 			// count the orders to ship in theorical stock when some are already removed by invoice validation.
 			// de ceux du produit par defaut (par exemple si pays different entre vendeur et acheteur).
+			// employee informations
 			// et on met la quantité de la ligne dans la limite du "budget" indiqué par dispatch.qty
 			// if "frequency" is empty or = 0, the reccurence is disabled
 			// mise a jour des reponses utilisateurs dans la base
@@ -570,10 +625,13 @@
 			// on verifie si l'objet est utilisé
 			// only if socid not filled else it's allready done upper
 			// procédure de remplacement de la table pour ajouter la contrainte
+			// reload page to retrieve customer informations
+			// reload page to retrieve supplier informations
 			// save curent cell padding
 			// si le curseur est un booleen on retourne la valeur 0
 			// this conf is actually hidden, by default we use 10% for "be carefull or warning"
 			//Add hook to filter on user (for exemple on usergroup define in custom modules)
+			//Another call for easy debugg
 			//Calcultate new task end date with difference between origin proj end date and origin task end date
 			//Calcultate new task start date with difference between old proj start date and origin task start date
 			//Calcultate new task start date with difference between origin proj start date and origin task start date
@@ -585,9 +643,11 @@
 			//XXX: Should be done just befor commit no ?
 			//but the note is saved, so just add a notification will be enought
 			//if ($user->socid > 0) $socid = $user->socid;    // For external user, no check is done on company because readability is managed by public status of project and assignement.
+			//print  $langs->trans("Desription").' : ';
 			//print "connexion de type=".$conf->db->type." sur host=".$conf->db->host." port=".$conf->db->port." user=".$conf->db->user." name=".$conf->db->name;
 			//si le sujet n'est pas celui qui a été effacé alors on concatene
 			//si on voit une erreur, le fond de la case est rouge
+			The selectForForms is called with parameter $objectfield defined, so tha app can retreive the filter inside the ajax component instead of being provided as parameters. The
 			console.log("Load desciption into text area : "+description);
 			continue; // We discard parametes starting with ?
 			dol_print_error('', get_class($this)."::load_previous_next_ref was called on objet with property table_element not defined");
@@ -643,6 +703,8 @@
 			if ($value) {		// If we have -1 here, pb is into insert, not into ouptut (fix insert instead of changing code here to compensate)
 			if (empty($objimport->array_import_convertvalue[0][$tmpcode])) {	// If source file does not need convertion
 			jQuery("#mouvement").change(function() {
+			let hours = hour.getHours().toString().padStart(2, "0"); // Formater pour obtenir deux chiffres
+			let mins = hour.getMinutes().toString().padStart(2, "0"); // Formater pour obtenir deux chiffres
 			preg_match('/\((.+)\)/i', $objp->label, $reg); // Si texte entoure de parenthese on tente recherche de traduction
 			print " - Error cant find payment mode for ".$condpayment."\n";
 			print "Expedition inexistante ou acces refuse";
@@ -651,6 +713,8 @@
 			print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=transfert">'.$langs->trans("TransferStock").'</a>';
 			print '<a class="cursoradd" href="'.$urltocreate.'">'; // Explicit link, usefull for nojs interfaces
 			print '<div class="div-table-responsive-no-min">'; // You can use div-table-responsive-no-min if you dont need reserved height for your table
+			print '<input id="assujtva_value" name="assujtva_value" type="checkbox" ' . (GETPOSTISSET('assujtva_value') ? 'checked="checked"' : 'checked="checked"') . ' value="1">'; // Assujeti par defaut en creation
+			print __METHOD__." module accouting must be enabled.\n";
 			print __METHOD__." module accouting must be enabled.\n"; exit(-1);
 			return -1; // Alternate souce not found
 			return false; // Sould be 6
@@ -672,6 +736,7 @@
 		$action = ''; // Do not show form post if there was at least one successfull sent
 		$action = 'transfert';
 		$allways = $this->get_all_ways(); // Load array of categories
+		$asciiDocTable = "[options=\"header\"]\n|===\n|Objet | URLs\n";
 		$buyingprice = (GETPOST('buying_price') != '' ? GETPOST('buying_price') : ''); // If buying_price is '0', we muste keep this value
 		$buyingprice = price2num(GETPOST('buying_price') != '' ? GETPOST('buying_price') : ''); // If buying_price is '0', we muste keep this value
 		$canbedeleted = $object->can_be_deleted(); // Renvoi vrai si compte sans mouvements
@@ -692,6 +757,7 @@
 		$keyval = substr($nvpstr, $intial, $keypos);
 		$ldap = new Ldap(); // Les parametres sont passes et recuperes via $conf
 		$localobject->date=dol_mktime(12, 0, 0, 1, 1, 1915);	// we use year 1915 to be sure to not have existing invoice for this year (usefull only if numbering is {0000@1}
+		$localobject->date_creation = dol_mktime(12, 0, 0, 1, 1, 1980);	// we use year 1915 to be sure to not have existing invoice for this year (usefull only if numbering is {0000@1}
 		$mail = 'bidon@unvalid.unvalid';
 		$myclone = clone $object; // PHP clone is a shallow copy only, not a real clone, so properties of references will keep the reference (refering to the same target/variable)
 		$nbofsubproducts = count($prodschild); // This include only first level of childs
@@ -724,6 +790,7 @@
 		$sql .= " AND mc.statut NOT IN (-1,0)"; // -1 erreur, 0 non envoye, 1 envoye avec succes
 		$sql .= " WHERE u.email != ''"; // u.email IS NOT NULL est implicite dans ce test
 		$sql .= " WHERE u.email <> ''"; // u.email IS NOT NULL est implicite dans ce test
+		$sql .= " label = 'Annulation mouvement ID ".((int) $this->id)."',";
 		$sql .= " tms = tms"; // La date de derniere modif doit changer sauf pour la mise a jour de date de derniere connexion
 		$sql .= ", '".$this->db->escape($url)."'";		// dperecated
 		$sql = "SELECT id_users, nom as name, id_sondage, reponses";
@@ -761,6 +828,7 @@
 		$this->rights[$r][3] = 0; // La permission est-elle une permission par defaut
 		$this->rights[$r][3] = 1; // La permission est-elle une permission par defaut
 		$this->rights[1][1] = 'Lire ses notes de frais et deplacements et celles de sa hierarchy';
+		$this->rights[1][3] = 0; // La permission est-elle une permission par defaut
 		$this->rights[1][3] = 1; // La permission est-elle une permission par defaut
 		$this->rights[2][3] = 0; // La permission est-elle une permission par defaut
 		$this->rights[3][1] = 'Lire mouvements de stocks';
@@ -773,6 +841,7 @@
 		'filles' => array('name'=>'filles', 'type'=>'tns:FillesArray')
 		'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Validated', 2=>'Paid', 3=>'Abandonned')),
 		'qty_regulated' => array('type'=>'double', 'label'=>'QtyDelta', 'visible'=>1, 'enabled'=>1, 'position'=>34, 'index'=>1, 'help'=>'Qty aadded or removed (filled once movements are validated)'),
+		'type_mouvement' =>array('type'=>'smallint(6)', 'label'=>'Type mouvement', 'enabled'=>1, 'visible'=>-1, 'position'=>45),
 		/* Definition de la date limite */
 		/* Liste des taches et role sur les projets ou taches */
 		/* width: ...px; If I use with, there is trouble on size of flex boxes solved with min + (max that is a little bit higer than min) */
@@ -821,8 +890,11 @@
 		// Exclude unsubscribed email adresses
 		// Files missings
 		// First, we get the max value (reponse immediate car champ indexe)
+		// For a string that is already HTML (contains HTML tags) but badly formated
+		// For a string that is already HTML (contains HTML tags) with special tags but badly formated
 		// For backward compatiblity, we detect file stored into an old path
 		// For each file build select list with PDF extention
+		// For exemple if element is project
 		// For external user, no check is done on company because readability is managed by public status of project and assignement.
 		// For external user, no check is done on company permission because readability is managed by public status of project and assignement.
 		// Forced filter on socid is similar to forced filter on project. TODO Use project assignement to allow to not use filter on project
@@ -831,6 +903,7 @@
 		// How the date for data are formated (format used bu jsgantt)
 		// How the date for data are formated (format used by dol_print_date)
 		// If lib not found in language file, we get label from cache/databse
+		// If on smartphone or optmized for small screen
 		// If option choosed, we create invoice
 		// If resultset not provided, we take the last used by connexion
 		// If stock increment is done on reception (recommanded choice)
@@ -844,6 +917,7 @@
 		// Links beetween objects are stored in this table
 		// Load extrafields if not allready done
 		// Load extrafiels if not allready does
+		// Load librairies to check BAN
 		// Log the init of hook but only for hooks thare are declared to be managed
 		// Loop on each line keword was found into file.
 		// Mis a jour contact
@@ -862,6 +936,7 @@
 		// Option to reload page to retrieve customer informations.
 		// Ordre SQL ne necessitant pas de connexion a une base (exemple: CREATE DATABASE)
 		// Ouput page under the Dolibarr top menu
+		// Parameteres execution
 		// Permet de commencer l'impression de l'etiquette desiree dans le cas ou la page a deja servie
 		// Permettre l'exclusion de groupes
 		// Permettre l'inclusion de groupes
@@ -870,6 +945,7 @@
 		// Refresh / Reload web site (for non javascript browers)
 		// Remove '<' into remainging, so remove non closing html tags like '<abc' or '<<abc'. Note: '<123abc' is not a html tag (can be kept), but '<abc123' is (must be removed).
 		// Remove fields not relevent to categories
+		// Renaming can be done when we rename globaly a bank receipt but not when changing 1 line from one receipt into another one.
 		// Replace protected special codes with matching number of _ as wild card caracter
 		// Reset the substraction for this amount
 		// Retained warranty : usualy use on construction industry
@@ -893,7 +969,10 @@
 		// TODO Remove hooks with type 'output' (exemple createFrom). All hooks must be converted into 'addreplace' hooks.
 		// TODO Remove hooks with type 'output' (exemple getNomUrl). All hooks must be converted into 'addreplace' hooks.
 		// Tableau des parametres complementaires du post
+		// Test with restricthtml + MAIN_RESTRICTHTML_ONLY_VALID_HTML + MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY to test disabling of bad atrributes
+		// Test with restricthtml + MAIN_RESTRICTHTML_ONLY_VALID_HTML only to test disabling of bad atrributes
 		// Test with restricthtml + MAIN_RESTRICTHTML_ONLY_VALID_HTML to test disabling of bad atrributes
+		// Test with restricthtml + MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY only to test disabling of bad atrributes
 		// Test with restricthtml + MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES to test disabling of bad atrributes
 		// The image must have the class 'boxhandle' beause it's value used in DOM draggable objects to define the area used to catch the full object
 		// This improvment as provided by 'SirSir' to
@@ -927,9 +1006,11 @@
 		// reload page to retrieve customer informations
 		// reload page to retrieve supplier informations
 		// save curent cell padding
+		// submited to nothing.
 		// substract 1 because array start from 0
 		// this conf is actually hidden, by default we use 10% for "be carefull or warning"
 		// to allow mask usage for dir, we shoul introduce a new param "isdir" to 1 to complete newmask like this
+		// variable submitted at all, so no way to make a difference between variable not submited and variable
 		//$array_selected = array("s.rowid"=>1, "s.nom"=>2);	// Mut be fields found into declaration of dataset
 		//$signature_line = dol_hash($keyforsignature, '5'); // Not really usefull
 		//Add hook to filter on user (for exemple on usergroup define in custom modules)
@@ -956,6 +1037,7 @@
 		console.log("Change montly amount echeance="+echeance+" idcap="+idcap+" capital="+capital);
 		dol_syslog("The user login has a validity between [".$user->datestartvalidity." and ".$user->dateendvalidity."], curren date is ".dol_now());
 		dol_syslog("Warning: Function form_constantes is calle with parameter strictw3c = 0, this is deprecated. Value must be 2 now.", LOG_DEBUG);
+		dol_syslog("line.php update bank line to set the new bank receipt nuber", LOG_DEBUG);
 		dol_syslog(get_class($this)."::getCustomerAccount Try to find the first system customer id for ".$site." of thirdparty id=".$id." (exemple: cus_.... for stripe)", LOG_DEBUG);
 		dol_syslog(get_class($this)."::setCategoriesCommon Oject Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
 		foreach ($allways as $way) {
@@ -965,9 +1047,12 @@
 		http_response_code(202);		// If we use 202, this is not really an error message, but this allow to ouput message on command line tools
 		if (!$login || (in_array('ldap', $authmode) && empty($passwordtotest))) {	// With LDAP we refused empty password because some LDAP are "opened" for anonymous access so connexion is a success.
 		if (!empty($this->phone)) {	// If a phone of thirdparty is defined, we add it ot mobile of contacts
+		if (!getDolGlobalString('PDF_BANK_HIDE_NUMBER_SHOW_ONLY_BICIBAN')) {    // Note that some countries still need bank number, BIC/IBAN not enougth for them
 		if (!is_array($this->userassigned) && !empty($this->userassigned)) {	// For backward compatibility when userassigned was an int instead fo array
 		if ($lines[$i]->fk_parent == $parent || $level < 0) {       // if $level = -1, we dont' use sublevel recursion, we show all lines
 		if ($lines[$i]->fk_task_parent == $parent || $level < 0) {       // if $level = -1, we dont' use sublevel recursion, we show all lines
+		if ($objectfield) {	// We must retreive the objectdesc from the field or extrafield
+		if ($this->label == 'Annulation mouvement ID'.$this->id) {
 		if ($user->hasRight('stock', 'mouvement', 'creer')) {
 		if (GETPOST('import_name')) {	// If we have submited a form, we take value used fot the update try
 		if (dol_strlen($phone) == 10) {// fixe 6 chiffres +352_AA_BB_CC
@@ -993,10 +1078,12 @@
 		print 'This website or feature is currently temporarly not available or failed after a technical error.<br><br>This may be due to a maintenance operation. Current status of operation ('.dol_print_date(dol_now(), 'dayhourrfc').') are on next line...<br><br>'."\n";
 		return $childs;
 		return $objet->compteur;
+		setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentities("Lable")), null, 'errors');
 		unset($object->supplierprices);	// Mut use another API to get them
 		while ($i < $nblot) {	// Loop on each serie
 		} else // We decrease agressiveness
 		} else {	// If thirdparty unkown, output the waiting account
+		} else { // We decrease agressiveness
 		} else { // old method. deprecated because ot can't retrieve type
 		} elseif (!empty($this->childtables)) {	// If object has childs linked with a foreign key field, we check all child tables.
 		} elseif (!empty($this->fk_element) && !empty($this->childtables)) {	// If object has childs linked with a foreign key field, we check all child tables.
@@ -1043,6 +1130,7 @@
 	 *	@param	int			$socid      	Id ot third party or 0 for all or -1 for empty list
 	 *	@param	int		$id      Id du paiement dont il faut afficher les infos
 	 *	@param	mixed				$gm			'gmt'=Input informations are GMT values, 'tzserver'=Local to server TZ
+	 *	@param	string		$method		Method of transmision to bank (0=Internet, 1=Api...)
 	 *	@param	string		$method		method of transmision to bank
 	 *	@param	string		$method		method of transmision to bank (0=Internet, 1=Api...)
 	 *	@param	string	$dolibarr_main_db_pass 		Mot de passe user a creer
@@ -1065,6 +1153,7 @@
 	 *	@param  string	$close_note	Commentaire renseigne si on classe a payee alors que paiement incomplet (cas escompte par exemple)
 	 *	@return		array		Tableau info des attributs
 	 *	@return		int                     < 0 if KO (infinit loop), >= 0 if OK
+	 *	@return		int                     Return integer < 0 if KO (infinit loop), >= 0 if OK
 	 *	@return	array				Tableau des informations des champs de la table
 	 *	@return	string		Error code (Exemples: DB_ERROR_TABLE_ALREADY_EXISTS, DB_ERROR_RECORD_ALREADY_EXISTS...)
 	 *	@return 	int		0 en cas de succes
@@ -1073,6 +1162,8 @@
 	 *	Charge indicateurs this->nb pour le tableau de bord
 	 *	Charge les informations d'ordre info dans l'objet commande
 	 *	Charge les informations d'ordre info dans l'objet facture
+	 *	Classify the reception as invoiced (used for exemple by trigger when WORKFLOW_RECEPTION_CLASSIFY_BILLED_INVOICE is on)
+	 *	Classify the shipping as invoiced (used for exemple by trigger when WORKFLOW_SHIPPING_CLASSIFY_BILLED_INVOICE is on)
 	 *	Connexion to server
 	 *	Define properties fullpath, fullrelativename, fulllabel of a directory of array this->cats and all its childs.
 	 *	For category id_categ and its childs available in this->cats, define property fullpath and fulllabel.
@@ -1119,6 +1210,7 @@
 	 *                  					      Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
 	 *                  					      Sinon la TVA proposee par defaut=0. Fin de regle.
 	 *                  		                  Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
+	 *                                 		 		- string (categories ids seprated by comma)
 	 *                                                          - string (categories ids seprated by comma)
 	 *                                      - string (categories ids seprated by comma)
 	 *                                      All types can also return some values into an array ->results that will be finaly merged into this->resArray for caller.
@@ -1305,6 +1397,7 @@
 	 * @param	array		$arrayofcriterias			          Array of available search criterias. Example: array($object->element => $object->fields, 'otherfamily' => otherarrayoffields, ...)
 	 * @param	array		$search_component_params	          Array of selected search criterias
 	 * @param	bool			$gm			1=Input informations are GMT values, otherwise local to server TZ
+	 * @param	int			$fk_product_stock	id product_stock for objet
 	 * @param	int		$disabledoutputofmessages	Clear all messages stored into session without diplaying them
 	 * @param	int		$lineid		Id of production line to filter childs
 	 * @param	int		$noescapecommand	1=Do not escape command. Warning: Using this parameter needs you alreay have sanitized the $command parameter. If not, it will lead to security vulnerability.
@@ -1343,6 +1436,7 @@
 	 * @param   array   $info   content informations of field
 	 * @param   boolean $confirmnow                         false=default, true=try to confirm immediatly after create (if conditions are ok)
 	 * @param   double		$alreadypaid	0=No payment already done, >0=Some payments were already done (we recommand to put here amount payed if you have it, 1 otherwise)
+	 * @param   int		$fk_socpeople			Id of thirdparty contact (if source = 'external') or id of user (if souce = 'internal') to link
 	 * @param   int 	$fk_socpeople       	Id of thirdparty contact (if source = 'external') or id of user (if souce = 'internal') to link
 	 * @param   int         	$default_font_size  default siez of font
 	 * @param   int     $id      id du paiement dont il faut afficher les infos
@@ -1355,6 +1449,7 @@
 	 * @param  int	  $disablestockchangeforsubproduct	Disable stock change for sub-products of kit (usefull only if product is a subproduct)
 	 * @param  int    $nbmax 	Number maxium of photos (0=no maximum)
 	 * @param  int $id             		Id of product to search childs of
+	 * @param  string        $extrafieldsobjectkey	The key to use to store retreived data (commonly $object->table_element)
 	 * @param  string        $extrafieldsobjectkey	The key to use to store retreived data (for example $object->table_element)
 	 * @param  string  $moreparam      			To add more parametes on html input tag
 	 * @param  string  $moreparam      To add more parametes on html input tag
@@ -1374,11 +1469,19 @@
 	 * @param int       $month 				Specifig month - Can be empty
 	 * @param int       $year 				Specifig year - Can be empty
 	 * @param int $_type  Interger value representing Mail Transport Type
+	 * @param string			$properties			Restrict the data returned to theses properties. Ignored if empty. Comma separated list of properties names
+	 * @param string		   $properties			Restrict the data returned to theses properties. Ignored if empty. Comma separated list of properties names
 	 * @param string	$str 		Original string to encode and optionaly truncate
 	 * @param string 		$page 				Url of page to call if confirmation is OK. Can contains parameters (param 'action' and 'confirm' will be reformated)
 	 * @param string 	$editvalue 		When in edit mode, use this value as $value instead of value (for example, you can provide here a formated price instead of numeric value, or a select combo). Use '' to use same than $value
 	 * @param string 	$output_format 	(html/opton (for option html only)/array (to return options arrays
 	 * @param string    $page           Page name (website id must also be filled if this parameter is used). Exemple 'myaliaspage' or 'fr/myaliaspage'
+	 * @param string    $properties			Restrict the data returned to theses properties. Ignored if empty. Comma separated list of properties names
+	 * @param string    $properties		  Restrict the data returned to theses properties. Ignored if empty. Comma separated list of properties names
+	 * @param string    $properties		Restrict the data returned to theses properties. Ignored if empty. Comma separated list of properties names
+	 * @param string    $properties	Restrict the data returned to theses properties. Ignored if empty. Comma separated list of properties names
+	 * @param string  $properties			Restrict the data returned to theses properties. Ignored if empty. Comma separated list of properties names
+	 * @param string  $properties Restrict the data returned to theses properties. Ignored if empty. Comma separated list of properties names
 	 * @param string $_path Path to the sendmail execuable
 	 * @param string $key Authentification key
 	 * @param string $selected Id remise fixe pre-selectionnee
@@ -1388,6 +1491,7 @@
 	 * @return	string		Error code (Exemples: DB_ERROR_TABLE_ALREADY_EXISTS, DB_ERROR_RECORD_ALREADY_EXISTS...)
 	 * @return 	float|string			Total amout of discount
 	 * @return 	int            				<0 if KO, number of equipments found if OK
+	 * @return 	int            				Return integer <0 if KO, number of equipments found if OK
 	 * @return 	string			Javacript code to manage dependency
 	 * @return    string        Error code (Exemples: DB_ERROR_TABLE_ALREADY_EXISTS, DB_ERROR_RECORD_ALREADY_EXISTS...)
 	 * @return  array                			Array with inforation on table
@@ -1406,15 +1510,20 @@
 	 * @var array Custom family informations
 	 * @var array Header informations. Usually created at runtime by loadBox().
 	 * @var array box dependancies
+	 * @var boolean $standard_conforming_string		Set this to true if postgres accept only standard encoding of sting using '' and not \'
+	 * @var int	Postion
 	 * @var int 	Date for cancelation
 	 * @var int 	ID for cancelation
 	 * @var int -1=Unkown duration
 	 * @var int Amount can be choosen by the visitor during subscription (0 or 1)
 	 * @var int Code modifiable si il est invalide
 	 * @var int Subsription required (0 or 1)
+	 * @var int availabilty ID
 	 * @var integer|string Date delivery planed
 	 * @var string	The name of constant to use to scan ODT files (Exemple: 'COMMANDE_ADDON_PDF_ODT_PATH')
 	 * @var string Hash to identify ticket publically
+	 * @var string availabilty code
+	 * @var string availabilty label
 	 * @var string ref custome
 	 * Active Directory ne supporte pas les connexions anonymes
 	 * Attention ce module est utilise par defaut si aucun module n'a
@@ -1434,6 +1543,7 @@
 	 * Exemple: { "socid": 2, "date": 1595196000, "type": 0, "lines": [{ "fk_product": 2, "qty": 1 }] }
 	 * Flag to 1 if we must clean ambiguous charaters for the autogeneration of password (List of ambiguous char is in $this->Ambi)
 	 * Function to know all custom groupd from an accounting account
+	 * Function to shwo the combo select to chose a type of field (varchar, int, email, ...)
 	 * Function used to return childs of Mo
 	 * If paid completelly, this->close_code will be null
 	 * Inserts all informations into database.
@@ -1492,6 +1602,7 @@
 	 * Return connexion ID
 	 * Return direct childs id of a category into an array
 	 * Return list of auxilary accounts. Cumulate list from customers, suppliers and users.
+	 * Return list of categories having choosed type
 	 * Return list of product formated for output
 	 * Return the addtional SQL JOIN query for filtering a list by a category
 	 * Return the addtional SQL SELECT query for filtering a list by a category
@@ -1509,6 +1620,7 @@
 	 * ete definit dans la configuration
 	 * or a COMMA delimted string, and inserts them into a highly
 	 * reload conf value from databases is an aliase of loadValueFromConf
+	 * reverse mouvement for object by updating infos
 	 * statique et publique. Le nombre de parametres est determine automatiquement.
 	 * the tagret is useful with hooks : that allow externals modules to add setup items on good place
 	 * the underlaying array is destroyed and reconstructed.
@@ -1520,6 +1632,7 @@
 	# Log directoves        
 	$IBS_RETOUR = "montant:M;ref:R;auto:A;trans:T"; // Format des parametres du get de validation en reponse (url a definir sous paybox)
 	$alwaysuncheckedmodules = array('dav', 'dynamicprices', 'incoterm', 'loan', 'multicurrency', 'paybox', 'paypal', 'stripe', 'google', 'printing', 'scanner', 'skype', 'website'); // Module we dont want by default
+	$alwaysuncheckedmodules = array('dav', 'dynamicprices', 'incoterm', 'loan', 'multicurrency', 'paybox', 'paypal', 'stripe', 'google', 'printing', 'scanner', 'socialnetworks', 'website'); // Module we dont want by default
 	$amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
 	$controle = $tableau[$report][10];
 	$data = getDecodeValue($mege, $type);
@@ -1530,6 +1643,8 @@
 	$intial = 0;
 	$ldaprecords = $ldap->getRecords('*', $conf->global->LDAP_MEMBER_DN, $conf->global->LDAP_KEY_MEMBERS, $required_fields, 'member'); // Fiter on 'member' filter param
 	$ldaprecords = $ldap->getRecords('*', $conf->global->LDAP_USER_DN, $conf->global->LDAP_KEY_USERS, $required_fields, 'user'); // Fiter on 'user' filter param
+	$ldaprecords = $ldap->getRecords('*', getDolGlobalString('LDAP_MEMBER_DN'), getDolGlobalString('LDAP_KEY_MEMBERS'), $required_fields, 'member'); // Fiter on 'member' filter param
+	$ldaprecords = $ldap->getRecords('*', getDolGlobalString('LDAP_USER_DN'), getDolGlobalString('LDAP_KEY_USERS'), $required_fields, 'user'); // Fiter on 'user' filter param
 	$mege = imap_fetchbody($mbox, $jk, $fpos);
 	$mege = imap_fetchbody($mbox, $jk, $fpos, FT_UID);
 	$object->status = $object->fk_statut; // for backwad compatibility
@@ -1556,6 +1671,7 @@
 	// Add format informations and link to download example
 	// Affichage de la liste des projets de la semaine
 	// Bit 1:	0 ligne normale - 1 si ligne de remise fixe
+	// Build filter to diplay only concerned lines
 	// By default use tls decied by PHP.
 	// Cas des parametres TAX_MODE_SELL/BUY_SERVICE/PRODUCT
 	// Chargement de la classe
@@ -1621,9 +1737,11 @@
 	// We open a list of transaction of a dedicated account and no page was set by defaut
 	// When a dictionnary is commented
 	// add properties and declare them in consturctor
+	// buil format asciidoc for urls in table
 	// but in some situations that is required (update legal informations for example)
 	// for gravatar use get_avatar_from_service('gravatar', md5 hash email@adress, size-in-px )
 	// on transfert les données de l'un vers l'autre
+	// rewrite dictionnary if
 	// si le filtrage est parametre pour l'export ou pas
 	// start and end date that change with time andd that may be different that the period of reference for price.
 	// verify informations entred
@@ -1674,6 +1792,7 @@
 	print '<option value="1"'.(GETPOST('mouvement') ? ' selected="selected"' : '').'>'.$langs->trans("Delete").'</option>';
 	print '<select name="mouvement" id="mouvement" class="minwidth100 valignmiddle">';
 	print '<tr><td colspan="2"><br>*** Force modules not found physicaly to be disabled (only modules adding js, css or hooks can be detected as removed physicaly)</td></tr>';
+	print 'Failed to open '.$outputfile.' for ouput.'."\n";
 	print 'Missing paramater s, c or a';
 	print 'Sorry, it seems your internet connexion is off.<br>';
 	print ajax_combobox("mouvement");
@@ -1687,6 +1806,7 @@
 	public $date_delivery; // Date delivery planed
 	public $debug_api; // usefull if no dialog
 	public $emetteur; // Objet societe qui emet
+	public $fk_origin_stock;		// rowid in llx_product_batch table (not usefull)
 	public $graph; // Objet Graph (Artichow, Phplot...)
 	public $infofiles; // Used to return informations by function getDocumentsLink
 	public $lastsearch_values; // To store last saved search criterias for user
@@ -1878,6 +1998,7 @@
  *       \brief      Onglet informations personnelles d'un contact
  *       \brief      Page fiche LDAP groupe
  *       \brief      Tab to manage contacts/adresses of proposal
+ *      @return boolean                 	True if informations are valid, false otherwise
  *      @return boolean                 True if informations are valid, false otherwise
  *      @return string						Formated string
  *      @return string				Return list fo image format
@@ -1930,8 +2051,10 @@
  *  @return	string					String with formated amounts ('19,6' or '19,6%' or '8.5% (NPR)' or '8.5% *' or '19,6 (CODEX)')
  *  @return array|int               Array with details of VATs (per rate), -1 if no accountancy module, -2 if not yet developped, -3 if error
  *  @return array|int               Array with details of VATs (per third parties), -1 if no accountancy module, -2 if not yet developped, -3 if error
+ *  Classe mere des modeles de numerotation des references de bon de livraison
  *  Classe mere des modeles de numerotation des references de members
  *  Classe mere des modeles de numerotation des references de projets
+ *  Classe mere des modeles de numerotation des tickets de caisse
  *  Classe permettant la gestion des stats des expensereports et notes de frais
  *  Delete files into database index using search criterias.
  *  Get formated error messages to output (Used to show messages on html output).
@@ -1996,6 +2119,7 @@
  * @param string   $objectname   name of object whant to remove
  * @retun   boolean
  * @return	array					returns an associtive array containing the response from the server.
+ * @return	string							Formated string
  * @return	string				A HTML table that conatins a list with open (unpaid) supplier invoices
  * @return	string			Formated value
  * @return 	array						Array of mesures
@@ -2018,6 +2142,7 @@
  * Correspondance des livraisons et des commandes clients dans la table llx_co_liv
  * Creation objet $langs (must be before all other code)
  * For action=add, use:     $var = GETPOST('var');		// No GETPOSTISSET, so GETPOST always called and default value is retreived if not a form POST, and value of form is retreived if it is a form POST.
+ * Generate Urls and add them to documentaion module
  * It is usefull to search for a particular key and displaying arrays.
  * Lattest modified orders
  * Les catégories filles, sous tableau dez la catégorie
@@ -2072,14 +2197,17 @@
 $FULLTAG = GETPOST("fulltag", 'alpha'); // fulltag is tag with more informations
 $array = array(1=>'Value 1', 2=>'Value 2', 3=>'Value 3 ith a very long text. aze eazeae e ae aeae a e a ea ea ea e a e aea e ae aeaeaeaze.');
 $conf = new stdClass(); // instantiate $conf explicitely
+$html .= ' monthes people</b><br>';
 $permissiontoadd = $user->rights->stock->mouvement->creer;
 $permissiontodelete = $user->rights->stock->mouvement->creer; // There is no deletion permission for stock movement as we shoul dnever delete
 $permissiontodelete = $user->rights->stock->mouvement->creer; // There is no deletion permission for stock movement as we should never delete
 $permissiontoread = $user->rights->stock->mouvement->lire;
+$seledted = !getDolGlobalString('BLOCKEDLOG_DISABLE_NOT_ALLOWED_FOR_COUNTRY') ? array() : explode(',', getDolGlobalString('BLOCKEDLOG_DISABLE_NOT_ALLOWED_FOR_COUNTRY'));
 $seledted = empty($conf->global->BLOCKEDLOG_DISABLE_NOT_ALLOWED_FOR_COUNTRY) ? array() : explode(',', $conf->global->BLOCKEDLOG_DISABLE_NOT_ALLOWED_FOR_COUNTRY);
 $sql = "SELECT id_users, nom as name, id_sondage, reponses";
 $sql = 'SELECT nom as name, reponses';
 $tag = GETPOST('tag');	// To retreive the emailing, and recipient
+$tmpday = -date("w", dol_mktime(12, 0, 0, $month, 1, $year, 'gmt')) + 2; // date('w') is 0 fo sunday
 $usercancreate = $user->rights->stock->mouvement->creer;
 $usercancreate = (($user->rights->stock->mouvement->creer));
 $usercandelete = $user->rights->stock->mouvement->creer;
@@ -2138,6 +2266,7 @@ $usercanread = (($user->rights->stock->mouvement->lire));
 /* Affichage de la liste des projets du mois */
 /* Badge style is based on boostrap framework */
 /* Force values on one colum for small screen */
+/* The buttonplus isgrowing on hover (dont know why). This is to avoid to have the cellegrowing too */
 /* To make a div popup, we must use a position aboluste inside a position relative */
 /* USING IMAGES FOR WEATHER INTEAD OF FONT AWESOME */
 /* Warning: setting this may make screen not beeing refreshed after a combo selection */
@@ -2206,6 +2335,7 @@ $usercanread = (($user->rights->stock->mouvement->lire));
 // TODO ajouter regle pour restreindre acces paiement
 // Table to store complete informations (will replace all other table). Key is table name.
 // Test to check image can be publically viewed is done inside the viewimage.php wrapper.
+// The session_set_save_handler() at end of this fille will replace default session management.
 // This 2 lines are usefull only if we want to exclude some Urls from the explorer
 // This refresh list of dirs, not list of files (for preformance reason). List of files is refresh only if dir was not synchronized.
 // To disable a constant whithout javascript
@@ -2214,9 +2344,11 @@ $usercanread = (($user->rights->stock->mouvement->lire));
 // We keep it with value ForceBuyingPriceIfNull = 2 for retroactive effect but results are unpredicable.
 // We must filter on assignement table
 // badge color ajustement for color blind
+// chek if salary pl
 // current rule: uptodate = the end date is in future or no subcription required
 // librarie core
 // librarie jobs
+// reverse mouvement of stock
 // status color ajustement for color blind
 //Activate Set adress in list
 //Another call for easy debugg
@@ -2363,6 +2495,7 @@ NEW: Add SQL contraint on product_stock table to allow only exsting product and
 NEW: Add email in event history, for reminder email of expired subsription
 NEW: Add exemple of setup for multitail to render dolibarr log files
 NEW: Add hidden option MAIN_EMAIL_SUPPORT_ACK to restore Email ack checkbox (feature abandonned by mailers)
+NEW: Add more company informations (ProfId7 to 10) (#25266)
 NEW: Add option to disable globaly some notifications emails.
 NEW: Add option to display thirdparty adress in combolist
 NEW: Add ressource extrafields.
@@ -2394,15 +2527,20 @@ NEW: Implement option SUPPLIER_ORDER_USE_DISPATCH_STATUS to add a status into ea
 NEW: Introduce position of records into dictionnary of type of contacts
 NEW: Introduce use of cache for thumbs images of users to save bandwith.
 NEW: Look and feel v11: Some setup pages are by default direclty in edit mode.
+NEW: Menu editor is reponsive
 NEW: Merge all boxes "related objects" into one. This save a lot of room on most card and avoid often horizontal scoll.
 NEW: ModuleBuilder - More feature that can be modifed after module generation
+NEW: ModuleBuilder: for edit name of dictionnary and delete it in MB
 NEW: On page to see/edit contact of an ojbect, the status of contact is visible (for both external and internal users).
 NEW: Page to check if the operations/items created between two dates have attached item(s) and possibility to download all attachements
+NEW: Payment: Can edit account on miscellaneous payment (if not transfered)
 NEW: Product stock and subproduct stock are independant
+NEW: Products: Add SQL contraint on product_stock table to allow only existing product and warehouse #23543
 NEW: Remove tooltip tipTip library replaced with standatd jquery tooltip
 NEW: Start to introduce search filters on dictionnaries for vat list.
 NEW: Support of deployement of metapackages
 NEW: When a new field to show into lists is selected, the form is automatically submited and field added.
+NEW: When an user unset the batch management of products, transformation of each batch stock mouvement in global stock mouvement
 NEW: X-Axis on graph are shown verticaly when there is a lot of values.
 NEW: add API shipment mode dictionnary
 NEW: add a prospect status for the contact with managment of custom icon
@@ -2430,11 +2568,13 @@ Note: You can use git-buildpackage -us -uc --git-ignore-new  if you want to test
 Store, search and retreive any article to keep your knowledge into a database. It can be used to manage a list of FAQ, or a database
 The keyword can be ommitted if your commit does not fit in any of the following categories:
 The output patch file can then be submited on Dolibarr
+The output patch file can then be submited on Dolibarr dev mailing-list, with explanation on its goal, for inclusion in main branch.
 This directory contains ruleset files to use to develop Dolibarr EPR & CRM.
 This directory contains several subdirectories with entries for informations on Dolibarr.<br>
 This docker image intended for developpement usage.
 This docker image is intended for developpement usage.
 This module provides a sheduled job that scan regularly one or several IMAP email boxes, with filtering rules, to automatically record data in your application, like
+Une ligne represente un element : data[$x]
 Upgrading to any other version or database system is abolutely required BEFORE trying to
 We recommand to install Dolibarr ERP CRM on your own server (as most Open Source software, download and use is free: [https://www.dolibarr.org/download](https://www.dolibarr.org/download)) to get access on every side of application.
 You must avoid tests that could cause degradation or interruption of our service (refrain from using automated tools, and limit yourself about requests per second), that's why we recommand to install software on your own platform.
@@ -2447,6 +2587,7 @@ elseif ($year && $month && $day) $daytoparsegmt = dol_mktime(0, 0, 0, $month, $d
 function getMarginInfos($pvht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $paht)
 function print_actions_filter($form, $canedit, $status, $year, $month, $day, $showbirthday, $filtera, $filtert, $filterd, $pid, $socid, $action, $showextcals = array(), $actioncode = '', $usergroupid = '', $excludetype = '', $resourceid = 0)
 if (!$user->hasRight('stock', 'mouvement', 'lire')) {
+if (!empty($conf->variants->eabled) && !getDolGlobalString('VARIANT_ALLOW_STOCK_MOVEMENT_ON_VARIANT_PARENT')) {	// Add test to exclude products that has variants
 if (!empty($conf->variants->eabled) && empty($conf->global->VARIANT_ALLOW_STOCK_MOVEMENT_ON_VARIANT_PARENT)) {	// Add test to exclude products that has variants
 if (!empty($contactname)) { // acces a partir du module de recherche
 if ($action == "transfert") {
@@ -2462,3 +2603,4 @@ print '<div class="div-table-responsive-no-min">'; // You can use div-table-resp
 print_barre_liste($langs->trans("Sessions"), $page, $_SERVER["PHP_SELF"], "", $sortfield, $sortorder, '', $num, ($num ? $num : ''), 'setup'); // Do not show numer (0) if no session found (it means we can't know)
 print_liste_field_titre("Prority", $_SERVER["PHP_SELF"], "t.priority", "", $param, '', $sortfield, $sortorder);
 session_start(); // To be able to keep info into session (used for not losing pass during navigation. pass must not transit through parmaeters)
+} // this are value submited after submit of action 'submitdateselect'

+ 18 - 18
dev/tools/dolibarr-postgres2mysql.php

@@ -37,14 +37,14 @@ if (substr($sapi_type, 0, 3) == 'cgi') {
 	exit();
 }
 
-error_reporting(E_ALL & ~ E_DEPRECATED);
+error_reporting(E_ALL & ~E_DEPRECATED);
 define('PRODUCT', "pg2mysql");
 define('VERSION', "2.0");
 
 // this is the default, it can be overridden here, or specified as the third parameter on the command line
 $config['engine'] = "InnoDB";
 
-if (! ($argv[1] && $argv[2])) {
+if (!($argv[1] && $argv[2])) {
 	echo "Usage: php pg2mysql_cli.php <inputfilename> <outputfilename> [engine]\n";
 	exit();
 } else {
@@ -141,7 +141,7 @@ function pg2mysql_large($infilename, $outfilename)
 	echo "Filesize: " . formatsize($fs) . "\n";
 
 	while ($instr = fgets($infp)) {
-		$linenum ++;
+		$linenum++;
 		$memusage = round(memory_get_usage(true) / 1024 / 1024);
 		$len = strlen($instr);
 		$pgsqlchunk[] = $instr;
@@ -163,7 +163,7 @@ function pg2mysql_large($infilename, $outfilename)
 		}
 
 		if (strlen($instr) > 3 && ($instr[$len - 3] == ")" && $instr[$len - 2] == ";" && $instr[$len - 1] == "\n") && $inquotes == false) {
-			$chunkcount ++;
+			$chunkcount++;
 
 			if ($linenum % 10000 == 0) {
 				$currentpos = ftell($infp);
@@ -246,7 +246,7 @@ function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 	if (is_array($input)) {
 		$lines = $input;
 	} else {
-		$lines = split("\n", $input);
+		$lines = explode("\n", $input);
 	}
 
 	if ($header) {
@@ -302,7 +302,7 @@ function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 				$output .= 'DROP TABLE IF EXISTS `' . $reg2[1] . '`;' . "\n";
 			}
 			$output .= $line;
-			$linenumber ++;
+			$linenumber++;
 			continue;
 		}
 
@@ -313,7 +313,7 @@ function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 			$output .= $tbl_extra;
 			$output .= $line;
 
-			$linenumber ++;
+			$linenumber++;
 			$tbl_extra = "";
 			continue;
 		}
@@ -395,10 +395,10 @@ function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 			if (strstr($line, " CONSTRAINT ")) {
 				$line = "";
 				// and if the previous output ended with a , remove the ,
-				$lastchr = substr($output, - 2, 1);
+				$lastchr = substr($output, -2, 1);
 				// echo "lastchr=$lastchr";
 				if ($lastchr == ",") {
-					$output = substr($output, 0, - 2) . "\n";
+					$output = substr($output, 0, -2) . "\n";
 				}
 			}
 
@@ -408,9 +408,9 @@ function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 		if (substr($line, 0, 11) == "INSERT INTO") {
 			$line = str_replace('public.', '', $line);
 
-			if (substr($line, - 3, - 1) == ");") {
+			if (substr($line, -3, -1) == ");") {
 				// we have a complete insert on one line
-				list ($before, $after) = explode(" VALUES ", $line, 2);
+				list($before, $after) = explode(" VALUES ", $line, 2);
 				// we only replace the " with ` in what comes BEFORE the VALUES
 				// (ie, field names, like INSERT INTO table ("bla","bla2") VALUES ('s:4:"test"','bladata2');
 				// should convert to INSERT INTO table (`bla`,`bla2`) VALUES ('s:4:"test"','bladata2');
@@ -424,13 +424,13 @@ function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 				$after = str_replace(", E'", ", '", $after);
 
 				$output .= $before . " VALUES " . $after;
-				$linenumber ++;
+				$linenumber++;
 				continue;
 			} else {
 				// this insert spans multiple lines, so keep dumping the lines until we reach a line
 				// that ends with ");"
 
-				list ($before, $after) = explode(" VALUES ", $line, 2);
+				list($before, $after) = explode(" VALUES ", $line, 2);
 				// we only replace the " with ` in what comes BEFORE the VALUES
 				// (ie, field names, like INSERT INTO table ("bla","bla2") VALUES ('s:4:"test"','bladata2');
 				// should convert to INSERT INTO table (`bla`,`bla2`) VALUES ('s:4:"test"','bladata2');
@@ -453,7 +453,7 @@ function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 
 				$output .= $before . " VALUES " . $after;
 				do {
-					$linenumber ++;
+					$linenumber++;
 
 					// in after, we need to watch out for escape format strings, ie (E'escaped \r in a string'), and ('bla',E'escaped \r in a string')
 					// ugh i guess its possible these strings could exist IN the data as well, but the only way to solve that is to process these lines one character
@@ -477,7 +477,7 @@ function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 						}
 						// echo "inquotes=$inquotes\n";
 					}
-				} while (substr($lines[$linenumber], - 3, - 1) != ");" || $inquotes);
+				} while (substr($lines[$linenumber], -3, -1) != ");" || $inquotes);
 			}
 		}
 		if (substr($line, 0, 16) == "ALTER TABLE ONLY") {
@@ -486,14 +486,14 @@ function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 			$line = str_replace("public.", "", $line);
 			$pkey = $line;
 
-			$linenumber ++;
+			$linenumber++;
 			if (!empty($lines[$linenumber])) {
 				$line = $lines[$linenumber];
 			} else {
 				$line = '';
 			}
 
-			if (strstr($line, " PRIMARY KEY ") && substr($line, - 3, - 1) == ");") {
+			if (strstr($line, " PRIMARY KEY ") && substr($line, -3, -1) == ");") {
 				$reg2 = array();
 				if (preg_match('/ALTER TABLE ([^\s]+)/', $pkey, $reg2)) {
 					if (empty($arrayofprimaryalreadyintabledef[$reg2[1]])) {
@@ -580,7 +580,7 @@ function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 			}
 		}
 
-		$linenumber ++;
+		$linenumber++;
 	}
 
 	return array('output' => $output,'outputatend' => $outputatend);

+ 3 - 3
dev/tools/fixdosfiles.sh

@@ -10,21 +10,21 @@
 # Syntax
 if [ "x$1" != "xlist" -a "x$1" != "xfix" ]
 then
-	echo "This script detect or clean files with CR+LF into files with LF only. All source files are included, also files into includes."
+	echo "This script detect or clean files with CR+LF into files with LF only. All source files are included, including files into includes."
 	echo "Usage: fixdosfiles.sh [list|fix]"
 fi
 
 # To detec
 if [ "x$1" = "xlist" ]
 then
-	find . \( -iname "functions" -o -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" -o -iname "*.pml" \) -exec file "{}" + | grep -v "CRLF" | grep -v 'custom\/' | grep -v 'documents\/website' | grep -v 'documents\/medias' | grep -v 'documents\/sellyoursaas' | grep CRLF
+	find . \( -iname "functions" -o -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" -o -iname "*.pml" \) -exec file "{}" + | grep -v 'custom\/' | grep -v 'documents\/website' | grep -v 'documents\/medias' | grep -v 'documents\/sellyoursaas' | grep CRLF
 #	find . \( -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" \) -exec file "{}" + | grep -v "CRLF" | grep -v 'custom\/' | grep -v 'documents\/website' | grep -v 'documents\/medias' | grep -v 'documents\/sellyoursaas' | grep -v 'htdocs\/includes' | grep CRLF
 fi
 
 # To convert
 if [ "x$1" = "xfix" ]
 then
-	for fic in `find . \( -iname "functions" -o -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" -o -iname "*.pml" \) -exec file "{}" + | grep -v "CRLF" | grep -v 'custom\/' | grep -v 'documents\/website' | grep -v 'documents\/medias' | grep -v 'documents\/sellyoursaas' | grep CRLF | awk -F':' '{ print $1 }' `
+	for fic in `find . \( -iname "functions" -o -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" -o -iname "*.pml" \) -exec file "{}" + | grep -v 'custom\/' | grep -v 'documents\/website' | grep -v 'documents\/medias' | grep -v 'documents\/sellyoursaas' | grep CRLF | awk -F':' '{ print $1 }' `
 	do
 		echo "Fix file $fic"
 		dos2unix "$fic"

+ 12 - 6
dev/tools/github_authors_and_commits_peryear.sh

@@ -1,10 +1,11 @@
 #!/bin/sh
 #
 # Count number of different contributors and number of commits for a given year.
+# Can be used for statistics (for example to generate the inforgraphy of the year)
 #
  
 if [ "x$1" = "x" ]; then
-	echo "Usage: $0 YEAR"
+	echo "Usage: $0 YEARSTART [YEAREND]"
 	exit
 fi
 
@@ -12,10 +13,15 @@ fi
 FROM=$1-01-01
 TO=$1-12-31
 
-echo "Number of contributors for the year"
-echo "git log --since $FROM --before $TO | grep ^Author | sort -u -f -i -b | wc -l"
-git log --since $FROM --before $TO | grep ^Author | sort -u -f -i -b | wc -l
+if [ "x$2" != "x" ]; then
+	TO=$2-12-31
+fi
+
+echo "--- Number of contributors for the year"
+echo "git log --since $FROM --before $TO | grep ^Author | awk -F'<' '{ print $1 }' | iconv -f UTF-8 -t ASCII//TRANSLIT | sort -u -f -i -b | wc -l"
+git log --since $FROM --before $TO | grep '^Author' | awk -F"<" '{ print $1 }' | iconv -f UTF-8 -t ASCII//TRANSLIT | sort -u -f -i -b | wc -l
 
 
-echo "Number of commit for the year"
-git log --pretty='format:%cd' --date=format:'%Y' | uniq -c | awk '{print "Year: "$2", commits: "$1}' | grep "Year: $1"
+echo "--- Number of commit for the year"
+echo "git log --pretty='format:%cd' --date=format:'%Y' | sort | uniq -c | awk '{ if (\$2 >= '"$1"') { print \"Year: \"\$2\", commits: \"\$1 } }'"
+git log --pretty='format:%cd' --date=format:'%Y' | sort | uniq -c | awk '{ if ($2 >= '$1') { print "Year: "$2", commits: "$1 } }'

+ 38 - 7
dev/tools/github_commits_perversion.sh

@@ -3,16 +3,47 @@
 # Count number of commits per user and per versions (using date for version detection)
 #
 
-Releases=("17.0" "18.0" "develop")
-Dates=("2023-02-01" "2023-08-31" "2050-01-01")
-let "counter = 1"
+Releases=("3.9" "4.0" "5.0" "6.0" "7.0" "8.0" "9.0" "10.0" "11.0" "12.0" "13.0" "14.0" "15.0" "16.0" "17.0" "18.0" "develop")
+let "counter = 0"
 
+echo "Copy script into /tmp/github_commits_perversion.sh"
+cp $0 /tmp/github_commits_perversion.sh
+
+echo "Delete /tmp/git"
+rm -fr /tmp/git
+echo "Create and go into /tmp/git"
+mkdir /tmp/git
+cd /tmp/git
+git clone https://github.com/Dolibarr/dolibarr.git
+
+cd /tmp/git/dolibarr
+
+firstline=1
 for i in "${Releases[@]}"
 do
-  echo "=== $counter git shortlog -s -n  --after=${Dates[counter-1]} --before=${Dates[counter]}"
-  git shortlog -s -n  --after=${Dates[counter-1]} --before=${Dates[counter]}
-  echo -n "Total $i: " 
-  git log --pretty=oneline --after=${Dates[counter-1]} --before=${Dates[counter]} | wc -l
+  if [ $firstline -eq 1 ]; then
+    firstline=0
+  	continue
+  fi
+  
+  #echo "=== Version $i (counter $counter):"
+  echo "=== Version $i (counter $counter):"
+  echo "Get common commit ID between origin/${Releases[counter]} and origin/${Releases[counter+1]}"
+  echo "git merge-base origin/${Releases[counter]} origin/${Releases[counter+1]}"
+  commitidcommon=`git merge-base origin/${Releases[counter]} origin/${Releases[counter+1]}`
+  echo "Found commitid=$commitidcommon"
+  
+  echo "Checkout into version $i"
+  git checkout $i  
+  #git shortlog -s -n  --after=YYYY-MM-DD --before=YYYY-MM-DD | tr '[:lower:]' '[:upper:]' > /tmp/github_commits_perversion.txt
+  git shortlog -s -n $commitidcommon.. | tr '[:lower:]' '[:upper:]' > /tmp/github_commits_perversion.txt
+  #cat /tmp/github_commits_perversion.txt
+  echo "Total for version $i:"
+  echo -n "- Nb of commits: " 
+  git log $commitidcommon.. --pretty=oneline | tr '[:lower:]' '[:upper:]' > /tmp/github_commits_perversion2.txt
+  cat /tmp/github_commits_perversion2.txt | wc -l
+  echo -n "- Nb of different authors: " 
+  awk ' { print $2 } ' < /tmp/github_commits_perversion.txt | sort -u | wc -l
   echo "=======================" 
   echo
   let "counter +=1"

+ 57 - 0
dev/tools/make_sprite.sh

@@ -0,0 +1,57 @@
+#!/bin/bash
+
+# Based of: https://gist.github.com/jaymzcd/342399 and https://github.com/buren/flag-sprite-maker
+
+# uses imagemagick to stich together all images in a folder and
+# then writes a css file with the correct offsets along with a
+# test html page for verification that its all good
+
+# Usage:
+# $ ./make_sprite.sh path class_name image_extension
+
+set -euo pipefail
+IFS=$'\n\t'
+
+name='output'; # output will be placed in a folder named this
+path="${1:-}"  # Path to flag images
+classname=${2:-flag}"-sprite"
+ext="."${3:-png}; # the extension to iterate over for input files
+
+css="$name/$classname.css";
+html="$name/test.html";
+
+rm -fr $name;
+mkdir $name;
+touch $css $html;
+
+echo "Generating sprite file...";
+convert $path*$ext -append $name/$classname$ext;
+echo "Sprite complete! - Creating css & test output...";
+
+echo -e "<html>\n<head>\n\t<link rel=\"stylesheet\" href=\"`basename $css`\" />\n</head>\n<body>\n\t<h1>Sprite test page</h1>\n" >> $html
+
+echo -e ".$classname {\n\tbackground:url('$classname$ext') no-repeat top left; display:inline-block;\n}" >> $css;
+counter=0;
+offset=0;
+for file in $path*$ext
+do
+    width=`identify -format "%[fx:w]" "$file"`;
+    height=`identify -format "%[fx:h]" "$file"`;
+    idname=`basename "$file" $ext`;
+    clean=${idname// /-}
+    echo -e ".$classname.$clean {" >> $css;
+    echo -e "\tbackground-position:0 -${offset}px;" >> $css;
+    echo -e "\twidth: ${width}px;" >> $css;
+    echo -e "\theight: ${height}px;\n}" >> $css;
+
+    echo -e "<div style=\"display:inline-block;width:100px;\"><div style=\"overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap;\">$clean</div> <a href=\"#\" class=\"$classname $clean\"></a></div>\n" >> $html;
+
+    let offset+=$height;
+    let counter+=1;
+    echo -e "\t#$counter done";
+done
+
+echo -e "<h2>Full sprite:</h2>\n<img src=\"$classname$ext\" />" >> $html;
+echo -e "</body>\n</html>" >> $html;
+
+echo -e "\nComplete! - $counter sprites created, css written & test page output.";

+ 3 - 0
dev/tools/php-cs-fixer/.gitignore

@@ -0,0 +1,3 @@
+/vendor/
+/composer.json
+/composer.lock

+ 80 - 0
dev/tools/php-cs-fixer/run-php-cs-fixer.sh

@@ -0,0 +1,80 @@
+#!/bin/bash
+
+#
+# Usage:
+#   Optionally set COMPOSER_CMD to the command to use to run composer.
+#   Optionally set COMPOSER_VENDOR_DIR to your vendor path for composer.
+#
+#   Run php-cs-fixer by calling this script:
+#     ./run-php-cs-fixer.sh check    # Only checks (not available with PHP 7.0)
+#     ./run-php-cs-fixer.sh fix      # Fixes
+#
+#   You can fix only a few files using
+#     ./run-php-cs-fixer.sh fix htdocs/path/to/myfile.php
+#
+#   You can run this from the root directory of dolibarr
+#     dev/tools/run-php-cs-fixer.sh fix htdocs/path/to/myfile.php
+#
+#   You can provide the environment variables on the CLI like this:
+#     COMPOSER_CMD="php ~/composer.phar" COMPOSER_VENDOR_DIR="~/vendor" ./run-php-cs-fixer.sh
+#
+#   or export them:
+#     export COMPOSER_CMD="~/composer.phar"
+#     export COMPOSER_VENDOR_DIR="~/vendor"
+#     ./run-php-cs-fixer.sh
+#
+#  Or some other method
+#
+
+
+MYDIR=$(dirname "$(realpath "$0")")
+export COMPOSER_VENDOR_DIR=${COMPOSER_VENDOR_DIR:=$MYDIR/vendor}
+COMPOSER_CMD=${COMPOSER_CMD:composer}
+MINPHPVERSION="7.0"
+
+
+echo "***** run-php-cs-fixer.sh *****"
+
+if [ "x$1" = "x" ]; then
+	echo "Syntax: run-php-cs-fixer.sh check|fix  [path_from_root_project]"
+	exit 1;
+fi
+
+
+#
+# Check composer is available
+#
+if [ ! -r "${COMPOSER_CMD}" ] ; then
+	echo composer is not available or not in path. You can give the path of composer by setting COMPOSER_CMD=/pathto/composer
+	echo Example: export COMPOSER_CMD="~/composer.phar"
+	echo Example: export COMPOSER_CMD="/usr/local/bin/composer"
+	exit 1;
+fi
+
+
+#
+# Install/update php-cs-fixer
+#
+echo Install php-cs-fixer
+PHP_CS_FIXER="${COMPOSER_VENDOR_DIR}/bin/php-cs-fixer"
+if [ ! -r "${PHP_CS_FIXER}" ] ; then
+  [[ ! -e "${COMPOSER_VENDOR_DIR}" ]] && ${COMPOSER_CMD} install
+  [[ -e "${COMPOSER_VENDOR_DIR}" ]] && ${COMPOSER_CMD} update
+  php${MINPHPVERSION} ${COMPOSER_CMD} require --dev friendsofphp/php-cs-fixer
+  echo
+fi
+
+
+# With PHP 7.0, php-cs-fixer is V2 (command check not supported)
+# With PHP 8.2, php-cs-fixer is V3
+ 
+(
+  echo cd "${MYDIR}/../../.."
+  cd "${MYDIR}/../../.." || exit
+  CMD=
+  # If no argument, run check by default
+  [[ "$1" == "" ]] && CMD=check
+  # shellcheck disable=SC2086
+  echo php${MINPHPVERSION} "${PHP_CS_FIXER}" $CMD "$@"
+  php${MINPHPVERSION} "${PHP_CS_FIXER}" $CMD "$@"
+)

+ 2 - 0
dev/tools/rector/.gitignore

@@ -0,0 +1,2 @@
+/test.php
+/vendor

+ 28 - 0
dev/tools/rector/README.md

@@ -0,0 +1,28 @@
+### Refactoring code with [rector](https://getrector.com)
+
+
+#### Installation
+
+Run in this folder
+```shell
+cd dev/tools/rector
+```
+
+Install rector with composer
+```shell
+composer install
+```
+
+
+#### Usage
+
+##### To make changes (Add --dry-run for test mode only)
+```shell
+./vendor/bin/rector process --dry-run
+```
+
+##### To make changes on a given directory
+
+```shell
+./vendor/bin/rector process [--dry-run] [--clear-cache] ../../../htdocs/core/
+```

+ 19 - 0
dev/tools/rector/composer.json

@@ -0,0 +1,19 @@
+{
+  "name": "dolibarr/rector",
+  "type": "project",
+  "license": "GplV3",
+  "authors": [
+    {
+      "name": "Dev2a",
+      "email": "contact@dev2a.pro"
+    }
+  ],
+  "require-dev": {
+    "rector/rector": "^0.18.5"
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Dolibarr\\Rector\\": "./src"
+    }
+  }
+}

+ 137 - 0
dev/tools/rector/composer.lock

@@ -0,0 +1,137 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "f2998987cad52db5ab60d5ff0672ce05",
+    "packages": [],
+    "packages-dev": [
+        {
+            "name": "phpstan/phpstan",
+            "version": "1.10.38",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpstan/phpstan.git",
+                "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5302bb402c57f00fb3c2c015bac86e0827e4b691",
+                "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2|^8.0"
+            },
+            "conflict": {
+                "phpstan/phpstan-shim": "*"
+            },
+            "bin": [
+                "phpstan",
+                "phpstan.phar"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "PHPStan - PHP Static Analysis Tool",
+            "keywords": [
+                "dev",
+                "static analysis"
+            ],
+            "support": {
+                "docs": "https://phpstan.org/user-guide/getting-started",
+                "forum": "https://github.com/phpstan/phpstan/discussions",
+                "issues": "https://github.com/phpstan/phpstan/issues",
+                "security": "https://github.com/phpstan/phpstan/security/policy",
+                "source": "https://github.com/phpstan/phpstan-src"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/ondrejmirtes",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/phpstan",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-10-06T14:19:14+00:00"
+        },
+        {
+            "name": "rector/rector",
+            "version": "0.18.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/rectorphp/rector.git",
+                "reference": "2a3b82f317e431fc142d21f3303891a4e64c96eb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/rectorphp/rector/zipball/2a3b82f317e431fc142d21f3303891a4e64c96eb",
+                "reference": "2a3b82f317e431fc142d21f3303891a4e64c96eb",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2|^8.0",
+                "phpstan/phpstan": "^1.10.35"
+            },
+            "conflict": {
+                "rector/rector-doctrine": "*",
+                "rector/rector-downgrade-php": "*",
+                "rector/rector-phpunit": "*",
+                "rector/rector-symfony": "*"
+            },
+            "bin": [
+                "bin/rector"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Instant Upgrade and Automated Refactoring of any PHP code",
+            "keywords": [
+                "automation",
+                "dev",
+                "migration",
+                "refactoring"
+            ],
+            "support": {
+                "issues": "https://github.com/rectorphp/rector/issues",
+                "source": "https://github.com/rectorphp/rector/tree/0.18.5"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/tomasvotruba",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-10-05T11:25:40+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": [],
+    "plugin-api-version": "2.2.0"
+}

+ 62 - 0
dev/tools/rector/rector.php

@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+
+use Rector\Config\RectorConfig;
+use Rector\Core\ValueObject\PhpVersion;
+use Rector\Set\ValueObject\LevelSetList;
+
+return static function (RectorConfig $rectorConfig): void {
+	$rectorConfig->phpVersion(PhpVersion::PHP_71);
+	//$rectorConfig->indent(' ', 4);
+
+	// Traits seems not supported correctly by rector without declaring them as bootstrapFiles
+	$arrayoftraitfiles = array(
+		__DIR__ . '/../../../htdocs/core/class/commonincoterm.class.php',
+		__DIR__ . '/../../../htdocs/core/class/commonpeople.class.php',
+		__DIR__ . '/../../../htdocs/core/class/commonsocialnetworks.class.php'
+	);
+	$rectorConfig->bootstrapFiles($arrayoftraitfiles);
+
+	$rectorConfig->paths([
+		__DIR__ . '/../../../htdocs/',
+		__DIR__ . '/../../../scripts/',
+		__DIR__ . '/../../../test/phpunit/',
+	]);
+	$rectorConfig->skip([
+		'**/includes/**',
+		'**/custom/**',
+		'**/vendor/**',
+		'**/rector/**',		// Disable this line to test the "test.php" file.
+		__DIR__ . '/../../../htdocs/custom/',
+		__DIR__ . '/../../../htdocs/install/doctemplates/*'
+	]);
+	$rectorConfig->parallel(240);
+
+
+	// Register rules
+
+	//$rectorConfig->rule(Rector\Php71\Rector\List_\ListToArrayDestructRector::class);
+	//$rectorConfig->rule(Rector\Php72\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector::class);
+	//$rectorConfig->rule(Rector\Php72\Rector\FuncCall\GetClassOnNullRector::class);
+	//$rectorConfig->rule(Rector\Php72\Rector\Assign\ListEachRector::class);
+	//$rectorConfig->rule(Rector\Php72\Rector\FuncCall\ParseStrWithResultArgumentRector::class);
+	//$rectorConfig->rule(Rector\Php72\Rector\FuncCall\StringifyDefineRector::class);
+
+	//$rectorConfig->rule(ReplaceEachAssignmentWithKeyCurrentRector::class);
+
+	$rectorConfig->rule(Rector\CodeQuality\Rector\FuncCall\FloatvalToTypeCastRector::class);
+	$rectorConfig->rule(Rector\CodeQuality\Rector\FuncCall\BoolvalToTypeCastRector::class);
+	$rectorConfig->rule(Rector\CodeQuality\Rector\NotEqual\CommonNotEqualRector::class);
+	//$rectorconfig->rule(Rector\CodeQuality\Rector\If_\CompleteMissingIfElseBracketRector::class);
+	$rectorConfig->rule(Rector\CodeQuality\Rector\For_\ForRepeatedCountToOwnVariableRector::class);
+
+	$rectorConfig->rule(Dolibarr\Rector\Renaming\GlobalToFunction::class);
+	$rectorConfig->rule(Dolibarr\Rector\Renaming\UserRightsToFunction::class);
+	$rectorConfig->rule(Dolibarr\Rector\Renaming\EmptyGlobalToFunction::class);
+
+	// Add all predefined rules to migrate to up to php 71
+	// $rectorConfig->sets([
+	//	LevelSetList::UP_TO_PHP_71
+	// ]);
+};

+ 216 - 0
dev/tools/rector/src/Renaming/EmptyGlobalToFunction.php

@@ -0,0 +1,216 @@
+<?php
+
+namespace Dolibarr\Rector\Renaming;
+
+use PhpParser\Node;
+use PhpParser\Node\Arg;
+use PhpParser\Node\Expr\ArrayDimFetch;
+use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
+use PhpParser\Node\Expr\BinaryOp\Concat;
+use PhpParser\Node\Expr\BinaryOp\Equal;
+use PhpParser\Node\Expr\BooleanNot;
+use PhpParser\Node\Expr\Empty_;
+use PhpParser\Node\Expr\FuncCall;
+use PhpParser\Node\Expr\Isset_;
+use PhpParser\Node\Expr\PropertyFetch;
+use PhpParser\Node\Name;
+use PhpParser\Node\Scalar\String_;
+use Rector\Core\NodeManipulator\BinaryOpManipulator;
+use Rector\Core\Rector\AbstractRector;
+use Rector\Php71\ValueObject\TwoNodeMatch;
+use Symplify\RuleDocGenerator\Exception\PoorDocumentationException;
+use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
+use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
+use Rector\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector;
+
+/**
+ * Class with Rector custom rule to fix code
+ */
+class EmptyGlobalToFunction extends AbstractRector
+{
+	/**
+	 * @var \Rector\Core\NodeManipulator\BinaryOpManipulator
+	 */
+	private $binaryOpManipulator;
+
+	/**
+	 * Constructor
+	 *
+	 * @param BinaryOpManipulator $binaryOpManipulator The $binaryOpManipulator
+	 */
+	public function __construct(BinaryOpManipulator $binaryOpManipulator)
+	{
+		$this->binaryOpManipulator = $binaryOpManipulator;
+	}
+
+	/**
+	 * getRuleDefinition
+	 *
+	 * @return RuleDefinition
+	 * @throws PoorDocumentationException
+	 */
+	public function getRuleDefinition(): RuleDefinition
+	{
+		return new RuleDefinition(
+			'Change $conf->global to getDolGlobal',
+			[new CodeSample(
+				'$conf->global->CONSTANT',
+				'getDolGlobalInt(\'CONSTANT\')'
+			)]
+		);
+	}
+
+	/**
+	 * Return a node type from https://github.com/rectorphp/php-parser-nodes-docs/
+	 *
+	 * @return string[]
+	 */
+	public function getNodeTypes(): array
+	{
+		return [Node\Expr\BooleanNot::class, Node\Expr\Empty_::class];
+	}
+
+	/**
+	 * refactor
+	 *
+	 * @param 	Node 					$node 		A node
+	 * @return	FuncCall|BooleanNot
+	 */
+	public function refactor(Node $node)
+	{
+		if ($node instanceof Node\Expr\BooleanNot) {
+			if (!$node->expr instanceof Node\Expr\Empty_) {
+				return null;
+			}
+			// node is !empty(...) so we set node to ...
+			$newnode = $node->expr->expr;
+
+			$tmpglobal = $newnode->var;
+			if (is_null($tmpglobal)) {
+				return null;
+			}
+			if (!$this->isName($tmpglobal, 'global')) {
+				return null;
+			}
+
+			$tmpconf = $tmpglobal->var;
+			if (!$this->isName($tmpconf, 'conf')) {
+				return null;
+			}
+
+			$nameforconst = $this->getName($newnode);
+			if (is_null($nameforconst)) {
+				return null;
+			}
+			$constName = new String_($nameforconst);
+
+			// We found a node !empty(conf->global->XXX)
+			return new FuncCall(
+				new Name('getDolGlobalString'),
+				[new Arg($constName)]
+			);
+		}
+
+
+		if ($node instanceof Node\Expr\Empty_) {
+			// node is empty(...) so we set node to ...
+			$newnode = $node->expr;
+
+			$tmpglobal = $newnode->var;
+			if (is_null($tmpglobal)) {
+				return null;
+			}
+			if (!$this->isName($tmpglobal, 'global')) {
+				return null;
+			}
+
+			$tmpconf = $tmpglobal->var;
+			if (!$this->isName($tmpconf, 'conf')) {
+				return null;
+			}
+
+			$nameforconst = $this->getName($newnode);
+			if (is_null($nameforconst)) {
+				return null;
+			}
+			$constName = new String_($nameforconst);
+
+			return new BooleanNot(new FuncCall(
+				new Name('getDolGlobalString'),
+				[new Arg($constName)]
+			));
+		}
+
+		return null;
+	}
+
+	/**
+	 * Get nodes with check empty
+	 *
+	 * @param BooleanAnd $booleanAnd A BooleandAnd
+	 * @return    TwoNodeMatch|null
+	 */
+	private function resolveTwoNodeMatch(BooleanAnd $booleanAnd): ?TwoNodeMatch
+	{
+		return $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
+			$booleanAnd,
+			// $conf->global == $value
+			function (Node $node): bool {
+				if (!$node instanceof Equal) {
+					return \false;
+				}
+				return $this->isGlobalVar($node->left);
+			},
+			// !empty(...) || isset(...)
+			function (Node $node): bool {
+				if ($node instanceof BooleanNot && $node->expr instanceof Empty_) {
+					return $this->isGlobalVar($node->expr->expr);
+				}
+				if (!$node instanceof Isset_) {
+					return $this->isGlobalVar($node);
+				}
+				return \true;
+			}
+		);
+	}
+
+	/**
+	 * Check if node is a global access with format conf->global->XXX
+	 *
+	 * @param Node 	$node 	A node
+	 * @return bool			Return true if noe is conf->global->XXX
+	 */
+	private function isGlobalVar($node)
+	{
+		if (!$node instanceof PropertyFetch) {
+			return false;
+		}
+		if (!$this->isName($node->var, 'global')) {
+			return false;
+		}
+		$global = $node->var;
+		if (!$global instanceof PropertyFetch) {
+			return false;
+		}
+		if (!$this->isName($global->var, 'conf')) {
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * @param 	Node 		$node 	Node to be parsed
+	 * @return 	Node|void			Return the name of the constant
+	 */
+	private function getConstName($node)
+	{
+		if ($node instanceof PropertyFetch && $node->name instanceof Node\Expr) {
+			return $node->name;
+		}
+		$name = $this->getName($node);
+		if (empty($name)) {
+			return;
+		}
+		return new String_($name);
+	}
+}

+ 391 - 0
dev/tools/rector/src/Renaming/GlobalToFunction.php

@@ -0,0 +1,391 @@
+<?php
+
+namespace Dolibarr\Rector\Renaming;
+
+use PhpParser\Node;
+use PhpParser\Node\Arg;
+use PhpParser\Node\Expr\ArrayDimFetch;
+use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
+use PhpParser\Node\Expr\BinaryOp\Concat;
+use PhpParser\Node\Expr\BinaryOp\Equal;
+use PhpParser\Node\Expr\BooleanNot;
+use PhpParser\Node\Expr\Empty_;
+use PhpParser\Node\Expr\FuncCall;
+use PhpParser\Node\Expr\Isset_;
+use PhpParser\Node\Expr\MethodCall;
+use PhpParser\Node\Expr\PropertyFetch;
+use PhpParser\Node\Name;
+use PhpParser\Node\Scalar\String_;
+use Rector\Core\NodeManipulator\BinaryOpManipulator;
+use Rector\Core\Rector\AbstractRector;
+use Rector\Php71\ValueObject\TwoNodeMatch;
+use Symplify\RuleDocGenerator\Exception\PoorDocumentationException;
+use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
+use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
+use PhpParser\Node\Expr\BinaryOp\NotEqual;
+use PhpParser\Node\Expr\BinaryOp\Greater;
+use PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
+use PhpParser\Node\Expr\BinaryOp\Smaller;
+use PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
+use PhpParser\Node\Expr\BinaryOp\NotIdentical;
+
+/**
+ * Class with Rector custom rule to fix code
+ */
+class GlobalToFunction extends AbstractRector
+{
+	/**
+	 * @var \Rector\Core\NodeManipulator\BinaryOpManipulator
+	 */
+	private $binaryOpManipulator;
+
+	/**
+	 * Constructor
+	 *
+	 * @param BinaryOpManipulator $binaryOpManipulator The $binaryOpManipulator
+	 */
+	public function __construct(BinaryOpManipulator $binaryOpManipulator)
+	{
+		$this->binaryOpManipulator = $binaryOpManipulator;
+	}
+
+	/**
+	 * getRuleDefinition
+	 *
+	 * @return RuleDefinition
+	 * @throws PoorDocumentationException
+	 */
+	public function getRuleDefinition(): RuleDefinition
+	{
+		return new RuleDefinition(
+			'Change $conf->global to getDolGlobal in context (1) conf->global Operator Value or (2) function(conf->global...)',
+			[new CodeSample(
+				'$conf->global->CONSTANT',
+				'getDolGlobalInt(\'CONSTANT\')'
+			)]
+		);
+	}
+
+	/**
+	 * Return a node type from https://github.com/rectorphp/php-parser-nodes-docs/
+	 *
+	 * @return string[]
+	 */
+	public function getNodeTypes(): array
+	{
+		return [FuncCall::class, MethodCall::class, Equal::class, NotEqual::class, Greater::class, GreaterOrEqual::class, Smaller::class, SmallerOrEqual::class, NotIdentical::class, BooleanAnd::class, Concat::class, ArrayDimFetch::class];
+	}
+
+	/**
+	 * refactor
+	 *
+	 * @param Node 	$node 		A node
+	 * @return    				FuncCall|Equal|Concat|ArrayDimFetch|void
+	 * 							return $node unchanged or void to do nothing
+	 */
+	public function refactor(Node $node)
+	{
+		if ($node instanceof Node\Expr\ArrayDimFetch) {
+			if (!isset($node->dim)) {
+				return;
+			}
+			if ($this->isGlobalVar($node->dim)) {
+				$constName = $this->getConstName($node->dim);
+				if (empty($constName)) {
+					return;
+				}
+				$node->dim = new FuncCall(
+					new Name('getDolGlobalString'),
+					[new Arg($constName)]
+				);
+			}
+			return $node;
+		}
+
+		if ($node instanceof FuncCall) {
+			$tmpfunctionname = $this->getName($node);
+			// If function is ok. We must avoid a lot of cases like isset(), empty()
+			if (in_array($tmpfunctionname, array('dol_escape_htmltag', 'dol_hash', 'make_substitutions', 'min', 'max', 'explode'))) {
+				//print "tmpfunctionname=".$tmpfunctionname."\n";
+				$args = $node->getArgs();
+				$nbofparam = count($args);
+
+				if ($nbofparam >= 1) {
+					$tmpargs = $args;
+					foreach ($args as $key => $arg) {	// only 1 element in this array
+						//var_dump($key);
+						//var_dump($arg->value);exit;
+						if ($this->isGlobalVar($arg->value)) {
+							$constName = $this->getConstName($arg->value);
+							if (empty($constName)) {
+								return;
+							}
+							$a = new FuncCall(new Name('getDolGlobalString'), [new Arg($constName)]);
+							$tmpargs[$key] = new Arg($a);
+
+							$r = new FuncCall(new Name($tmpfunctionname), $tmpargs);
+							return $r;
+						}
+					}
+				}
+			}
+			return $node;
+		}
+
+		if ($node instanceof MethodCall) {
+			$tmpmethodname = $this->getName($node->name);
+			// If function is ok. We must avoid a lot of cases
+			if (in_array($tmpmethodname, array('idate'))) {
+				//print "tmpmethodname=".$tmpmethodname."\n";
+				$expr = $node->var;
+				$args = $node->getArgs();
+				$nbofparam = count($args);
+
+				if ($nbofparam >= 1) {
+					$tmpargs = $args;
+					foreach ($args as $key => $arg) {	// only 1 element in this array
+						//var_dump($key);
+						//var_dump($arg->value);exit;
+						if ($this->isGlobalVar($arg->value)) {
+							$constName = $this->getConstName($arg->value);
+							if (empty($constName)) {
+								return;
+							}
+							$a = new FuncCall(new Name('getDolGlobalString'), [new Arg($constName)]);
+							$tmpargs[$key] = new Arg($a);
+
+							$r = new MethodCall($expr, $tmpmethodname, $tmpargs);
+							return $r;
+						}
+					}
+				}
+			}
+			return $node;
+		}
+
+		if ($node instanceof Concat) {
+			if ($this->isGlobalVar($node->left)) {
+				$constName = $this->getConstName($node->left);
+				if (empty($constName)) {
+					return;
+				}
+				$leftConcat = new FuncCall(
+					new Name('getDolGlobalString'),
+					[new Arg($constName)]
+				);
+				$rightConcat = $node->right;
+			}
+			if ($this->isGlobalVar($node->right)) {
+				$constName = $this->getConstName($node->right);
+				if (empty($constName)) {
+					return;
+				}
+				$rightConcat = new FuncCall(
+					new Name('getDolGlobalString'),
+					[new Arg($constName)]
+				);
+				$leftConcat = $node->left;
+			}
+			if (!isset($leftConcat, $rightConcat)) {
+				return;
+			}
+			return new Concat($leftConcat, $rightConcat);
+		}
+
+		if ($node instanceof BooleanAnd) {
+			$nodes = $this->resolveTwoNodeMatch($node);
+			if (!isset($nodes)) {
+				return;
+			}
+
+			/** @var Equal $node */
+			$node = $nodes->getFirstExpr();
+		}
+
+		// Now process all comparison like:
+		// $conf->global->... Operator Value
+
+		$typeofcomparison = '';
+		if ($node instanceof Equal) {
+			$typeofcomparison = 'Equal';
+		}
+		if ($node instanceof NotEqual) {
+			$typeofcomparison = 'NotEqual';
+		}
+		if ($node instanceof Greater) {
+			$typeofcomparison = 'Greater';
+		}
+		if ($node instanceof GreaterOrEqual) {
+			$typeofcomparison = 'GreaterOrEqual';
+		}
+		if ($node instanceof Smaller) {
+			$typeofcomparison = 'Smaller';
+		}
+		if ($node instanceof SmallerOrEqual) {
+			$typeofcomparison = 'SmallerOrEqual';
+		}
+		if ($node instanceof NotIdentical) {
+			$typeofcomparison = 'NotIdentical';
+			//var_dump($node->left);
+		}
+		if (empty($typeofcomparison)) {
+			return;
+		}
+
+		if (!$this->isGlobalVar($node->left)) {
+			return;
+		}
+
+		// Test the type after the comparison conf->global->xxx to know the name of function
+		$typeright = $node->right->getType();
+		switch ($typeright) {
+			case 'Scalar_LNumber':
+				$funcName = 'getDolGlobalInt';
+				break;
+			case 'Scalar_String':
+				$funcName = 'getDolGlobalString';
+				break;
+			default:
+				return;
+		}
+
+		$constName = $this->getConstName($node->left);
+		if (empty($constName)) {
+			return;
+		}
+
+		if ($typeofcomparison == 'Equal') {
+			return new Equal(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+				),
+				$node->right
+			);
+		}
+		if ($typeofcomparison == 'NotEqual') {
+			return new NotEqual(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+					),
+				$node->right
+				);
+		}
+		if ($typeofcomparison == 'Greater') {
+			return new Greater(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+				),
+				$node->right
+			);
+		}
+		if ($typeofcomparison == 'GreaterOrEqual') {
+			return new GreaterOrEqual(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+				),
+				$node->right
+			);
+		}
+		if ($typeofcomparison == 'Smaller') {
+			return new Smaller(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+				),
+				$node->right
+			);
+		}
+		if ($typeofcomparison == 'SmallerOrEqual') {
+			return new SmallerOrEqual(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+				),
+				$node->right
+			);
+		}
+		if ($typeofcomparison == 'NotIdentical') {
+			return new NotIdentical(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+					),
+				$node->right
+				);
+		}
+	}
+
+	/**
+	 * Get nodes with check empty
+	 *
+	 * @param BooleanAnd $booleanAnd A BooleandAnd
+	 * @return    TwoNodeMatch|null
+	 */
+	private function resolveTwoNodeMatch(BooleanAnd $booleanAnd): ?TwoNodeMatch
+	{
+		return $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
+			$booleanAnd,
+			// Function to check if we are in the case $conf->global->... == $value
+			function (Node $node): bool {
+				if (!$node instanceof Equal) {
+					return \false;
+				}
+				return $this->isGlobalVar($node->left);
+			},
+			// !empty(...) || isset(...)
+			function (Node $node): bool {
+				if ($node instanceof BooleanNot && $node->expr instanceof Empty_) {
+					return $this->isGlobalVar($node->expr->expr);
+				}
+				if (!$node instanceof Isset_) {
+					return $this->isGlobalVar($node);
+				}
+				return \true;
+			}
+		);
+	}
+
+	/**
+	 * Check if node is a global access with format conf->global->XXX
+	 *
+	 * @param Node 	$node 	A node
+	 * @return bool			Return true if node is conf->global->XXX
+	 */
+	private function isGlobalVar($node)
+	{
+		if (!$node instanceof PropertyFetch) {
+			return false;
+		}
+		if (!$this->isName($node->var, 'global')) {
+			return false;
+		}
+		$global = $node->var;
+		if (!$global instanceof PropertyFetch) {
+			return false;
+		}
+		if (!$this->isName($global->var, 'conf')) {
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * @param 	Node 		$node 	Node to be parsed
+	 * @return 	Node|void			Return the name of the constant
+	 */
+	private function getConstName($node)
+	{
+		if ($node instanceof PropertyFetch && $node->name instanceof Node\Expr) {
+			return $node->name;
+		}
+		$name = $this->getName($node);
+		if (empty($name)) {
+			return;
+		}
+		return new String_($name);
+	}
+}

+ 153 - 0
dev/tools/rector/src/Renaming/UserRightsToFunction.php

@@ -0,0 +1,153 @@
+<?php
+
+namespace Dolibarr\Rector\Renaming;
+
+use PhpParser\Node;
+use PhpParser\Node\Arg;
+use PhpParser\Node\Scalar\String_;
+use PhpParser\NodeTraverser;
+use Rector\Core\PhpParser\Node\NodeFactory;
+use Rector\Core\Rector\AbstractRector;
+use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
+use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
+
+/**
+ * Class to refactor User rights
+ */
+class UserRightsToFunction extends AbstractRector
+{
+	/**
+	 * @param \Rector\Core\PhpParser\Node\NodeFactory $nodeFactory node factory
+	 */
+	public function __construct(NodeFactory $nodeFactory)
+	{
+		$this->nodeFactory = $nodeFactory;
+	}
+
+	/**
+	 * @throws \Symplify\RuleDocGenerator\Exception\PoorDocumentationException
+	 * @return RuleDefinition
+	 */
+	public function getRuleDefinition(): RuleDefinition
+	{
+		return new RuleDefinition(
+			'Change \$user->rights->module->permission to \$user->hasRight(\'module\', \'permission\')',
+			[new CodeSample(
+				'$user->rights->module->permission',
+				'$user->hasRight(\'module\', \'permission\')'
+			)]
+		);
+	}
+
+	/**
+	 * Return a node type from https://github.com/rectorphp/php-parser-nodes-docs/
+	 *
+	 * @return string[]
+	 */
+	public function getNodeTypes(): array
+	{
+		return [
+			Node\Expr\Assign::class,
+			Node\Expr\PropertyFetch::class,
+			Node\Expr\BooleanNot::class,
+			Node\Expr\Empty_::class,
+			Node\Expr\Isset_::class,
+			Node\Stmt\ClassMethod::class
+		];
+	}
+
+	/**
+	 * @param \PhpParser\Node $node node to be changed
+	 * @return \PhpParser\Node|\PhpParser\Node[]|\PhpParser\Node\Expr\MethodCall|void|null| int
+	 */
+	public function refactor(Node $node)
+	{
+		if ($node instanceof Node\Stmt\ClassMethod) {
+			$excludeMethods = ['getrights', 'hasRight'];
+			/** @var \PHPStan\Analyser\MutatingScope $scope */
+			$scope = $node->getAttribute('scope');
+			$class = $scope->getClassReflection();
+			$classes = ['UserGroup', 'User'];
+			if (isset($class) && in_array($class->getName(), $classes)) {
+				if (in_array($this->getName($node), $excludeMethods)) {
+					return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
+				}
+			}
+		}
+		if ($node instanceof Node\Expr\Assign) {
+			return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
+		}
+		$isInverse = false;
+		if ($node instanceof Node\Expr\BooleanNot) {
+			if (!$node->expr instanceof Node\Expr\Empty_) {
+				return null;
+			}
+			$node = $node->expr->expr;
+		}
+		if ($node instanceof Node\Expr\Empty_) {
+			$node = $node->expr;
+			$isInverse = true;
+		}
+		if ($node instanceof Node\Expr\Isset_) {
+			// Take first arg for isset (No code found with multiple isset).
+			$node = $node->vars[0];
+		}
+		if (!$node instanceof Node\Expr\PropertyFetch) {
+			return;
+		}
+		$data = $this->getRights($node);
+		if (!isset($data)) {
+			return;
+		}
+		$args = [new Arg($data['module']), new Arg($data['perm1'])];
+		if (!empty($data['perm2'])) {
+			$args[] = new Arg($data['perm2']);
+		}
+		$method = $this->nodeFactory->createMethodCall($data['user'], 'hasRight', $args);
+		if ($isInverse) {
+			return new Node\Expr\BooleanNot($method);
+		}
+		return $method;
+	}
+
+	/**
+	 * @param \PhpParser\Node\Expr\PropertyFetch $node node
+	 * @return array|null
+	 */
+	private function getRights(Node\Expr\PropertyFetch $node)
+	{
+		$perm2 = '';
+		if (!$node->var instanceof Node\Expr\PropertyFetch) {
+			return null;
+		}
+		// Add a test to avoid rector error on html.formsetup.class.php
+		if (!$node->name instanceof Node\Expr\Variable && is_null($this->getName($node))) {
+			//var_dump($node);
+			return null;
+			//exit;
+		}
+		$perm1 = $node->name instanceof Node\Expr\Variable ? $node->name : new String_($this->getName($node));
+		$moduleNode = $node->var;
+		if (!$moduleNode instanceof Node\Expr\PropertyFetch) {
+			return null;
+		}
+		if (!$moduleNode->var instanceof Node\Expr\PropertyFetch) {
+			return null;
+		}
+		if (!$this->isName($moduleNode->var, 'rights')) {
+			$perm2 = $perm1;
+			$perm1 = $moduleNode->name instanceof Node\Expr\Variable ? $moduleNode->name : new String_($this->getName($moduleNode));
+			$moduleNode = $moduleNode->var;
+		}
+		$module = $moduleNode->name instanceof Node\Expr\Variable ? $moduleNode->name : new String_($this->getName($moduleNode));
+		$rights = $moduleNode->var;
+		if (!$this->isName($rights, 'rights') || !isset($perm1) || !isset($module)) {
+			return null;
+		}
+		if (!$rights->var instanceof Node\Expr\Variable) {
+			return null;
+		}
+		$user = $rights->var;
+		return compact('user', 'module', 'perm1', 'perm2');
+	}
+}

+ 0 - 146
dev/tools/spider.php

@@ -1,146 +0,0 @@
-#!/usr/bin/env php
-<?php
-/*
- * 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    dev/tools/spider.php
- * \brief   Script to spider Dolibarr app.
- *
- * To use it:
- * - Disable module "bookmark"
- * - Exclude param  optioncss, token, sortfield, sortorder
- */
-
-$crawledLinks=array();
-const MAX_DEPTH=2;
-
-
-/**
- * @param  string $url   URL
- * @param  string $depth Depth
- * @return string       String
- */
-function followLink($url, $depth = 0)
-{
-	global $crawledLinks;
-	$crawling=array();
-	if ($depth>MAX_DEPTH) {
-		echo "<div style='color:red;'>The Crawler is giving up!</div>";
-		return;
-	}
-	$options=array(
-		'http'=>array(
-			'method'=>"GET",
-			'user-agent'=>"gfgBot/0.1\n"
-		)
-	);
-	$context=stream_context_create($options);
-	$doc=new DomDocument();
-	@$doc->loadHTML(file_get_contents($url, false, $context));
-	$links=$doc->getElementsByTagName('a');
-	$pageTitle=getDocTitle($doc, $url);
-	$metaData=getDocMetaData($doc);
-	foreach ($links as $i) {
-		$link=$i->getAttribute('href');
-		if (ignoreLink($link)) continue;
-		$link=convertLink($url, $link);
-		if (!in_array($link, $crawledLinks)) {
-			$crawledLinks[]=$link;
-			$crawling[]=$link;
-			insertIntoDatabase($link, $pageTitle, $metaData, $depth);
-		}
-	}
-	foreach ($crawling as $crawlURL)
-		followLink($crawlURL, $depth+1);
-}
-
-/**
- * @param  string $site Site
- * @param  string $path Path
- * @return string       String
- */
-function convertLink($site, $path)
-{
-	if (substr_compare($path, "//", 0, 2)==0)
-		return parse_url($site)['scheme'].$path;
-	elseif (substr_compare($path, "http://", 0, 7)==0
-		or substr_compare($path, "https://", 0, 8)==0
-		or substr_compare($path, "www.", 0, 4)==0
-	)
-		return $path;
-	else return $site.'/'.$path;
-}
-
-/**
- * @param  string $url URL
- * @return boolean
- */
-function ignoreLink($url)
-{
-	return $url[0]=="#" or substr($url, 0, 11) == "javascript:";
-}
-
-/**
- * @param  string $link     URL
- * @param  string $title    Title
- * @param  string $metaData Array
- * @param  int    $depth    Depth
- * @return void
- */
-function insertIntoDatabase($link, $title, &$metaData, $depth)
-{
-	//global $crawledLinks;
-
-	echo "Inserting new record {URL= ".$link.", Title = '$title', Description = '".$metaData['description']."', Keywords = ' ".$metaData['keywords']."'}<br/><br/><br/>";
-
-	//²$crawledLinks[]=$link;
-}
-
-/**
- * @param  string $doc Doc
- * @param  string $url URL
- * @return string               URL/Title
- */
-function getDocTitle(&$doc, $url)
-{
-	$titleNodes=$doc->getElementsByTagName('title');
-	if (count($titleNodes)==0 or !isset($titleNodes[0]->nodeValue))
-		return $url;
-	$title=str_replace('', '\n', $titleNodes[0]->nodeValue);
-	return (strlen($title)<1)?$url:$title;
-}
-
-/**
- * @param  string $doc Doc
- * @return array                Array
- */
-function getDocMetaData(&$doc)
-{
-	$metaData=array();
-	$metaNodes=$doc->getElementsByTagName('meta');
-	foreach ($metaNodes as $node)
-		$metaData[$node->getAttribute("name")] = $node->getAttribute("content");
-	if (!isset($metaData['description']))
-		$metaData['description']='No Description Available';
-	if (!isset($metaData['keywords'])) $metaData['keywords']='';
-	return array(
-		'keywords'=>str_replace('', '\n', $metaData['keywords']),
-		'description'=>str_replace('', '\n', $metaData['description'])
-	);
-}
-
-
-followLink("http://localhost/dolibarr_dev/htdocs");

+ 0 - 0
scripts/invoices/facturex-pdfextractxml.py → dev/tools/test/facturex-pdfextractxml.py


+ 1 - 0
dev/tools/test/testtcpdf.php

@@ -1,4 +1,5 @@
 <?php
+
 //============================================================+
 // File name   : example_016.php
 // Begin       : 2008-03-04

+ 1 - 0
dev/tools/test/testutf.php

@@ -1,4 +1,5 @@
 <?php
+
 // This source file must be UTF-8 encoded
 /*
 $filename='filewithé';

+ 4 - 5
dev/translation/autotranslator.class.php

@@ -74,7 +74,6 @@ class autoTranslator
 	 */
 	private function parseRefLangTranslationFiles()
 	{
-
 		$files = $this->getTranslationFilesArray($this->_refLang);
 		$counter = 1;
 		foreach ($files as $file) {
@@ -241,7 +240,7 @@ class autoTranslator
 			if ($this->_outputpagecode == 'UTF-8') {
 				$val=$this->translateTexts(array($value), substr($this->_refLang, 0, 2), substr($my_destlang, 0, 2));
 			} else {
-				$val=utf8_decode($this->translateTexts(array($value), substr($this->_refLang, 0, 2), substr($my_destlang, 0, 2)));
+				$val=mb_convert_encoding($this->translateTexts(array($value), substr($this->_refLang, 0, 2), substr($my_destlang, 0, 2)), 'ISO-8859-1');
 			}
 		}
 
@@ -276,7 +275,7 @@ class autoTranslator
 	private function getLineValue($line)
 	{
 		$arraykey = explode('=', $line, 2);
-		return trim(isset($arraykey[1])?$arraykey[1]:'');
+		return trim(isset($arraykey[1]) ? $arraykey[1] : '');
 	}
 
 	/**
@@ -334,8 +333,8 @@ class autoTranslator
 		//print "Url to translate: ".$url."\n";
 
 		if (! function_exists("curl_init")) {
-			  print "Error, your PHP does not support curl functions.\n";
-			  die();
+			print "Error, your PHP does not support curl functions.\n";
+			die();
 		}
 
 		$ch = curl_init();

+ 8 - 7
dev/translation/sanity_check_en_langfiles.php

@@ -210,7 +210,8 @@ foreach ($dups as $string => $pages) {
 	$inadmin=0;
 	foreach ($pages as $file => $lines) {
 		if ($file == 'main.lang') {
-			$inmain=1; $inadmin=0;
+			$inmain=1;
+			$inadmin=0;
 		}
 		if ($file == 'admin.lang' && ! $inmain) {
 			$inadmin=1;
@@ -568,16 +569,16 @@ if ((!empty($_REQUEST['unused']) && $_REQUEST['unused'] == 'true') || (isset($ar
 	if (empty($unused)) {
 		print "No string not used found.\n";
 	} else {
-		$filetosave='/tmp/'.($argv[2]?$argv[2]:"").'notused.lang';
+		$filetosave='/tmp/'.($argv[2] ? $argv[2] : "").'notused.lang';
 		print "Strings in en_US that are never used are saved into file ".$filetosave.":\n";
 		file_put_contents($filetosave, implode("", $unused));
 		print "To remove from original file, run command :\n";
-		if (($argv[2]?$argv[2]:"")) {
-			print 'cd htdocs/langs/en_US; mv '.($argv[2]?$argv[2]:"")." ".($argv[2]?$argv[2]:"").".tmp; ";
+		if (($argv[2] ? $argv[2] : "")) {
+			print 'cd htdocs/langs/en_US; mv '.($argv[2] ? $argv[2] : "")." ".($argv[2] ? $argv[2] : "").".tmp; ";
 		}
-		print "diff ".($argv[2]?$argv[2]:"").".tmp ".$filetosave." | grep \< | cut  -b 3- > ".($argv[2]?$argv[2]:"");
-		if (($argv[2]?$argv[2]:"")) {
-			print "; rm ".($argv[2]?$argv[2]:"").".tmp;\n";
+		print "diff ".($argv[2] ? $argv[2] : "").".tmp ".$filetosave." | grep \< | cut  -b 3- > ".($argv[2] ? $argv[2] : "");
+		if (($argv[2] ? $argv[2] : "")) {
+			print "; rm ".($argv[2] ? $argv[2] : "").".tmp;\n";
 		}
 	}
 }

+ 14 - 5
dev/translation/strip_language_file.php

@@ -58,10 +58,10 @@ $rc = 0;
 
 // Get and check arguments
 
-$lPrimary = isset($argv[1])?$argv[1]:'';
-$lSecondary = isset($argv[2])?$argv[2]:'';
+$lPrimary = isset($argv[1]) ? $argv[1] : '';
+$lSecondary = isset($argv[2]) ? $argv[2] : '';
 $lEnglish = 'en_US';
-$filesToProcess = isset($argv[3])?$argv[3]:'';
+$filesToProcess = isset($argv[3]) ? $argv[3] : '';
 
 if (empty($lPrimary) || empty($lSecondary) || empty($filesToProcess)) {
 	$rc = 1;
@@ -272,7 +272,16 @@ foreach ($filesToProcess as $fileToProcess) {
 
 			// key is redundant
 			if (array_key_exists($key, $aPrimary)) {
-				print "Key $key is redundant in file $lPrimaryFile (line: $cnt) - Already found into ".$fileFirstFound[$key]." (line: ".$lineFirstFound[$key].").\n";
+				print "Key $key is redundant in file $lPrimaryFile (line: $cnt)";
+				if (!empty($fileFirstFound[$key])) {
+					print " - Already found into ".$fileFirstFound[$key];
+					print " (line: ".$lineFirstFound[$key].").\n";
+				} else {
+					$fileFirstFound[$key] = $fileToProcess;
+					$lineFirstFound[$key] = $cnt;
+
+					print " - Already found into main file.\n";
+				}
 				continue;
 			} else {
 				$fileFirstFound[$key] = $fileToProcess;
@@ -308,7 +317,7 @@ foreach ($filesToProcess as $fileToProcess) {
 				|| in_array($key, $arrayofkeytoalwayskeep) || preg_match('/^FormatDate/', $key) || preg_match('/^FormatHour/', $key)
 				) {
 				//print "Key $key differs (aSecondary=".$aSecondary[$key].", aPrimary=".$aPrimary[$key].", aEnglish=".$aEnglish[$key].") so we add it into new secondary language (line: $cnt).\n";
-				fwrite($oh, $key."=".(empty($aSecondary[$key])?$aPrimary[$key]:$aSecondary[$key])."\n");
+				fwrite($oh, $key."=".(empty($aSecondary[$key]) ? $aPrimary[$key] : $aSecondary[$key])."\n");
 			}
 		}
 		if (! feof($handle)) {

+ 0 - 1
htdocs/.gitignore

@@ -24,5 +24,4 @@
 /abricot*
 /nomenclature*
 /of/
-/workstation/
 /oblyon*

+ 134 - 46
htdocs/accountancy/admin/account.php

@@ -1,7 +1,7 @@
 <?php
-/* Copyright (C) 2013-2016 Olivier Geffroy      <jeff@jeffinfo.com>
- * Copyright (C) 2013-2020 Alexandre Spangaro   <aspangaro@open-dsi.fr>
- * Copyright (C) 2016-2018 Laurent Destailleur  <eldy@users.sourceforge.net>
+/* Copyright (C) 2013-2016  Olivier Geffroy     <jeff@jeffinfo.com>
+ * Copyright (C) 2013-2024  Alexandre Spangaro  <aspangaro@easya.solutions>
+ * Copyright (C) 2016-2018  Laurent Destailleur <eldy@users.sourceforge.net>
  *
  * 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
@@ -39,7 +39,7 @@ $id = GETPOST('id', 'int');
 $rowid = GETPOST('rowid', 'int');
 $massaction = GETPOST('massaction', 'aZ09');
 $optioncss = GETPOST('optioncss', 'alpha');
-$contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'accountingaccountlist'; // To manage different context of search
+$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'accountingaccountlist'; // To manage different context of search
 $mode = GETPOST('mode', 'aZ'); // The output mode ('list', 'kanban', 'hierarchy', 'calendar', ...)
 
 $search_account = GETPOST('search_account', 'alpha');
@@ -49,7 +49,7 @@ $search_accountparent = GETPOST('search_accountparent', 'alpha');
 $search_pcgtype = GETPOST('search_pcgtype', 'alpha');
 $search_import_key = GETPOST('search_import_key', 'alpha');
 $toselect = GETPOST('toselect', 'array');
-$limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : $conf->liste_limit;
+$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
 $confirm = GETPOST('confirm', 'alpha');
 
 $chartofaccounts = GETPOST('chartofaccounts', 'int');
@@ -66,7 +66,7 @@ if (!$user->hasRight('accounting', 'chartofaccount')) {
 }
 
 // Load variable for pagination
-$limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : $conf->liste_limit;
+$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
 $sortfield = GETPOST('sortfield', 'aZ09comma');
 $sortorder = GETPOST('sortorder', 'aZ09comma');
 $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
@@ -111,7 +111,8 @@ $hookmanager->initHooks(array('accountancyadminaccount'));
  */
 
 if (GETPOST('cancel', 'alpha')) {
-	$action = 'list'; $massaction = '';
+	$action = 'list';
+	$massaction = '';
 }
 if (!GETPOST('confirmmassaction', 'alpha')) {
 	$massaction = '';
@@ -143,10 +144,13 @@ if (empty($reshook)) {
 		$search_labelshort = "";
 		$search_accountparent = "";
 		$search_pcgtype = "";
+		$search_import_key = "";
 		$search_array_options = array();
 	}
 	if ((GETPOST('valid_change_chart', 'alpha') && GETPOST('chartofaccounts', 'int') > 0)	// explicit click on button 'Change and load' with js on
 		|| (GETPOST('chartofaccounts', 'int') > 0 && GETPOST('chartofaccounts', 'int') != getDolGlobalInt('CHARTOFACCOUNTS'))) {	// a submit of form is done and chartofaccounts combo has been modified
+		$error = 0;
+
 		if ($chartofaccounts > 0 && $permissiontoadd) {
 			// Get language code for this $chartofaccounts
 			$sql = 'SELECT code FROM '.MAIN_DB_PREFIX.'c_country as c, '.MAIN_DB_PREFIX.'accounting_system as a';
@@ -154,7 +158,9 @@ if (empty($reshook)) {
 			$resql = $db->query($sql);
 			if ($resql) {
 				$obj = $db->fetch_object($resql);
-				$country_code = $obj->code;
+				if ($obj) {
+					$country_code = $obj->code;
+				}
 			} else {
 				dol_print_error($db);
 			}
@@ -217,14 +223,15 @@ if (empty($reshook)) {
 /*
  * View
  */
-
 $form = new Form($db);
 $formaccounting = new FormAccounting($db);
 
-llxHeader('', $langs->trans("ListAccounts"));
+$help_url = 'EN:Module_Double_Entry_Accounting#Setup|FR:Module_Comptabilit&eacute;_en_Partie_Double#Configuration';
+
+llxHeader('', $langs->trans("ListAccounts"), $help_url);
 
 if ($action == 'delete') {
-	$formconfirm = $html->formconfirm($_SERVER["PHP_SELF"].'?id='.$id, $langs->trans('DeleteAccount'), $langs->trans('ConfirmDeleteAccount'), 'confirm_delete', '', 0, 1);
+	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$id, $langs->trans('DeleteAccount'), $langs->trans('ConfirmDeleteAccount'), 'confirm_delete', '', 0, 1);
 	print $formconfirm;
 }
 
@@ -233,11 +240,24 @@ $pcgver = getDolGlobalInt('CHARTOFACCOUNTS');
 $sql = "SELECT aa.rowid, aa.fk_pcg_version, aa.pcg_type, aa.account_number, aa.account_parent, aa.label, aa.labelshort, aa.fk_accounting_category,";
 $sql .= " aa.reconcilable, aa.active, aa.import_key,";
 $sql .= " a2.rowid as rowid2, a2.label as label2, a2.account_number as account_number2";
+
+// Add fields from hooks
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+$sql .= $hookmanager->resPrint;
+$sql = preg_replace('/,\s*$/', '', $sql);
+
 $sql .= " FROM ".MAIN_DB_PREFIX."accounting_account as aa";
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_system as asy ON aa.fk_pcg_version = asy.pcg_version AND aa.entity = ".((int) $conf->entity);
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as a2 ON a2.rowid = aa.account_parent AND a2.entity = ".((int) $conf->entity);
+
+// Add table from hooks
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldListFrom', $parameters, $object); // Note that $action and $object may have been modified by hook
+$sql .= $hookmanager->resPrint;
+
 $sql .= " WHERE asy.rowid = ".((int) $pcgver);
-//print $sql;
+
 if (strlen(trim($search_account))) {
 	$lengthpaddingaccount = 0;
 	if (getDolGlobalInt('ACCOUNTING_LENGTH_GACCOUNT') || getDolGlobalInt('ACCOUNTING_LENGTH_AACCOUNT')) {
@@ -260,7 +280,7 @@ if (strlen(trim($search_account))) {
 			$search_account_tmp_clean = $search_account_tmp;
 			$search_account_clean = $search_account;
 			$startchar = '%';
-			if (strpos($search_account_tmp, '^') === 0) {
+			if (substr($search_account_tmp, 0, 1) === '^') {
 				$startchar = '';
 				$search_account_tmp_clean = preg_replace('/^\^/', '', $search_account_tmp);
 				$search_account_clean = preg_replace('/^\^/', '', $search_account);
@@ -284,6 +304,15 @@ if (strlen(trim($search_accountparent)) && $search_accountparent != '-1') {
 if (strlen(trim($search_pcgtype))) {
 	$sql .= natural_search("aa.pcg_type", $search_pcgtype);
 }
+if (strlen(trim($search_import_key))) {
+	$sql .= natural_search("aa.import_key", $search_import_key);
+}
+
+// Add where from hooks
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+$sql .= $hookmanager->resPrint;
+
 $sql .= $db->order($sortfield, $sortorder);
 //print $sql;
 
@@ -298,16 +327,6 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	}
 }
 
-// List of mass actions available
-if ($user->hasRight('accounting', 'chartofaccount')) {
-	$arrayofmassactions['predelete'] = '<span class="fa fa-trash paddingrightonly"></span>'.$langs->trans("Delete");
-}
-if (in_array($massaction, array('presend', 'predelete', 'closed'))) {
-	$arrayofmassactions = array();
-}
-
-$massactionbutton = $form->selectMassAction('', $arrayofmassactions);
-$arrayofselected = is_array($toselect) ? $toselect : array();
 $sql .= $db->plimit($limit + 1, $offset);
 
 dol_syslog('accountancy/admin/account.php:: $sql='.$sql);
@@ -316,6 +335,8 @@ $resql = $db->query($sql);
 if ($resql) {
 	$num = $db->num_rows($resql);
 
+	$arrayofselected = is_array($toselect) ? $toselect : array();
+
 	$param = '';
 	if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
 		$param .= '&contextpage='.urlencode($contextpage);
@@ -338,13 +359,18 @@ if ($resql) {
 	if ($search_pcgtype) {
 		$param .= '&search_pcgtype='.urlencode($search_pcgtype);
 	}
-	if ($optioncss != '') {
+	if ($search_import_key) {
 		$param .= '&search_import_key='.urlencode($search_import_key);
 	}
 	if ($optioncss != '') {
 		$param .= '&optioncss='.urlencode($optioncss);
 	}
 
+	// Add $param from hooks
+	$parameters = array();
+	$reshook = $hookmanager->executeHooks('printFieldListSearchParam', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+	$param .= $hookmanager->resPrint;
+
 	if (!empty($conf->use_javascript_ajax)) {
 		print '<!-- Add javascript to reload page when we click "Change plan" -->
 			<script type="text/javascript">
@@ -358,7 +384,19 @@ if ($resql) {
 	    	</script>';
 	}
 
+	// List of mass actions available
+	if ($user->hasRight('accounting', 'chartofaccount')) {
+		$arrayofmassactions['predelete'] = '<span class="fa fa-trash paddingrightonly"></span>'.$langs->trans("Delete");
+	}
+	if (in_array($massaction, array('presend', 'predelete', 'closed'))) {
+		$arrayofmassactions = array();
+	}
+
+	$massactionbutton = $form->selectMassAction('', $arrayofmassactions);
+
 	$newcardbutton = '';
+	$newcardbutton = dolGetButtonTitle($langs->trans('Addanaccount'), '', 'fa fa-plus-circle', DOL_URL_ROOT.'/accountancy/admin/card.php?action=create', '', $permissiontoadd);
+
 
 	print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
 	if ($optioncss != '') {
@@ -371,10 +409,10 @@ if ($resql) {
 	print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
 	print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
 
-	$newcardbutton .= dolGetButtonTitle($langs->trans("New"), $langs->trans("Addanaccount"), 'fa fa-plus-circle', './card.php?action=create');
-	include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php';
 	print_barre_liste($langs->trans('ListAccounts'), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'accounting_account', 0, $newcardbutton, '', $limit, 0, 0, 1);
 
+	include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php';
+
 	// Box to select active chart of account
 	print $langs->trans("Selectchartofaccounts")." : ";
 	print '<select class="flat minwidth200" name="chartofaccounts" id="chartofaccounts">';
@@ -391,11 +429,11 @@ if ($resql) {
 		print '<option value="-1">&nbsp;</option>';
 		while ($i < $numbis) {
 			$obj = $db->fetch_object($resqlchart);
-
-			print '<option value="'.$obj->rowid.'"';
-			print ($pcgver == $obj->rowid) ? ' selected' : '';
-			print '>'.$obj->pcg_version.' - '.$obj->label.' - ('.$obj->country_code.')</option>';
-
+			if ($obj) {
+				print '<option value="'.$obj->rowid.'"';
+				print ($pcgver == $obj->rowid) ? ' selected' : '';
+				print '>'.$obj->pcg_version.' - '.$obj->label.' - ('.$obj->country_code.')</option>';
+			}
 			$i++;
 		}
 	} else {
@@ -407,8 +445,8 @@ if ($resql) {
 
 	print '<br>';
 
-	$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
+	$parameters = array();
+	$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 	print $hookmanager->resPrint;
 
 	print '<br>';
@@ -418,6 +456,11 @@ if ($resql) {
 	$selectedfields .= (count($arrayofmassactions) ? $form->showCheckAddButtons('checkforselect', 1) : '');
 
 	$moreforfilter = '';
+	if ($moreforfilter) {
+		print '<div class="liste_titre liste_titre_bydiv centpercent">';
+		print $moreforfilter;
+		print '</div>';
+	}
 
 	$accountstatic = new AccountingAccount($db);
 	$accountparent = new AccountingAccount($db);
@@ -427,12 +470,14 @@ if ($resql) {
 	print '<div class="div-table-responsive">';
 	print '<table class="tagtable liste'.($moreforfilter ? " listwithfilterbefore" : "").'">'."\n";
 
-	// Line for search fields
+	// Fields title search
+	// --------------------------------------------------------------------
 	print '<tr class="liste_titre_filter">';
+
 	// Action column
 	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
-		print '<td class="liste_titre maxwidthsearch">';
-		$searchpicto = $form->showFilterButtons();
+		print '<td class="liste_titre center maxwidthsearch">';
+		$searchpicto = $form->showFilterButtons('left');
 		print $searchpicto;
 		print '</td>';
 	}
@@ -458,11 +503,17 @@ if ($resql) {
 	if (!empty($arrayfields['categories']['checked'])) {
 		print '<td class="liste_titre"></td>';
 	}
+
+	// Fields from hook
+	$parameters = array('arrayfields'=>$arrayfields);
+	$reshook = $hookmanager->executeHooks('printFieldListOption', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+	print $hookmanager->resPrint;
+
 	// Import key
 	if (!empty($arrayfields['aa.import_key']['checked'])) {
 		print '<td class="liste_titre"><input type="text" class="flat width75" name="search_import_key" value="'.$search_import_key.'"></td>';
 	}
-	if ($conf->global->MAIN_FEATURES_LEVEL >= 2) {
+	if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) {
 		if (!empty($arrayfields['aa.reconcilable']['checked'])) {
 			print '<td class="liste_titre">&nbsp;</td>';
 		}
@@ -472,52 +523,77 @@ if ($resql) {
 	}
 	// Action column
 	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
-		print '<td class="liste_titre maxwidthsearch">';
+		print '<td class="liste_titre center maxwidthsearch">';
 		$searchpicto = $form->showFilterButtons();
 		print $searchpicto;
 		print '</td>';
 	}
-	print '</tr>';
+	print '</tr>'."\n";
+
+	$totalarray = array();
+	$totalarray['nbfield'] = 0;
+
+	// Fields title label
+	// --------------------------------------------------------------------
 	print '<tr class="liste_titre">';
 	// Action column
 	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
-		print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
+		print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch actioncolumn ');
+		$totalarray['nbfield']++;
 	}
 	if (!empty($arrayfields['aa.account_number']['checked'])) {
 		print_liste_field_titre($arrayfields['aa.account_number']['label'], $_SERVER["PHP_SELF"], "aa.account_number", "", $param, '', $sortfield, $sortorder);
+		$totalarray['nbfield']++;
 	}
 	if (!empty($arrayfields['aa.label']['checked'])) {
 		print_liste_field_titre($arrayfields['aa.label']['label'], $_SERVER["PHP_SELF"], "aa.label", "", $param, '', $sortfield, $sortorder);
+		$totalarray['nbfield']++;
 	}
 	if (!empty($arrayfields['aa.labelshort']['checked'])) {
 		print_liste_field_titre($arrayfields['aa.labelshort']['label'], $_SERVER["PHP_SELF"], "aa.labelshort", "", $param, '', $sortfield, $sortorder);
+		$totalarray['nbfield']++;
 	}
 	if (!empty($arrayfields['aa.account_parent']['checked'])) {
 		print_liste_field_titre($arrayfields['aa.account_parent']['label'], $_SERVER["PHP_SELF"], "aa.account_parent", "", $param, '', $sortfield, $sortorder, 'left ');
+		$totalarray['nbfield']++;
 	}
 	if (!empty($arrayfields['aa.pcg_type']['checked'])) {
 		print_liste_field_titre($arrayfields['aa.pcg_type']['label'], $_SERVER["PHP_SELF"], 'aa.pcg_type,aa.account_number', '', $param, '', $sortfield, $sortorder, '', $arrayfields['aa.pcg_type']['help'], 1);
+		$totalarray['nbfield']++;
 	}
 	if (!empty($arrayfields['categories']['checked'])) {
 		print_liste_field_titre($arrayfields['categories']['label'], $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, '', $arrayfields['categories']['help'], 1);
+		$totalarray['nbfield']++;
 	}
+
+	// Hook fields
+	$parameters = array('arrayfields'=>$arrayfields, 'param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder);
+	$reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+	print $hookmanager->resPrint;
+
 	if (!empty($arrayfields['aa.import_key']['checked'])) {
 		print_liste_field_titre($arrayfields['aa.import_key']['label'], $_SERVER["PHP_SELF"], 'aa.import_key', '', $param, '', $sortfield, $sortorder, '', $arrayfields['aa.import_key']['help'], 1);
+		$totalarray['nbfield']++;
 	}
-	if ($conf->global->MAIN_FEATURES_LEVEL >= 2) {
+	if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) {
 		if (!empty($arrayfields['aa.reconcilable']['checked'])) {
 			print_liste_field_titre($arrayfields['aa.reconcilable']['label'], $_SERVER["PHP_SELF"], 'aa.reconcilable', '', $param, '', $sortfield, $sortorder);
+			$totalarray['nbfield']++;
 		}
 	}
 	if (!empty($arrayfields['aa.active']['checked'])) {
 		print_liste_field_titre($arrayfields['aa.active']['label'], $_SERVER["PHP_SELF"], 'aa.active', '', $param, '', $sortfield, $sortorder);
+		$totalarray['nbfield']++;
 	}
 	// Action column
 	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
 		print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
+		$totalarray['nbfield']++;
 	}
 	print "</tr>\n";
 
+	// Loop on record
+	// --------------------------------------------------------------------
 	$i = 0;
 	while ($i < min($num, $limit)) {
 		$obj = $db->fetch_object($resql);
@@ -631,6 +707,11 @@ if ($resql) {
 			}
 		}
 
+		// Fields from hook
+		$parameters = array('arrayfields'=>$arrayfields, 'obj'=>$obj, 'i'=>$i, 'totalarray'=>&$totalarray);
+		$reshook = $hookmanager->executeHooks('printFieldListValue', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+		print $hookmanager->resPrint;
+
 		// Import id
 		if (!empty($arrayfields['aa.import_key']['checked'])) {
 			print "<td>";
@@ -641,8 +722,8 @@ if ($resql) {
 			}
 		}
 
-		if ($conf->global->MAIN_FEATURES_LEVEL >= 2) {
-			// Activated or not reconciliation on accounting account
+		if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) {
+			// Activated or not reconciliation on an accounting account
 			if (!empty($arrayfields['aa.reconcilable']['checked'])) {
 				print '<td class="center">';
 				if (empty($obj->reconcilable)) {
@@ -719,9 +800,16 @@ if ($resql) {
 		print '<tr><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("None").'</span></td></tr>';
 	}
 
-	print "</table>";
-	print "</div>";
-	print '</form>';
+	$db->free($resql);
+
+	$parameters = array('arrayfields'=>$arrayfields, 'sql'=>$sql);
+	$reshook = $hookmanager->executeHooks('printFieldListFooter', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+	print $hookmanager->resPrint;
+
+	print '</table>'."\n";
+	print '</div>'."\n";
+
+	print '</form>'."\n";
 } else {
 	dol_print_error($db);
 }

+ 45 - 47
htdocs/accountancy/admin/accountmodel.php

@@ -8,7 +8,7 @@
  * Copyright (C) 2011       Remy Younes             <ryounes@gmail.com>
  * Copyright (C) 2012-2015  Marcos García           <marcosgdf@gmail.com>
  * Copyright (C) 2012       Christophe Battarel     <christophe.battarel@ltairis.fr>
- * Copyright (C) 2011-2016  Alexandre Spangaro      <aspangaro@open-dsi.fr>
+ * Copyright (C) 2011-2024  Alexandre Spangaro      <aspangaro@easya.solutions>
  * Copyright (C) 2015       Ferran Marcet           <fmarcet@2byte.es>
  * Copyright (C) 2016       Raphaël Doursenaud      <rdoursenaud@gpcsolutions.fr>
  *
@@ -47,7 +47,7 @@ if (isModEnabled('accounting')) {
 // Load translation files required by the page
 $langs->loadLangs(array('accountancy', 'admin', 'companies', 'compta', 'errors', 'holiday', 'hrm', 'resource'));
 
-$action = GETPOST('action', 'aZ09') ?GETPOST('action', 'aZ09') : 'view';
+$action = GETPOST('action', 'aZ09') ? GETPOST('action', 'aZ09') : 'view';
 $confirm = GETPOST('confirm', 'alpha');
 $id = 31;
 $rowid = GETPOST('rowid', 'alpha');
@@ -59,7 +59,7 @@ $actl[0] = img_picto($langs->trans("Disabled"), 'switch_off', 'class="size15x"')
 $actl[1] = img_picto($langs->trans("Activated"), 'switch_on', 'class="size15x"');
 
 $listoffset = GETPOST('listoffset', 'alpha');
-$listlimit = GETPOST('listlimit', 'int') > 0 ?GETPOST('listlimit', 'int') : 1000;
+$listlimit = GETPOST('listlimit', 'int') > 0 ? GETPOST('listlimit', 'int') : 1000;
 $active = 1;
 
 $sortfield = GETPOST("sortfield", 'aZ09comma');
@@ -125,18 +125,10 @@ $tabfieldinsert[31] = "pcg_version,label,fk_country";
 $tabrowid = array();
 $tabrowid[31] = "";
 
-// Condition to show dictionary in setup page
-$tabcond = array();
-$tabcond[31] = isModEnabled('accounting');
-
 // List of help for fields
 $tabhelp = array();
 $tabhelp[31] = array('pcg_version'=>$langs->trans("EnterAnyCode"));
 
-// List of check for fields (NOT USED YET)
-$tabfieldcheck = array();
-$tabfieldcheck[31] = array();
-
 
 // Define elementList and sourceList (used for dictionary type of contacts "llx_c_type_contact")
 $elementList = array();
@@ -173,9 +165,12 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 			if ($fieldnamekey == 'pcg_version') {
 				$fieldnamekey = 'Pcg_version';
 			}
-			if ($fieldnamekey == 'libelle' || ($fieldnamekey == 'label')) {
+			if ($fieldnamekey == 'label') {
 				$fieldnamekey = 'Label';
 			}
+			if ($fieldnamekey == 'country') {
+				$fieldnamekey = "Country";
+			}
 
 			setEventMessages($langs->transnoentities("ErrorFieldRequired", $langs->transnoentities($fieldnamekey)), null, 'errors');
 		}
@@ -195,9 +190,9 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 	// Si verif ok et action add, on ajoute la ligne
 	if ($ok && GETPOST('actionadd', 'alpha')) {
 		if ($tabrowid[$id]) {
-			// Recupere id libre pour insertion
+			// Get free id for insert
 			$newid = 0;
-			$sql = "SELECT max(".$tabrowid[$id].") newid from ".$tabname[$id];
+			$sql = "SELECT MAX(".$tabrowid[$id].") newid from ".$tabname[$id];
 			$result = $db->query($sql);
 			if ($result) {
 				$obj = $db->fetch_object($result);
@@ -296,11 +291,6 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 			setEventMessages($db->error(), null, 'errors');
 		}
 	}
-	//$_GET["id"]=GETPOST('id', 'int');       // Force affichage dictionnaire en cours d'edition
-}
-
-if (GETPOST('actioncancel', 'alpha')) {
-	//$_GET["id"]=GETPOST('id', 'int');       // Force affichage dictionnaire en cours d'edition
 }
 
 if ($action == 'confirm_delete' && $confirm == 'yes') {       // delete
@@ -411,7 +401,9 @@ if ($action == 'disable_favorite') {
 $form = new Form($db);
 $formadmin = new FormAdmin($db);
 
-llxHeader();
+$help_url = 'EN:Module_Double_Entry_Accounting#Setup|FR:Module_Comptabilit&eacute;_en_Partie_Double#Configuration';
+
+llxHeader('', $langs->trans("Pcg_version"), $help_url);
 
 $titre = $langs->trans($tablib[$id]);
 $linkback = '';
@@ -458,6 +450,7 @@ if ($id) {
 	print '<table class="noborder centpercent">';
 
 	// Form to add a new line
+
 	if ($tabname[$id]) {
 		$fieldlist = explode(',', $tabfield[$id]);
 
@@ -472,12 +465,14 @@ if ($id) {
 			if ($fieldlist[$field] == 'code') {
 				$valuetoshow = $langs->trans("Code");
 			}
-			if ($fieldlist[$field] == 'libelle' || $fieldlist[$field] == 'label') {
+			if ($fieldlist[$field] == 'label') {
 				$valuetoshow = $langs->trans("Label");
+				$class = 'minwidth300';
 			}
 			if ($fieldlist[$field] == 'country') {
 				if (in_array('region_id', $fieldlist)) {
-					print '<td>&nbsp;</td>'; continue;
+					print '<td>&nbsp;</td>';
+					continue;
 				}		// For region page, we do not show the country input
 				$valuetoshow = $langs->trans("Country");
 			}
@@ -487,6 +482,7 @@ if ($id) {
 			if ($fieldlist[$field] == 'pcg_version' || $fieldlist[$field] == 'fk_pcg_version') {
 				$valuetoshow = $langs->trans("Pcg_version");
 			}
+			//var_dump($value);
 
 			if ($valuetoshow != '') {
 				print '<td class="'.$class.'">';
@@ -524,7 +520,8 @@ if ($id) {
 		$tmpaction = 'create';
 		$parameters = array('fieldlist'=>$fieldlist, 'tabname'=>$tabname[$id]);
 		$reshook = $hookmanager->executeHooks('createDictionaryFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks
-		$error = $hookmanager->error; $errors = $hookmanager->errors;
+		$error = $hookmanager->error;
+		$errors = $hookmanager->errors;
 
 		if (empty($reshook)) {
 			fieldListAccountModel($fieldlist, $obj, $tabname[$id], 'add');
@@ -621,20 +618,24 @@ if ($id) {
 					$tmpaction = 'edit';
 					$parameters = array('fieldlist'=>$fieldlist, 'tabname'=>$tabname[$id]);
 					$reshook = $hookmanager->executeHooks('editDictionaryFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks
-					$error = $hookmanager->error; $errors = $hookmanager->errors;
+					$error = $hookmanager->error;
+					$errors = $hookmanager->errors;
 
 					if (empty($reshook)) {
 						fieldListAccountModel($fieldlist, $obj, $tabname[$id], 'edit');
 					}
 
-					print '<td colspan="3" class="right"><a name="'.(!empty($obj->rowid) ? $obj->rowid : $obj->code).'">&nbsp;</a><input type="submit" class="button button-edit" name="actionmodify" value="'.$langs->trans("Modify").'">';
-					print '&nbsp;<input type="submit" class="button button-cancel" name="actioncancel" value="'.$langs->trans("Cancel").'"></td>';
+					print '<td colspan="3" class="right">';
+					print '<a name="'.(!empty($obj->rowid) ? $obj->rowid : $obj->code).'">&nbsp;</a><input type="submit" class="button button-edit" name="actionmodify" value="'.$langs->trans("Modify").'">';
+					print '&nbsp;<input type="submit" class="button button-cancel" name="actioncancel" value="'.$langs->trans("Cancel").'">';
+					print '</td>';
 				} else {
 					$tmpaction = 'view';
 					$parameters = array('fieldlist'=>$fieldlist, 'tabname'=>$tabname[$id]);
 					$reshook = $hookmanager->executeHooks('viewDictionaryFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks
 
-					$error = $hookmanager->error; $errors = $hookmanager->errors;
+					$error = $hookmanager->error;
+					$errors = $hookmanager->errors;
 
 					if (empty($reshook)) {
 						foreach ($fieldlist as $field => $value) {
@@ -674,9 +675,11 @@ if ($id) {
 					}
 
 					// Can an entry be erased or disabled ?
-					$iserasable = 1; $canbedisabled = 1; $canbemodified = 1; // true by default
+					$iserasable = 1;
+					$canbedisabled = 1;
+					$canbemodified = 1; // true by default
 
-					$url = $_SERVER["PHP_SELF"].'?token='.newToken().($page ? '&page='.$page : '').'&sortfield='.$sortfield.'&sortorder='.$sortorder.'&rowid='.(!empty($obj->rowid) ? $obj->rowid : (!empty($obj->code) ? $obj->code : '')).'&code='.(!empty($obj->code) ?urlencode($obj->code) : '');
+					$url = $_SERVER["PHP_SELF"].'?token='.newToken().($page ? '&page='.$page : '').'&sortfield='.$sortfield.'&sortorder='.$sortorder.'&rowid='.(!empty($obj->rowid) ? $obj->rowid : (!empty($obj->code) ? $obj->code : '')).'&code='.(!empty($obj->code) ? urlencode($obj->code) : '');
 					if ($param) {
 						$url .= '&'.$param;
 					}
@@ -710,6 +713,8 @@ if ($id) {
 
 				$i++;
 			}
+		} else {
+			print '<tr><td colspan="6"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
 		}
 	} else {
 		dol_print_error($db);
@@ -737,11 +742,10 @@ $db->close();
  *  @param		string	$context		'add'=Output field for the "add form", 'edit'=Output field for the "edit form", 'hide'=Output field for the "add form" but we dont want it to be rendered
  *	@return		void
  */
-function fieldListAccountModel($fieldlist, $obj = '', $tabname = '', $context = '')
+function fieldListAccountModel($fieldlist, $obj = null, $tabname = '', $context = '')
 {
-	global $conf, $langs, $db;
+	global $langs, $db;
 	global $form;
-	global $region_id;
 	global $elementList, $sourceList;
 
 	$formadmin = new FormAdmin($db);
@@ -774,29 +778,23 @@ function fieldListAccountModel($fieldlist, $obj = '', $tabname = '', $context =
 				print '<td>';
 			}
 			if ($fieldlist[$field] == 'type_cdr') {
-				print $form->selectarray($fieldlist[$field], array(0=>$langs->trans('None'), 1=>$langs->trans('AtEndOfMonth'), 2=>$langs->trans('CurrentNext')), (!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]}:''));
+				print $form->selectarray($fieldlist[$field], array(0=>$langs->trans('None'), 1=>$langs->trans('AtEndOfMonth'), 2=>$langs->trans('CurrentNext')), (!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]} : ''));
 			} else {
-				print $form->selectyesno($fieldlist[$field], (!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]}:''), 1);
+				print $form->selectyesno($fieldlist[$field], (!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]} : ''), 1);
 			}
 			print '</td>';
 		} elseif ($fieldlist[$field] == 'code' && isset($obj->{$fieldlist[$field]})) {
-			print '<td><input type="text" class="flat" value="'.(!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]}:'').'" size="10" name="'.$fieldlist[$field].'"></td>';
+			print '<td><input type="text" class="flat" value="'.(!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]} : '').'" size="10" name="'.$fieldlist[$field].'"></td>';
 		} else {
 			print '<td>';
-			$size = ''; $class = '';
-			if ($fieldlist[$field] == 'code') {
-				$size = 'size="8" ';
-			}
-			if ($fieldlist[$field] == 'position') {
-				$size = 'size="4" ';
-			}
-			if ($fieldlist[$field] == 'libelle') {
-				$size = 'centpercent';
+			$class = '';
+			if ($fieldlist[$field] == 'pcg_version') {
+				$class = 'width150';
 			}
-			if ($fieldlist[$field] == 'sortorder' || $fieldlist[$field] == 'sens' || $fieldlist[$field] == 'category_type') {
-				$size = 'size="2" ';
+			if ($fieldlist[$field] == 'label') {
+				$class = 'width300';
 			}
-			print '<input type="text" '.$size.' class="flat'.($class ? ' '.$class : '').'" value="'.(isset($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]}:'').'" name="'.$fieldlist[$field].'">';
+			print '<input type="text" class="flat'.($class ? ' '.$class : '').'" value="'.(isset($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]} : '').'" name="'.$fieldlist[$field].'">';
 			print '</td>';
 		}
 	}

+ 4 - 4
htdocs/accountancy/admin/card.php

@@ -1,6 +1,6 @@
 <?php
 /* Copyright (C) 2013-2014  Olivier Geffroy     <jeff@jeffinfo.com>
- * Copyright (C) 2013-2020  Alexandre Spangaro  <aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2024  Alexandre Spangaro  <aspangaro@easya.solutions>
  * Copyright (C) 2014       Florian Henry       <florian.henry@open-concept.pro>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -85,7 +85,7 @@ if ($action == 'add' && $user->hasRight('accounting', 'chartofaccount')) {
 			// Clean code
 
 			// To manage zero or not at the end of the accounting account
-			if (!empty($conf->global->ACCOUNTING_MANAGE_ZERO)) {
+			if (getDolGlobalString('ACCOUNTING_MANAGE_ZERO')) {
 				$account_number = $account_number;
 			} else {
 				$account_number = clean_account($account_number);
@@ -148,7 +148,7 @@ if ($action == 'add' && $user->hasRight('accounting', 'chartofaccount')) {
 			// Clean code
 
 			// To manage zero or not at the end of the accounting account
-			if (!empty($conf->global->ACCOUNTING_MANAGE_ZERO)) {
+			if (getDolGlobalString('ACCOUNTING_MANAGE_ZERO')) {
 				$account_number = $account_number;
 			} else {
 				$account_number = clean_account($account_number);
@@ -215,7 +215,7 @@ $accountsystem->fetch(getDolGlobalInt('CHARTOFACCOUNTS'));
 
 $title = $langs->trans('AccountAccounting')." - ".$langs->trans('Card');
 
-$help_url = 'EN:Category:Accounting';
+$help_url = 'EN:Module_Double_Entry_Accounting#Setup|FR:Module_Comptabilit&eacute;_en_Partie_Double#Configuration';
 
 llxheader('', $title, $help_url);
 

+ 5 - 2
htdocs/accountancy/admin/categories.php

@@ -1,6 +1,6 @@
 <?php
 /* Copyright (C) 2016       Jamal Elbaz         <jamelbaz@gmail.pro>
- * Copyright (C) 2017-2022  Alexandre Spangaro  <aspangaro@open-dsi.fr>
+ * Copyright (C) 2017-2024  Alexandre Spangaro  <aspangaro@easya.solutions>
  * Copyright (C) 2022       Laurent Destailleur <eldy@users.sourceforge.net>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -113,7 +113,9 @@ if ($action == 'delete') {
 $form = new Form($db);
 $formaccounting = new FormAccounting($db);
 
-llxheader('', $langs->trans('AccountingCategory'));
+$help_url = 'EN:Module_Double_Entry_Accounting#Setup|FR:Module_Comptabilit&eacute;_en_Partie_Double#Configuration';
+
+llxHeader('', $langs->trans('AccountingCategory'), $help_url);
 
 $linkback = '<a href="'.DOL_URL_ROOT.'/accountancy/admin/categories_list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
 $titlepicto = 'setup';
@@ -135,6 +137,7 @@ $s = $formaccounting->select_accounting_category($cat_id, 'account_category', 1,
 if ($formaccounting->nbaccounts_category <= 0) {
 	print '<span class="opacitymedium">'.$s.'</span>';
 } else {
+	print $s;
 	print '<input type="submit" class="button small" value="'.$langs->trans("Select").'">';
 }
 print '</td></tr>';

+ 36 - 21
htdocs/accountancy/admin/categories_list.php

@@ -1,6 +1,6 @@
 <?php
-/* Copyright (C) 2004-2023  Laurent Destailleur     <eldy@users.sourceforge.net>
- * Copyright (C) 2011-2021  Alexandre Spangaro      <aspangaro@open-dsi.fr>
+/* Copyright (C) 2004-2023  Laurent Destailleur      <eldy@users.sourceforge.net>
+ * Copyright (C) 2011-2024  Alexandre Spangaro       <aspangaro@easya.solutions>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -36,7 +36,7 @@ require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountancycategory.class.php
 // Load translation files required by the page
 $langs->loadLangs(array("errors", "admin", "companies", "resource", "holiday", "accountancy", "hrm"));
 
-$action = GETPOST('action', 'aZ09') ?GETPOST('action', 'aZ09') : 'view';
+$action = GETPOST('action', 'aZ09') ? GETPOST('action', 'aZ09') : 'view';
 $confirm = GETPOST('confirm', 'alpha');
 $id = 32;
 $rowid = GETPOST('rowid', 'alpha');
@@ -53,7 +53,7 @@ $actl[0] = img_picto($langs->trans("Disabled"), 'switch_off', 'class="size15x"')
 $actl[1] = img_picto($langs->trans("Activated"), 'switch_on', 'class="size15x"');
 
 $listoffset = GETPOST('listoffset', 'alpha');
-$listlimit = GETPOST('listlimit', 'int') > 0 ?GETPOST('listlimit', 'int') : 1000;
+$listlimit = GETPOST('listlimit', 'int') > 0 ? GETPOST('listlimit', 'int') : 1000;
 
 $sortfield = GETPOST("sortfield", 'aZ09comma');
 $sortorder = GETPOST("sortorder", 'aZ09comma');
@@ -200,9 +200,9 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 	// Si verif ok et action add, on ajoute la ligne
 	if ($ok && GETPOST('actionadd', 'alpha')) {
 		if ($tabrowid[$id]) {
-			// Recupere id libre pour insertion
+			// Get free id for insert
 			$newid = 0;
-			$sql = "SELECT max(".$tabrowid[$id].") newid from ".$tabname[$id];
+			$sql = "SELECT MAX(".$tabrowid[$id].") newid from ".$tabname[$id];
 			$result = $db->query($sql);
 			if ($result) {
 				$obj = $db->fetch_object($result);
@@ -414,7 +414,9 @@ if ($action == 'disable_favorite') {
 $form = new Form($db);
 $formadmin = new FormAdmin($db);
 
-llxHeader('', $langs->trans('DictionaryAccountancyCategory'));
+$help_url = 'EN:Module_Double_Entry_Accounting#Setup|FR:Module_Comptabilit&eacute;_en_Partie_Double#Configuration';
+
+llxHeader('', $langs->trans('DictionaryAccountancyCategory'), $help_url);
 
 $titre = $langs->trans($tablib[$id]);
 $linkback = '';
@@ -507,6 +509,7 @@ if ($tabname[$id]) {
 		}
 		if ($fieldlist[$field] == 'code') {
 			$valuetoshow = $langs->trans("Code");
+			$class = 'width75';
 		}
 		if ($fieldlist[$field] == 'libelle' || $fieldlist[$field] == 'label') {
 			$valuetoshow = $langs->trans("Label");
@@ -581,7 +584,8 @@ if ($tabname[$id]) {
 	$tmpaction = 'create';
 	$parameters = array('fieldlist'=>$fieldlist, 'tabname'=>$tabname[$id]);
 	$reshook = $hookmanager->executeHooks('createDictionaryFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks
-	$error = $hookmanager->error; $errors = $hookmanager->errors;
+	$error = $hookmanager->error;
+	$errors = $hookmanager->errors;
 
 	if (empty($reshook)) {
 		fieldListAccountingCategories($fieldlist, $obj, $tabname[$id], 'add');
@@ -784,7 +788,8 @@ if ($resql) {
 				$tmpaction = 'edit';
 				$parameters = array('fieldlist'=>$fieldlist, 'tabname'=>$tabname[$id]);
 				$reshook = $hookmanager->executeHooks('editDictionaryFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks
-				$error = $hookmanager->error; $errors = $hookmanager->errors;
+				$error = $hookmanager->error;
+				$errors = $hookmanager->errors;
 
 				// Actions
 				if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
@@ -810,13 +815,16 @@ if ($resql) {
 				}
 			} else {
 				// Can an entry be erased or disabled ?
-				$iserasable = 1; $canbedisabled = 1; $canbemodified = 1; // true by default
+				$iserasable = 1;
+				$canbedisabled = 1;
+				$canbemodified = 1; // true by default
 				if (isset($obj->code)) {
 					if (($obj->code == '0' || $obj->code == '' || preg_match('/unknown/i', $obj->code))) {
-						$iserasable = 0; $canbedisabled = 0;
+						$iserasable = 0;
+						$canbedisabled = 0;
 					}
 				}
-				$url = $_SERVER["PHP_SELF"].'?'.($page ? 'page='.$page.'&' : '').'sortfield='.$sortfield.'&sortorder='.$sortorder.'&rowid='.(!empty($obj->rowid) ? $obj->rowid : (!empty($obj->code) ? $obj->code : '')).'&code='.(!empty($obj->code) ?urlencode($obj->code) : '');
+				$url = $_SERVER["PHP_SELF"].'?'.($page ? 'page='.$page.'&' : '').'sortfield='.$sortfield.'&sortorder='.$sortorder.'&rowid='.(!empty($obj->rowid) ? $obj->rowid : (!empty($obj->code) ? $obj->code : '')).'&code='.(!empty($obj->code) ? urlencode($obj->code) : '');
 				if ($param) {
 					$url .= '&'.$param;
 				}
@@ -828,7 +836,8 @@ if ($resql) {
 				$parameters = array('fieldlist'=>$fieldlist, 'tabname'=>$tabname[$id]);
 				$reshook = $hookmanager->executeHooks('viewDictionaryFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks
 
-				$error = $hookmanager->error; $errors = $hookmanager->errors;
+				$error = $hookmanager->error;
+				$errors = $hookmanager->errors;
 
 				// Actions
 				if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
@@ -863,16 +872,19 @@ if ($resql) {
 								$key = $langs->trans("Country".strtoupper($obj->country_code));
 								$valuetoshow = ($key != "Country".strtoupper($obj->country_code) ? $obj->country_code." - ".$key : $obj->country);
 							}
-						} elseif (in_array($fieldlist[$field], array('label', 'range_account', 'formula'))) {
+						} elseif (in_array($fieldlist[$field], array('label', 'formula'))) {
 							$class = "tdoverflowmax250";
 							$title = $valuetoshow;
+						} elseif (in_array($fieldlist[$field], array('range_account'))) {
+							$class = "tdoverflowmax250 small";
+							$title = $valuetoshow;
 						} elseif ($fieldlist[$field] == 'region_id' || $fieldlist[$field] == 'country_id') {
 							$showfield = 0;
 						}
 
 						// Show value for field
 						if ($showfield) {
-							print '<!-- '.$fieldlist[$field].' --><td class="'.$class.'"'.($title ? ' title="'.dol_escape_htmltag($title).'"': '').'>'.dol_escape_htmltag($valuetoshow).'</td>';
+							print '<!-- '.$fieldlist[$field].' --><td class="'.$class.'"'.($title ? ' title="'.dol_escape_htmltag($title).'"' : '').'>'.dol_escape_htmltag($valuetoshow).'</td>';
 						}
 					}
 				}
@@ -948,7 +960,7 @@ $db->close();
  *  @param		string	$context		'add'=Output field for the "add form", 'edit'=Output field for the "edit form", 'hide'=Output field for the "add form" but we dont want it to be rendered
  *	@return		void
  */
-function fieldListAccountingCategories($fieldlist, $obj = '', $tabname = '', $context = '')
+function fieldListAccountingCategories($fieldlist, $obj = null, $tabname = '', $context = '')
 {
 	global $conf, $langs, $db;
 	global $form, $mysoc;
@@ -981,20 +993,23 @@ function fieldListAccountingCategories($fieldlist, $obj = '', $tabname = '', $co
 			}
 		} elseif ($fieldlist[$field] == 'category_type') {
 			print '<td>';
-			print $form->selectyesno($fieldlist[$field], (!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]}:''), 1);
+			print $form->selectyesno($fieldlist[$field], (!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]} : ''), 1);
 			print '</td>';
 		} elseif ($fieldlist[$field] == 'code' && isset($obj->{$fieldlist[$field]})) {
-			print '<td><input type="text" class="flat minwidth100" value="'.(!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]}:'').'" name="'.$fieldlist[$field].'"></td>';
+			print '<td><input type="text" class="flat minwidth100" value="'.(!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]} : '').'" name="'.$fieldlist[$field].'"></td>';
 		} else {
 			print '<td>';
 			$class = '';
-			if (in_array($fieldlist[$field], array('code', 'range_account', 'label', 'formula'))) {
-				$class = 'maxwidth100';
+			if (in_array($fieldlist[$field], array('code', 'formula'))) {
+				$class = 'maxwidth75';
+			}
+			if (in_array($fieldlist[$field], array('label', 'range_account'))) {
+				$class = 'maxwidth150';
 			}
 			if ($fieldlist[$field] == 'position') {
 				$class = 'maxwidth50';
 			}
-			print '<input type="text" class="flat'.($class ? ' '.$class : '').'" value="'.(isset($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]}:'').'" name="'.$fieldlist[$field].'">';
+			print '<input type="text" class="flat'.($class ? ' '.$class : '').'" value="'.(isset($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]} : '').'" name="'.$fieldlist[$field].'">';
 			print '</td>';
 		}
 	}

+ 40 - 4
htdocs/accountancy/admin/closure.php

@@ -1,5 +1,5 @@
 <?php
-/* Copyright (C) 2019       Alexandre Spangaro      <aspangaro@open-dsi.fr>
+/* Copyright (C) 2019-2024  Alexandre Spangaro      <aspangaro@easya.solutions>
  *
  * 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
@@ -61,6 +61,24 @@ if ($action == 'update') {
 		$error++;
 	}
 
+	$accountinggroupsusedforbalancesheetaccount = GETPOST('ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_BALANCE_SHEET_ACCOUNT', 'alphanohtml');
+	if (!empty($accountinggroupsusedforbalancesheetaccount)) {
+		if (!dolibarr_set_const($db, 'ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_BALANCE_SHEET_ACCOUNT', $accountinggroupsusedforbalancesheetaccount, 'chaine', 0, '', $conf->entity)) {
+			$error++;
+		}
+	} else {
+		$error++;
+	}
+
+	$accountinggroupsusedforincomestatement = GETPOST('ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_INCOME_STATEMENT', 'alpha');
+	if (!empty($accountinggroupsusedforincomestatement)) {
+		if (!dolibarr_set_const($db, 'ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_INCOME_STATEMENT', $accountinggroupsusedforincomestatement, 'chaine', 0, '', $conf->entity)) {
+			$error++;
+		}
+	} else {
+		$error++;
+	}
+
 	foreach ($list_account_main as $constname) {
 		$constvalue = GETPOST($constname, 'alpha');
 		if (!dolibarr_set_const($db, $constname, $constvalue, 'chaine', 0, '', $conf->entity)) {
@@ -83,7 +101,11 @@ if ($action == 'update') {
 $form = new Form($db);
 $formaccounting = new FormAccounting($db);
 
-llxHeader();
+$title = $langs->trans('Closure');
+
+$help_url = 'EN:Module_Double_Entry_Accounting#Setup|FR:Module_Comptabilit&eacute;_en_Partie_Double#Configuration';
+
+llxHeader('', $title, $help_url);
 
 $linkback = '';
 print load_fiche_titre($langs->trans('MenuClosureAccounts'), $linkback, 'title_accountancy');
@@ -117,12 +139,26 @@ foreach ($list_account_main as $key) {
 
 // Journal
 print '<tr class="oddeven">';
-print '<td width="50%">'.$langs->trans("ACCOUNTING_CLOSURE_DEFAULT_JOURNAL").'</td>';
+print '<td class="fieldrequired">'.$langs->trans("ACCOUNTING_CLOSURE_DEFAULT_JOURNAL").'</td>';
 print '<td>';
-$defaultjournal = $conf->global->ACCOUNTING_CLOSURE_DEFAULT_JOURNAL;
+$defaultjournal = getDolGlobalString('ACCOUNTING_CLOSURE_DEFAULT_JOURNAL');
 print $formaccounting->select_journal($defaultjournal, "ACCOUNTING_CLOSURE_DEFAULT_JOURNAL", 9, 1, 0, 0);
 print '</td></tr>';
 
+// Accounting groups used for the balance sheet account
+print '<tr class="oddeven">';
+print '<td class="fieldrequired">'.$langs->trans("ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_BALANCE_SHEET_ACCOUNT").'</td>';
+print '<td>';
+print '<input type="text" size="100" id="ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_BALANCE_SHEET_ACCOUNT" name="ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_BALANCE_SHEET_ACCOUNT" value="' . dol_escape_htmltag(getDolGlobalString('ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_BALANCE_SHEET_ACCOUNT')). '">';
+print '</td></tr>';
+
+// Accounting groups used for the income statement
+print '<tr class="oddeven">';
+print '<td class="fieldrequired">'.$langs->trans("ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_INCOME_STATEMENT").'</td>';
+print '<td>';
+print '<input type="text" size="100" id="ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_INCOME_STATEMENT" name="ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_INCOME_STATEMENT" value="' . dol_escape_htmltag(getDolGlobalString('ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_INCOME_STATEMENT')). '">';
+print '</td></tr>';
+
 print "</table>\n";
 
 print '<div class="center"><input type="submit" class="button button-edit" name="button" value="'.$langs->trans('Modify').'"></div>';

+ 7 - 6
htdocs/accountancy/admin/defaultaccounts.php

@@ -1,7 +1,7 @@
 <?php
 /* Copyright (C) 2013-2014  Olivier Geffroy         <jeff@jeffinfo.com>
  * Copyright (C) 2013-2014  Florian Henry           <florian.henry@open-concept.pro>
- * Copyright (C) 2013-2023  Alexandre Spangaro      <aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2024  Alexandre Spangaro      <aspangaro@easya.solutions>
  * Copyright (C) 2014-2015  Ari Elbaz (elarifr)     <github@accedinfo.com>
  * Copyright (C) 2014       Marcos García           <marcosgdf@gmail.com>
  * Copyright (C) 2014       Juanjo Menent           <jmenent@2byte.es>
@@ -88,14 +88,14 @@ $list_account[] = 'ACCOUNTING_VAT_BUY_ACCOUNT';
 
 $list_account[] = 'ACCOUNTING_VAT_PAY_ACCOUNT';
 
-if (!empty($conf->global->ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE)) {
+if (getDolGlobalString('ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE')) {
 	$list_account[] = 'ACCOUNTING_VAT_BUY_REVERSE_CHARGES_CREDIT';
 	$list_account[] = 'ACCOUNTING_VAT_BUY_REVERSE_CHARGES_DEBIT';
 }
 if (isModEnabled('banque')) {
 	$list_account[] = 'ACCOUNTING_ACCOUNT_TRANSFER_CASH';
 }
-if (!empty($conf->global->INVOICE_USE_RETAINED_WARRANTY)) {
+if (getDolGlobalString('INVOICE_USE_RETAINED_WARRANTY')) {
 	$list_account[] = 'ACCOUNTING_ACCOUNT_CUSTOMER_RETAINED_WARRANTY';
 }
 if (isModEnabled('don')) {
@@ -198,7 +198,9 @@ if ($action == 'setACCOUNTING_ACCOUNT_SUPPLIER_USE_AUXILIARY_ON_DEPOSIT') {
 $form = new Form($db);
 $formaccounting = new FormAccounting($db);
 
-llxHeader();
+$help_url = 'EN:Module_Double_Entry_Accounting#Setup|FR:Module_Comptabilit&eacute;_en_Partie_Double#Configuration';
+
+llxHeader('', $langs->trans('MenuDefaultAccounts'), $help_url);
 
 $linkback = '';
 print load_fiche_titre($langs->trans('MenuDefaultAccounts'), $linkback, 'title_accountancy');
@@ -265,8 +267,6 @@ foreach ($list_account as $key) {
 			print img_picto('', 'payment_vat', 'class="pictofixedwidth"');
 		} elseif (preg_match('/^ACCOUNTING_VAT/', $key)) {
 			print img_picto('', 'vat', 'class="pictofixedwidth"');
-			/*} elseif (preg_match('/^ACCOUNTING_REVENUESTAMP/', $key)) {
-			print img_picto('', 'vat', 'class="pictofixedwidth"');*/
 		} elseif (preg_match('/^ACCOUNTING_ACCOUNT_CUSTOMER/', $key)) {
 			print img_picto('', 'bill', 'class="pictofixedwidth"');
 		} elseif (preg_match('/^LOAN_ACCOUNTING_ACCOUNT/', $key)) {
@@ -280,6 +280,7 @@ foreach ($list_account as $key) {
 		} elseif (preg_match('/^ACCOUNTING_ACCOUNT_SUSPENSE/', $key)) {
 			print img_picto('', 'question', 'class="pictofixedwidth"');
 		}
+		// Note: account for revenue stamp are store into dictionary of revenue stamp. There is no default value.
 		print $label;
 		print '</td>';
 		// Value

+ 6 - 6
htdocs/accountancy/admin/export.php

@@ -1,6 +1,6 @@
 <?php
 /* Copyright (C) 2013-2014  Olivier Geffroy		<jeff@jeffinfo.com>
- * Copyright (C) 2013-2022  Alexandre Spangaro	<aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2024  Alexandre Spangaro	<aspangaro@easya.solutions>
  * Copyright (C) 2014	    Florian Henry		<florian.henry@open-concept.pro>
  * Copyright (C) 2014       Marcos García        <marcosgdf@gmail.com>
  * Copyright (C) 2014	    Juanjo Menent		<jmenent@2byte.es>
@@ -134,9 +134,9 @@ if ($action == 'update') {
 
 $form = new Form($db);
 
+$help_url = 'EN:Module_Double_Entry_Accounting#Setup|FR:Module_Comptabilit&eacute;_en_Partie_Double#Configuration';
 $title = $langs->trans('ExportOptions');
-llxHeader('', $title);
-
+llxHeader('', $title, $help_url);
 
 $linkback = '';
 // $linkback = '<a href="' . DOL_URL_ROOT . '/admin/modules.php?restore_lastsearch_values=1">' . $langs->trans("BackToModuleList") . '</a>';
@@ -152,7 +152,7 @@ foreach ($listparam as $key => $param) {
 	print '        {'."\n";
 	print '            //console.log("'.$param['label'].'");'."\n";
 	if (empty($param['ACCOUNTING_EXPORT_FORMAT'])) {
-		print '            jQuery("#ACCOUNTING_EXPORT_FORMAT").val("'.$conf->global->ACCOUNTING_EXPORT_FORMAT.'");'."\n";
+		print '            jQuery("#ACCOUNTING_EXPORT_FORMAT").val("'.getDolGlobalString('ACCOUNTING_EXPORT_FORMAT').'");'."\n";
 		print '            jQuery("#ACCOUNTING_EXPORT_FORMAT").prop("disabled", true);'."\n";
 	} else {
 		print '            jQuery("#ACCOUNTING_EXPORT_FORMAT").val("'.$param['ACCOUNTING_EXPORT_FORMAT'].'");'."\n";
@@ -162,7 +162,7 @@ foreach ($listparam as $key => $param) {
 		print '            jQuery("#ACCOUNTING_EXPORT_SEPARATORCSV").val("");'."\n";
 		print '            jQuery("#ACCOUNTING_EXPORT_SEPARATORCSV").prop("disabled", true);'."\n";
 	} else {
-		print '            jQuery("#ACCOUNTING_EXPORT_SEPARATORCSV").val("'.$conf->global->ACCOUNTING_EXPORT_SEPARATORCSV.'");'."\n";
+		print '            jQuery("#ACCOUNTING_EXPORT_SEPARATORCSV").val("'.getDolGlobalString('ACCOUNTING_EXPORT_SEPARATORCSV').'");'."\n";
 		print '            jQuery("#ACCOUNTING_EXPORT_SEPARATORCSV").removeAttr("disabled");'."\n";
 	}
 	if (empty($param['ACCOUNTING_EXPORT_ENDLINE'])) {
@@ -174,7 +174,7 @@ foreach ($listparam as $key => $param) {
 		print '            jQuery("#ACCOUNTING_EXPORT_DATE").val("");'."\n";
 		print '            jQuery("#ACCOUNTING_EXPORT_DATE").prop("disabled", true);'."\n";
 	} else {
-		print '            jQuery("#ACCOUNTING_EXPORT_DATE").val("'.$conf->global->ACCOUNTING_EXPORT_DATE.'");'."\n";
+		print '            jQuery("#ACCOUNTING_EXPORT_DATE").val("'.getDolGlobalString('ACCOUNTING_EXPORT_DATE').'");'."\n";
 		print '            jQuery("#ACCOUNTING_EXPORT_DATE").removeAttr("disabled");'."\n";
 	}
 	print '        }'."\n";

+ 20 - 10
htdocs/accountancy/admin/fiscalyear.php

@@ -1,5 +1,5 @@
 <?php
-/* Copyright (C) 2013-2018  Alexandre Spangaro  <aspangaro@open-dsi.fr>
+/* Copyright (C) 2013-2024  Alexandre Spangaro  <aspangaro@easya.solutions>
  *
  * 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
@@ -29,7 +29,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/fiscalyear.class.php';
 $action = GETPOST('action', 'aZ09');
 
 // Load variable for pagination
-$limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : $conf->liste_limit;
+$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
 $sortfield = GETPOST('sortfield', 'aZ09comma');
 $sortorder = GETPOST('sortorder', 'aZ09comma');
 $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
@@ -58,8 +58,9 @@ static $tmpstatut2label = array(
 		'1' => 'CloseFiscalYear'
 );
 
+// Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
 $object = new Fiscalyear($db);
-
+$hookmanager->initHooks(array('fiscalyearlist'));
 
 // Security check
 if ($user->socid > 0) {
@@ -69,7 +70,6 @@ if (!$user->hasRight('accounting', 'fiscalyear', 'write')) {              // If
 	accessforbidden();
 }
 
-
 /*
  * Actions
  */
@@ -87,7 +87,7 @@ $fiscalyearstatic = new Fiscalyear($db);
 
 $title = $langs->trans('AccountingPeriods');
 
-$help_url = "EN:Module_Double_Entry_Accounting";
+$help_url = 'EN:Module_Double_Entry_Accounting#Setup|FR:Module_Comptabilit&eacute;_en_Partie_Double#Configuration';
 
 llxHeader('', $title, $help_url);
 
@@ -112,15 +112,22 @@ $sql .= $db->plimit($limit + 1, $offset);
 $result = $db->query($sql);
 if ($result) {
 	$num = $db->num_rows($result);
+	$param = '';
 
-	$i = 0;
-
+	$parameters = array('param' => $param);
+	$reshook = $hookmanager->executeHooks('addMoreActionsButtonsList', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+	if ($reshook < 0) {
+		setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
+	}
 
-	$addbutton .= dolGetButtonTitle($langs->trans('NewFiscalYear'), '', 'fa fa-plus-circle', 'fiscalyear_card.php?action=create', '', $user->hasRight('accounting', 'fiscalyear', 'write'));
+	$newcardbutton = empty($hookmanager->resPrint) ? '' : $hookmanager->resPrint;
 
+	if (empty($reshook)) {
+		$newcardbutton .= dolGetButtonTitle($langs->trans('NewFiscalYear'), '', 'fa fa-plus-circle', 'fiscalyear_card.php?action=create', '', $user->hasRight('accounting', 'fiscalyear', 'write'));
+	}
 
 	$title = $langs->trans('AccountingPeriods');
-	print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $params, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'title_accountancy', 0, $addbutton, '', $limit, 1);
+	print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'calendar', 0, $newcardbutton, '', $limit, 1);
 
 	print '<div class="div-table-responsive">';
 	print '<table class="tagtable liste centpercent">';
@@ -134,6 +141,9 @@ if ($result) {
 	print '<td class="right">'.$langs->trans("Status").'</td>';
 	print '</tr>';
 
+	// Loop on record
+	// --------------------------------------------------------------------
+	$i = 0;
 	if ($num) {
 		while ($i < $num && $i < $max) {
 			$obj = $db->fetch_object($result);
@@ -159,7 +169,7 @@ if ($result) {
 			$i++;
 		}
 	} else {
-		print '<tr class="oddeven"><td colspan="7"><span class="opacitymedium">'.$langs->trans("None").'</span></td></tr>';
+		print '<tr class="oddeven"><td colspan="7"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
 	}
 	print '</table>';
 	print '</div>';

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff