Pārlūkot izejas kodu

Merge branch '19_fix_sort_stocktodate' of github.com:FHenry/dolibarr into 19-mmi_fhenry

Florian HENRY 2 mēneši atpakaļ
vecāks
revīzija
e6791df974
100 mainītis faili ar 1361 papildinājumiem un 495 dzēšanām
  1. 3 0
      .github/changed-lines-count-labeler.yml
  2. 86 0
      .github/scripts/get_changed_php.sh
  3. 21 0
      .github/workflows/pr-18-autolabel.yaml
  4. 47 13
      .github/workflows/pr-18.yaml
  5. 22 25
      .github/workflows/pre-commit.yml
  6. 1 1
      .github/workflows/stale-issues-safe.yml
  7. 28 0
      .github/workflows/test.yaml
  8. 3 1
      .pre-commit-config.yaml
  9. 102 93
      build/makepack-dolibarr.pl
  10. 7 0
      build/tgz/tar_exclude.txt
  11. 6 0
      build/zip/zip_exclude.txt
  12. 3 3
      htdocs/accountancy/admin/categories_list.php
  13. 2 2
      htdocs/accountancy/admin/fiscalyear.php
  14. 1 1
      htdocs/accountancy/class/accountancyexport.class.php
  15. 4 4
      htdocs/accountancy/class/accountancyimport.class.php
  16. 49 18
      htdocs/accountancy/class/bookkeeping.class.php
  17. 7 1
      htdocs/adherents/class/api_members.class.php
  18. 7 2
      htdocs/adherents/class/api_memberstypes.class.php
  19. 7 1
      htdocs/adherents/class/api_subscriptions.class.php
  20. 5 0
      htdocs/admin/dict.php
  21. 1 1
      htdocs/admin/mails.php
  22. 7 1
      htdocs/bom/class/api_boms.class.php
  23. 7 1
      htdocs/categories/class/api_categories.class.php
  24. 2 2
      htdocs/comm/action/class/actioncomm.class.php
  25. 6 0
      htdocs/comm/action/class/api_agendaevents.class.php
  26. 11 10
      htdocs/comm/action/list.php
  27. 6 6
      htdocs/comm/contact.php
  28. 16 2
      htdocs/comm/mailing/class/html.formadvtargetemailing.class.php
  29. 16 4
      htdocs/comm/propal/card.php
  30. 7 1
      htdocs/comm/propal/class/api_proposals.class.php
  31. 7 1
      htdocs/commande/class/api_orders.class.php
  32. 6 5
      htdocs/compta/bank/bankentries_list.php
  33. 6 0
      htdocs/compta/bank/class/api_bankaccounts.class.php
  34. 8 8
      htdocs/compta/facture/card-rec.php
  35. 3 3
      htdocs/compta/facture/card.php
  36. 10 4
      htdocs/compta/facture/class/api_invoices.class.php
  37. 8 0
      htdocs/compta/facture/class/facture-rec.class.php
  38. 28 18
      htdocs/compta/facture/class/facture.class.php
  39. 8 7
      htdocs/compta/journal/purchasesjournal.php
  40. 10 9
      htdocs/compta/journal/sellsjournal.php
  41. 1 1
      htdocs/compta/localtax/index.php
  42. 1 1
      htdocs/compta/paiement/cheque/card.php
  43. 7 1
      htdocs/contrat/class/api_contracts.class.php
  44. 2 4
      htdocs/contrat/class/contrat.class.php
  45. 11 0
      htdocs/contrat/services_list.php
  46. 29 12
      htdocs/core/actions_massactions.inc.php
  47. 9 0
      htdocs/core/actions_sendmails.inc.php
  48. 22 4
      htdocs/core/ajax/ajaxtooltip.php
  49. 3 3
      htdocs/core/boxes/box_graph_product_distribution.php
  50. 1 1
      htdocs/core/class/CMailFile.class.php
  51. 11 7
      htdocs/core/class/commonobject.class.php
  52. 2 2
      htdocs/core/class/dolgraph.class.php
  53. 5 1
      htdocs/core/class/extrafields.class.php
  54. 17 0
      htdocs/core/class/fileupload.class.php
  55. 8 11
      htdocs/core/class/fiscalyear.class.php
  56. 3 2
      htdocs/core/class/html.form.class.php
  57. 2 2
      htdocs/core/class/html.formaccounting.class.php
  58. 26 22
      htdocs/core/class/html.formticket.class.php
  59. 2 2
      htdocs/core/get_menudiv.php
  60. 4 2
      htdocs/core/js/lib_foot.js.php
  61. 3 1
      htdocs/core/lib/company.lib.php
  62. 1 0
      htdocs/core/lib/files.lib.php
  63. 45 27
      htdocs/core/lib/functions.lib.php
  64. 48 2
      htdocs/core/lib/pdf.lib.php
  65. 1 1
      htdocs/core/lib/sendings.lib.php
  66. 7 0
      htdocs/core/modules/DolibarrModules.class.php
  67. 2 2
      htdocs/core/modules/asset/doc/doc_generic_asset_odt.modules.php
  68. 5 1
      htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php
  69. 1 1
      htdocs/core/modules/fichinter/doc/pdf_soleil.modules.php
  70. 1 1
      htdocs/core/modules/modStock.class.php
  71. 8 0
      htdocs/core/modules/project/task/doc/doc_generic_task_odt.modules.php
  72. 1 0
      htdocs/core/modules/societe/doc/doc_generic_odt.modules.php
  73. 5 1
      htdocs/core/modules/stocktransfer/doc/pdf_eagle_proforma.modules.php
  74. 5 1
      htdocs/core/modules/supplier_invoice/doc/pdf_canelle.modules.php
  75. 20 17
      htdocs/core/tpl/advtarget.tpl.php
  76. 11 2
      htdocs/core/tpl/massactions_pre.tpl.php
  77. 1 1
      htdocs/core/tpl/passwordreset.tpl.php
  78. 115 32
      htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php
  79. 10 10
      htdocs/core/triggers/interface_95_modZapier_ZapierTriggers.class.php
  80. 7 1
      htdocs/don/class/api_donations.class.php
  81. 1 1
      htdocs/emailcollector/class/emailcollector.class.php
  82. 7 1
      htdocs/expedition/class/api_shipments.class.php
  83. 9 10
      htdocs/expedition/dispatch.php
  84. 0 2
      htdocs/expedition/shipment.php
  85. 8 3
      htdocs/expensereport/card.php
  86. 7 1
      htdocs/expensereport/class/api_expensereports.class.php
  87. 1 0
      htdocs/expensereport/class/expensereport.class.php
  88. 8 1
      htdocs/fourn/class/api_supplier_invoices.class.php
  89. 7 0
      htdocs/fourn/class/api_supplier_orders.class.php
  90. 46 23
      htdocs/fourn/class/fournisseur.commande.class.php
  91. 8 1
      htdocs/fourn/commande/list.php
  92. 5 5
      htdocs/fourn/contact.php
  93. 2 0
      htdocs/fourn/facture/card-rec.php
  94. 19 14
      htdocs/fourn/facture/card.php
  95. 2 1
      htdocs/fourn/facture/list.php
  96. 1 0
      htdocs/hrm/skill_tab.php
  97. 7 0
      htdocs/imports/import.php
  98. 2 2
      htdocs/includes/odtphp/odf.php
  99. 3 3
      htdocs/includes/tecnickcom/tcpdf/include/barcodes/pdf417.php
  100. 171 0
      htdocs/install/mysql/migration/12.0.0-13.0.0.sql

+ 3 - 0
.github/changed-lines-count-labeler.yml

@@ -0,0 +1,3 @@
+# Add this tag for any changes for more than 1 line
+"Pending analysis of PR (maintenance team)":
+  min: 1

+ 86 - 0
.github/scripts/get_changed_php.sh

@@ -0,0 +1,86 @@
+#!/bin/bash
+# Copyright (C) 2025		MDW	<mdeweerd@users.noreply.github.com>
+
+set -euo pipefail
+
+# This script retrieves the list of changed PHP files for a pull request
+# using the GitHub API and sets two outputs:
+#   - any_changed: "true" if at least one PHP file changed, "false" otherwise
+#   - all_changed_files: space-separated list of changed PHP file paths
+#
+# Required environment variables:
+#   GITHUB_TOKEN      - GitHub token with repo access
+#   GITHUB_REPOSITORY - "owner/repo"
+#   GITHUB_EVENT_PATH - Path to the event JSON payload
+
+# Verify required environment variables are set
+if [[ -z "${GITHUB_TOKEN:-}" ]]; then
+	echo "GITHUB_TOKEN is not set" >&2
+	exit 1
+fi
+if [[ -z "${GITHUB_REPOSITORY:-}" ]]; then
+	echo "GITHUB_REPOSITORY is not set" >&2
+	exit 1
+fi
+if [[ -z "${GITHUB_EVENT_PATH:-}" ]]; then
+	echo "GITHUB_EVENT_PATH is not set" >&2
+	exit 1
+fi
+
+# Extract the pull request number from the event payload
+pr_number=$(jq --raw-output '.pull_request.number' "$GITHUB_EVENT_PATH")
+if [[ "$pr_number" == "null" ]]; then
+	echo "Not a pull request event"
+	exit 0
+fi
+
+# Split repository into owner and repo name
+# Split repository into owner and repo name using Bash parameter expansion
+owner="${GITHUB_REPOSITORY%%/*}"  # Extract text before the first '/'
+repo="${GITHUB_REPOSITORY##*/}"   # Extract text after the last '/'
+
+page=1
+per_page=100
+changed_php_files=()
+
+# Loop through all pages to gather changed files
+while true; do
+	response=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
+		"https://api.github.com/repos/${owner}/${repo}/pulls/${pr_number}/files?per_page=${per_page}&page=${page}")
+
+	# Filter for files ending with .php and add them to the list
+	mapfile -t files < <(echo "$response" | jq -r '.[] | select(.filename | test("\\.php$")) | .filename')
+	changed_php_files+=("${files[@]}")
+
+	# Check if we have reached the last page (less than per_page results)
+	count=$(echo "$response" | jq 'length')
+	if (( count < per_page )); then
+		break
+	fi
+	((page++))
+done
+
+
+# Build a space-separated string of changed PHP files
+# This does not cope with files that have spaces.
+# But such files do not exist in the project (at least not for the
+# files we are filtering).
+all_changed_files=$(IFS=" " ; echo "${changed_php_files[*]}")
+
+
+# Determine changed files flag
+if [ -z "$all_changed_files" ]; then
+	any_changed="false"
+else
+	any_changed="true"
+fi
+
+# Set outputs for GitHub Actions if GITHUB_OUTPUT is available
+if [ -n "${GITHUB_OUTPUT:-}" ]; then
+	echo "any_changed=${any_changed}" >> "$GITHUB_OUTPUT"
+	echo "all_changed_files=${all_changed_files}" >> "$GITHUB_OUTPUT"
+else
+	# Otherwise, print the outputs
+	echo "any_changed=${any_changed}"
+	echo "all_changed_files=${all_changed_files}"
+fi

+ 21 - 0
.github/workflows/pr-18-autolabel.yaml

@@ -0,0 +1,21 @@
+name: "Set label for v18"
+on:
+  pull_request:
+    types: [opened, synchronize, reopened]
+    branches:
+      - "18.0"
+  push:
+    branches:
+      - "18.0"
+
+jobs:
+  changed-lines-count-labeler:
+    runs-on: ubuntu-latest
+    name: An action for automatically labelling pull requests based on the changed lines count
+    steps:
+    - name: Set a label
+      uses: vkirilichev/changed-lines-count-labeler@v0.2
+      with:
+        repo-token: ${{ secrets.GITHUB_TOKEN }}
+        configuration-path: .github/changed-lines-count-labeler.yml
+      continue-on-error: true

+ 47 - 13
.github/workflows/pr-18.yaml

@@ -1,20 +1,54 @@
+name: Set reviewer for v18
 on:
   pull_request:
-    types:
-      - opened
+    types: [opened, synchronize, reopened]
+    branches:
+      - "18.0"
+  push:
     branches:
       - "18.0"
 
 jobs:
-  run:
-    runs-on: ubuntu-22.04
-    permissions:
-      pull-requests: write
+  pr18:
+    runs-on: ubuntu-latest
+
+    #env:
+    #  GH_TOKEN: ${{ github.token }}
+    #  GH_TOKENS: ${{ secrets.GITHUB_TOKEN }}
+
     steps:
-      - name: Create PR review request
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          url: ${{ github.event.pull_request.html_url }}
-        run: |
-          gh pr edit "$url" --add-assignee lvessiller-opendsi,rycks --add-reviewer lvessiller-opendsi,rycks
-          gh pr merge "$url" --merge --auto
+    - name: Checkout repository
+      uses: actions/checkout@v3
+
+    #- name: Install GitHub CLI
+    #  run: |
+    #    sudo apt update
+    #    sudo apt install gh -y
+
+    - name: Assign reviewer method 1
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        url: ${{ github.event.pull_request.html_url }}
+      run: |
+        gh pr edit "$url" --add-assignee rycks --add-reviewer rycks
+        gh pr merge "$url" --merge --auto
+      continue-on-error: true
+
+    - name: Assign reviewer method 2
+      env:
+        #REVIEWER: "eldy,lvessiller-opendsi,rycks" # Remplacez par le nom d'utilisateur GitHub du reviewer
+        REVIEWER: "rycks" # Remplacez par le nom d'utilisateur GitHub du reviewer
+      run: |
+        echo "Run action by ${{ github.actor }}"        
+        echo "github.token=${{ github.token }}"
+        echo "secrets.GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}"
+        echo "GITHUB_EVENT_PATH=$GITHUB_EVENT_PATH"
+        echo Get the pr_number
+        pr_number=$(jq --raw-output .number < $GITHUB_EVENT_PATH)
+        echo "pr_number=$pr_number"
+        echo Authenticate login gh
+        gh auth login --with-token <<< "${{ secrets.GITHUB_TOKEN }}"
+        gh auth setup-git
+        echo Set the reviewer
+        gh pr edit $pr_number --add-reviewer "$REVIEWER"
+      continue-on-error: true

+ 22 - 25
.github/workflows/pre-commit.yml

@@ -14,18 +14,18 @@ jobs:
         run: sudo apt-get update && sudo apt-get install cppcheck
         if: false
 
+      # Checkout git sources to analyze
+      - uses: actions/checkout@v4
+
       # The next uses the git API because there is no clone yet.
       # This is faster for a big repo.
       - name: Get all changed php files (if PR)
         id: changed-php
-        uses: tj-actions/changed-files@v42
-        if: github.event_name == 'pull_request'
-        with:
-          files: |
-            **.php
+        if: env.gh_event == 'pull_request'
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: ./.github/scripts/get_changed_php.sh
 
-      # Checkout git sources to analyze
-      - uses: actions/checkout@v4
       # Action setup-python needs a requirements.txt or pyproject.toml
       # This ensures one of them exists.
       - name: Create requirements.txt if no requirements.txt or pyproject.toml
@@ -42,6 +42,21 @@ jobs:
         with:
           path: ~/.cache/pre-commit/
           key: pre-commit-4|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }}
+
+      - name: Extract PHP version
+        id: extract-php-version
+        run: |
+          PHP_VERSION=$(sed -n 's/.*\$arrayphpmaxversionwarning\s*=\s*array\s*(\s*\([0-9]\+\)\s*,\s*\([0-9]\+\).*/\1.\2/p' htdocs/install/check.php)
+          echo "PHP_VERSION=$PHP_VERSION" >> $GITHUB_ENV
+
+      - name: Setup PHPCS
+        uses: shivammathur/setup-php@v2
+        # Install proper php version, and also install phpcs which may be needed
+        with:
+          php-version: ${{ env.PHP_VERSION }}  # Version from check.php
+          coverage: none # disable xdebug, pcov
+          tools: phpcs
+
       # Run all the precommit tools (defined into pre-commit-config.yaml).
       # We can force exclusion of some of them here.
       - name: Run pre-commit hooks
@@ -62,24 +77,6 @@ jobs:
       #     files: |
       #        **.php
 
-      - name: Setup PHPCS
-        uses: shivammathur/setup-php@v2
-        # Install when we're going to run phpcs
-        if: |
-          steps.changed-php.outputs.any_changed == 'true'
-          ||
-          (
-            github.event_name == 'push'
-            && (
-                 github.event.ref == 'refs/heads/develop'
-               || endsWith(github.event.ref, '.0')
-            )
-          )
-        with:
-          php-version: 8.1
-          coverage: none # disable xdebug, pcov
-          tools: phpcs
-
       - name: Run some pre-commit hooks on selected changed files only
         if: steps.changed-php.outputs.any_changed == 'true'
         env:

+ 1 - 1
.github/workflows/stale-issues-safe.yml

@@ -8,7 +8,7 @@ on:
     types: [created]
   workflow_dispatch:
   
-permissions: {} # none
+permissions: {} # no restriction by default
 
 jobs:
   stale:

+ 28 - 0
.github/workflows/test.yaml

@@ -0,0 +1,28 @@
+name: Test github actions
+on:
+  workflow_dispatch:
+  pull_request:
+    types: [opened, reopened, synchronize]
+  push:
+
+env:
+  ENVGHT: ${{ secrets.GITHUB_TOKEN }}
+  ENVGHU: ${{ github.token }}
+  TEST_ACCESS_KEY: ${{ secrets.TEST_ACCESS_KEY }}
+  TEST_VAR_REPO: ${{ vars.TEST_VAR_REPO }}
+  ENVLOCAL: "varenvlocal"
+
+jobs:
+  testjob:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Log
+        run: |
+          echo "Run action by ${{ github.actor }}"        
+          echo "github.token=${{ github.token }}"
+          echo "secrets.GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}"
+          echo "GITHUB_EVENT_PATH=$GITHUB_EVENT_PATH"
+          echo "repo-token: ${{secrets.GITHUB_TOKEN}}"
+          echo "secret repository TEST_ACCESS_KEY: ${{secrets.TEST_ACCESS_KEY}}"
+          echo "variable repository : ${{vars.TEST_VAR_REPO}}"
+          echo "ENVLOCAL: ${{env.ENVLOCAL}}"

+ 3 - 1
.pre-commit-config.yaml

@@ -6,7 +6,7 @@ repos:
     rev: v4.5.0
     hooks:
       - id: no-commit-to-branch
-        args: [--branch, develop, --pattern, \d+.0]
+        args: [--branch, develop, --pattern, \d+.0$]
       - id: check-yaml
         args: [--unsafe]
       - id: check-json
@@ -87,6 +87,8 @@ repos:
           ]
         pass_filenames: false # Run on all files
       - id: php-lint
+        exclude:
+          (?x)^(htdocs/includes/symfony/var-dumper/Tests/.*)$
       - id: php-stan
         stages: [manual]
         files: \.(php)$

+ 102 - 93
build/makepack-dolibarr.pl

@@ -4,7 +4,7 @@
 # \brief        Dolibarr package builder (tgz, zip, rpm, deb, exe, aps)
 # \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: 
+# 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'
 #DESTISTABLE='/media/HDDATA1_LD/Mes Sites/Web/Dolibarr/dolibarr.org/files/stable'
 #DESTIMODULES='/media/HDDATA1_LD/Mes Sites/Web/Admin1/wwwroot/files/modules'
@@ -18,9 +18,8 @@ use Term::ANSIColor;
 
 # Change this to defined target for option 98 and 99
 $PROJECT="dolibarr";
-$PUBLISHSTABLE="eldy,dolibarr\@frs.sourceforge.net:/home/frs/project/dolibarr";
-$PUBLISHBETARC="dolibarr\@vmprod1.dolibarr.org:/home/dolibarr/asso.dolibarr.org/dolibarr_documents/website/www.dolibarr.org/files";
-
+$PUBLISHBETARC="$ENV{'DESTIASSOLOGIN'}\@vmprod1.dolibarr.org:/home/dolibarr/asso.dolibarr.org/dolibarr_documents/website/www.dolibarr.org/files";
+$PUBLISHSTABLE="$ENV{'DESTISFLOGIN'}\@frs.sourceforge.net:/home/frs/project/dolibarr";
 
 #@LISTETARGET=("TGZ","ZIP","RPM_GENERIC","RPM_FEDORA","RPM_MANDRIVA","RPM_OPENSUSE","DEB","EXEDOLIWAMP","SNAPSHOT");   # Possible packages
 @LISTETARGET=("TGZ","ZIP","RPM_GENERIC","RPM_FEDORA","RPM_MANDRIVA","RPM_OPENSUSE","DEB","EXEDOLIWAMP","SNAPSHOT");   # Possible packages
@@ -36,7 +35,7 @@ $PUBLISHBETARC="dolibarr\@vmprod1.dolibarr.org:/home/dolibarr/asso.dolibarr.org/
 "RPM_FEDORA"=>"rpmbuild",
 "RPM_MANDRIVA"=>"rpmbuild",
 "RPM_OPENSUSE"=>"rpmbuild",
-"DEB"=>"dpkg dpatch",
+"DEB"=>"dpkg",
 "FLATPACK"=>"flatpack",
 "EXEDOLIWAMP"=>"ISCC.exe",
 "SNAPSHOT"=>"tar"
@@ -129,7 +128,7 @@ if (! $TEMP || ! -d $TEMP) {
 	print "$PROG.$Extension aborted.\n";
 	sleep 2;
 	exit 2;
-} 
+}
 $BUILDROOT="$TEMP/buildroot";
 
 
@@ -172,7 +171,7 @@ $newbuild = $BUILD;
 $newbuild =~ s/(dev|alpha)/1/gi;                # dev
 $newbuild =~ s/beta(.?)/2/gi;                   # beta    			(we want beta1, beta2, betax to be same package name)
 $newbuild =~ s/rc(.?)/3/gi;                     # rc				(we want rc1, rc2, rcx to be same package name)
-if ($newbuild !~ /-/) { $newbuild.='-4'; }      # finale is same than rc. 
+if ($newbuild !~ /-/) { $newbuild.='-4'; }      # finale is same than rc.
 # now newbuild is 0-1 or 0-4 for example. Note that for native package (see debian/source/format), we should not use a dash part but to get a better version management
 $build = $newbuild;
 $build =~ s/-.*$//g;
@@ -190,8 +189,8 @@ for (0..@ARGV-1) {
 	if ($ARGV[$_] =~ /^-*target=(\w+)/i)   { $target=$1; $batch=1; }
 	if ($ARGV[$_] =~ /^-*desti=(.+)/i)     { $DESTI=$1; }
 	if ($ARGV[$_] =~ /^-*prefix=(.+)/i)    {
-		$PREFIX=$1; 
-		$FILENAMESNAPSHOT.="-".$PREFIX; 
+		$PREFIX=$1;
+		$FILENAMESNAPSHOT.="-".$PREFIX;
 	}
 }
 if ($ENV{"DESTIBETARC"} && $BUILD =~ /[a-z]/i)   { $DESTI = $ENV{"DESTIBETARC"}; }	# Force output dir if env DESTIBETARC is defined
@@ -210,7 +209,7 @@ print "Target directory (DESTI) : $DESTI\n";
 # Choose package targets
 #-----------------------
 if ($target) {
-	if ($target eq "ALL") { 
+	if ($target eq "ALL") {
 		foreach my $key (@LISTETARGET) {
 			if ($key ne 'SNAPSHOT' && $key ne 'SF' && $key ne 'ASSO') { $CHOOSEDTARGET{$key}=1; }
 		}
@@ -236,10 +235,10 @@ else {
 		printf(" %2d - %-14s  (%s)\n",$cpt,"ASSO (publish)","Need ".$REQUIREMENTPUBLISH{"ASSO"});
 		$cpt=99;
 		printf(" %2d - %-14s  (%s)\n",$cpt,"SF (publish)","Need ".$REQUIREMENTPUBLISH{"SF"});
-	
+
 		# Ask which target to build
 		print "Choose one target number or several separated with space (0 - ".$cpt."): ";
-		$NUM_SCRIPT=<STDIN>; 
+		$NUM_SCRIPT=<STDIN>;
 		chomp($NUM_SCRIPT);
 		if ($NUM_SCRIPT !~ /^[0-9\s]+$/)
 		{
@@ -286,11 +285,11 @@ foreach my $target (sort keys %CHOOSEDTARGET) {
 			print "Error: You asked creation of several rpms. Because all rpm have same name, you must defined an environment variable DESTI to tell packager where it can create subdirs for each generated package.\n";
 			exit;
 		}
-		$atleastonerpm=1;			
-	} 
-	foreach my $req (split(/[,\s]/,$REQUIREMENTTARGET{$target})) 
+		$atleastonerpm=1;
+	}
+	foreach my $req (split(/[,\s]/,$REQUIREMENTTARGET{$target}))
 	{
-		# Test    
+		# Test
 		print "Test requirement for target $target: Search '$req'... ";
 		$newreq=$req; $newparam='';
 		if ($newreq eq 'zip') { $newparam.='-h'; }
@@ -299,12 +298,12 @@ foreach my $target (sort keys %CHOOSEDTARGET) {
 		print "Test command ".$cmd."... ";
 		$ret=`$cmd`;
 		$coderetour=$?; $coderetour2=$coderetour>>8;
-		if ($coderetour != 0 && (($coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i) || ($coderetour2 == 127 && $OS !~ /windows/)) && $PROGPATH) { 
+		if ($coderetour != 0 && (($coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i) || ($coderetour2 == 127 && $OS !~ /windows/)) && $PROGPATH) {
 			# Not found error, we try in PROGPATH
 			$ret=`"$PROGPATH/$ALTERNATEPATH{$req}/$req\" 2>&1`;
 			$coderetour=$?; $coderetour2=$coderetour>>8;
 			$REQUIREMENTTARGET{$target}="$PROGPATH/$ALTERNATEPATH{$req}/$req";
-		} 
+		}
 
 		if ($coderetour != 0 && (($coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i) || ($coderetour2 == 127 && $OS !~ /windows/))) {
 			# Not found error
@@ -333,7 +332,7 @@ $nbofpublishneedchangelog=0;
 foreach my $target (sort keys %CHOOSEDTARGET) {
 	if ($target eq '-CHKSUM') { $nbofpublishneedchangelog++; }
 	if ($CHOOSEDTARGET{$target} < 0) { next; }
-	if ($target ne 'EXE' && $target ne 'EXEDOLIWAMP' && $target ne '-CHKSUM') 
+	if ($target ne 'EXE' && $target ne 'EXEDOLIWAMP' && $target ne '-CHKSUM')
 	{
 		$nboftargetneedbuildroot++;
 	}
@@ -397,10 +396,10 @@ if ($nboftargetok) {
 		print "Go to directory $SOURCE\n";
 		$olddir=getcwd();
 		chdir("$SOURCE");
-		
+
 		print "Clean $SOURCE/htdocs/includes/autoload.php\n";
 		$ret=`rm -f  $SOURCE/htdocs/includes/autoload.php`;
-		
+
 		$ret=`git ls-files . --exclude-standard --others`;
 		if ($ret)
 		{
@@ -409,12 +408,16 @@ if ($nboftargetok) {
 				print "Canceled.\n";
 				exit;
 		}
-		
+
 	   	print 'Create xml check file with md5 checksum with command php '.$SOURCE.'/build/generate_filelist_xml.php release='.$MAJOR.'.'.$MINOR.'.'.$BUILD."\n";
 	  	$ret=`php $SOURCE/build/generate_filelist_xml.php release=$MAJOR.$MINOR.$BUILD`;
 	  	print $ret."\n";
 	  	# Copy to final dir
 	  	$NEWDESTI=$DESTI;
+		if ( !-d "$NEWDESTI/signatures" ) {
+			use File::Path qw( make_path );
+		    make_path "$NEWDESTI/signatures" or die "Failed to create path: $NEWDESTI/signatures";
+		}
 		print "Copy \"$SOURCE/htdocs/install/filelist-$MAJOR.$MINOR.$BUILD.xml\" to $NEWDESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml\n";
 	    use File::Copy qw(copy);
 	    copy "$SOURCE/htdocs/install/filelist-$MAJOR.$MINOR.$BUILD.xml", "$NEWDESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml";
@@ -427,13 +430,13 @@ if ($nboftargetok) {
 		print "Go to directory $SOURCE\n";
 		$olddir=getcwd();
 		chdir("$SOURCE");
-		
+
 		print 'Run git tag -a -m "'.$MAJOR.'.'.$MINOR.'.'.$BUILD.'" "'.$MAJOR.'.'.$MINOR.'.'.$BUILD.'"'."\n";
 		$ret=`git tag -a -m "$MAJOR.$MINOR.$BUILD" "$MAJOR.$MINOR.$BUILD" 2>&1`;
 		if ($ret =~ /(already exists|existe déjà)/)
 		{
 			print "WARNING: Tag ".$MAJOR.'.'.$MINOR.'.'.$BUILD." already exists. Overwrite (y/N) ? ";
-			$QUESTIONOVERWRITETAG=<STDIN>; 
+			$QUESTIONOVERWRITETAG=<STDIN>;
 			chomp($QUESTIONOVERWRITETAG);
 			if ($QUESTIONOVERWRITETAG =~ /(o|y)/)
 			{
@@ -452,7 +455,7 @@ if ($nboftargetok) {
 		}
 		chdir("$olddir");
 	}
-	
+
 	# Update buildroot if required
 	#-----------------------------
 	if ($nboftargetneedbuildroot)
@@ -462,7 +465,7 @@ if ($nboftargetok) {
 
 			print "Delete directory $BUILDROOT\n";
 			$ret=`rm -fr "$BUILDROOT"`;
-		
+
 			mkdir "$BUILDROOT";
 			mkdir "$BUILDROOT/$PROJECT";
 			print "Copy $SOURCE into $BUILDROOT/$PROJECT\n";
@@ -488,7 +491,7 @@ if ($nboftargetok) {
 		$ret=`rm -f  $BUILDROOT/$PROJECT/phpstan.neon`;
 		$ret=`rm -f  $BUILDROOT/$PROJECT/pom.xml`;
 		$ret=`rm -f  $BUILDROOT/$PROJECT/README-*.md`;
-		
+
 		$ret=`rm -fr $BUILDROOT/$PROJECT/build/html`;
 		$ret=`rm -f  $BUILDROOT/$PROJECT/build/Doli*-*`;
 		$ret=`rm -f  $BUILDROOT/$PROJECT/build/dolibarr_*.deb`;
@@ -557,20 +560,20 @@ if ($nboftargetok) {
 		$ret=`rm -f  $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot11.png`;
 		$ret=`rm -f  $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot12.png`;
 
-		# Security to avoid to package data files 
+		# Security to avoid to package data files
         print "Remove documents dir\n";
 		$ret=`rm -fr $BUILDROOT/$PROJECT/document`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/documents`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/document`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/documents`;
-        
+
         print "Remove subdir of custom dir\n";
    	    print "find $BUILDROOT/$PROJECT/htdocs/custom/* -type d -exec rm -fr {} \\;\n";
    	    $ret=`find $BUILDROOT/$PROJECT/htdocs/custom/* -type d -exec rm -fr {} \\; >/dev/null 2>&1`;	# For custom we want to remove all subdirs but not files
    	    print "find $BUILDROOT/$PROJECT/htdocs/custom/* -type l -exec rm -fr {} \\;\n";
    	    $ret=`find $BUILDROOT/$PROJECT/htdocs/custom/* -type l -exec rm -fr {} \\; >/dev/null 2>&1`;	# For custom we want to remove all subdirs, even symbolic links, but not files
 
-		# Removed known external modules to avoid any error when packaging from env where external modules are tested 
+		# Removed known external modules to avoid any error when packaging from env where external modules are tested
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/abricot*`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/accountingexport*`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/allscreens*`;
@@ -596,15 +599,15 @@ if ($nboftargetok) {
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/timesheet*`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/webmail*`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/theme/common/fontawesome-5/svgs`;
-		
+
 		# Removed other test files
 	    $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/public/test`;
 	    $ret=`rm -fr $BUILDROOT/$PROJECT/test`;
 	    $ret=`rm -fr $BUILDROOT/$PROJECT/Thumbs.db $BUILDROOT/$PROJECT/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/*/*/Thumbs.db`;
 	    $ret=`rm -f  $BUILDROOT/$PROJECT/.cvsignore $BUILDROOT/$PROJECT/*/.cvsignore $BUILDROOT/$PROJECT/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/*/*/.cvsignore`;
 	    $ret=`rm -f  $BUILDROOT/$PROJECT/.gitignore $BUILDROOT/$PROJECT/*/.gitignore $BUILDROOT/$PROJECT/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/*/*/.gitignore`;
-		
-		# Removed files installed by the awful composer   	    
+
+		# Removed files installed by the awful composer
    	    $ret=`rm -f  $BUILDROOT/$PROJECT/htdocs/includes/geoip/sample*.*`;
         $ret=`rm -f  $BUILDROOT/$PROJECT/htdocs/includes/bin`;
         $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/ckeditor/adapters`;		# Keep this removal in case we embed libraries
@@ -639,7 +642,7 @@ if ($nboftargetok) {
         $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`;
@@ -649,14 +652,14 @@ if ($nboftargetok) {
 
 	# Build package for each target
 	#------------------------------
-	foreach my $target (sort keys %CHOOSEDTARGET) 
+	foreach my $target (sort keys %CHOOSEDTARGET)
 	{
 		if ($CHOOSEDTARGET{$target} < 0) { next; }
 		if ($target eq '-CHKSUM') { next; }
-		
+
 		print "\nBuild package for target $target\n";
 
-		if ($target eq 'SNAPSHOT') 
+		if ($target eq 'SNAPSHOT')
 		{
 			$NEWDESTI=$DESTI;
 
@@ -680,13 +683,13 @@ if ($nboftargetok) {
 			next;
 		}
 
-		if ($target eq 'TGZ') 
+		if ($target eq 'TGZ')
 		{
 			$NEWDESTI=$DESTI;
 			if ($NEWDESTI =~ /stable/)
 			{
 				mkdir($DESTI.'/standard');
-				if (-d $DESTI.'/standard') { $NEWDESTI=$DESTI.'/standard'; } 
+				if (-d $DESTI.'/standard') { $NEWDESTI=$DESTI.'/standard'; }
 			}
 
 			print "Remove target $FILENAMETGZ.tgz...\n";
@@ -700,7 +703,7 @@ if ($nboftargetok) {
 
 			$ret=`rm -fr $BUILDROOT/$FILENAMETGZ/build/exe`;
 			$ret=`rm -fr $BUILDROOT/$FILENAMETGZ/htdocs/includes/ckeditor/_source`;	# We can't remove it with exclude file, we need it for some tarball packages
-			
+
 			print "Compress $FILENAMETGZ into $FILENAMETGZ.tgz...\n";
 			$cmd="tar --exclude-vcs --exclude-from \"$BUILDROOT/$PROJECT/build/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$BUILDROOT/$FILENAMETGZ.tgz\" $FILENAMETGZ";
 			print "$cmd\n";
@@ -712,14 +715,14 @@ if ($nboftargetok) {
 			next;
 		}
 
-		if ($target eq 'XZ') 
+		if ($target eq 'XZ')
 		{
 			$NEWDESTI=$DESTI;
 			if ($NEWDESTI =~ /stable/)
 			{
 				mkdir($DESTI.'/standard');
 				if (-d $DESTI.'/standard') { $NEWDESTI=$DESTI.'/standard'; }
-			} 
+			}
 
 			print "Remove target $FILENAMEXZ.xz...\n";
 			unlink("$NEWDESTI/$FILENAMEXZ.xz");
@@ -732,7 +735,7 @@ if ($nboftargetok) {
 
 			$ret=`rm -fr $BUILDROOT/$FILENAMEXZ/build/exe`;
 			$ret=`rm -fr $BUILDROOT/$FILENAMEXZ/htdocs/includes/ckeditor/_source`;	# We can't remove it with exclude file, we need it for some tarball packages
-			
+
 			print "Compress $FILENAMEXZ into $FILENAMEXZ.xz...\n";
 
 			print "Go to directory $BUILDROOT\n";
@@ -748,15 +751,15 @@ if ($nboftargetok) {
 			$ret=`mv "$BUILDROOT/$FILENAMEXZ.xz" "$NEWDESTI/$FILENAMEXZ.xz"`;
 			next;
 		}
-		
-		if ($target eq 'ZIP') 
+
+		if ($target eq 'ZIP')
 		{
 			$NEWDESTI=$DESTI;
 			if ($NEWDESTI =~ /stable/)
 			{
 				mkdir($DESTI.'/standard');
 				if (-d $DESTI.'/standard') { $NEWDESTI=$DESTI.'/standard'; }
-			} 
+			}
 
 			print "Remove target $FILENAMEZIP.zip...\n";
 			unlink("$NEWDESTI/$FILENAMEZIP.zip");
@@ -779,14 +782,14 @@ if ($nboftargetok) {
 			print $cmd."\n";
 			$ret= `$cmd`;
 			chdir("$olddir");
-						
+
 			# Move to final dir
 			print "Move $FILENAMEZIP.zip to $NEWDESTI/$FILENAMEZIP.zip\n";
 			$ret=`mv "$BUILDROOT/$FILENAMEZIP.zip" "$NEWDESTI/$FILENAMEZIP.zip"`;
 			next;
 		}
-	
-		if ($target =~ /RPM/)	                 # Linux only 
+
+		if ($target =~ /RPM/)	                 # Linux only
 		{
 			$NEWDESTI=$DESTI;
 			$subdir="package_rpm_generic";
@@ -797,7 +800,7 @@ if ($nboftargetok) {
 			{
 				mkdir($DESTI.'/'.$subdir);
 				if (-d $DESTI.'/'.$subdir) { $NEWDESTI=$DESTI.'/'.$subdir; }
-			} 
+			}
 
 			if ($RPMDIR eq "") { $RPMDIR=$ENV{'HOME'}."/rpmbuild"; }
 
@@ -810,7 +813,7 @@ if ($nboftargetok) {
 
 			print "Create directory $BUILDROOT/$FILENAMETGZ2\n";
 			$ret=`rm -fr $BUILDROOT/$FILENAMETGZ2`;
-			
+
 			print "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMETGZ2\n";
 			$cmd="cp -pr '$BUILDROOT/$PROJECT' '$BUILDROOT/$FILENAMETGZ2'";
 			$ret=`$cmd`;
@@ -836,7 +839,7 @@ if ($nboftargetok) {
 			if ($target =~ /FEDO/i) { $BUILDFICSRC="${FILENAME}_fedora.spec"; }
 			if ($target =~ /MAND/i) { $BUILDFICSRC="${FILENAME}_mandriva.spec"; }
 			if ($target =~ /OPEN/i) { $BUILDFICSRC="${FILENAME}_opensuse.spec"; }
-			
+
 			use Date::Language;
 			$lang=Date::Language->new('English');
 			$datestring = $lang->time2str("%a %b %e %Y", time);
@@ -854,7 +857,7 @@ if ($nboftargetok) {
 			}
 			close SPECFROM;
 			close SPECTO;
-	
+
 			print "Copy patch file to $RPMDIR/SOURCES\n";
 			$ret=`cp "$SOURCE/build/rpm/dolibarr-forrpm.patch" "$RPMDIR/SOURCES"`;
 			$ret=`chmod 644 $RPMDIR/SOURCES/dolibarr-forrpm.patch`;
@@ -876,14 +879,14 @@ if ($nboftargetok) {
 			next;
 		}
 
-		if ($target eq 'DEB') 
+		if ($target eq 'DEB')
 		{
 			$NEWDESTI=$DESTI;
 			if ($NEWDESTI =~ /stable/)
 			{
 				mkdir($DESTI.'/package_debian-ubuntu');
 				if (-d $DESTI.'/package_debian-ubuntu') { $NEWDESTI=$DESTI.'/package_debian-ubuntu'; }
-			} 
+			}
 
 			$olddir=getcwd();
 
@@ -964,13 +967,18 @@ if ($nboftargetok) {
 			$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/select2/LICENSE`;
 			$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/mike42/escpos-php/LICENSE.md`;
 			$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/mobiledetect/mobiledetectlib/LICENSE.txt`;
-			
+
 			# Removed files we don't need (already removed)
 			#$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source`;
-			
+			$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/.codeclimate.yml`;
+			$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/.pre-commit-config.yaml`;
+			$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/.vscode`;
+			$ret=`find $BUILDROOT/$PROJECT.tmp/ -type f -name '.editorconfig' -exec rm {} \\;`;
+			$ret=`find $BUILDROOT/$PROJECT.tmp/ -type f -name '.travis.yml' -exec rm {} \\;`;
+
 			# Rename upstream changelog to match debian rules
 			$ret=`mv $BUILDROOT/$PROJECT.tmp/ChangeLog $BUILDROOT/$PROJECT.tmp/changelog`;
-			
+
 			# Prepare source package (init debian dir)
 			print "Create directory $BUILDROOT/$PROJECT.tmp/debian\n";
 			$ret=`mkdir "$BUILDROOT/$PROJECT.tmp/debian"`;
@@ -1008,7 +1016,7 @@ if ($nboftargetok) {
 			$ret=`cp -f  "$SOURCE/build/debian/dolibarr.postrm"         "$BUILDROOT/$PROJECT.tmp/debian"`;
 			$ret=`cp -f  "$SOURCE/build/debian/dolibarr.templates"      "$BUILDROOT/$PROJECT.tmp/debian"`;
 			$ret=`cp -f  "$SOURCE/build/debian/install.forced.php.install"      "$BUILDROOT/$PROJECT.tmp/debian"`;
-			
+
 			# Set owners and permissions
 			#print "Set owners on files/dir\n";
 			#$ret=`chown -R root.root $BUILDROOT/$PROJECT.tmp`;
@@ -1039,8 +1047,8 @@ if ($nboftargetok) {
 			$ret=`$cmd`;
 			$cmd="find $BUILDROOT/$PROJECT.tmp/scripts -name '*.sh' -type f -exec chmod 755 {} \\; ";
 			$ret=`$cmd`;
-			
-		
+
+
 			print "Rename directory $BUILDROOT/$PROJECT.tmp into $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n";
 			$cmd="mv $BUILDROOT/$PROJECT.tmp $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build";
 			$ret=`$cmd`;
@@ -1048,14 +1056,14 @@ if ($nboftargetok) {
 
 			print "Go into directory $BUILDROOT\n";
 			chdir("$BUILDROOT");
-			
+
 			# We need a tarball to be able to build "quilt" debian package (not required for native but we need patch so it is not a native)
 			print "Compress $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build into $BUILDROOT/$FILENAMEDEBNATIVE.orig.tar.gz...\n";
 			$cmd="tar --exclude-vcs --exclude-from \"$BUILDROOT/$PROJECT/build/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$BUILDROOT/$FILENAMEDEBNATIVE.orig.tar.gz\" $PROJECT-$MAJOR.$MINOR.$build";
 			print $cmd."\n";
 			$ret=`$cmd`;
 
-			# Creation of source package          
+			# Creation of source package
 			print "Go into directory $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n";
 			chdir("$BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build");
 			#$cmd="dpkg-source -b $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build";
@@ -1074,12 +1082,12 @@ if ($nboftargetok) {
 			$ret=`mv $BUILDROOT/*_all.deb "$NEWDESTI/"`;
 			$ret=`mv $BUILDROOT/*.dsc "$NEWDESTI/"`;
 			$ret=`mv $BUILDROOT/*.orig.tar.gz "$NEWDESTI/"`;
-			#$ret=`mv $BUILDROOT/*.debian.tar.xz "$NEWDESTI/"`;		# xz file is generated when build/debian/sources/option 
+			#$ret=`mv $BUILDROOT/*.debian.tar.xz "$NEWDESTI/"`;		# xz file is generated when build/debian/sources/option
 			$ret=`mv $BUILDROOT/*.debian.tar.gz "$NEWDESTI/"`;
 			$ret=`mv $BUILDROOT/*.changes "$NEWDESTI/"`;
 			next;
 		}
-		
+
 		if ($target eq 'EXEDOLIWAMP')
 		{
 			$NEWDESTI=$DESTI;
@@ -1087,22 +1095,22 @@ if ($nboftargetok) {
 			{
 				mkdir($DESTI.'/package_windows');
 				if (-d $DESTI.'/package_windows') { $NEWDESTI=$DESTI.'/package_windows'; }
-			} 
+			}
 
      		print "Remove target $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe...\n";
     		unlink "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe";
- 
+
  			if ($OS eq 'windows') {
  				print "Check that ISCC.exe is in your PATH.\n";
 			} else {
  				print "Check that in your Wine setup, you have created a Z: drive that point to your / directory.\n";
 			}
-			
+
  			$SOURCEBACK=$SOURCE;
  			$SOURCEBACK =~ s/\//\\/g;
 
     		print "Prepare file \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.tmp.iss\" from \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.iss\"\n";
-    		
+
     		#$ret=`cat "$SOURCE/build/exe/doliwamp/doliwamp.iss" | sed -e 's/__FILENAMEEXEDOLIWAMP__/$FILENAMEEXEDOLIWAMP/g' > "$SOURCE/build/exe/doliwamp/doliwamp.tmp.iss"`;
     		open(IN, '<' . $SOURCE."/build/exe/doliwamp/doliwamp.iss") or die $!;
 			open(OUT, '>' . "$SOURCE/build/exe/doliwamp/doliwamp.tmp.iss") or die $!;
@@ -1115,7 +1123,7 @@ if ($nboftargetok) {
 			close(OUT);
 
     		print "Compil exe $FILENAMEEXEDOLIWAMP.exe file from iss file \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.tmp.iss\" on OS $OS\n";
-    		
+
  			if ($OS eq 'windows') {
 	    		$cmd= "ISCC.exe \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.tmp.iss\"";
 	    	} else {
@@ -1129,26 +1137,26 @@ if ($nboftargetok) {
 			print "Move \"$SOURCE\\build\\$FILENAMEEXEDOLIWAMP.exe\" to $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe\n";
     		rename("$SOURCE/build/$FILENAMEEXEDOLIWAMP.exe","$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe");
             print "Move $SOURCE/build/$FILENAMEEXEDOLIWAMP.exe to $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe\n";
-            
+
             use File::Copy;
 
             #$ret=`mv "$SOURCE/build/$FILENAMEEXEDOLIWAMP.exe" "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"`;
             $ret=move("$SOURCE/build/$FILENAMEEXEDOLIWAMP.exe", "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe");
-            
+
             print "Remove tmp file $SOURCE/build/exe/doliwamp/doliwamp.tmp.iss\n";
             #$ret=`rm "$SOURCE/build/exe/doliwamp/doliwamp.tmp.iss"`;
             $ret=unlink("$SOURCE/build/exe/doliwamp/doliwamp.tmp.iss");
-            
+
     		next;
     	}
     }
 
 	# Publish package for each target
 	#--------------------------------
-	foreach my $target (sort keys %CHOOSEDPUBLISH) 
+	foreach my $target (sort keys %CHOOSEDPUBLISH)
 	{
 		if ($CHOOSEDPUBLISH{$target} < 0) { next; }
-	
+
 		print "\nList of files to publish (BUILD=$BUILD)\n";
 		%filestoscansf=(
 			"$DESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml"=>'none',				# none means it won't be published on SF
@@ -1171,7 +1179,8 @@ if ($nboftargetok) {
 			"$DESTI/package_debian-ubuntu/${FILENAMEDEB}_all.deb"=>'package_debian-ubuntu',
 			"$DESTI/package_debian-ubuntu/${FILENAMEDEB}_amd64.changes"=>'package_debian-ubuntu',
 			"$DESTI/package_debian-ubuntu/${FILENAMEDEB}.dsc"=>'package_debian-ubuntu',
-			"$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.xz"=>'package_debian-ubuntu',
+			#"$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.xz"=>'package_debian-ubuntu',
+			"$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.gz"=>'package_debian-ubuntu',
 			"$DESTI/package_debian-ubuntu/${FILENAMEDEBSHORT}.orig.tar.gz"=>'package_debian-ubuntu',
 			"$DESTI/package_windows/$FILENAMEEXEDOLIWAMP.exe"=>'package_windows',
 			"$DESTI/standard/$FILENAMETGZ.tgz"=>'standard',
@@ -1206,26 +1215,26 @@ if ($nboftargetok) {
 			print "\n";
 		}
 
-		if ($target eq 'SF' || $target eq 'ASSO') 
+		if ($target eq 'SF' || $target eq 'ASSO')
 		{
 			print "\n";
-			
+
 			if ($target eq 'SF') { $PUBLISH = $PUBLISHSTABLE; }
 			if ($target eq 'ASSO' && $BUILD =~ /[a-z]/i)   { $PUBLISH = $PUBLISHBETARC.'/lastbuild'; }
 			if ($target eq 'ASSO' && $BUILD =~ /^[0-9]+$/) { $PUBLISH = $PUBLISHBETARC.'/stable'; }
-			
+
 			$NEWPUBLISH=$PUBLISH;
 			print "Publish to target $NEWPUBLISH. Click enter or CTRL+C...\n";
 
 			# Ask which target to build
-			$NUM_SCRIPT=<STDIN>; 
+			$NUM_SCRIPT=<STDIN>;
 			chomp($NUM_SCRIPT);
 
 			print "Create empty dir /tmp/emptydir. We need it to create target dir using rsync.\n";
 			$ret=`mkdir -p "/tmp/emptydir/"`;
-			
+
 			%filestoscan=%filestoscansf;
-			
+
 			foreach my $file (sort keys %filestoscan)
 			{
 				$found=0;
@@ -1235,30 +1244,30 @@ if ($nboftargetok) {
 	    		if ($target eq 'SF') {
 	    			if ($filestoscan{$file} eq 'none') {
 	    				next;
-	    			} 
+	    			}
 	    			$destFolder="$NEWPUBLISH/$filestoscan{$file}/".$MAJOR.'.'.$MINOR.'.'.$BUILD;
 	    		}
 	    		elsif ($target eq 'ASSO' and $NEWPUBLISH =~ /stable/) {
 	    			$destFolder="$NEWPUBLISH/$filestoscanstableasso{$file}";
-	    		} 
+	    		}
 	    		elsif ($target eq 'ASSO' and $NEWPUBLISH !~ /stable/) {
 	    			$destFolder="$NEWPUBLISH";
-	    		} 
+	    		}
 	    		else	# No more used
 	    		{
 	    			$dirnameonly=$file;
-	    			$dirnameonly =~ s/.*\/([^\/]+)\/[^\/]+$/$1/;  
+	    			$dirnameonly =~ s/.*\/([^\/]+)\/[^\/]+$/$1/;
 	    			$filenameonly=$file;
-	    			$filenameonly =~ s/.*\/[^\/]+\/([^\/])+$/$1/;  
+	    			$filenameonly =~ s/.*\/[^\/]+\/([^\/])+$/$1/;
 	    			$destFolder="$NEWPUBLISH/$dirnameonly";
 	    		}
 
 				print "\n";
 	    		print "Publish file ".$file." to ".$destFolder."\n";
 
-				# mkdir	   
+				# mkdir
 				#my $ssh = Net::SSH::Perl->new("frs.sourceforge.net");
-				#$ssh->login("$user","$pass"); 		
+				#$ssh->login("$user","$pass");
 				#use String::ShellQuote qw( shell_quote );
 				#$ssh->cmd('mkdir '.shell_quote($destFolder).' && exit');
 
@@ -1267,20 +1276,20 @@ if ($nboftargetok) {
 				#$sftp->mkdir($destFolder)
 
 				#$command="ssh eldy,dolibarr\@frs.sourceforge.net mkdir -p \"$destFolder\"";
-				#print "$command\n";	
+				#print "$command\n";
 				#my $ret=`$command 2>&1`;
 
 				$command="rsync -s -e 'ssh' --recursive /tmp/emptydir/ \"".$destFolder."\"";
-				print "$command\n";	
+				print "$command\n";
 				my $ret=`$command 2>&1`;
 
 				$command="rsync -s -e 'ssh' \"$file\" \"".$destFolder."\"";
-				print "$command\n";	
+				print "$command\n";
 				my $ret2=`$command 2>&1`;
 				print "$ret2\n";
 			}
 		}
-	}    
+	}
 }
 
 print "\n----- Summary -----\n";

+ 7 - 0
build/tgz/tar_exclude.txt

@@ -3,6 +3,13 @@
 .git
 .gitignore
 .scrutinizer.yml
+.travis.yml
+.vscode
+.idea
+.editorconfig
+.codeclimate.yml
+.pre-commit-config.yaml
+.mailmap
 Thumbs.db
 build/exe
 build/html

+ 6 - 0
build/zip/zip_exclude.txt

@@ -22,3 +22,9 @@ dolibarr*.deb
 dolibarr*.zip
 cvschangelogbuilder_dolibarr*
 dolibarr_install.log
+.travis.yml
+.vscode
+.idea
+.editorconfig
+.codeclimate.yml
+.pre-commit-config.yaml

+ 3 - 3
htdocs/accountancy/admin/categories_list.php

@@ -87,7 +87,7 @@ $tablib[32] = "DictionaryAccountancyCategory";
 
 // Requests to extract data
 $tabsql = array();
-$tabsql[32] = "SELECT a.rowid as rowid, a.code as code, a.label, a.range_account, a.category_type, a.formula, a.position as position, a.fk_country as country_id, c.code as country_code, c.label as country, a.active FROM ".MAIN_DB_PREFIX."c_accounting_category as a, ".MAIN_DB_PREFIX."c_country as c WHERE a.fk_country=c.rowid and c.active=1";
+$tabsql[32] = "SELECT a.rowid as rowid, a.code as code, a.label, a.range_account, a.category_type, a.formula, a.position as position, a.fk_country as country_id, c.code as country_code, c.label as country, a.active FROM ".MAIN_DB_PREFIX."c_accounting_category as a, ".MAIN_DB_PREFIX."c_country as c WHERE a.fk_country=c.rowid AND c.active=1 AND a.entity IN (".getEntity('c_accounting_category').")";
 
 // Criteria to sort dictionaries
 $tabsqlsort = array();
@@ -99,11 +99,11 @@ $tabfield[32] = "code,label,range_account,category_type,formula,position,country
 
 // Name of editing fields for record modification
 $tabfieldvalue = array();
-$tabfieldvalue[32] = "code,label,range_account,category_type,formula,position,country_id";
+$tabfieldvalue[32] = "code,label,range_account,category_type,formula,position,country_id,entity";
 
 // Name of the fields in the table for inserting a record
 $tabfieldinsert = array();
-$tabfieldinsert[32] = "code,label,range_account,category_type,formula,position,fk_country";
+$tabfieldinsert[32] = "code,label,range_account,category_type,formula,position,fk_country,entity";
 
 // Name of the rowid if the field is not of type autoincrement
 // Example: "" if id field is "rowid" and has autoincrement on

+ 2 - 2
htdocs/accountancy/admin/fiscalyear.php

@@ -1,5 +1,5 @@
 <?php
-/* Copyright (C) 2013-2024  Alexandre Spangaro  <aspangaro@easya.solutions>
+/* Copyright (C) 2013-2025	Alexandre Spangaro			<alexandre@inovea-conseil.com>
  *
  * 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
@@ -123,7 +123,7 @@ if ($result) {
 	$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'));
+		$newcardbutton .= dolGetButtonTitle($langs->trans('NewFiscalYear'), '', 'fa fa-plus-circle', DOL_URL_ROOT.'/accountancy/admin/fiscalyear_card.php?action=create', '', $user->hasRight('accounting', 'fiscalyear', 'write'));
 	}
 
 	$title = $langs->trans('AccountingPeriods');

+ 1 - 1
htdocs/accountancy/class/accountancyexport.class.php

@@ -981,7 +981,7 @@ class AccountancyExport
 
 			// We need to keep the 10 lastest number of invoice doc_ref not the beginning part that is the unusefull almost same part
 			// $tab['num_piece3'] = str_pad(self::trunc($line->piece_num, 10), 10);
-			$tab['num_piece3'] = substr(self::trunc($line->doc_ref, 20), -10);
+			$tab['num_piece3'] = str_pad(substr(self::trunc($line->doc_ref, 20), -10), 10);
 			$tab['reserved'] = str_repeat(' ', 10); // position 159
 			$tab['currency_amount'] = str_repeat(' ', 13); // position 169
 			// get document file

+ 4 - 4
htdocs/accountancy/class/accountancyimport.class.php

@@ -113,12 +113,12 @@ class AccountancyImport
 
 
 	/**
-	 *  Compute direction
+	 * Compute direction
 	 *
 	 * @param   array       $arrayrecord        Array of read values: [fieldpos] => (['val']=>val, ['type']=>-1=null,0=blank,1=string), [fieldpos+1]...
 	 * @param   array       $listfields         Fields list to add
 	 * @param 	int			$record_key         Record key
-	 * @return  mixed							Value
+	 * @return  string							Value D or C or ""
 	 */
 	public function computeDirection(&$arrayrecord, $listfields, $record_key)
 	{
@@ -132,9 +132,9 @@ class AccountancyImport
 				$sens = 'C';
 			}
 
-			return "'" . $this->db->escape($sens) . "'";
+			return $sens;
 		}
 
-		return "''";
+		return "";
 	}
 }

+ 49 - 18
htdocs/accountancy/class/bookkeeping.class.php

@@ -3,6 +3,7 @@
  * Copyright (C) 2015-2022  Alexandre Spangaro  <aspangaro@open-dsi.fr>
  * Copyright (C) 2015-2020  Florian Henry       <florian.henry@open-concept.pro>
  * Copyright (C) 2018-2020  Frédéric France     <frederic.france@netlogic.fr>
+ * Copyright (C) 2024		Jose MARTINEZ	    <jose.martinez@pichinov.com>
  *
  * 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
@@ -26,6 +27,7 @@
 
 // Class
 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/fiscalyear.class.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
@@ -327,7 +329,7 @@ class BookKeeping extends CommonObject
 		$this->piece_num = 0;
 
 		// First check if line not yet already in bookkeeping.
-		// Note that we must include 'doc_type - fk_doc - numero_compte - label' to be sure to have unicity of line (because we may have several lines
+		// Note that we must include 'doc_type - fk_doc - numero_compte - label - subledger_account (if not empty)' to be sure to have unicity of line (because we may have several lines
 		// with same doc_type, fk_doc, numero_compte for 1 invoice line when using localtaxes with same account)
 		// WARNING: This is not reliable, label may have been modified. This is just a small protection.
 		// The page that make transfer make the test on couple (doc_type - fk_doc) only.
@@ -341,6 +343,9 @@ class BookKeeping extends CommonObject
 		}
 		$sql .= " AND numero_compte = '".$this->db->escape($this->numero_compte)."'";
 		$sql .= " AND label_operation = '".$this->db->escape($this->label_operation)."'";
+		if (!empty($this->subledger_account)) {
+			$sql .= " AND subledger_account = '".$this->db->escape($this->subledger_account)."'";
+		}
 		$sql .= " AND entity = ".$conf->entity; // Do not use getEntity for accounting features
 
 		$resql = $this->db->query($sql);
@@ -2692,10 +2697,8 @@ class BookKeeping extends CommonObject
 
 				$sql = 'SELECT';
 				$sql .= " t.numero_compte,";
-				$sql .= " t.label_compte,";
 				if ($separate_auxiliary_account) {
-					$sql .= " t.subledger_account,";
-					$sql .= " t.subledger_label,";
+					$sql .= " NULLIF(t.subledger_account, '') as subledger_account,"; // fix db issues with Null or "" values
 				}
 				$sql .= " aa.pcg_type,";
 				$sql .= " (SUM(t.credit) - SUM(t.debit)) as opening_balance";
@@ -2707,10 +2710,11 @@ class BookKeeping extends CommonObject
 				$sql .= ' AND aa.pcg_type IN (' . $this->db->sanitize(implode(',', $pcg_type_filter), 1) . ')';
 				$sql .= " AND DATE(t.doc_date) >= '" . $this->db->idate($fiscal_period->date_start) . "'";
 				$sql .= " AND DATE(t.doc_date) <= '" . $this->db->idate($fiscal_period->date_end) . "'";
-				$sql .= ' GROUP BY t.numero_compte, t.label_compte, aa.pcg_type';
+				$sql .= ' GROUP BY t.numero_compte, aa.pcg_type';
 				if ($separate_auxiliary_account) {
-					$sql .= ' ,t.subledger_account, t.subledger_label';
+					$sql .= " , NULLIF(t.subledger_account, '')";
 				}
+				$sql .= ' HAVING (SUM(t.credit) - SUM(t.debit)) != 0 '; // Exclude rows with opening_balance = 0
 				$sql .= $this->db->order("t.numero_compte", "ASC");
 
 				$resql = $this->db->query($sql);
@@ -2732,23 +2736,38 @@ class BookKeeping extends CommonObject
 							$bookkeeping = new BookKeeping($this->db);
 							$bookkeeping->doc_date = $new_fiscal_period->date_start;
 							$bookkeeping->date_lim_reglement = '';
-							$bookkeeping->doc_ref = $new_fiscal_period->label;
+							$bookkeeping->doc_ref = $fiscal_period->label;
 							$bookkeeping->date_creation = $now;
 							$bookkeeping->doc_type = 'closure';
-							$bookkeeping->fk_doc = $new_fiscal_period->id;
+							$bookkeeping->fk_doc = $fiscal_period->id;
 							$bookkeeping->fk_docdet = 0; // Useless, can be several lines that are source of this record to add
 							$bookkeeping->thirdparty_code = '';
 
 							if ($separate_auxiliary_account) {
 								$bookkeeping->subledger_account = $obj->subledger_account;
-								$bookkeeping->subledger_label = $obj->subledger_label;
+								$sql = 'SELECT';
+								$sql .= " subledger_label";
+								$sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element;
+								$sql .= " WHERE subledger_account = '" . $this->db->escape($obj->subledger_account) . "'";
+								$sql .= " ORDER BY doc_date DESC";
+								$sql .= " LIMIT 1";
+								$result = $this->db->query($sql);
+								if (!$result) {
+									$this->errors[] = 'Error: ' . $this->db->lasterror();
+									dol_syslog(__METHOD__ . ' ' . join(',', $this->errors), LOG_ERR);
+									$error++;
+								}
+								$objtmp = $this->db->fetch_object($result);
+								$bookkeeping->subledger_label = $objtmp->subledger_label; // latest subledger label used
 							} else {
-								$bookkeeping->subledger_account = '';
-								$bookkeeping->subledger_label = '';
+								$bookkeeping->subledger_account = null;
+								$bookkeeping->subledger_label = null;
 							}
 
 							$bookkeeping->numero_compte = $obj->numero_compte;
-							$bookkeeping->label_compte = $obj->label_compte;
+							$accountingaccount = new AccountingAccount($this->db);
+							$accountingaccount->fetch('', $obj->numero_compte);
+							$bookkeeping->label_compte = $accountingaccount->label; // latest account label used
 
 							$bookkeeping->label_operation = $new_fiscal_period->label;
 							$bookkeeping->montant = $mt;
@@ -2779,20 +2798,32 @@ class BookKeeping extends CommonObject
 						$bookkeeping = new BookKeeping($this->db);
 						$bookkeeping->doc_date = $new_fiscal_period->date_start;
 						$bookkeeping->date_lim_reglement = '';
-						$bookkeeping->doc_ref = $new_fiscal_period->label;
+						$bookkeeping->doc_ref = $fiscal_period->label;
 						$bookkeeping->date_creation = $now;
 						$bookkeeping->doc_type = 'closure';
-						$bookkeeping->fk_doc = $new_fiscal_period->id;
+						$bookkeeping->fk_doc = $fiscal_period->id;
 						$bookkeeping->fk_docdet = 0; // Useless, can be several lines that are source of this record to add
 						$bookkeeping->thirdparty_code = '';
 
 						if ($separate_auxiliary_account) {
-							$bookkeeping->subledger_label = '';
 							$bookkeeping->subledger_account = $obj->subledger_account;
-							$bookkeeping->subledger_label = $obj->subledger_label;
+							$sql = 'SELECT';
+							$sql .= " subledger_label";
+							$sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element;
+							$sql .= " WHERE subledger_account = '" . $this->db->escape($obj->subledger_account) . "'";
+							$sql .= " ORDER BY doc_date DESC";
+							$sql .= " LIMIT 1";
+							$result = $this->db->query($sql);
+							if (!$result) {
+								$this->errors[] = 'Error: ' . $this->db->lasterror();
+								dol_syslog(__METHOD__ . ' ' . join(',', $this->errors), LOG_ERR);
+								$error++;
+							}
+							$objtmp = $this->db->fetch_object($result);
+							$bookkeeping->subledger_label = $objtmp->subledger_label; // latest subledger label used
 						} else {
-							$bookkeeping->subledger_account = '';
-							$bookkeeping->subledger_label = '';
+							$bookkeeping->subledger_account = null;
+							$bookkeeping->subledger_label = null;
 						}
 
 						$bookkeeping->numero_compte = $accountingaccount->account_number;

+ 7 - 1
htdocs/adherents/class/api_members.class.php

@@ -358,7 +358,13 @@ class Members extends DolibarrApi
 					}
 				}
 			} else {
-				$member->$field = $value;
+				if ($field == 'array_options' && is_array($value)) {
+					foreach ($value as $index => $val) {
+						$member->array_options[$index] = $this->_checkValForAPI($field, $val, $member);
+					}
+					continue;
+				}
+				$member->$field = $this->_checkValForAPI($field, $value, $member);
 			}
 		}
 

+ 7 - 2
htdocs/adherents/class/api_memberstypes.class.php

@@ -202,10 +202,15 @@ class MembersTypes extends DolibarrApi
 				$membertype->context['caller'] = $request_data['caller'];
 				continue;
 			}
-
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$membertype->array_options[$index] = $this->_checkValForAPI($field, $val, $membertype);
+				}
+				continue;
+			}
 			// Process the status separately because it must be updated using
 			// the validate(), resiliate() and exclude() methods of the class AdherentType.
-			$membertype->$field = $value;
+			$membertype->$field = $this->_checkValForAPI($field, $value, $membertype);
 		}
 
 		// If there is no error, update() returns the number of affected rows

+ 7 - 1
htdocs/adherents/class/api_subscriptions.class.php

@@ -196,7 +196,13 @@ class Subscriptions extends DolibarrApi
 				continue;
 			}
 
-			$subscription->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$subscription->array_options[$index] = $this->_checkValForAPI($field, $val, $subscription);
+				}
+				continue;
+			}
+			$subscription->$field = $this->_checkValForAPI($field, $value, $subscription);
 		}
 
 		if ($subscription->update(DolibarrApiAccess::$user) > 0) {

+ 5 - 0
htdocs/admin/dict.php

@@ -2114,6 +2114,11 @@ if ($id > 0) {
 					$canbemodified = 1;
 				}
 
+				if ($tabname[$id] == "c_product_nature" && in_array($obj->code, array(0, 1))) {
+					$canbedisabled = 0;
+					$canbemodified = 0;
+					$iserasable = 0;
+				}
 				// Build Url. The table is id=, the id of line is rowid=
 				$rowidcol = empty($tabrowid[$id]) ? 'rowid' : $tabrowid[$id];
 				// If rowidcol not defined

+ 1 - 1
htdocs/admin/mails.php

@@ -43,7 +43,7 @@ if (!$user->admin) {
 
 $usersignature = $user->signature;
 // For action = test or send, we ensure that content is not html, even for signature, because for this we want a test with NO html.
-if ($action == 'test' || ($action == 'send' && $trackid = 'test')) {
+if ($action == 'test' || ($action == 'send' && $trackid == 'test')) {
 	$usersignature = dol_string_nohtmltag($usersignature, 2);
 }
 

+ 7 - 1
htdocs/bom/class/api_boms.class.php

@@ -251,7 +251,13 @@ class Boms extends DolibarrApi
 				continue;
 			}
 
-			$this->bom->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->bom->array_options[$index] = $this->_checkValForAPI($field, $val, $this->bom);
+				}
+				continue;
+			}
+			$this->bom->$field = $this->_checkValForAPI($field, $value, $this->bom);
 		}
 
 		$this->checkRefNumbering();

+ 7 - 1
htdocs/categories/class/api_categories.class.php

@@ -248,7 +248,13 @@ class Categories extends DolibarrApi
 				continue;
 			}
 
-			$this->category->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->category->array_options[$index] = $this->_checkValForAPI($field, $val, $this->category);
+				}
+				continue;
+			}
+			$this->category->$field = $this->_checkValForAPI($field, $value, $this->category);
 		}
 
 		if ($this->category->update(DolibarrApiAccess::$user) > 0) {

+ 2 - 2
htdocs/comm/action/class/actioncomm.class.php

@@ -629,7 +629,7 @@ class ActionComm extends CommonObject
 				foreach ($this->userassigned as $key => $val) {
 					// Common value with new behavior is to have $val = array('id'=>iduser, 'transparency'=>0|1) and $this->userassigned is an array of iduser => $val.
 					if (!is_array($val)) {	// For backward compatibility when $val='id'.
-						$val = array('id'=>$val);
+						$val = array('id' => $val);
 					}
 
 					if ($val['id'] > 0) {
@@ -1271,7 +1271,7 @@ class ActionComm extends CommonObject
 					$already_inserted = array();
 					foreach (array_keys($this->socpeopleassigned) as $key => $val) {
 						if (!is_array($val)) {	// For backward compatibility when val=id
-							$val = array('id'=>$val);
+							$val = array('id' => $val);
 						}
 						if (!empty($already_inserted[$val['id']])) {
 							continue;

+ 6 - 0
htdocs/comm/action/class/api_agendaevents.class.php

@@ -279,6 +279,12 @@ class AgendaEvents extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->actioncomm->array_options[$index] = $this->_checkValForAPI($field, $val, $this->actioncomm);
+				}
+				continue;
+			}
 			$this->actioncomm->$field = $this->_checkValForAPI($field, $value, $this->actioncomm);
 		}
 

+ 11 - 10
htdocs/comm/action/list.php

@@ -443,20 +443,25 @@ if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
 }
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON a.fk_soc = s.rowid";
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."socpeople as sp ON a.fk_contact = sp.rowid";
-$sql .= " ,".MAIN_DB_PREFIX."c_actioncomm as c";
+$sql .= " INNER JOIN ".MAIN_DB_PREFIX."c_actioncomm as c ON c.id = a.fk_action";
 // We must filter on resource table
 if ($resourceid > 0) {
-	$sql .= ", ".MAIN_DB_PREFIX."element_resources as r";
+	$sql .= " INNER JOIN ".MAIN_DB_PREFIX."element_resources as r ON r.element_type = 'action' AND r.element_id = a.id";
 }
 // We must filter on assignement table
 if ($filtert > 0 || $usergroup > 0) {
-	$sql .= ", ".MAIN_DB_PREFIX."actioncomm_resources as ar";
+	$sql .= " INNER JOIN ".MAIN_DB_PREFIX."actioncomm_resources as ar ON ar.fk_actioncomm = a.id AND ar.element_type='user'";
 }
 if ($usergroup > 0) {
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."usergroup_user as ugu ON ugu.fk_user = ar.fk_element";
 }
-$sql .= " WHERE c.id = a.fk_action";
-$sql .= ' AND a.entity IN ('.getEntity('agenda').')';
+
+// Add table from hooks
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldListFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+$sql .= $hookmanager->resPrint;
+
+$sql .= " WHERE a.entity IN (".getEntity('agenda').")";
 // Condition on actioncode
 if (!empty($actioncode)) {
 	if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
@@ -487,7 +492,7 @@ if (!empty($actioncode)) {
 	}
 }
 if ($resourceid > 0) {
-	$sql .= " AND r.element_type = 'action' AND r.element_id = a.id AND r.resource_id = ".((int) $resourceid);
+	$sql .= " AND r.resource_id = ".((int) $resourceid);
 }
 if ($pid) {
 	$sql .= " AND a.fk_project=".((int) $pid);
@@ -498,10 +503,6 @@ if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
 if ($socid > 0) {
 	$sql .= " AND s.rowid = ".((int) $socid);
 }
-// We must filter on assignement table
-if ($filtert > 0 || $usergroup > 0) {
-	$sql .= " AND ar.fk_actioncomm = a.id AND ar.element_type='user'";
-}
 if ($type) {
 	$sql .= " AND c.id = ".((int) $type);
 }

+ 6 - 6
htdocs/comm/contact.php

@@ -37,7 +37,7 @@ if (!$sortorder) {
 	$sortorder = "ASC";
 }
 if (!$sortfield) {
-	$sortfield = "p.name";
+	$sortfield = "p.lastname";
 }
 if ($page < 0) {
 	$page = 0;
@@ -81,7 +81,7 @@ if ($type == "f") {
  */
 
 $sql = "SELECT s.rowid, s.nom as name, st.libelle as stcomm";
-$sql .= ", p.rowid as cidp, p.name, p.firstname, p.email, p.phone";
+$sql .= ", p.rowid as cidp, p.lastname, p.firstname, p.email, p.phone";
 $sql .= " FROM ".MAIN_DB_PREFIX."c_stcomm as st,";
 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
 	$sql .= " ".MAIN_DB_PREFIX."societe_commerciaux as sc,";
@@ -106,7 +106,7 @@ if ($socid) {
 	$sql .= " AND s.rowid = ".((int) $socid);
 }
 if (!empty($search_lastname)) {
-	$sql .= " AND p.name LIKE '%".$db->escape($search_lastname)."%'";
+	$sql .= " AND p.lastname LIKE '%".$db->escape($search_lastname)."%'";
 }
 if (!empty($search_firstname)) {
 	$sql .= " AND p.firstname LIKE '%".$db->escape($search_firstname)."%'";
@@ -115,8 +115,8 @@ if (!empty($search_company)) {
 	$sql .= " AND s.nom LIKE '%".$db->escape($search_company)."%'";
 }
 if (!empty($contactname)) { // acces a partir du module de recherche
-	$sql .= " AND (p.name LIKE '%".$db->escape($contactname)."%' OR lower(p.firstname) LIKE '%".$db->escape($contactname)."%') ";
-	$sortfield = "p.name";
+	$sql .= " AND (p.lastname LIKE '%".$db->escape($contactname)."%' OR lower(p.firstname) LIKE '%".$db->escape($contactname)."%') ";
+	$sortfield = "p.lastname";
 	$sortorder = "ASC";
 }
 
@@ -136,7 +136,7 @@ if ($resql) {
 
 	print '<table class="liste centpercent">';
 	print '<tr class="liste_titre">';
-	print_liste_field_titre("Lastname", $_SERVER["PHP_SELF"], "p.name", $begin, $param, "", $sortfield, $sortorder);
+	print_liste_field_titre("Lastname", $_SERVER["PHP_SELF"], "p.lastname", $begin, $param, "", $sortfield, $sortorder);
 	print_liste_field_titre("Firstname", $_SERVER["PHP_SELF"], "p.firstname", $begin, $param, "", $sortfield, $sortorder);
 	print_liste_field_titre("Company", $_SERVER["PHP_SELF"], "s.nom", $begin, $param, "", $sortfield, $sortorder);
 	print_liste_field_titre("Email");

+ 16 - 2
htdocs/comm/mailing/class/html.formadvtargetemailing.class.php

@@ -255,7 +255,7 @@ class FormAdvTargetEmailing extends Form
 				}
 			}
 			if (!empty($InfoFieldList[1])) {
-				$sql .= " ORDER BY nom";
+				$sql .= $this->db->order($InfoFieldList[1]);
 			}
 			// $sql.= ' WHERE entity = '.$conf->entity;
 
@@ -266,7 +266,7 @@ class FormAdvTargetEmailing extends Form
 				if ($num) {
 					while ($i < $num) {
 						$obj = $this->db->fetch_object($resql);
-						$labeltoshow = dol_trunc($obj->$InfoFieldList[1], 90);
+						$labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 90);
 						$options_array[$obj->rowid] = $labeltoshow;
 						$i++;
 					}
@@ -332,6 +332,20 @@ class FormAdvTargetEmailing extends Form
 		global $conf, $langs;
 
 		$form = new Form($this->db);
+		foreach ($options_array as $okey => $val) {
+			if ((string) $okey == '') {
+				continue;
+			}
+
+			$valarray = explode('|', $val);
+			$val = $valarray[0];
+
+			if ($val) {
+				$options_array[$okey] = $langs->trans($val);
+			} else {
+				$options_array[$okey] = $val;
+			}
+		}
 		$return = $form->multiselectarray($htmlname, $options_array, $selected_array, 0, 0, '', 0, 295);
 		return $return;
 	}

+ 16 - 4
htdocs/comm/propal/card.php

@@ -1766,6 +1766,7 @@ if ($action == 'create') {
 	$fk_account = GETPOST('fk_account', 'int');
 
 	// Load objectsrc
+	$objectsrc = null;
 	if (!empty($origin) && !empty($originid)) {
 		// Parse element/subelement (ex: project_task)
 		$element = $subelement = $origin;
@@ -1777,6 +1778,14 @@ if ($action == 'create') {
 
 		if ($element == 'project') {
 			$projectid = $originid;
+
+			// Fetch project and thirdparty
+			$project = new Project($db);
+			$project->fetch($projectid);
+			if ($project->socid > 0) {
+				$soc = new Societe($db);
+				$soc->fetch($project->socid);
+			}
 		} else {
 			// For compatibility
 			if ($element == 'order' || $element == 'commande') {
@@ -1808,9 +1817,9 @@ if ($action == 'create') {
 
 			$soc = $objectsrc->thirdparty;
 
-			$cond_reglement_id 	= (!empty($objectsrc->cond_reglement_id) ? $objectsrc->cond_reglement_id : (!empty($soc->cond_reglement_id) ? $soc->cond_reglement_id : 0));
-			$mode_reglement_id 	= (!empty($objectsrc->mode_reglement_id) ? $objectsrc->mode_reglement_id : (!empty($soc->mode_reglement_id) ? $soc->mode_reglement_id : 0));
-			$warehouse_id       = (!empty($objectsrc->warehouse_id) ? $objectsrc->warehouse_id : (!empty($soc->warehouse_id) ? $soc->warehouse_id : 0));
+			$cond_reglement_id  = (!empty($objectsrc->cond_reglement_id) ? $objectsrc->cond_reglement_id : (!empty($soc->cond_reglement_id) ? $soc->cond_reglement_id : 0));
+			$mode_reglement_id  = (!empty($objectsrc->mode_reglement_id) ? $objectsrc->mode_reglement_id : (!empty($soc->mode_reglement_id) ? $soc->mode_reglement_id : 0));
+			$warehouse_id      = (!empty($objectsrc->warehouse_id) ? $objectsrc->warehouse_id : (!empty($soc->warehouse_id) ? $soc->warehouse_id : 0));
 
 			// Replicate extrafields
 			$objectsrc->fetch_optionals();
@@ -1825,7 +1834,10 @@ if ($action == 'create') {
 				}
 			}
 		}
-	} else {
+	}
+
+	// Load default values from thirdparty
+	if (!empty($soc)) {
 		$cond_reglement_id  = empty($soc->cond_reglement_id) ? $cond_reglement_id : $soc->cond_reglement_id;
 		$deposit_percent    = empty($soc->deposit_percent) ? $deposit_percent : $soc->deposit_percent;
 		$mode_reglement_id  = empty($soc->mode_reglement_id) ? $mode_reglement_id : $soc->mode_reglement_id;

+ 7 - 1
htdocs/comm/propal/class/api_proposals.class.php

@@ -708,7 +708,13 @@ class Proposals extends DolibarrApi
 				continue;
 			}
 
-			$this->propal->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->propal->array_options[$index] = $this->_checkValForAPI($field, $val, $this->propal);
+				}
+				continue;
+			}
+			$this->propal->$field = $this->_checkValForAPI($field, $value, $this->propal);
 		}
 
 		// update end of validity date

+ 7 - 1
htdocs/commande/class/api_orders.class.php

@@ -662,7 +662,13 @@ class Orders extends DolibarrApi
 				continue;
 			}
 
-			$this->commande->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->commande->array_options[$index] = $this->_checkValForAPI($field, $val, $this->commande);
+				}
+				continue;
+			}
+			$this->commande->$field = $this->_checkValForAPI($field, $value, $this->commande);
 		}
 
 		// Update availability

+ 6 - 5
htdocs/compta/bank/bankentries_list.php

@@ -597,9 +597,9 @@ $sql = "SELECT b.rowid, b.dateo as do, b.datev as dv, b.amount, b.label, b.rappr
 $sql .= " b.fk_account, b.fk_type, b.fk_bordereau,";
 $sql .= " ba.rowid as bankid, ba.ref as bankref";
 // Add fields from extrafields
-if (!empty($extrafields->attributes[$object->table_element]['label'])) {
-	foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) {
-		$sql .= ($extrafields->attributes[$object->table_element]['type'][$key] != 'separate' ? ", ef.".$key." as options_".$key : '');
+if (!empty($extrafields->attributes[$extrafieldsobjectkey]['label'])) {
+	foreach ($extrafields->attributes[$extrafieldsobjectkey]['label'] as $key => $val) {
+		$sql .= ($extrafields->attributes[$extrafieldsobjectkey]['type'][$key] != 'separate' ? ", ef.".$key." as options_".$key : '');
 	}
 }
 // Add fields from hooks
@@ -612,8 +612,8 @@ if ($search_bid > 0) {
 }
 $sql .= " ".MAIN_DB_PREFIX."bank_account as ba,";
 $sql .= " ".MAIN_DB_PREFIX."bank as b";
-if (!empty($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$object->table_element."_extrafields as ef on (b.rowid = ef.fk_object)";
+if (!empty($extrafields->attributes[$extrafieldsobjectkey]['label']) && is_array($extrafields->attributes[$extrafieldsobjectkey]['label']) && count($extrafields->attributes[$extrafieldsobjectkey]['label'])) {
+	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$extrafieldsobjectkey."_extrafields as ef on (b.rowid = ef.fk_object)";
 }
 
 // Add fields from hooks
@@ -1831,6 +1831,7 @@ if ($resql) {
 		}
 
 		// Extra fields
+		$obj = $objp; // Because extrafield template use $obj and not $objp as object variable name
 		include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_print_fields.tpl.php';
 		// Fields from hook
 		$parameters=array('arrayfields'=>$arrayfields, 'object'=>$object, 'obj'=>$objp, 'i'=>$i, 'totalarray'=>&$totalarray);

+ 6 - 0
htdocs/compta/bank/class/api_bankaccounts.class.php

@@ -345,6 +345,12 @@ class BankAccounts extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$account->array_options[$index] = $this->_checkValForAPI($field, $val, $account);
+				}
+				continue;
+			}
 			$account->$field = $this->_checkValForAPI($field, $value, $account);
 		}
 

+ 8 - 8
htdocs/compta/facture/card-rec.php

@@ -93,7 +93,10 @@ $pagenext = $page + 1;
 $object = new FactureRec($db);
 if (($id > 0 || $ref) && $action != 'create' && $action != 'add') {
 	$ret = $object->fetch($id, $ref);
-	if (!$ret) {
+	if ($ret < 0) {
+		dol_print_error($db, $object->error, $object->errors);
+		exit;
+	} elseif (! $ret) {
 		setEventMessages($langs->trans("ErrorRecordNotFound"), null, 'errors');
 	}
 }
@@ -1344,14 +1347,10 @@ if ($action == 'create') {
 		}
 		print '</tr></table>';
 		print '</td><td>';
-		if ($object->type != Facture::TYPE_CREDIT_NOTE) {
-			if ($action == 'editconditions') {
-				$form->form_conditions_reglement($_SERVER['PHP_SELF'].'?facid='.$object->id, $object->cond_reglement_id, 'cond_reglement_id');
-			} else {
-				$form->form_conditions_reglement($_SERVER['PHP_SELF'].'?facid='.$object->id, $object->cond_reglement_id, 'none');
-			}
+		if ($action == 'editconditions') {
+			$form->form_conditions_reglement($_SERVER['PHP_SELF'].'?facid='.$object->id, $object->cond_reglement_id, 'cond_reglement_id');
 		} else {
-			print '&nbsp;';
+			$form->form_conditions_reglement($_SERVER['PHP_SELF'].'?facid='.$object->id, $object->cond_reglement_id, 'none');
 		}
 		print '</td></tr>';
 
@@ -1781,6 +1780,7 @@ if ($action == 'create') {
 		// List of actions on element
 		include_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php';
 		$formactions = new FormActions($db);
+		$morehtmlcenter = '';
 		$somethingshown = $formactions->showactions($object, $object->element, (is_object($object->thirdparty) ? $object->thirdparty->id : 0), 1, '', $MAXEVENT, '', $morehtmlcenter);
 
 		print '</div>';

+ 3 - 3
htdocs/compta/facture/card.php

@@ -3193,9 +3193,9 @@ if ($action == 'create') {
 				$expesrc->fetch_optionals();
 				$object->array_options = $expesrc->array_options;
 			} else {
-				$cond_reglement_id 	= (!empty($objectsrc->cond_reglement_id) ? $objectsrc->cond_reglement_id : (!empty($soc->cond_reglement_id) ? $soc->cond_reglement_id : 0));
-				$mode_reglement_id 	= (!empty($objectsrc->mode_reglement_id) ? $objectsrc->mode_reglement_id : (!empty($soc->mode_reglement_id) ? $soc->mode_reglement_id : 0));
-				$fk_account         = (!empty($objectsrc->fk_account) ? $objectsrc->fk_account : (!empty($soc->fk_account) ? $soc->fk_account : 0));
+				$cond_reglement_id 	= (!empty($objectsrc->cond_reglement_id) ? $objectsrc->cond_reglement_id : (!empty($soc->cond_reglement_id) ? $soc->cond_reglement_id : (!empty($cond_reglement_id) ? $cond_reglement_id : 0)));
+				$mode_reglement_id 	= (!empty($objectsrc->mode_reglement_id) ? $objectsrc->mode_reglement_id : (!empty($soc->mode_reglement_id) ? $soc->mode_reglement_id : (!empty($mode_reglement_id) ? $mode_reglement_id : 0)));
+				$fk_account         = (!empty($objectsrc->fk_account) ? $objectsrc->fk_account : (!empty($soc->fk_account) ? $soc->fk_account : (!empty($fk_account) ? $fk_account : 0)));
 				//$remise_percent 	= (!empty($objectsrc->remise_percent) ? $objectsrc->remise_percent : (!empty($soc->remise_percent) ? $soc->remise_percent : 0));
 				//$remise_absolue 	= (!empty($objectsrc->remise_absolue) ? $objectsrc->remise_absolue : (!empty($soc->remise_absolue) ? $soc->remise_absolue : 0));
 

+ 10 - 4
htdocs/compta/facture/class/api_invoices.class.php

@@ -633,7 +633,13 @@ class Invoices extends DolibarrApi
 				continue;
 			}
 
-			$this->invoice->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->invoice->array_options[$index] = $this->_checkValForAPI($field, $val, $this->invoice);
+				}
+				continue;
+			}
+			$this->invoice->$field = $this->_checkValForAPI($field, $value, $this->invoice);
 		}
 
 		// update bank account
@@ -643,11 +649,11 @@ class Invoices extends DolibarrApi
 			}
 		}
 
-		if ($this->invoice->update(DolibarrApiAccess::$user)) {
+		if ($this->invoice->update(DolibarrApiAccess::$user) > 0) {
 			return $this->get($id);
+		} else {
+			throw new RestException(500, $this->invoice->error);
 		}
-
-		return false;
 	}
 
 	/**

+ 8 - 0
htdocs/compta/facture/class/facture-rec.class.php

@@ -136,6 +136,13 @@ class FactureRec extends CommonInvoice
 	public $cond_reglement_code; // Code in llx_c_paiement
 	public $mode_reglement_code; // Code in llx_c_paiement
 
+	public $fk_multicurrency;
+	public $multicurrency_code;
+	public $multicurrency_tx;
+	public $multicurrency_total_ht;
+	public $multicurrency_total_tva;
+	public $multicurrency_total_ttc;
+
 	public $suspended; // status
 
 	public $auto_validate; // 0 to create in draft, 1 to create and validate the new invoice
@@ -1415,6 +1422,7 @@ class FactureRec extends CommonInvoice
 					$nb_create++;
 					$this->output .= $langs->trans("InvoiceGeneratedFromTemplate", $facture->ref, $facturerec->ref)."\n";
 				} else {
+					$this->output .= $langs->trans("InvoiceGeneratedFromTemplateError", $facture->ref, $facturerec->ref, $this->error)."\n";
 					$this->db->rollback("createRecurringInvoices Process invoice template id=".$facturerec->id.", ref=".$facturerec->ref);
 				}
 

+ 28 - 18
htdocs/compta/facture/class/facture.class.php

@@ -75,6 +75,11 @@ class Facture extends CommonInvoice
 	 */
 	public $table_element_line = 'facturedet';
 
+	/**
+	 * @var string Name of class line
+	 */
+	public $class_element_line = 'FactureLigne';
+
 	/**
 	 * @var string Fieldname with ID of parent key if this field has a parent
 	 */
@@ -974,11 +979,6 @@ class Facture extends CommonInvoice
 				$fk_parent_line = 0;
 
 				foreach ($_facrec->lines as $i => $val) {
-					if ($_facrec->lines[$i]->fk_product) {
-						$prod = new Product($this->db);
-						$res = $prod->fetch($_facrec->lines[$i]->fk_product);
-					}
-
 					// Reset fk_parent_line for no child products and special product
 					if (($_facrec->lines[$i]->product_type != 9 && empty($_facrec->lines[$i]->fk_parent_line)) || $_facrec->lines[$i]->product_type == 9) {
 						$fk_parent_line = 0;
@@ -986,6 +986,11 @@ class Facture extends CommonInvoice
 
 					// For line from template invoice, we use data from template invoice
 					/*
+					if ($_facrec->lines[$i]->fk_product) {
+						$prod = new Product($this->db);
+						$res = $prod->fetch($_facrec->lines[$i]->fk_product);
+					}
+
 					$tva_tx = get_default_tva($mysoc,$soc,$prod->id);
 					$tva_npr = get_default_npr($mysoc,$soc,$prod->id);
 					if (empty($tva_tx)) $tva_npr=0;
@@ -2451,9 +2456,6 @@ class Facture extends CommonInvoice
 		if (empty($this->type)) {
 			$this->type = self::TYPE_STANDARD;
 		}
-		if (isset($this->subtype)) {
-			$this->subtype = trim($this->subtype);
-		}
 		if (isset($this->ref)) {
 			$this->ref = trim($this->ref);
 		}
@@ -2490,6 +2492,9 @@ class Facture extends CommonInvoice
 		if (isset($this->retained_warranty)) {
 			$this->retained_warranty = (float) $this->retained_warranty;
 		}
+		if (!isset($this->fk_user_author) && isset($this->user_author) ) {
+			$this->fk_user_author = $this->user_author;
+		}
 
 
 		// Check parameters
@@ -2517,8 +2522,8 @@ class Facture extends CommonInvoice
 		$sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
 		$sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
 		$sql .= " revenuestamp=".((isset($this->revenuestamp) && $this->revenuestamp != '') ? $this->db->escape($this->revenuestamp) : "null").",";
-		$sql .= " fk_statut=".(isset($this->status) ? $this->db->escape($this->status) : "null").",";
-		$sql .= " fk_user_author=".(isset($this->user_author) ? $this->db->escape($this->user_author) : "null").",";
+		$sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : "null").",";
+		$sql .= " fk_user_author=".(isset($this->fk_user_author) ? ((int) $this->fk_user_author) : "null").",";
 		$sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->db->escape($this->fk_user_valid) : "null").",";
 		$sql .= " fk_facture_source=".(isset($this->fk_facture_source) ? $this->db->escape($this->fk_facture_source) : "null").",";
 		$sql .= " fk_projet=".(isset($this->fk_project) ? $this->db->escape($this->fk_project) : "null").",";
@@ -4207,6 +4212,11 @@ class Facture extends CommonInvoice
 				$rangmax = $this->line_max($fk_parent_line);
 				$this->line->rang = $rangmax + 1;
 			}
+			$apply_abs_price_on_credit_note=false;
+			if ($this->type == self::TYPE_CREDIT_NOTE  && !getDolGlobalInt('FACTURE_ENABLE_NEGATIVE_LINES') && !getDolGlobalInt('INVOICE_KEEP_DISCOUNT_LINES_AS_IN_ORIGIN')) {
+				$apply_abs_price_on_credit_note = true;
+			}
+
 
 			$this->line->id = $rowid;
 			$this->line->rowid = $rowid;
@@ -4223,14 +4233,14 @@ class Facture extends CommonInvoice
 			$this->line->localtax2_type		= empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
 
 			$this->line->remise_percent		= $remise_percent;
-			$this->line->subprice			= ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
+			$this->line->subprice			= ($apply_abs_price_on_credit_note ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
 			$this->line->date_start = $date_start;
 			$this->line->date_end			= $date_end;
-			$this->line->total_ht			= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ht) : $total_ht); // For credit note and if qty is negative, total is negative
-			$this->line->total_tva			= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_tva) : $total_tva);
+			$this->line->total_ht			= (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($total_ht) : $total_ht); // For credit note and if qty is negative, total is negative
+			$this->line->total_tva			= (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($total_tva) : $total_tva);
 			$this->line->total_localtax1	= $total_localtax1;
 			$this->line->total_localtax2	= $total_localtax2;
-			$this->line->total_ttc			= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ttc) : $total_ttc);
+			$this->line->total_ttc			= (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($total_ttc) : $total_ttc);
 			$this->line->info_bits			= $info_bits;
 			$this->line->special_code		= $special_code;
 			$this->line->product_type		= $type;
@@ -4243,10 +4253,10 @@ class Facture extends CommonInvoice
 			$this->line->pa_ht = $pa_ht;
 
 			// Multicurrency
-			$this->line->multicurrency_subprice		= ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht_devise) : $pu_ht_devise); // For credit note, unit price always negative, always positive otherwise
-			$this->line->multicurrency_total_ht 	= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_ht) : $multicurrency_total_ht); // For credit note and if qty is negative, total is negative
-			$this->line->multicurrency_total_tva 	= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_tva) : $multicurrency_total_tva);
-			$this->line->multicurrency_total_ttc 	= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_ttc) : $multicurrency_total_ttc);
+			$this->line->multicurrency_subprice		= ($apply_abs_price_on_credit_note ? -abs($pu_ht_devise) : $pu_ht_devise); // For credit note, unit price always negative, always positive otherwise
+			$this->line->multicurrency_total_ht 	= (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($multicurrency_total_ht) : $multicurrency_total_ht); // For credit note and if qty is negative, total is negative
+			$this->line->multicurrency_total_tva 	= (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($multicurrency_total_tva) : $multicurrency_total_tva);
+			$this->line->multicurrency_total_ttc 	= (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($multicurrency_total_ttc) : $multicurrency_total_ttc);
 
 			if (is_array($array_options) && count($array_options) > 0) {
 				// We replace values in this->line->array_options only for entries defined into $array_options

+ 8 - 7
htdocs/compta/journal/purchasesjournal.php

@@ -1,11 +1,11 @@
 <?php
-/* Copyright (C) 2007-2010  Laurent Destailleur     <eldy@users.sourceforge.net>
- * Copyright (C) 2007-2010  Jean Heimburger         <jean@tiaris.info>
- * Copyright (C) 2011-2014  Juanjo Menent           <jmenent@2byte.es>
- * Copyright (C) 2012       Regis Houssin           <regis.houssin@inodbox.com>
- * Copyright (C) 2011-2012  Alexandre spangaro      <aspangaro@open-dsi.fr>
- * Copyright (C) 2013       Marcos García           <marcosgdf@gmail.com>
- * Copyright (C) 2018       Frédéric France         <frederic.france@netlogic.fr>
+/* Copyright (C) 2007-2010	Laurent Destailleur		<eldy@users.sourceforge.net>
+ * Copyright (C) 2007-2010	Jean Heimburger			<jean@tiaris.info>
+ * Copyright (C) 2011-2014	Juanjo Menent			<jmenent@2byte.es>
+ * Copyright (C) 2012		Regis Houssin			<regis.houssin@inodbox.com>
+ * Copyright (C) 2011-2025	Alexandre spangaro		<alexandre@inovea-conseil.com>
+ * Copyright (C) 2013		Marcos García			<marcosgdf@gmail.com>
+ * Copyright (C) 2018		Frédéric France			<frederic.france@free.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -120,6 +120,7 @@ $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = fd.fk_product";
 $sql .= " JOIN ".MAIN_DB_PREFIX."facture_fourn as f ON f.rowid = fd.fk_facture_fourn";
 $sql .= " JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc";
 $sql .= " WHERE f.fk_statut > 0 AND f.entity IN (".getEntity('invoice').")";
+$sql .= " AND ct.entity IN (".getEntity('invoice').")";
 if (getDolGlobalString('FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS')) {
 	$sql .= " AND f.type IN (0,1,2)";
 } else {

+ 10 - 9
htdocs/compta/journal/sellsjournal.php

@@ -1,13 +1,13 @@
 <?php
-/* Copyright (C) 2007-2010  Laurent Destailleur     <eldy@users.sourceforge.net>
- * Copyright (C) 2007-2010  Jean Heimburger         <jean@tiaris.info>
- * Copyright (C) 2011-2014  Juanjo Menent           <jmenent@2byte.es>
- * Copyright (C) 2012       Regis Houssin           <regis.houssin@inodbox.com>
- * Copyright (C) 2011-2012  Alexandre Spangaro      <aspangaro@open-dsi.fr>
- * Copyright (C) 2012       Cédric Salvador         <csalvador@gpcsolutions.fr>
- * Copyright (C) 2013       Marcos García           <marcosgdf@gmail.com>
- * Copyright (C) 2014       Raphaël Doursenaud      <rdoursenaud@gpcsolutions.fr>
- * Copyright (C) 2018       Frédéric France         <frederic.france@netlogic.fr>
+/* Copyright (C) 2007-2010	Laurent Destailleur		<eldy@users.sourceforge.net>
+ * Copyright (C) 2007-2010	Jean Heimburger			<jean@tiaris.info>
+ * Copyright (C) 2011-2014	Juanjo Menent			<jmenent@2byte.es>
+ * Copyright (C) 2012		Regis Houssin			<regis.houssin@inodbox.com>
+ * Copyright (C) 2011-2025	Alexandre Spangaro		<alexandre@inovea-conseil.com>
+ * Copyright (C) 2012		Cédric Salvador			<csalvador@gpcsolutions.fr>
+ * Copyright (C) 2013		Marcos García			<marcosgdf@gmail.com>
+ * Copyright (C) 2014		Raphaël Doursenaud		<rdoursenaud@gpcsolutions.fr>
+ * Copyright (C) 2018		Frédéric France			<frederic.france@free.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -130,6 +130,7 @@ $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON f.rowid = fd.fk_facture";
 $sql .= " JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc";
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_tva ct ON fd.tva_tx = ct.taux AND fd.info_bits = ct.recuperableonly AND ct.fk_pays = ".((int) $idpays);
 $sql .= " WHERE f.entity IN (".getEntity('invoice').")";
+$sql .= " AND ct.entity IN (".getEntity('invoice').")";
 $sql .= " AND f.fk_statut > 0";
 if (getDolGlobalString('FACTURE_DEPOSITS_ARE_JUST_PAYMENTS')) {
 	$sql .= " AND f.type IN (".Facture::TYPE_STANDARD.",".Facture::TYPE_REPLACEMENT.",".Facture::TYPE_CREDIT_NOTE.",".Facture::TYPE_SITUATION.")";

+ 1 - 1
htdocs/compta/localtax/index.php

@@ -446,7 +446,7 @@ while ((($y < $yend) || ($y == $yend && $m <= $mend)) && $mcursor < 1000) {	// $
 
 
 	print '<tr class="oddeven">';
-	print '<td class="nowrap"><a href="'.DOL_URL_ROOT.'/compta/localtax/quadri_detail.php?leftmenu=tax_vat&month='.$m.'&year='.$y.'">'.dol_print_date(dol_mktime(0, 0, 0, $m, 1, $y), "%b %Y").'</a></td>';
+	print '<td class="nowrap"><a href="'.DOL_URL_ROOT.'/compta/localtax/quadri_detail.php?leftmenu=tax_vat&month='.$m.'&year='.$y.'&localTaxType='.$localTaxType.'">'.dol_print_date(dol_mktime(0, 0, 0, (int) $m, 1, (int) $y), "%b %Y").'</a></td>';
 
 	$x_coll_sum = 0;
 	foreach (array_keys($x_coll) as $rate) {

+ 1 - 1
htdocs/compta/paiement/cheque/card.php

@@ -544,7 +544,7 @@ if ($action == 'new') {
 		if (count($lines[$bid])) {
 			foreach ($lines[$bid] as $lid => $value) {
 				print '<tr class="oddeven">';
-				print '<td>'.dol_print_date($value["date"], 'day').'</td>';
+				print '<td>'.dol_print_date($value["paymentdate"], 'day').'</td>';
 				print '<td>'.$value["numero"]."</td>\n";
 				print '<td>'.$value["emetteur"]."</td>\n";
 				print '<td>'.$value["banque"]."</td>\n";

+ 7 - 1
htdocs/contrat/class/api_contracts.class.php

@@ -519,7 +519,13 @@ class Contracts extends DolibarrApi
 				continue;
 			}
 
-			$this->contract->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->contract->array_options[$index] = $this->_checkValForAPI($field, $val, $this->contract);
+				}
+				continue;
+			}
+			$this->contract->$field = $this->_checkValForAPI($field, $value, $this->contract);
 		}
 
 		if ($this->contract->update(DolibarrApiAccess::$user) > 0) {

+ 2 - 4
htdocs/contrat/class/contrat.class.php

@@ -3291,7 +3291,7 @@ class ContratLigne extends CommonObjectLine
 		$sql .= " t.label,"; // This field is not used. Only label of product
 		$sql .= " p.ref as product_ref,";
 		$sql .= " p.label as product_label,";
-		$sql .= " p.description as product_desc,";
+		$sql .= " p.description as product_description,";
 		$sql .= " p.fk_product_type as product_type,";
 		$sql .= " t.description,";
 		$sql .= " t.date_commande,";
@@ -3819,15 +3819,13 @@ class ContratLigne extends CommonObjectLine
 
 		$error = 0;
 
-		// statut actif : 4
-
 		$this->db->begin();
 
 		$sql = "UPDATE ".MAIN_DB_PREFIX."contratdet SET statut = ".((int) ContratLigne::STATUS_CLOSED).",";
 		$sql .= " date_cloture = '".$this->db->idate($date_end_real)."',";
 		$sql .= " fk_user_cloture = ".((int) $user->id).",";
 		$sql .= " commentaire = '".$this->db->escape($comment)."'";
-		$sql .= " WHERE rowid = ".((int) $this->id)." AND statut = ".((int) ContratLigne::STATUS_OPEN);
+		$sql .= " WHERE rowid = ".((int) $this->id)." AND statut <> ".((int) ContratLigne::STATUS_CLOSED);
 
 		$resql = $this->db->query($sql);
 		if ($resql) {

+ 11 - 0
htdocs/contrat/services_list.php

@@ -376,6 +376,13 @@ if (!empty($filter_opcloture) && $filter_opcloture == ' BETWEEN ') {
 // Add where from extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
 
+// 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;
 
 // Count total nb of records
@@ -488,6 +495,10 @@ if ($filter_datecloture_start != '') {
 }
 // Add $param from extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php';
+// 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;
 
 // List of mass actions available
 $arrayofmassactions = array(

+ 29 - 12
htdocs/core/actions_massactions.inc.php

@@ -1291,18 +1291,35 @@ if (!$error && ($action == 'updateprice' && $confirm == 'yes') && $permissiontoa
 				$result = $object->fetch($toselectid);
 				//var_dump($contcats);exit;
 				if ($result > 0) {
-					if ($obj->price_base_type == 'TTC') {
-						$newprice = $object->price_ttc * (100 + $pricepercentage) / 100;
-						$minprice = $object->price_min_ttc;
-					} else {
-						$newprice = $object->price * (100 + $pricepercentage) / 100;
-						$minprice = $object->price_min;
-					}
-					$res = $object->updatePrice($newprice, $obj->price_base_type, $user, $object->tva_tx, $minprice, 0, $object->tva_npr, 0, 0, array(), $object->default_vat_code);
-					if ($res > 0) {
-						$nbok++;
-					} else {
-						setEventMessages($object->error, $object->errors, 'errors');
+					if (getDolGlobalString('PRODUCT_PRICE_UNIQ')
+							|| getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
+						if ($object->price_base_type == 'TTC') {
+							$newprice = $object->price_ttc * (100 + $pricepercentage) / 100;
+							$minprice = $object->price_min_ttc;
+						} else {
+							$newprice = $object->price * (100 + $pricepercentage) / 100;
+							$minprice = $object->price_min;
+						}
+						$res = $object->updatePrice($newprice, $object->price_base_type, $user, $object->tva_tx, $minprice, 0, $object->tva_npr, 0, 0, array(), $object->default_vat_code);
+						if ($res > 0) {
+							$nbok++;
+						} else {
+							setEventMessages($object->error, $object->errors, 'errors');
+						}
+					} elseif (getDolGlobalString('PRODUIT_MULTIPRICES')) {
+						for ($level = 1; $level <= getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT'); $level++) {
+							if ($object->price_base_type == 'TTC') {
+								$newprice = $object->multiprices_ttc[$level] * (100 + $pricepercentage) / 100;
+								$minprice = $object->multiprices_min_ttc[$level];
+							} else {
+								$newprice = $object->multiprices[$level] * (100 + $pricepercentage) / 100;
+								$minprice = $object->multiprices_min[$level];
+							}
+							$res = $object->updatePrice($newprice, $object->price_base_type, $user, $object->tva_tx, $minprice, $level, $object->tva_npr, 0, 0, array(), $object->default_vat_code);
+							if ($res > 0) {
+								$nbok++;
+							}
+						}
 					}
 				} else {
 					setEventMessages($object->error, $object->errors, 'errors');

+ 9 - 0
htdocs/core/actions_sendmails.inc.php

@@ -203,6 +203,15 @@ if (($action == 'send' || $action == 'relance') && !GETPOST('addfile') && !GETPO
 				} elseif ($val == 'contact') { // Key selected means current contact
 					$tmparray[] = dol_string_nospecial($contact->getFullName($langs), ' ', array(",")).' <'.$contact->email.'>';
 					$sendtoid[] = $contact->id;
+				} elseif ($val && $object->element == 'project' && empty($object->socid)) {	// $val is the Id of a contact
+					$contact = new Contact($db);
+					$ret = $contact->fetch((int) $val);
+					if ($ret > 0 && !empty($contact->socid)) {
+						$thirdparty = new Societe($db);
+						$thirdparty->fetch($contact->socid);
+						$tmparray[] = $thirdparty->contact_get_property((int) $val, 'email');
+						$sendtoid[] = ((int) $val);
+					}
 				} elseif ($val) {	// $val is the Id of a contact
 					$tmparray[] = $thirdparty->contact_get_property((int) $val, 'email');
 					$sendtoid[] = ((int) $val);

+ 22 - 4
htdocs/core/ajax/ajaxtooltip.php

@@ -34,23 +34,40 @@ if (!defined('NOREQUIREHTML')) {
 if (!defined('NOREQUIREAJAX')) {
 	define('NOREQUIREAJAX', '1');
 }
+if (!defined('NOHEADERNOFOOTER')) {
+	define('NOHEADERNOFOOTER', '1');
+}
+
 include '../../main.inc.php';
 include_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
 
+/**
+ * @var Conf $conf
+ * @var DoliDB $db
+ * @var HookManager $hookmanager
+ * @var Translate $langs
+ * @var User $user
+ */
 
 $id = GETPOST('id', 'aZ09');
 $objecttype = GETPOST('objecttype', 'aZ09arobase');	// 'module' or 'myobject@mymodule', 'mymodule_myobject'
 
 $params = array('fromajaxtooltip' => 1);
 if (GETPOSTISSET('infologin')) {
-	$params['infologin'] = GETPOST('infologin', 'int');
+	$params['infologin'] = GETPOSTINT('infologin');
 }
 if (GETPOSTISSET('option')) {
 	$params['option'] = GETPOST('option', 'restricthtml');
 }
-
+$element_ref = '';
+if (is_numeric($id)) {
+	$id = (int) $id;
+} else {
+	$element_ref = $id;
+	$id = 0;
+}
 // Load object according to $element
-$object = fetchObjectByElement($id, $objecttype);
+$object = fetchObjectByElement($id, $objecttype, $element_ref);
 if (empty($object->element)) {
 	httponly_accessforbidden('Failed to get object with fetchObjectByElement(id='.$id.', objecttype='.$objecttype.')');
 }
@@ -78,10 +95,11 @@ top_httphead();
 $html = '';
 
 if (is_object($object)) {
+	'@phan-var-force CommonObject $object';
 	if ($object->id > 0 || !empty($object->ref)) {
 		/** @var CommonObject $object */
 		$html = $object->getTooltipContent($params);
-	} elseif ($res == 0) {
+	} elseif ($id > 0) {
 		$html = $langs->trans('Deleted');
 	}
 	unset($object);

+ 3 - 3
htdocs/core/boxes/box_graph_product_distribution.php

@@ -165,7 +165,7 @@ class box_graph_product_distribution extends ModeleBoxes
 				if (empty($data2)) {
 					$showpointvalue = 0;
 					$nocolor = 1;
-					$data2 = array(array(0=>$langs->trans("None"), 1=>1));
+					$data2 = array(array(0=>$langs->transnoentitiesnoconv("None"), 1=>1));
 				}
 
 				$filenamenb = $dir."/prodserforpropal-".$year.".png";
@@ -228,7 +228,7 @@ class box_graph_product_distribution extends ModeleBoxes
 				if (empty($data3)) {
 					$showpointvalue = 0;
 					$nocolor = 1;
-					$data3 = array(array(0=>$langs->trans("None"), 1=>1));
+					$data3 = array(array(0=>$langs->transnoentitiesnoconv("None"), 1=>1));
 				}
 
 				$filenamenb = $dir."/prodserfororder-".$year.".png";
@@ -293,7 +293,7 @@ class box_graph_product_distribution extends ModeleBoxes
 				if (empty($data1)) {
 					$showpointvalue = 0;
 					$nocolor = 1;
-					$data1 = array(array(0=>$langs->trans("None"), 1=>1));
+					$data1 = array(array(0=>$langs->transnoentitiesnoconv("None"), 1=>1));
 				}
 				$filenamenb = $dir."/prodserforinvoice-".$year.".png";
 				$fileurlnb = DOL_URL_ROOT.'/viewimage.php?modulepart=productstats&amp;file=prodserforinvoice-'.$year.'.png';

+ 1 - 1
htdocs/core/class/CMailFile.class.php

@@ -694,7 +694,7 @@ class CMailFile
 				$this->error = "Error in hook maildao sendMail ".$reshook;
 				dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
 
-				return $reshook;
+				return false;
 			}
 			if ($reshook == 1) {	// Hook replace standard code
 				return true;

+ 11 - 7
htdocs/core/class/commonobject.class.php

@@ -4184,7 +4184,11 @@ abstract class CommonObject
 						$module = 'adherent';
 					} elseif ($objecttype == 'contact') {
 						$module = 'societe';
+					} elseif ($objecttype == 'action') {
+						$module = 'agenda';
+						$subelement = 'actionComm';
 					}
+
 					// Set classfile
 					$classfile = strtolower($subelement);
 					$classname = ucfirst($subelement);
@@ -5374,16 +5378,12 @@ abstract class CommonObject
 			}
 			$this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
 		} elseif (!empty($line->fk_product)) {
-			$productstatic = new Product($this->db);
-			$productstatic->id = $line->fk_product;
-			$productstatic->ref = $line->ref;
-			$productstatic->type = $line->fk_product_type;
-			if (empty($productstatic->ref)) {
+			if (empty($line->product)) {
 				$line->fetch_product();
-				$productstatic = $line->product;
 			}
+			$productstatic = $line->product;
 
-			$this->tpl['label'] .= $productstatic->getNomUrl(1);
+			$this->tpl['label'] .= (is_object($productstatic) ? $productstatic->getNomUrl(1) : $line->ref);
 			$this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
 			// Dates
 			if ($line->product_type == 1 && ($date_start || $date_end)) {
@@ -8601,6 +8601,10 @@ abstract class CommonObject
 					if (($mode == 'create') && abs($visibility) != 1 && abs($visibility) != 3) {
 						continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
 					} elseif (($mode == 'edit') && abs($visibility) != 1 && abs($visibility) != 3 && abs($visibility) != 4) {
+						// We need to make sure, that the values of hidden extrafields are also part of $_POST. Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
+						$ef_name = 'options_' . $key;
+						$ef_value = $this->array_options[$ef_name];
+						$out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . $ef_value . '" />' . "\n";
 						continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
 					} elseif ($mode == 'view') {
 						if ((getDolGlobalInt('MMI_DOCUMENT_LINE_EXTRAFIELDS_ALTVIEW') && (abs($visibility) == 3 || $visibility<0))) {

+ 2 - 2
htdocs/core/class/dolgraph.class.php

@@ -1114,8 +1114,8 @@ class DolGraph
 					$values[$x] = (is_numeric($tmpvalue) ? $tmpvalue : null);
 					$arrayofgroupslegend[$i] = array(
 						'stacknum' => $tmpykey[1],
-						'legend' => $this->Legend[$tmpykey[1]],
-						'legendwithgroup' => $this->Legend[$tmpykey[1]] . ' - ' . $tmpykey[2]
+						'legend' => $this->Legend[$tmpykey[1]] ?? '',
+						'legendwithgroup' => ($this->Legend[$tmpykey[1]] ?? '') . ' - ' . $tmpykey[2]
 					);
 				} else {
 					$tmpvalue = (array_key_exists('y_' . $i, $valarray) ? $valarray['y_' . $i] : $valarray[$i + 1]);

+ 5 - 1
htdocs/core/class/extrafields.class.php

@@ -11,6 +11,7 @@
  * Copyright (C) 2017       Nicolas ZABOURI         <info@inovea-conseil.com>
  * Copyright (C) 2018-2022  Frédéric France         <frederic.france@netlogic.fr>
  * Copyright (C) 2022 		Antonin MARCHAL         <antonin@letempledujeu.fr>
+ * Copyright (C) 2024		Joachim Kueter			<git-jk@bloxera.com>
  *
  * 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
@@ -2415,10 +2416,13 @@ class ExtraFields
 					}
 					$value_key = dol_htmlcleanlastbr(GETPOST($keysuffix."options_".$key.$keyprefix, 'restricthtml'));
 				} else {
-					if (!GETPOST($keysuffix."options_".$key.$keyprefix)) {
+					if (!GETPOSTISSET($keysuffix."options_".$key.$keyprefix)) {
 						continue; // Value was not provided, we should not set it.
 					}
 					$value_key = GETPOST($keysuffix."options_".$key.$keyprefix);
+					if ($value_key === '') {
+						$value_key = null;
+					}
 				}
 
 				$array_options[$keysuffix."options_".$key] = $value_key; // No keyprefix here. keyprefix is used only for read.

+ 17 - 0
htdocs/core/class/fileupload.class.php

@@ -57,8 +57,11 @@ class FileUpload
 		$this->element = $element;
 
 		$pathname = str_replace('/class', '', $element_prop['classpath']);
+
+
 		$filename = dol_sanitizeFileName($element_prop['classfile']);
 		$dir_output = dol_sanitizePathName($element_prop['dir_output']);
+		$savingDocMask = '';
 
 		//print 'fileupload.class.php: element='.$element.' pathname='.$pathname.' filename='.$filename.' dir_output='.$dir_output."\n";
 
@@ -74,6 +77,11 @@ class FileUpload
 
 			$object_ref = dol_sanitizeFileName($object->ref);
 
+			// add object reference as file name prefix if const MAIN_DISABLE_SUGGEST_REF_AS_PREFIX is not enabled
+			if (!getDolGlobalInt('MAIN_DISABLE_SUGGEST_REF_AS_PREFIX')) {
+				$savingDocMask = $object_ref.'-__file__';
+			}
+
 			// Special cases to forge $object_ref used to forge $upload_dir
 			if ($element == 'invoice_supplier') {
 				$object_ref = get_exdir($object->id, 2, 0, 0, $object, 'invoice_supplier').$object_ref;
@@ -99,6 +107,7 @@ class FileUpload
 			'script_url' => $_SERVER['PHP_SELF'],
 			'upload_dir' => $dir_output.'/'.$object_ref.'/',
 			'upload_url' => DOL_URL_ROOT.'/document.php?modulepart='.$element.'&attachment=1&file=/'.$object_ref.'/',
+			'saving_doc_mask' => $savingDocMask,
 			'param_name' => 'files',
 			// Set the following option to 'POST', if your server does not support
 			// DELETE requests. This is a parameter sent to the client:
@@ -423,6 +432,14 @@ class FileUpload
 
 		if ($validate) {
 			if (dol_mkdir($this->options['upload_dir']) >= 0) {
+				// add object reference as file name prefix if const MAIN_DISABLE_SUGGEST_REF_AS_PREFIX is not enabled
+				$fileNameWithoutExt = preg_replace('/\.[^\.]+$/', '', $file->name);
+				$savingDocMask = $this->options['saving_doc_mask'];
+				if ($savingDocMask && strpos($savingDocMask, $fileNameWithoutExt) !== 0) {
+					$fileNameWithPrefix = preg_replace('/__file__/', $file->name, $savingDocMask);
+					$file->name = $fileNameWithPrefix;
+				}
+
 				$file_path = dol_sanitizePathName($this->options['upload_dir']).dol_sanitizeFileName($file->name);
 				$append_file = !$this->options['discard_aborted_uploads'] && dol_is_file($file_path) && $file->size > dol_filesize($file_path);
 

+ 8 - 11
htdocs/core/class/fiscalyear.class.php

@@ -1,7 +1,7 @@
 <?php
-/* Copyright (C) 2014-2020  Alexandre Spangaro  <aspangaro@open-dsi.fr>
- * Copyright (C) 2020       OScss-Shop          <support@oscss-shop.fr>
- * Copyright (C) 2023       Frédéric France     <frederic.france@netlogic.fr>
+/* Copyright (C) 2014-2025	Alexandre Spangaro			<alexandre@inovea-conseil.com>
+ * Copyright (C) 2020       OScss-Shop					<support@oscss-shop.fr>
+ * Copyright (C) 2023       Frédéric France				<frederic.france@free.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -122,9 +122,6 @@ class Fiscalyear extends CommonObject
 	public function __construct(DoliDB $db)
 	{
 		$this->db = $db;
-
-		$this->labelStatusShort = array(self::STATUS_OPEN => 'Opened', self::STATUS_CLOSED => 'Closed');
-		$this->labelStatus = array(self::STATUS_OPEN => 'Opened', self::STATUS_CLOSED => 'Closed');
 	}
 
 	/**
@@ -411,11 +408,11 @@ class Fiscalyear extends CommonObject
 		// phpcs:enable
 		if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
 			global $langs;
-			//$langs->load("mymodule@mymodule");
-			$this->labelStatus[self::STATUS_OPEN] = $langs->transnoentitiesnoconv('Draft');
-			$this->labelStatus[self::STATUS_CLOSED] = $langs->transnoentitiesnoconv('Enabled');
-			$this->labelStatusShort[self::STATUS_OPEN] = $langs->transnoentitiesnoconv('Enabled');
-			$this->labelStatusShort[self::STATUS_CLOSED] = $langs->transnoentitiesnoconv('Disabled');
+
+			$this->labelStatus[self::STATUS_OPEN] = $langs->transnoentitiesnoconv('FiscalYearOpened');
+			$this->labelStatus[self::STATUS_CLOSED] = $langs->transnoentitiesnoconv('FiscalYearClosed');
+			$this->labelStatusShort[self::STATUS_OPEN] = $langs->transnoentitiesnoconv('FiscalYearOpenedShort');
+			$this->labelStatusShort[self::STATUS_CLOSED] = $langs->transnoentitiesnoconv('FiscalYearClosedShort');
 		}
 
 		$statusType = 'status4';

+ 3 - 2
htdocs/core/class/html.form.class.php

@@ -4418,7 +4418,7 @@ class Form
 							if (depositPercent.length > 0) {
 								$("#' . $htmlname . '_deposit_percent_container").show().find("#' . $htmlname . '_deposit_percent").val(depositPercent);
 							} else {
-								$("#' . $htmlname . '_deposit_percent_container").hide();
+								$("#' . $htmlname . '_deposit_percent_container").hide().find("#' . $htmlname . '_deposit_percent").val(0);
 							}
 
 							return true;
@@ -8903,7 +8903,8 @@ class Form
 								escapeMarkup: function (markup) { return markup; }, 	// let our custom formatter work
 								// Specify format function for selected item
 								formatSelection: formatSelection,
-							 	templateSelection: formatSelection		/* For 4.0 */
+							 	templateSelection: formatSelection,		/* For 4.0 */
+							 	language: select2arrayoflanguage
 							});
 
 							/* Add also morecss to the css .select2 that is after the #htmlname, for component that are show dynamically after load, because select2 set

+ 2 - 2
htdocs/core/class/html.formaccounting.class.php

@@ -503,8 +503,6 @@ class FormAccounting extends Form
 				return -1;
 			}
 
-			ksort($aux_account);
-
 			$this->db->free($resql);
 
 			// Auxiliary user account
@@ -527,6 +525,8 @@ class FormAccounting extends Form
 			}
 			$this->db->free($resql);
 
+			ksort($aux_account);
+
 			if ($usecache) {
 				$this->options_cache[$usecache] = $aux_account;
 			}

+ 26 - 22
htdocs/core/class/html.formticket.class.php

@@ -333,7 +333,7 @@ class FormTicket
 
 		// Type of Ticket
 		print '<tr><td class="titlefield"><span class="fieldrequired"><label for="selecttype_code">'.$langs->trans("TicketTypeRequest").'</span></label></td><td>';
-		$this->selectTypesTickets((GETPOST('type_code', 'alpha') ? GETPOST('type_code', 'alpha') : $this->type_code), 'type_code', '', 2, 1, 0, 0, 'minwidth200');
+		$this->selectTypesTickets((GETPOST('type_code', 'alpha') ? GETPOST('type_code', 'alpha') : $this->type_code), 'type_code', '', 2, 'ifone', 0, 0, 'minwidth200');
 		print '</td></tr>';
 
 		// Group => Category
@@ -343,12 +343,12 @@ class FormTicket
 			$filter = 'public=1';
 		}
 		$selected = (GETPOST('category_code') ? GETPOST('category_code') : $this->category_code);
-		$this->selectGroupTickets($selected, 'category_code', $filter, 2, 1, 0, 0, 'minwidth200');
+		$this->selectGroupTickets($selected, 'category_code', $filter, 2, 'ifone', 0, 0, 'minwidth200');
 		print '</td></tr>';
 
 		// Severity => Priority
 		print '<tr><td><span class="fieldrequired"><label for="selectseverity_code">'.$langs->trans("TicketSeverity").'</span></label></td><td>';
-		$this->selectSeveritiesTickets((GETPOST('severity_code') ? GETPOST('severity_code') : $this->severity_code), 'severity_code', '', 2, 1);
+		$this->selectSeveritiesTickets((GETPOST('severity_code') ? GETPOST('severity_code') : $this->severity_code), 'severity_code', '', 2, 'ifone');
 		print '</td></tr>';
 
 		if (!empty($conf->knowledgemanagement->enabled)) {
@@ -702,8 +702,8 @@ class FormTicket
 	 *      @param  string|array	$selected		Id of preselected field or array of Ids
 	 *      @param  string			$htmlname		Nom de la zone select
 	 *      @param  string			$filtertype		To filter on field type in llx_c_ticket_type (array('code'=>xx,'label'=>zz))
-	 *      @param  int				$format			0=id+libelle, 1=code+code, 2=code+libelle, 3=id+code
-	 *      @param  int				$empty			1=peut etre vide, 0 sinon
+	 *      @param  int				$format			0=id+label, 1=code+code, 2=code+label, 3=id+code
+	 *      @param  int|string		$empty      	1 = can be empty or 'string' to show the string as the empty value, 0 = can't be empty, 'ifone' = can be empty but autoselected if there is one only
 	 *      @param  int				$noadmininfo	0=Add admin info, 1=Disable admin info
 	 *      @param  int				$maxlength		Max length of label
 	 *      @param	string			$morecss		More CSS
@@ -728,8 +728,8 @@ class FormTicket
 		$ticketstat->loadCacheTypesTickets();
 
 		print '<select id="select'.$htmlname.'" class="flat minwidth100'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.($multiselect ? '[]' : '').'"'.($multiselect ? ' multiple' : '').'>';
-		if ($empty) {
-			print '<option value="">&nbsp;</option>';
+		if ($empty && !$multiselect) {
+			print '<option value="">'.((is_numeric($empty) || $empty == 'ifone') ? '&nbsp;' : $empty).'</option>';
 		}
 
 		if (is_array($ticketstat->cache_types_tickets) && count($ticketstat->cache_types_tickets)) {
@@ -765,7 +765,9 @@ class FormTicket
 					print ' selected="selected"';
 				} elseif (in_array($id, $selected)) {
 					print ' selected="selected"';
-				} elseif ($arraytypes['use_default'] == "1" && empty($selected)) {
+				} elseif ($arraytypes['use_default'] == "1" && empty($selected) && !$multiselect) {
+					print ' selected="selected"';
+				} elseif (count($ticketstat->cache_types_tickets) == 1 && (!$empty || $empty == 'ifone')) {	// If only 1 choice, we autoselect it
 					print ' selected="selected"';
 				}
 
@@ -825,10 +827,10 @@ class FormTicket
 		$ticketstat = new Ticket($this->db);
 		$ticketstat->loadCacheCategoriesTickets($publicgroups ? 1 : -1);	// get list of active ticket groups
 
-		if ($use_multilevel <= 0) {
+		if ($use_multilevel <= 0) {	// Only one combo list to select the group of ticket (default)
 			print '<select id="select'.$htmlname.'" class="flat minwidth100'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'">';
 			if ($empty) {
-				print '<option value="">&nbsp;</option>';
+				print '<option value="">'.((is_numeric($empty) || $empty == 'ifone') ? '&nbsp;' : $empty).'</option>';
 			}
 
 			if (is_array($ticketstat->cache_category_tickets) && count($ticketstat->cache_category_tickets)) {
@@ -873,9 +875,9 @@ class FormTicket
 						print ' selected="selected"';
 					} elseif (isset($selected) && $selected == $id) {
 						print ' selected="selected"';
-					} elseif ($arraycategories['use_default'] == "1" && !$selected && !$empty) {
+					} elseif ($arraycategories['use_default'] == "1" && !$selected && (!$empty || $empty == 'ifone')) {
 						print ' selected="selected"';
-					} elseif (count($ticketstat->cache_category_tickets) == 1) {
+					} elseif (count($ticketstat->cache_category_tickets) == 1 && (!$empty || $empty == 'ifone')) {
 						print ' selected="selected"';
 					}
 
@@ -1147,14 +1149,14 @@ class FormTicket
 	/**
 	 *      Return html list of ticket severitys (priorities)
 	 *
-	 *      @param  string  $selected    Id severity pre-selected
-	 *      @param  string  $htmlname    Name of the select area
-	 *      @param  string  $filtertype  To filter on field type in llx_c_ticket_severity (array('code'=>xx,'label'=>zz))
-	 *      @param  int     $format      0 = id+label, 1 = code+code, 2 = code+label, 3 = id+code
-	 *      @param  int     $empty       1 = can be empty, 0 = or not
-	 *      @param  int     $noadmininfo 0 = add admin info, 1 = disable admin info
-	 *      @param  int     $maxlength   Max length of label
-	 *      @param  string  $morecss     More CSS
+	 *      @param  string  	$selected    	Id severity pre-selected
+	 *      @param  string  	$htmlname    	Name of the select area
+	 *      @param  string  	$filtertype  	To filter on field type in llx_c_ticket_severity (array('code'=>xx,'label'=>zz))
+	 *      @param  int     	$format      	0 = id+label, 1 = code+code, 2 = code+label, 3 = id+code
+	 *      @param  int|string	$empty      	1 = can be empty or 'string' to show the string as the empty value, 0 = can't be empty, 'ifone' = can be empty but autoselected if there is one only
+	 *      @param  int     	$noadmininfo 	0 = add admin info, 1 = disable admin info
+	 *      @param  int     	$maxlength   	Max length of label
+	 *      @param  string  	$morecss     	More CSS
 	 *      @return void
 	 */
 	public function selectSeveritiesTickets($selected = '', $htmlname = 'ticketseverity', $filtertype = '', $format = 0, $empty = 0, $noadmininfo = 0, $maxlength = 0, $morecss = '')
@@ -1175,7 +1177,7 @@ class FormTicket
 
 		print '<select id="select'.$htmlname.'" class="flat minwidth100'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'">';
 		if ($empty) {
-			print '<option value="">&nbsp;</option>';
+			print '<option value="">'.((is_numeric($empty) || $empty == 'ifone') ? '&nbsp;' : $empty).'</option>';
 		}
 
 		if (is_array($ticketstat->cache_severity_tickets) && count($ticketstat->cache_severity_tickets)) {
@@ -1211,7 +1213,9 @@ class FormTicket
 					print ' selected="selected"';
 				} elseif (isset($selected) && $selected == $id) {
 					print ' selected="selected"';
-				} elseif ($arrayseverities['use_default'] == "1" && empty($selected)) {
+				} elseif ($arrayseverities['use_default'] == "1" && empty($selected) && (!$empty || $empty == 'ifone')) {
+					print ' selected="selected"';
+				} elseif (count($ticketstat->cache_severity_tickets) == 1 && (!$empty || $empty == 'ifone')) {	// If only 1 choice, we autoselect it
 					print ' selected="selected"';
 				}
 

+ 2 - 2
htdocs/core/get_menudiv.php

@@ -273,7 +273,7 @@ if (!class_exists('MenuManager')) {
 	$menufound = 0;
 	$dirmenus = array_merge(array("/core/menus/"), (array) $conf->modules_parts['menus']);
 	foreach ($dirmenus as $dirmenu) {
-		$menufound = dol_include_once($dirmenu."standard/".$file_menu);
+		$menufound = dol_include_once($dirmenu."standard/".dol_sanitizeFileName($file_menu));
 		if ($menufound) {
 			break;
 		}
@@ -281,7 +281,7 @@ if (!class_exists('MenuManager')) {
 	if (!$menufound) {	// If failed to include, we try with standard
 		dol_syslog("You define a menu manager '".$file_menu."' that can not be loaded.", LOG_WARNING);
 		$file_menu = 'eldy_menu.php';
-		include_once DOL_DOCUMENT_ROOT."/core/menus/standard/".$file_menu;
+		include_once DOL_DOCUMENT_ROOT."/core/menus/standard/".dol_sanitizeFileName($file_menu);
 	}
 }
 $menumanager = new MenuManager($db, empty($user->socid) ? 0 : 1);

+ 4 - 2
htdocs/core/js/lib_foot.js.php

@@ -112,8 +112,10 @@ if (empty($conf->dol_no_mouse_hover)) {
 					success: function(response){
 						// Setting content option
 						console.log("ajax success");
-						elemfortooltip.tooltip("option","content",response);
-						elemfortooltip.tooltip("open");
+	  					if (elemfortooltip.is(":hover")) {
+							elemfortooltip.tooltip("option","content",response);
+							elemfortooltip.tooltip("open");
+	   					}
 					}
 				});
 			 }, opendelay));

+ 3 - 1
htdocs/core/lib/company.lib.php

@@ -2119,11 +2119,13 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = '', $noprin
 				$out .= dol_trunc($libelle, 120);
 			}
 			if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'mailing') {
-				$out .= '<a href="'.DOL_URL_ROOT.'/comm/mailing/card.php?id='.$histo[$key]['id'].'">'.img_object($langs->trans("ShowEMailing"), "email").' ';
 				$transcode = $langs->trans("Action".$histo[$key]['acode']);
 				$libelle = ($transcode != "Action".$histo[$key]['acode'] ? $transcode : 'Send mass mailing');
+				$out .= '<a href="'.DOL_URL_ROOT.'/comm/mailing/card.php?id='.$histo[$key]['id'].'"';
 				$out .= ' title="'.dol_escape_htmltag($libelle).'">';
+				$out .= img_object($langs->trans("ShowEMailing"), "email").' ';
 				$out .= dol_trunc($libelle, 120);
+				$out .= '</a>';
 			}
 			$out .= '</td>';
 

+ 1 - 0
htdocs/core/lib/files.lib.php

@@ -1450,6 +1450,7 @@ function dol_delete_file($file, $disableglob = 0, $nophperrors = 0, $nohook = 0,
 					}
 				}
 			} else {
+				$ok = true; // nothing to delete when glob is on must return ok
 				dol_syslog("No files to delete found", LOG_DEBUG);
 			}
 		} else {

+ 45 - 27
htdocs/core/lib/functions.lib.php

@@ -20,7 +20,7 @@
  * Copyright (C) 2022       Anthony Berton	         	<anthony.berton@bb2a.fr>
  * Copyright (C) 2022       Ferran Marcet           	<fmarcet@2byte.es>
  * Copyright (C) 2022       Charlene Benke           	<charlene@patas-monkey.com>
- * Copyright (C) 2023       Joachim Kueter              <git-jk@bloxera.com>
+ * Copyright (C) 2023-2024  Joachim Kueter              <git-jk@bloxera.com>
  *
  * 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
@@ -1907,8 +1907,10 @@ function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename =
 	if (!empty($message)) {
 		// Test log level
 		$logLevels = array(LOG_EMERG=>'EMERG', LOG_ALERT=>'ALERT', LOG_CRIT=>'CRITICAL', LOG_ERR=>'ERR', LOG_WARNING=>'WARN', LOG_NOTICE=>'NOTICE', LOG_INFO=>'INFO', LOG_DEBUG=>'DEBUG');
+
 		if (!array_key_exists($level, $logLevels)) {
-			throw new Exception('Incorrect log level');
+			dol_syslog('Error Bad Log Level '.$level, LOG_ERR);
+			$level = $logLevels[LOG_ERR];
 		}
 		if ($level > getDolGlobalInt('SYSLOG_LEVEL')) {
 			return;
@@ -8316,20 +8318,27 @@ function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null,
 			$substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
 			$substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
 			$substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
-			$substitutionarray['__DATE_DELIVERY__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, 'day', 0, $outputlangs) : '');
-			$substitutionarray['__DATE_DELIVERY_DAY__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%d") : '');
-			$substitutionarray['__DATE_DELIVERY_DAY_TEXT__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%A") : '');
-			$substitutionarray['__DATE_DELIVERY_MON__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%m") : '');
-			$substitutionarray['__DATE_DELIVERY_MON_TEXT__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%b") : '');
-			$substitutionarray['__DATE_DELIVERY_YEAR__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%Y") : '');
-			$substitutionarray['__DATE_DELIVERY_HH__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%H") : '');
-			$substitutionarray['__DATE_DELIVERY_MM__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%M") : '');
-			$substitutionarray['__DATE_DELIVERY_SS__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%S") : '');
+			// handle date_delivery: in customer order/supplier order, the property name is delivery_date, in shipment/reception it is date_delivery
+			$date_delivery = null;
+			if (property_exists($object, 'date_delivery')) {
+				$date_delivery =  $object->date_delivery;
+			} elseif (property_exists($object, 'delivery_date')) {
+				$date_delivery =  $object->delivery_date;
+			}
+			$substitutionarray['__DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', 0, $outputlangs) : '');
+			$substitutionarray['__DATE_DELIVERY_DAY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%d") : '');
+			$substitutionarray['__DATE_DELIVERY_DAY_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%A") : '');
+			$substitutionarray['__DATE_DELIVERY_MON__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%m") : '');
+			$substitutionarray['__DATE_DELIVERY_MON_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%b") : '');
+			$substitutionarray['__DATE_DELIVERY_YEAR__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%Y") : '');
+			$substitutionarray['__DATE_DELIVERY_HH__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%H") : '');
+			$substitutionarray['__DATE_DELIVERY_MM__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%M") : '');
+			$substitutionarray['__DATE_DELIVERY_SS__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%S") : '');
 
 			// For backward compatibility (deprecated)
 			$substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
 			$substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
-			$substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($object->delivery_date) ? dol_print_date($object->delivery_date, 'day', 0, $outputlangs) : '');
+			$substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', 0, $outputlangs) : '');
 			$substitutionarray['__SUPPLIER_ORDER_DELAY_DELIVERY__'] = (isset($object->availability_code) ? ($outputlangs->transnoentities("AvailabilityType".$object->availability_code) != 'AvailabilityType'.$object->availability_code ? $outputlangs->transnoentities("AvailabilityType".$object->availability_code) : $outputlangs->convToOutputCharset(isset($object->availability) ? $object->availability : '')) : '');
 			$substitutionarray['__EXPIRATION_DATE__'] = (isset($object->fin_validite) ? dol_print_date($object->fin_validite, 'daytext') : '');
 
@@ -9560,10 +9569,11 @@ function dol_osencode($str)
  * 		@param	string	$fieldid		Field to get
  *      @param  int		$entityfilter	Filter by entity
  *      @param	string	$filters		Filters to add. WARNING: string must be escaped for SQL and not coming from user input.
+ *      @param	bool    $useCache       If true (default), cache will be queried and updated.
  *      @return int						Return integer <0 if KO, Id of code if OK
  *      @see $langs->getLabelFromKey
  */
-function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '')
+function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '', $useCache = true)
 {
 	global $cache_codes;
 
@@ -9573,8 +9583,8 @@ function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid =
 	}
 
 	// Check in cache
-	if (isset($cache_codes[$tablename][$key][$fieldid])) {	// Can be defined to 0 or ''
-		return $cache_codes[$tablename][$key][$fieldid]; // Found in cache
+	if ($useCache && isset($cache_codes[$tablename][$fieldkey][$fieldid][$entityfilter][$filters][$key])) {	// Can be defined to 0 or ''
+		return $cache_codes[$tablename][$fieldkey][$fieldid][$entityfilter][$filters][$key]; // Found in cache
 	}
 
 	dol_syslog('dol_getIdFromCode (value for field '.$fieldid.' from key '.$key.' not found into cache)', LOG_DEBUG);
@@ -9592,13 +9602,15 @@ function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid =
 	$resql = $db->query($sql);
 	if ($resql) {
 		$obj = $db->fetch_object($resql);
+		$valuetoget = '';
 		if ($obj) {
-			$cache_codes[$tablename][$key][$fieldid] = $obj->valuetoget;
-		} else {
-			$cache_codes[$tablename][$key][$fieldid] = '';
+			$valuetoget = $obj->valuetoget;
 		}
 		$db->free($resql);
-		return $cache_codes[$tablename][$key][$fieldid];
+		if ($useCache) {
+			$cache_codes[$tablename][$fieldkey][$fieldid][$entityfilter][$filters][$key] = $valuetoget;
+		}
+		return $valuetoget;
 	} else {
 		return -1;
 	}
@@ -10629,7 +10641,12 @@ function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
 
 	$value = preg_replace('/\s*\|\s*/', '|', $value);
 
-	$crits = explode(' ', $value);
+	//natural mode search type 3 allow spaces into search ...
+	if ($mode == 3 || $mode == -3) {
+		$crits = explode(',', $value);
+	} else {
+		$crits = explode(' ', $value);
+	}
 	$res = '';
 	if (!is_array($fields)) {
 		$fields = array($fields);
@@ -10690,7 +10707,7 @@ function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
 							$listofcodes .= "'".$db->escape($val)."'";
 						}
 					}
-					$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -3 ? 'NOT ' : '')."IN (".$db->sanitize($listofcodes, 1).")";
+					$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -3 ? 'NOT ' : '')."IN (".$db->sanitize($listofcodes, 1, 0, 1).")";
 					$i2++; // a criteria for 1 more field was added to string
 				}
 				if ($mode == -3) {
@@ -10886,13 +10903,14 @@ function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param
 	// old behavior, return a string
 	if ($isAllowedForPreview) {
 		$tmpurl = DOL_URL_ROOT.'/document.php?modulepart='.urlencode($modulepart).'&attachment=0&file='.urlencode($relativepath).($param ? '&'.$param : '');
+
 		$title = $langs->trans("Preview");
 		//$title = '%27-alert(document.domain)-%27';
 		//$tmpurl = 'file='.urlencode("'-alert(document.domain)-'_small.jpg");
 
 		// We need to urlencode the parameter after the dol_escape_js($tmpurl) because  $tmpurl may contain n url with param file=abc%27def if file has a ' inside.
 		// and when we click on href with this javascript string, a urlcode is done by browser, converted the %27 of file param
-		return 'javascript:document_preview(\''.urlencode(dol_escape_js($tmpurl)).'\', \''.urlencode(dol_mimetype($relativepath)).'\', \''.urlencode(dol_escape_js($title)).'\')';
+		return 'javascript:document_preview(\''.urlencode(dol_escape_js($tmpurl)).'\', \''.urlencode(dol_mimetype($relativepath)).'\', \''.rawurlencode(dol_escape_js($title)).'\')';
 	} else {
 		return '';
 	}
@@ -11590,11 +11608,11 @@ function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $st
  *                              'classOverride' => '' // to replace class attribute of the button
  *                              ],
  *                              'confirm' => [
- *                              'url' => 'http://', // Overide Url to go when user click on action btn, if empty default url is $url.?confirm=yes, for no js compatibility use $url for fallback confirm.
- *                              'title' => '', // Overide title of modal,  if empty default title use "ConfirmBtnCommonTitle" lang key
- *                              'action-btn-label' => '', // Overide label of action button,  if empty default label use "Confirm" lang key
- *                              'cancel-btn-label' => '', // Overide label of cancel button,  if empty default label use "CloseDialog" lang key
- *                              'content' => '', // Overide text of content,  if empty default content use "ConfirmBtnCommonContent" lang key
+ *                              'url' => 'http://', // Override Url to go when user click on action btn, if empty default url is $url.?confirm=yes, for no js compatibility use $url for fallback confirm.
+ *                              'title' => '', // Override title of modal,  if empty default title use "ConfirmBtnCommonTitle" lang key
+ *                              'action-btn-label' => '', // Override label of action button,  if empty default label use "Confirm" lang key
+ *                              'cancel-btn-label' => '', // Override label of cancel button,  if empty default label use "CloseDialog" lang key
+ *                              'content' => '', // Override text of content,  if empty default content use "ConfirmBtnCommonContent" lang key
  *                              'modal' => true, // true|false to display dialog as a modal (with dark background)
  *                              'isDropDrown' => false, // true|false to display dialog as a dropdown (with dark background)
  *                              ],

+ 48 - 2
htdocs/core/lib/pdf.lib.php

@@ -1531,6 +1531,8 @@ function pdf_getlinedesc($object, $i, $outputlangs, $hideref = 0, $hidedesc = 0,
 	$note = (!empty($object->lines[$i]->note) ? $object->lines[$i]->note : '');
 	$dbatch = (!empty($object->lines[$i]->detail_batch) ? $object->lines[$i]->detail_batch : false);
 
+	$multilangsactive = getDolGlobalInt('MAIN_MULTILANGS');
+
 	if ($issupplierline) {
 		include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
 		$prodser = new ProductFournisseur($db);
@@ -1543,11 +1545,55 @@ function pdf_getlinedesc($object, $i, $outputlangs, $hideref = 0, $hidedesc = 0,
 		}
 	}
 
+	//id
+	$idprod = (!empty($object->lines[$i]->fk_product) ? $object->lines[$i]->fk_product : false);
 	if ($idprod) {
 		$prodser->fetch($idprod);
+		//load multilangs
+		if ($multilangsactive) {
+			$prodser->getMultiLangs();
+			$object->lines[$i]->multilangs = $prodser->multilangs;
+		}
+	}
+	//label
+	if (!empty($object->lines[$i]->label)) {
+		$label = $object->lines[$i]->label;
+	} else {
+		if (!empty($object->lines[$i]->multilangs[$outputlangs->defaultlang]['label']) && $multilangsactive) {
+			$label = $object->lines[$i]->multilangs[$outputlangs->defaultlang]['label'];
+		} else {
+			if (!empty($object->lines[$i]->product_label)) {
+				$label = $object->lines[$i]->product_label;
+			} else {
+				$label = '';
+			}
+		}
+	}
+	//description
+	if (!empty($object->lines[$i]->desc)) {
+		$desc = $object->lines[$i]->desc;
+	} else {
+		if (!empty($object->lines[$i]->multilangs[$outputlangs->defaultlang]['description']) && $multilangsactive) {
+			$desc = $object->lines[$i]->multilangs[$outputlangs->defaultlang]['description'];
+		} else {
+			if (!empty($object->lines[$i]->description)) {
+				$desc = $object->lines[$i]->description;
+			} else {
+				$desc = '';
+			}
+		}
+	}
+	//ref supplier
+	$ref_supplier = (!empty($object->lines[$i]->ref_supplier) ? $object->lines[$i]->ref_supplier : (!empty($object->lines[$i]->ref_fourn) ? $object->lines[$i]->ref_fourn : '')); // TODO Not yet saved for supplier invoices, only supplier orders
+	//note
+	$note = (!empty($object->lines[$i]->note) ? $object->lines[$i]->note : '');
+	//dbatch
+	$dbatch = (!empty($object->lines[$i]->detail_batch) ? $object->lines[$i]->detail_batch : false);
+
+	if ($idprod) {
 		// If a predefined product and multilang and on other lang, we renamed label with label translated
-		if (getDolGlobalInt('MAIN_MULTILANGS') && ($outputlangs->defaultlang != $langs->defaultlang)) {
-			$translatealsoifmodified = (getDolGlobalString('MAIN_MULTILANG_TRANSLATE_EVEN_IF_MODIFIED')); // By default if value was modified manually, we keep it (no translation because we don't have it)
+		if ($multilangsactive && ($outputlangs->defaultlang != $langs->defaultlang)) {
+			$translatealsoifmodified = getDolGlobalString('MAIN_MULTILANG_TRANSLATE_EVEN_IF_MODIFIED'); // By default if value was modified manually, we keep it (no translation because we don't have it)
 
 			// TODO Instead of making a compare to see if param was modified, check that content contains reference translation. If yes, add the added part to the new translation
 			// ($textwasnotmodified is replaced with $textwasmodifiedorcompleted and we add completion).

+ 1 - 1
htdocs/core/lib/sendings.lib.php

@@ -441,7 +441,7 @@ function show_list_sending_receive($origin, $origin_id, $filter = '')
 				// Informations on receipt
 				if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
 					include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
-					$expedition->fetchObjectLinked($expedition->id, $expedition->element);
+					$expedition->fetchObjectLinked($expedition->id, $expedition->element, null, 'delivery');
 					//var_dump($expedition->linkedObjects);
 
 					$receiving = '';

+ 7 - 0
htdocs/core/modules/DolibarrModules.class.php

@@ -1505,6 +1505,7 @@ class DolibarrModules // Can not be abstract, because we need to instantiate it
 				$dateend = isset($this->cronjobs[$key]['dateend']) ? $this->cronjobs[$key]['dateend'] : '';
 				$status = isset($this->cronjobs[$key]['status']) ? $this->cronjobs[$key]['status'] : '';
 				$test = isset($this->cronjobs[$key]['test']) ? $this->cronjobs[$key]['test'] : ''; // Line must be enabled or not (so visible or not)
+				$datenextrun = isset($this->cronjobs[$key]['datenextrun']) ? $this->cronjobs[$key]['datenextrun'] : '';
 
 				// Search if cron entry already present
 				$sql = "SELECT count(*) as nb FROM ".MAIN_DB_PREFIX."cronjob";
@@ -1546,6 +1547,9 @@ class DolibarrModules // Can not be abstract, because we need to instantiate it
 							if (is_int($priority)) {
 								$sql .= ' priority,';
 							}
+							if (!empty($datenextrun)) {
+								$sql .= ' datenextrun,';
+							}
 							if (is_int($status)) {
 								$sql .= ' status,';
 							}
@@ -1572,6 +1576,9 @@ class DolibarrModules // Can not be abstract, because we need to instantiate it
 							if (is_int($priority)) {
 								$sql .= "'".$this->db->escape($priority)."', ";
 							}
+							if (!empty($datenextrun)) {
+								$sql .= "'".$this->db->idate($datenextrun)."', ";
+							}
 							if (is_int($status)) {
 								$sql .= ((int) $status).", ";
 							}

+ 2 - 2
htdocs/core/modules/asset/doc/doc_generic_asset_odt.modules.php

@@ -391,7 +391,7 @@ class doc_generic_asset_odt extends ModelePDFAsset
 					}
 				}
 				// Replace tags of lines
-				try {
+				/*try {
 					$foundtagforlines = 1;
 					try {
 						$listlines = $odfHandler->setSegment('lines');
@@ -429,7 +429,7 @@ class doc_generic_asset_odt extends ModelePDFAsset
 					$this->error = $e->getMessage();
 					dol_syslog($this->error, LOG_WARNING);
 					return -1;
-				}
+				}*/
 
 				// Replace labels translated
 				$tmparray = $outputlangs->get_translations_for_substitutions();

+ 5 - 1
htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php

@@ -286,7 +286,11 @@ class pdf_standard_asset extends ModelePDFAsset
 
 				// Set path to the background PDF File
 				if (getDolGlobalString('MAIN_ADD_PDF_BACKGROUND')) {
-					$pagecount = $pdf->setSourceFile($conf->mycompany->multidir_output[$object->entity].'/' . getDolGlobalString('MAIN_ADD_PDF_BACKGROUND'));
+					$logodir = $conf->mycompany->dir_output;
+					if (!empty($conf->mycompany->multidir_output[$object->entity])) {
+						$logodir = $conf->mycompany->multidir_output[$object->entity];
+					}
+					$pagecount = $pdf->setSourceFile($logodir .'/' . getDolGlobalString('MAIN_ADD_PDF_BACKGROUND'));
 					$tplidx = $pdf->importPage(1);
 				}
 

+ 1 - 1
htdocs/core/modules/fichinter/doc/pdf_soleil.modules.php

@@ -277,7 +277,7 @@ class pdf_soleil extends ModelePDFFicheinter
 				$desc = dol_htmlentitiesbr($text, 1);
 				//print $outputlangs->convToOutputCharset($desc); exit;
 
-				$pdf->writeHTMLCell(180, 3, 10, $tab_top + 5, $outputlangs->convToOutputCharset($desc), 0, 1);
+				$pdf->writeHTMLCell(180, 3, $this->posxdesc - 1, $tab_top + 5, $outputlangs->convToOutputCharset($desc), 0, 1);
 				$nexY = $pdf->GetY();
 
 				$pdf->line($this->marge_gauche, $nexY, $this->page_largeur - $this->marge_droite, $nexY);

+ 1 - 1
htdocs/core/modules/modStock.class.php

@@ -458,7 +458,7 @@ class modStock extends DolibarrModules
 		);
 		$this->import_updatekeys_array[$r] = array('ps.fk_product'=>'Product', 'ps.fk_entrepot'=>"Warehouse");
 		$this->import_run_sql_after_array[$r] = array(    // Because we may change data that are denormalized, we must update dernormalized data after.
-			'UPDATE '.MAIN_DB_PREFIX.'product as p SET p.stock = (SELECT SUM(ps.reel) FROM '.MAIN_DB_PREFIX.'product_stock ps WHERE ps.fk_product = p.rowid);'
+			'UPDATE '.MAIN_DB_PREFIX.'product as p SET stock = (SELECT SUM(ps.reel) FROM '.MAIN_DB_PREFIX.'product_stock ps WHERE ps.fk_product = p.rowid);'
 		);
 	}
 

+ 8 - 0
htdocs/core/modules/project/task/doc/doc_generic_task_odt.modules.php

@@ -469,6 +469,14 @@ class doc_generic_task_odt extends ModelePDFTask
 			return -1;
 		}
 
+		// Add odtgeneration hook
+		if (!is_object($hookmanager)) {
+			include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
+			$hookmanager = new HookManager($this->db);
+		}
+		$hookmanager->initHooks(array('odtgeneration'));
+		global $action;
+
 		if (!is_object($outputlangs)) {
 			$outputlangs = $langs;
 		}

+ 1 - 0
htdocs/core/modules/societe/doc/doc_generic_odt.modules.php

@@ -212,6 +212,7 @@ class doc_generic_odt extends ModeleThirdPartyDoc
 			$hookmanager = new HookManager($this->db);
 		}
 		$hookmanager->initHooks(array('odtgeneration'));
+		global $action;
 
 		if (!is_object($outputlangs)) {
 			$outputlangs = $langs;

+ 5 - 1
htdocs/core/modules/stocktransfer/doc/pdf_eagle_proforma.modules.php

@@ -275,7 +275,11 @@ class pdf_eagle_proforma extends ModelePDFCommandes
 				$pdf->SetFont(pdf_getPDFFont($outputlangs));
 				// Set path to the background PDF File
 				if (getDolGlobalString('MAIN_ADD_PDF_BACKGROUND')) {
-					$pagecount = $pdf->setSourceFile($conf->mycompany->multidir_output[$object->entity].'/' . getDolGlobalString('MAIN_ADD_PDF_BACKGROUND'));
+					$logodir = $conf->mycompany->dir_output;
+					if (!empty($conf->mycompany->multidir_output[$object->entity])) {
+						$logodir = $conf->mycompany->multidir_output[$object->entity];
+					}
+					$pagecount = $pdf->setSourceFile($logodir .'/' . getDolGlobalString('MAIN_ADD_PDF_BACKGROUND'));
 					$tplidx = $pdf->importPage(1);
 				}
 

+ 5 - 1
htdocs/core/modules/supplier_invoice/doc/pdf_canelle.modules.php

@@ -250,7 +250,11 @@ class pdf_canelle extends ModelePDFSuppliersInvoices
 				$pdf->SetFont(pdf_getPDFFont($outputlangs));
 				// Set path to the background PDF File
 				if (getDolGlobalString('MAIN_ADD_PDF_BACKGROUND')) {
-					$pagecount = $pdf->setSourceFile($conf->mycompany->multidir_output[$object->entity].'/' . getDolGlobalString('MAIN_ADD_PDF_BACKGROUND'));
+					$logodir = $conf->mycompany->dir_output;
+					if (!empty($conf->mycompany->multidir_output[$object->entity])) {
+						$logodir = $conf->mycompany->multidir_output[$object->entity];
+					}
+					$pagecount = $pdf->setSourceFile($logodir .'/' . getDolGlobalString('MAIN_ADD_PDF_BACKGROUND'));
 					$tplidx = $pdf->importPage(1);
 				}
 

+ 20 - 17
htdocs/core/tpl/advtarget.tpl.php

@@ -277,7 +277,10 @@ if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
 	$extrafields->fetch_name_optionals_label($elementtype);
 	foreach ($extrafields->attributes[$elementtype]['label'] as $key => $val) {
 		if ($key != 'ts_nameextra' && $key != 'ts_payeur') {
-			print '<tr><td>'.$extrafields->attributes[$elementtype]['label'][$key];
+			if (isset($extrafields->attributes[$elementtype]['langfile'][$key])) {
+				$langs->load($extrafields->attributes[$elementtype]['langfile'][$key]);
+			}
+			print '<tr><td>'.$langs->trans($extrafields->attributes[$elementtype]['label'][$key]);
 			if (!empty($array_query['options_'.$key]) || (is_array($array_query['options_'.$key]) && count($array_query['options_'.$key]) > 0)) {
 				print img_picto($langs->trans('AdvTgtUse'), 'ok.png@advtargetemailing');
 			}
@@ -293,9 +296,9 @@ if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
 			} elseif (($extrafields->attributes[$elementtype]['type'][$key] == 'date') || ($extrafields->attributes[$elementtype]['type'][$key] == 'datetime')) {
 				print '<table class="nobordernopadding"><tr>';
 				print '<td>'.$langs->trans("AdvTgtStartDt").'</td><td>';
-				print $form->selectDate('', 'options_'.$key.'_st_dt');
+				print $form->selectDate('', 'options_'.$key.'_st_dt', 0, 0, 1);
 				print '</td><td>'.$langs->trans("AdvTgtEndDt").'</td><td>';
-				print $form->selectDate('', 'options_'.$key.'_end_dt');
+				print $form->selectDate('', 'options_'.$key.'_end_dt', 0, 0, 1);
 				print '</td></tr></table>';
 
 				print '</td><td>'."\n";
@@ -311,19 +314,19 @@ if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
 					$array_query['options_'.$key]
 				);
 				print '</td><td>'."\n";
-			} elseif (($extrafields->attributes[$elementtype]['type'][$key] == 'select')) {
-				print $formadvtargetemaling->advMultiselectarray('options_'.$key, $extrafields->attributes[$key]['param']['options'], $array_query['options_'.$key]);
+			} elseif ($extrafields->attributes[$elementtype]['type'][$key] == 'select') {
+				print $formadvtargetemaling->advMultiselectarray('options_'.$key, $extrafields->attributes[$elementtype]['param'][$key]['options'], $array_query['options_'.$key]);
 				print '</td><td>'."\n";
-			} elseif (($extrafields->attributes[$elementtype]['type'][$key] == 'sellist')) {
-				print $formadvtargetemaling->advMultiselectarraySelllist('options_'.$key, $extrafields->attributes[$key]['param']['options'], $array_query['options_'.$key]);
+			} elseif ($extrafields->attributes[$elementtype]['type'][$key] == 'sellist') {
+				print $formadvtargetemaling->advMultiselectarraySelllist('options_'.$key, $extrafields->attributes[$elementtype]['param'][$key]['options'], $array_query['options_'.$key]);
 				print '</td><td>'."\n";
 			} else {
 				print '<table class="nobordernopadding"><tr>';
 				print '<td></td><td>';
 				if (is_array($array_query['options_'.$key])) {
-					print $extrafields->showInputField($key, implode(',', $array_query['options_'.$key]));
+					print $extrafields->showInputField($key, implode(',', $array_query['options_'.$key]), '', '', '', '', 0, 'societe', 1);
 				} else {
-					print $extrafields->showInputField($key, $array_query['options_'.$key]);
+					print $extrafields->showInputField($key, $array_query['options_'.$key], '', '', '', '', 0, 'societe', 1);
 				}
 				print '</td></tr></table>';
 
@@ -492,9 +495,9 @@ if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
 			} elseif (($extrafields->attributes[$elementtype]['type'][$key] == 'date') || ($extrafields->attributes[$elementtype]['type'][$key] == 'datetime')) {
 				print '<table class="nobordernopadding"><tr>';
 				print '<td>'.$langs->trans("AdvTgtStartDt").'</td><td>';
-				print $form->selectDate('', 'options_'.$key.'_st_dt_cnct');
+				print $form->selectDate('', 'options_'.$key.'_st_dt_cnct', 0, 0, 1);
 				print '</td><td>'.$langs->trans("AdvTgtEndDt").'</td><td>';
-				print $form->selectDate('', 'options_'.$key.'_end_dt_cnct');
+				print $form->selectDate('', 'options_'.$key.'_end_dt_cnct', 0, 0, 1);
 				print '</td></tr></table>';
 				print '</td><td>'."\n";
 				print $form->textwithpicto('', $langs->trans("AdvTgtSearchDtHelp"), 1, 'help');
@@ -509,17 +512,17 @@ if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
 					$array_query['options_'.$key.'_cnct']
 				);
 				print '</td><td>'."\n";
-			} elseif (($extrafields->attributes[$elementtype]['type'][$key] == 'select')) {
-				print $formadvtargetemaling->advMultiselectarray('options_'.$key.'_cnct', $extrafields->attributes[$key]['param']['options'], $array_query['options_'.$key.'_cnct']);
+			} elseif ($extrafields->attributes[$elementtype]['type'][$key] == 'select') {
+				print $formadvtargetemaling->advMultiselectarray('options_'.$key.'_cnct', $extrafields->attributes[$elementtype]['param'][$key]['options'], $array_query['options_'.$key.'_cnct']);
 				print '</td><td>'."\n";
-			} elseif (($extrafields->attributes[$elementtype]['type'][$key] == 'sellist')) {
-				print $formadvtargetemaling->advMultiselectarraySelllist('options_'.$key.'_cnct', $extrafields->attributes[$key]['param']['options'], $array_query['options_'.$key.'_cnct']);
+			} elseif ($extrafields->attributes[$elementtype]['type'][$key] == 'sellist') {
+				print $formadvtargetemaling->advMultiselectarraySelllist('options_'.$key.'_cnct', $extrafields->attributes[$elementtype]['param'][$key]['options'], $array_query['options_'.$key.'_cnct']);
 				print '</td><td>'."\n";
 			} else {
 				if (is_array($array_query['options_'.$key.'_cnct'])) {
-					print $extrafields->showInputField($key, implode(',', $array_query['options_'.$key.'_cnct']), '', '_cnct');
+					print $extrafields->showInputField($key, implode(',', $array_query['options_'.$key.'_cnct']), '', '_cnct', '', '', 0, 'socpeople', 1);
 				} else {
-					print $extrafields->showInputField($key, $array_query['options_'.$key.'_cnct'], '', '_cnct');
+					print $extrafields->showInputField($key, $array_query['options_'.$key.'_cnct'], '', '_cnct', '', '', 0, 'socpeople', 1);
 				}
 				print '</td><td>'."\n";
 			}

+ 11 - 2
htdocs/core/tpl/massactions_pre.tpl.php

@@ -98,7 +98,11 @@ if ($massaction == 'preaffecttag' && isModEnabled('category')) {
 	}
 }
 
-if ($massaction == 'preupdateprice') {
+if ($massaction == 'preupdateprice'
+ && (getDolGlobalString('PRODUCT_PRICE_UNIQ')
+		|| getDolGlobalString('PRODUIT_CUSTOMER_PRICES')
+		|| getDolGlobalString('PRODUIT_MULTIPRICES')
+	)) {
 	$formquestion = array();
 
 	$valuefield = '<div style="display: flex; align-items: center; justify-content: flex-end; padding-right: 150px">';
@@ -112,7 +116,12 @@ if ($massaction == 'preupdateprice') {
 				'value' => $valuefield
 			);
 
-	print $form->formconfirm($_SERVER["PHP_SELF"], $langs->trans("ConfirmUpdatePrice"), $langs->trans("ConfirmUpdatePriceQuestion", count($toselect)), "updateprice", $formquestion, 1, 0, 200, 500, 1);
+	$descConfirmPreUpdatePrice=$langs->trans("ConfirmUpdatePriceQuestion", count($toselect));
+	if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
+		$descConfirmPreUpdatePrice=$langs->trans("ConfirmUpdatePriceQuestion", count($toselect)*getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT') .' ('.$langs->transnoentities('PricingRule').', '.$langs->transnoentities('MultiPricesNumPrices').')');
+	}
+
+	print $form->formconfirm($_SERVER["PHP_SELF"], $langs->trans("ConfirmUpdatePrice"), $descConfirmPreUpdatePrice, "updateprice", $formquestion, 1, 0, 200, 500, 1);
 }
 
 if ($massaction == 'presetsupervisor') {

+ 1 - 1
htdocs/core/tpl/passwordreset.tpl.php

@@ -29,7 +29,7 @@ if (empty($conf) || !is_object($conf)) {
 }
 
 // DDOS protection
-$size = (int) $_SERVER['CONTENT_LENGTH'];
+$size = (int) ($_SERVER['CONTENT_LENGTH'] ?? 0);
 if ($size > 10000) {
 	$langs->loadLangs(array("errors", "install"));
 	httponly_accessforbidden('<center>'.$langs->trans("ErrorRequestTooLarge").'<br><a href="'.DOL_URL_ROOT.'">'.$langs->trans("ClickHereToGoToApp").'</a></center>', 413, 1);

+ 115 - 32
htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php

@@ -1,8 +1,8 @@
 <?php
 /*
  * Copyright (C) 2014-2016  Jean-François Ferry	<hello@librethic.io>
- * 				 2016       Christophe Battarel <christophe@altairis.fr>
- * Copyright (C) 2023		Benjamin Falière	<benjamin.faliere@altairis.fr>
+ * Copyright (C) 2016       Christophe Battarel <christophe@altairis.fr>
+ * Copyright (C) 2023-2025	Benjamin Falière	<benjamin@faliere.com>
  *
  * 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
@@ -87,38 +87,17 @@ class InterfaceTicketEmail extends DolibarrTriggers
 								$appli = $mysoc->name;
 
 								// Send email to assigned user
-								$subject = '['.$appli.'] '.$langs->transnoentities('TicketAssignedToYou');
-								$message = '<p>'.$langs->transnoentities('TicketAssignedEmailBody', $object->track_id, dolGetFirstLastname($user->firstname, $user->lastname))."</p>";
-								$message .= '<ul><li>'.$langs->trans('Title').' : '.$object->subject.'</li>';
-								$message .= '<li>'.$langs->trans('Type').' : '.$object->type_label.'</li>';
-								$message .= '<li>'.$langs->trans('Category').' : '.$object->category_label.'</li>';
-								$message .= '<li>'.$langs->trans('Severity').' : '.$object->severity_label.'</li>';
-								// Extrafields
-								if (is_array($object->array_options) && count($object->array_options) > 0) {
-									foreach ($object->array_options as $key => $value) {
-										$message .= '<li>'.$langs->trans($key).' : '.$value.'</li>';
-									}
-								}
-
-								$message .= '</ul>';
-								$message .= '<p>'.$langs->trans('Message').' : <br>'.$object->message.'</p>';
-								$message .= '<p><a href="'.dol_buildpath('/ticket/card.php', 2).'?track_id='.$object->track_id.'">'.$langs->trans('SeeThisTicketIntomanagementInterface').'</a></p>';
-
 								$sendto = $userstat->email;
-								$from = dolGetFirstLastname($user->firstname, $user->lastname).'<'.$user->email.'>';
-
-								$message = dol_nl2br($message);
+								$subject_assignee = 'TicketAssignedToYou';
+								$body_assignee = 'TicketAssignedEmailBody';
+								$see_ticket_assignee = 'SeeThisTicketIntomanagementInterface';
 
 								if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
 									$old_MAIN_MAIL_AUTOCOPY_TO = $conf->global->MAIN_MAIL_AUTOCOPY_TO;
 									$conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
 								}
-								include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
-								$mailfile = new CMailFile($subject, $sendto, $from, $message, $filepath, $mimetype, $filename, '', '', 0, -1);
-								if ($mailfile->error) {
-									setEventMessages($mailfile->error, $mailfile->errors, 'errors');
-								} else {
-									$result = $mailfile->sendfile();
+								if (!empty($sendto)) {
+									$this->composeAndSendAssigneeMessage($sendto, $subject_assignee, $body_assignee, $see_ticket_assignee, $object, $langs);
 								}
 								if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
 									$conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
@@ -177,10 +156,15 @@ class InterfaceTicketEmail extends DolibarrTriggers
 
 				$subject_admin = 'TicketNewEmailSubjectAdmin';
 				$body_admin = 'TicketNewEmailBodyAdmin';
+
 				$subject_customer = 'TicketNewEmailSubjectCustomer';
 				$body_customer = 'TicketNewEmailBodyCustomer';
 				$see_ticket_customer = 'TicketNewEmailBodyInfosTrackUrlCustomer';
 
+				$subject_assignee = 'TicketAssignedToYou';
+				$body_assignee = 'TicketAssignedEmailBody';
+				$see_ticket_assignee = 'SeeThisTicketIntomanagementInterface';
+
 				// Send email to notification email
 				if (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO') && empty($object->context['disableticketemail'])) {
 					$sendto = !getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO') ? '' : $conf->global->TICKET_NOTIFICATION_EMAIL_TO;
@@ -189,6 +173,34 @@ class InterfaceTicketEmail extends DolibarrTriggers
 					}
 				}
 
+				// Send email to assignee if an assignee was set at creation
+				if ($object->fk_user_assign > 0 && $object->fk_user_assign != $user->id && empty($object->context['disableticketemail'])) {
+					$userstat = new User($this->db);
+					$res = $userstat->fetch($object->fk_user_assign);
+					if ($res > 0) {
+						// Send email to notification email
+						if (!getDolGlobalString('TICKET_DISABLE_ALL_MAILS')) {
+							// Send email to assigned user
+							$sendto = $userstat->email;
+							if (!getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
+								$old_MAIN_MAIL_AUTOCOPY_TO = $conf->global->MAIN_MAIL_AUTOCOPY_TO;
+								$conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
+							}
+
+							if (!empty($sendto)) {
+								$this->composeAndSendAssigneeMessage($sendto, $subject_assignee, $body_assignee, $see_ticket_assignee, $object, $langs);
+							}
+
+							if (!getDolUserString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
+								$conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
+							}
+						}
+					} else {
+						$this->error = $userstat->error;
+						$this->errors = $userstat->errors;
+					}
+				}
+
 				// Send email to customer
 				if (!getDolGlobalString('TICKET_DISABLE_CUSTOMER_MAILS') && empty($object->context['disableticketemail']) && $object->notify_tiers_at_create) {
 					$sendto = '';
@@ -428,10 +440,13 @@ class InterfaceTicketEmail extends DolibarrTriggers
 		}
 		$message_customer .= '<p>'.$langs->trans('Message').' : <br><br>'.$message.'</p><br>';
 
-		$url_public_ticket = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/', 2)).'view.php?track_id='.$object->track_id;
-
-		$message_customer .= '<p>'.$langs->trans($see_ticket).' : <a href="'.$url_public_ticket.'">'.$url_public_ticket.'</a></p>';
-		$message_customer .= '<p>'.$langs->trans('TicketEmailPleaseDoNotReplyToThisEmail').'</p>';
+		if (getDolGlobalInt('TICKET_ENABLE_PUBLIC_INTERFACE')) {
+			$url_public_ticket = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/', 2)).'view.php?track_id='.((int) $object->track_id);
+			$message_customer .= '<p>'.$langs->trans($see_ticket).' : <a href="'.$url_public_ticket.'">'.$url_public_ticket.'</a></p>';
+			$message_customer .= '<p>'.$langs->trans('TicketEmailPleaseDoNotReplyToThisEmail').'</p>';
+		} else {
+			$message_customer .= '<p>'.$langs->trans('TicketEmailPleaseDoNotReplyToThisEmailNoInterface').'</p>';
+		}
 
 		$from = (!getDolGlobalString('MAIN_INFO_SOCIETE_NOM') ? '' : getDolGlobalString('MAIN_INFO_SOCIETE_NOM') . ' ').'<' . getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM').'>';
 
@@ -460,4 +475,72 @@ class InterfaceTicketEmail extends DolibarrTriggers
 			$conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
 		}
 	}
+
+	/**
+	 * Composes and sends a message concerning a ticket, to be sent to user assigned to the ticket
+	 *
+	 * @param string 	$sendto			Addresses to send the mail, format "first@address.net, second@address.net, " etc.
+	 * @param string 	$base_subject	email subject. Non-translated string.
+	 * @param string	$body			email body (first line). Non-translated string.
+	 * @param string 	$see_ticket		string indicating the ticket public address
+	 * @param Ticket 	$object			the ticket thet the email refers to
+	 * @param Translate $langs			the translation object
+	 * @return void
+	 */
+	private function composeAndSendAssigneeMessage($sendto, $base_subject, $body, $see_ticket, Ticket $object, Translate $langs)
+	{
+		global $conf, $user, $mysoc;
+
+		// Init to avoid errors
+		$filepath = array();
+		$filename = array();
+		$mimetype = array();
+
+		// Send email to assigned user
+		$appli = $mysoc->name;
+
+		$subject = '['.$appli.'] '.$langs->transnoentities($base_subject);
+		$message = '<p>'.$langs->transnoentities($body, $object->track_id, dolGetFirstLastname($user->firstname, $user->lastname))."</p>";
+		$message .= '<ul><li>'.$langs->trans('Title').' : '.$object->subject.'</li>';
+		$message .= '<li>'.$langs->trans('Type').' : '.$object->type_label.'</li>';
+		$message .= '<li>'.$langs->trans('Category').' : '.$object->category_label.'</li>';
+		$message .= '<li>'.$langs->trans('Severity').' : '.$object->severity_label.'</li>';
+		// Extrafields
+		if (is_array($object->array_options) && count($object->array_options) > 0) {
+			foreach ($object->array_options as $key => $value) {
+				$message .= '<li>'.$langs->trans($key).' : '.$value.'</li>';
+			}
+		}
+
+		$message .= '</ul>';
+		$message .= '<p>'.$langs->trans('Message').' : <br>'.$object->message.'</p>';
+		$message .= '<p><a href="'.dol_buildpath('/ticket/card.php', 2).'?track_id='.$object->track_id.'">'.$langs->trans($see_ticket).'</a></p>';
+
+		$from = dolGetFirstLastname($user->firstname, $user->lastname).'<'.$user->email.'>';
+
+		$message = dol_nl2br($message);
+
+		$old_MAIN_MAIL_AUTOCOPY_TO = null;
+		if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
+			$old_MAIN_MAIL_AUTOCOPY_TO = getDolGlobalString('MAIN_MAIL_AUTOCOPY_TO');
+			$conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
+		}
+
+		include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
+		$mailfile = new CMailFile($subject, $sendto, $from, $message, $filepath, $mimetype, $filename, '', '', 0, -1);
+		if ($mailfile->error) {
+			setEventMessages($mailfile->error, $mailfile->errors, 'errors');
+		} else {
+			$result = $mailfile->sendfile();
+			if ($result) {
+				// update last_msg_sent date
+				$object->fetch($object->id);
+				$object->date_last_msg_sent = dol_now();
+				$object->update($user);
+			}
+		}
+		if (!getDolUserString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
+			$conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
+		}
+	}
 }

+ 10 - 10
htdocs/core/triggers/interface_95_modZapier_ZapierTriggers.class.php

@@ -224,7 +224,7 @@ class InterfaceZapierTriggers extends DolibarrTriggers
 			case 'ORDER_CLASSIFY_BILLED':
 			case 'ORDER_SETDRAFT':
 			case 'LINEORDER_INSERT':
-			case 'LINEORDER_UPDATE':
+			case 'LINEORDER_MODIFY':
 			case 'LINEORDER_DELETE':
 				break;
 			// Supplier orders
@@ -239,7 +239,7 @@ class InterfaceZapierTriggers extends DolibarrTriggers
 			// case 'ORDER_SUPPLIER_RECEIVE':
 			// case 'LINEORDER_SUPPLIER_DISPATCH':
 			// case 'LINEORDER_SUPPLIER_CREATE':
-			// case 'LINEORDER_SUPPLIER_UPDATE':
+			// case 'LINEORDER_SUPPLIER_MODIFY':
 
 			// Proposals
 			// case 'PROPAL_CREATE':
@@ -251,7 +251,7 @@ class InterfaceZapierTriggers extends DolibarrTriggers
 			// case 'PROPAL_CLOSE_REFUSED':
 			// case 'PROPAL_DELETE':
 			// case 'LINEPROPAL_INSERT':
-			// case 'LINEPROPAL_UPDATE':
+			// case 'LINEPROPAL_MODIFY':
 			// case 'LINEPROPAL_DELETE':
 
 			// SupplierProposal
@@ -264,7 +264,7 @@ class InterfaceZapierTriggers extends DolibarrTriggers
 			// case 'SUPPLIER_PROPOSAL_CLOSE_REFUSED':
 			// case 'SUPPLIER_PROPOSAL_DELETE':
 			// case 'LINESUPPLIER_PROPOSAL_INSERT':
-			// case 'LINESUPPLIER_PROPOSAL_UPDATE':
+			// case 'LINESUPPLIER_PROPOSAL_MODIFY':
 			// case 'LINESUPPLIER_PROPOSAL_DELETE':
 
 			// Contracts
@@ -274,7 +274,7 @@ class InterfaceZapierTriggers extends DolibarrTriggers
 			// case 'CONTRACT_CLOSE':
 			// case 'CONTRACT_DELETE':
 			// case 'LINECONTRACT_INSERT':
-			// case 'LINECONTRACT_UPDATE':
+			// case 'LINECONTRACT_MODIFY':
 			// case 'LINECONTRACT_DELETE':
 
 			// Bills
@@ -288,19 +288,19 @@ class InterfaceZapierTriggers extends DolibarrTriggers
 			// case 'BILL_DELETE':
 			// case 'BILL_PAYED':
 			// case 'LINEBILL_INSERT':
-			// case 'LINEBILL_UPDATE':
+			// case 'LINEBILL_MODIFY':
 			// case 'LINEBILL_DELETE':
 
 			//Supplier Bill
 			// case 'BILL_SUPPLIER_CREATE':
-			// case 'BILL_SUPPLIER_UPDATE':
+			// case 'BILL_SUPPLIER_MODIFY':
 			// case 'BILL_SUPPLIER_DELETE':
 			// case 'BILL_SUPPLIER_PAYED':
 			// case 'BILL_SUPPLIER_UNPAYED':
 			// case 'BILL_SUPPLIER_VALIDATE':
 			// case 'BILL_SUPPLIER_UNVALIDATE':
 			// case 'LINEBILL_SUPPLIER_CREATE':
-			// case 'LINEBILL_SUPPLIER_UPDATE':
+			// case 'LINEBILL_SUPPLIER_MODIFY':
 			// case 'LINEBILL_SUPPLIER_DELETE':
 
 			// Payments
@@ -316,7 +316,7 @@ class InterfaceZapierTriggers extends DolibarrTriggers
 
 			// Donation
 			// case 'DON_CREATE':
-			// case 'DON_UPDATE':
+			// case 'DON_MODIFY':
 			// case 'DON_DELETE':
 
 			// Interventions
@@ -325,7 +325,7 @@ class InterfaceZapierTriggers extends DolibarrTriggers
 			// case 'FICHINTER_VALIDATE':
 			// case 'FICHINTER_DELETE':
 			// case 'LINEFICHINTER_CREATE':
-			// case 'LINEFICHINTER_UPDATE':
+			// case 'LINEFICHINTER_MODIFY':
 			// case 'LINEFICHINTER_DELETE':
 
 			// Members

+ 7 - 1
htdocs/don/class/api_donations.class.php

@@ -240,7 +240,13 @@ class Donations extends DolibarrApi
 				continue;
 			}
 
-			$this->don->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->don->array_options[$index] = $this->_checkValForAPI($field, $val, $this->don);
+				}
+				continue;
+			}
+			$this->don->$field = $this->_checkValForAPI($field, $value, $this->don);
 		}
 
 		if ($this->don->update(DolibarrApiAccess::$user) > 0) {

+ 1 - 1
htdocs/emailcollector/class/emailcollector.class.php

@@ -1748,7 +1748,7 @@ class EmailCollector extends CommonObject
 						// Note: we can have
 						// Message-ID=A, In-Reply-To=B, References=B and message can BE an answer or NOT (a transfer rewriten)
 						$isanswer = 0;
-						if (preg_match('/Re\s*:\s+/i', $headers['Subject'])) {
+						if (preg_match('/^(回复|回覆|SV|Antw|VS|RE|Re|AW|Aw|ΑΠ|השב| תשובה | הועבר|Vá|R|RIF|BLS|Atb|RES|Odp|பதில்|YNT|ATB)\s*:\s+/i', $headers['Subject'])) {
 							$isanswer = 1;
 						}
 						//if ($headers['In-Reply-To'] != $headers['Message-ID'] && empty($headers['References'])) $isanswer = 1;	// If in-reply-to differs of message-id, this is a reply

+ 7 - 1
htdocs/expedition/class/api_shipments.class.php

@@ -457,7 +457,13 @@ class Shipments extends DolibarrApi
 				continue;
 			}
 
-			$this->shipment->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->shipment->array_options[$index] = $this->_checkValForAPI($field, $val, $this->shipment);
+				}
+				continue;
+			}
+			$this->shipment->$field = $this->_checkValForAPI($field, $value, $this->shipment);
 		}
 
 		if ($this->shipment->update(DolibarrApiAccess::$user) > 0) {

+ 9 - 10
htdocs/expedition/dispatch.php

@@ -347,7 +347,7 @@ if ($action == 'updatelines' && $usercancreate) {
 		setEventMessages($error, $errors, 'errors');
 	} else {
 		$db->commit();
-		setEventMessages($langs->trans("ReceptionUpdated"), null);
+		setEventMessages($langs->trans("ShipmentUpdated"), null);
 
 		header("Location: ".DOL_URL_ROOT.'/expedition/dispatch.php?id='.$object->id);
 		exit;
@@ -773,15 +773,14 @@ if ($object->id > 0 || !empty($object->ref)) {
 						print '</td>'; // Dispatch column
 						print '<td></td>'; // Warehouse column
 
-						/*$sql = "SELECT cfd.rowid, cfd.qty, cfd.fk_entrepot, cfd.batch, cfd.eatby, cfd.sellby, cfd.fk_product";
-						$sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseur_dispatch as cfd";
-						$sql .= " WHERE cfd.fk_commandefourndet = ".(int) $objp->rowid;*/
-
-						$sql = "SELECT ed.rowid, ed.qty, ed.fk_entrepot,";
-						$sql .= " eb.batch, eb.eatby, eb.sellby, cd.fk_product";
-						$sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed";
-						$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as eb on ed.rowid = eb.fk_expeditiondet";
-						$sql .= " JOIN ".MAIN_DB_PREFIX."commandedet as cd on ed.fk_origin_line = cd.rowid";
+						$sql  = "SELECT ed.rowid";
+						$sql .= ", cd.fk_product";
+						$sql .= ", ".$db->ifsql('eb.rowid IS NULL', 'ed.qty', 'eb.qty')." as qty";
+						$sql .= ", ed.fk_entrepot";
+						$sql .= ", eb.batch, eb.eatby, eb.sellby";
+						$sql .= " FROM ".$db->prefix()."expeditiondet as ed";
+						$sql .= " LEFT JOIN ".$db->prefix()."expeditiondet_batch as eb on ed.rowid = eb.fk_expeditiondet";
+						$sql .= " INNER JOIN ".$db->prefix()."commandedet as cd on ed.fk_origin_line = cd.rowid";
 						$sql .= " WHERE ed.fk_origin_line =".(int) $objp->rowid;
 						$sql .= " AND ed.fk_expedition =".(int) $object->id;
 						$sql .= " ORDER BY ed.rowid, ed.fk_origin_line";

+ 0 - 2
htdocs/expedition/shipment.php

@@ -250,8 +250,6 @@ if ($id > 0 || !empty($ref)) {
 		$author = new User($db);
 		$author->fetch($object->user_author_id);
 
-		$res = $object->fetch_optionals();
-
 		$head = commande_prepare_head($object);
 		print dol_get_fiche_head($head, 'shipping', $langs->trans("CustomerOrder"), -1, 'order');
 

+ 8 - 3
htdocs/expensereport/card.php

@@ -462,10 +462,15 @@ if (empty($reshook)) {
 						setEventMessages($mesg, null, 'mesgs');
 					} else {
 						$langs->load("other");
-						if ($mailfile->error) {
+						if (!empty($mailfile->error) || !empty($mailfile->errors)) {
 							$mesg = '';
-							$mesg .= $langs->trans('ErrorFailedToSendMail', $emailFrom, $emailTo);
-							$mesg .= '<br>'.$mailfile->error;
+							$mesg .= $langs->transnoentities('ErrorFailedToSendMail', dol_escape_htmltag($emailFrom), dol_escape_htmltag($emailTo));
+							if (!empty($mailfile->error)) {
+								$mesg .= '<br>' . $mailfile->error;
+							}
+							if (!empty($mailfile->errors) && is_array($mailfile->errors)) {
+								$mesg .= '<br>' . implode('<br>', $mailfile->errors);
+							}
 							setEventMessages($mesg, null, 'errors');
 						} else {
 							setEventMessages('No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS', null, 'warnings');

+ 7 - 1
htdocs/expensereport/class/api_expensereports.class.php

@@ -430,7 +430,13 @@ class ExpenseReports extends DolibarrApi
 				continue;
 			}
 
-			$this->expensereport->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->expensereport->array_options[$index] = $this->_checkValForAPI($field, $val, $this->expensereport);
+				}
+				continue;
+			}
+			$this->expensereport->$field = $this->_checkValForAPI($field, $value, $this->expensereport);
 		}
 
 		if ($this->expensereport->update(DolibarrApiAccess::$user) > 0) {

+ 1 - 0
htdocs/expensereport/class/expensereport.class.php

@@ -630,6 +630,7 @@ class ExpenseReport extends CommonObject
 		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as d";
 		if ($ref) {
 			$sql .= " WHERE d.ref = '".$this->db->escape($ref)."'";
+			$sql .= " AND d.entity IN (".getEntity('expensereport').")";
 		} else {
 			$sql .= " WHERE d.rowid = ".((int) $id);
 		}

+ 8 - 1
htdocs/fourn/class/api_supplier_invoices.class.php

@@ -274,10 +274,17 @@ class SupplierInvoices extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->invoice->array_options[$index] = $this->_checkValForAPI($field, $val, $this->invoice);
+				}
+				continue;
+			}
+
 			$this->invoice->$field = $this->_checkValForAPI($field, $value, $this->invoice);
 		}
 
-		if ($this->invoice->update($id, DolibarrApiAccess::$user)) {
+		if ($this->invoice->update(DolibarrApiAccess::$user)) {
 			return $this->get($id);
 		}
 

+ 7 - 0
htdocs/fourn/class/api_supplier_orders.class.php

@@ -294,6 +294,13 @@ class SupplierOrders extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->order->array_options[$index] = $this->_checkValForAPI($field, $val, $this->order);
+				}
+				continue;
+			}
+
 			$this->order->$field = $this->_checkValForAPI($field, $value, $this->order);
 		}
 

+ 46 - 23
htdocs/fourn/class/fournisseur.commande.class.php

@@ -1598,8 +1598,13 @@ class CommandeFournisseur extends CommonOrder
 						// End call triggers
 					}
 
-					$this->db->commit();
-					return $this->id;
+					if (!$error) {
+						$this->db->commit();
+						return $this->id;
+					} else {
+						$this->db->rollback();
+						return -4;
+					}
 				} else {
 					$this->error = $this->db->lasterror();
 					$this->db->rollback();
@@ -2293,35 +2298,52 @@ class CommandeFournisseur extends CommonOrder
 		}
 
 		$main = $this->db->prefix().'commande_fournisseurdet';
-		$ef = $main."_extrafields";
-		$sql = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_commande = ".((int) $this->id).")";
-		dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
-		if (!$this->db->query($sql)) {
-			$this->error = $this->db->lasterror();
-			$this->errors[] = $this->db->lasterror();
-			$error++;
+
+		if (!$error) {
+			$sql1 = "UPDATE ".$this->db->prefix()."commandedet SET fk_commandefourndet = NULL WHERE fk_commandefourndet IN (SELECT rowid FROM ".$this->db->sanitize($main)." WHERE fk_commande = ".((int) $this->id).")";
+			dol_syslog(__METHOD__." linked order lines", LOG_DEBUG);
+			if (!$this->db->query($sql1)) {
+				$error++;
+				$this->error = $this->db->lasterror();
+				$this->errors[] = $this->db->lasterror();
+			}
 		}
 
-		$sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseurdet WHERE fk_commande =".((int) $this->id);
-		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
-		if (!$this->db->query($sql)) {
-			$this->error = $this->db->lasterror();
-			$this->errors[] = $this->db->lasterror();
-			$error++;
+		if (!$error) {
+			$ef = $main."_extrafields";
+			$sql = "DELETE FROM ".$this->db->sanitize($ef)." WHERE fk_object IN (SELECT rowid FROM ".$this->db->sanitize($main)." WHERE fk_commande = ".((int) $this->id).")";
+			dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
+			if (!$this->db->query($sql)) {
+				$this->error = $this->db->lasterror();
+				$this->errors[] = $this->db->lasterror();
+				$error++;
+			}
 		}
 
-		$sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseur WHERE rowid =".((int) $this->id);
-		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
-		if ($resql = $this->db->query($sql)) {
-			if ($this->db->affected_rows($resql) < 1) {
+		if (!$error) {
+			$sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseurdet WHERE fk_commande = ".((int) $this->id);
+			dol_syslog(get_class($this)."::delete", LOG_DEBUG);
+			if (!$this->db->query($sql)) {
+				$this->error = $this->db->lasterror();
+				$this->errors[] = $this->db->lasterror();
+				$error++;
+			}
+		}
+
+		if (!$error) {
+			$sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseur WHERE rowid = ".((int) $this->id);
+			dol_syslog(get_class($this)."::delete", LOG_DEBUG);
+			if ($resql = $this->db->query($sql)) {
+				if ($this->db->affected_rows($resql) < 1) {
+					$this->error = $this->db->lasterror();
+					$this->errors[] = $this->db->lasterror();
+					$error++;
+				}
+			} else {
 				$this->error = $this->db->lasterror();
 				$this->errors[] = $this->db->lasterror();
 				$error++;
 			}
-		} else {
-			$this->error = $this->db->lasterror();
-			$this->errors[] = $this->db->lasterror();
-			$error++;
 		}
 
 		// Remove extrafields
@@ -3981,6 +4003,7 @@ class CommandeFournisseurLigne extends CommonOrderLine
 			return -2;
 		}
 	}
+
 	/**
 	 *	Update the line object into db
 	 *

+ 8 - 1
htdocs/fourn/commande/list.php

@@ -399,6 +399,7 @@ if (empty($reshook)) {
 					$societe = new Societe($db);
 					$societe->fetch($cmd->socid);
 					$objecttmp->vat_reverse_charge = $societe->vat_reverse_charge;
+					$objecttmp->thirdparty = $societe;
 				}
 				$objecttmp->socid = $cmd->socid;
 				$objecttmp->type = $objecttmp::TYPE_STANDARD;
@@ -504,10 +505,16 @@ if (empty($reshook)) {
 							if (($lines[$i]->product_type != 9 && empty($lines[$i]->fk_parent_line)) || $lines[$i]->product_type == 9) {
 								$fk_parent_line = 0;
 							}
+
+							$tva_tx = $lines[$i]->tva_tx;
+							if (!empty($lines[$i]->vat_src_code) && !preg_match('/\(/', $tva_tx)) {
+								$tva_tx .= ' ('.$lines[$i]->vat_src_code.')';
+							}
+
 							$result = $objecttmp->addline(
 								$desc,
 								$lines[$i]->subprice,
-								$lines[$i]->tva_tx,
+								$tva_tx,
 								$lines[$i]->localtax1_tx,
 								$lines[$i]->localtax2_tx,
 								$lines[$i]->qty,

+ 5 - 5
htdocs/fourn/contact.php

@@ -55,7 +55,7 @@ if (!$sortorder) {
 	$sortorder = "ASC";
 }
 if (!$sortfield) {
-	$sortfield = "p.name";
+	$sortfield = "p.lastname";
 }
 $limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
 
@@ -85,12 +85,12 @@ if (dol_strlen($stcomm)) {
 }
 
 if (dol_strlen($begin)) {
-	$sql .= " AND p.name LIKE '$begin%'";
+	$sql .= " AND p.lastname LIKE '$begin%'";
 }
 
 if ($contactname) {
-	$sql .= " AND p.name LIKE '%".strtolower($contactname)."%'";
-	$sortfield = "p.name";
+	$sql .= " AND p.lastname LIKE '%".strtolower($contactname)."%'";
+	$sortfield = "p.lastname";
 	$sortorder = "ASC";
 }
 
@@ -110,7 +110,7 @@ if ($result) {
 
 	print '<table class="liste centpercent">';
 	print '<tr class="liste_titre">';
-	print_liste_field_titre("Lastname", $_SERVER["PHP_SELF"], "p.name", $begin, "", "", $sortfield, $sortorder);
+	print_liste_field_titre("Lastname", $_SERVER["PHP_SELF"], "p.lastname", $begin, "", "", $sortfield, $sortorder);
 	print_liste_field_titre("Firstname", $_SERVER["PHP_SELF"], "p.firstname", $begin, "", "", $sortfield, $sortorder);
 	print_liste_field_titre("Company", $_SERVER["PHP_SELF"], "s.nom", $begin, "", "", $sortfield, $sortorder);
 	print_liste_field_titre("Email");

+ 2 - 0
htdocs/fourn/facture/card-rec.php

@@ -720,6 +720,7 @@ if (empty($reshook)) {
 					unset($_POST['date_end_fill']);
 					unset($_POST['situations']);
 					unset($_POST['progress']);
+					unset($_POST['fourn_ref']);
 				} else {
 					setEventMessages($object->error, $object->errors, 'errors');
 				}
@@ -862,6 +863,7 @@ if (empty($reshook)) {
 				unset($_POST['date_endyear']);
 				unset($_POST['situations']);
 				unset($_POST['progress']);
+				unset($_POST['fourn_ref']);
 			} else {
 				setEventMessages($object->error, $object->errors, 'errors');
 			}

+ 19 - 14
htdocs/fourn/facture/card.php

@@ -1111,7 +1111,7 @@ if (empty($reshook)) {
 					$object->origin_id = GETPOST('originid', 'int');
 
 
-					require_once DOL_DOCUMENT_ROOT.'/'.$element.'/class/'.$subelement.'.class.php';
+					dol_include_once('/'.$element.'/class/'.$subelement.'.class.php');
 					$classname = ucfirst($subelement);
 					if ($classname == 'Fournisseur.commande') {
 						$classname = 'CommandeFournisseur';
@@ -1139,7 +1139,7 @@ if (empty($reshook)) {
 
 					// Add lines
 					if ($id > 0) {
-						require_once DOL_DOCUMENT_ROOT.'/'.$element.'/class/'.$subelement.'.class.php';
+						dol_include_once('/'.$element.'/class/'.$subelement.'.class.php');
 						$classname = ucfirst($subelement);
 						if ($classname == 'Fournisseur.commande') {
 							$classname = 'CommandeFournisseur';
@@ -1324,6 +1324,11 @@ if (empty($reshook)) {
 									$date_end = $lines[$i]->date_end;
 								}
 
+								$tva_tx = $lines[$i]->tva_tx;
+								if (!empty($lines[$i]->vat_src_code) && !preg_match('/\(/', $tva_tx)) {
+									$tva_tx .= ' ('.$lines[$i]->vat_src_code.')';
+								}
+
 								// FIXME Missing special_code  into addline and updateline methods
 								$object->special_code = $lines[$i]->special_code;
 
@@ -1340,7 +1345,7 @@ if (empty($reshook)) {
 								$result = $object->addline(
 									$desc,
 									$pu,
-									$lines[$i]->tva_tx,
+									$tva_tx,
 									$lines[$i]->localtax1_tx,
 									$lines[$i]->localtax2_tx,
 									$lines[$i]->qty,
@@ -2092,7 +2097,7 @@ if ($action == 'create') {
 			$subelement = 'fournisseur.commande';
 		}
 
-		require_once DOL_DOCUMENT_ROOT.'/'.$element.'/class/'.$subelement.'.class.php';
+		dol_include_once('/'.$element.'/class/'.$subelement.'.class.php');
 		$classname = ucfirst($subelement);
 		if ($classname == 'Fournisseur.commande') {
 			$classname = 'CommandeFournisseur';
@@ -2691,7 +2696,9 @@ if ($action == 'create') {
 			require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
 			print '<tr><td>' . $langs->trans('VATReverseCharge') . '</td><td>';
 			// Try to propose to use VAT reverse charge even if the VAT reverse charge is not activated in the supplier card, if this corresponds to the context of use, the activation is proposed
-			if ($vat_reverse_charge == 1 || $societe->vat_reverse_charge == 1 || ($societe->country_code != 'FR' && isInEEC($societe) && !empty($societe->tva_intra))) {
+			if (GETPOSTISSET('vat_reverse_charge')) {  // Check if form was submitted previously
+				$vat_reverse_charge = (GETPOST('vat_reverse_charge', 'alpha') == 'on' || GETPOST('vat_reverse_charge', 'alpha') == '1') ? 1 : 0;
+			} elseif ($vat_reverse_charge == 1 || $societe->vat_reverse_charge == 1 || ($societe->country_code != 'FR' && isInEEC($societe) && !empty($societe->tva_intra))) {
 				$vat_reverse_charge = 1;
 			} else {
 				$vat_reverse_charge = 0;
@@ -3169,14 +3176,12 @@ if ($action == 'create') {
 			$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid, $langs->trans('DeleteProductLine'), $langs->trans('ConfirmDeleteProductLine'), 'confirm_deleteline', '', 0, 1);
 		}
 
-		if (!$formconfirm) {
-			$parameters = array('formConfirm' => $formconfirm, 'lineid'=>$lineid);
-			$reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
-			if (empty($reshook)) {
-				$formconfirm .= $hookmanager->resPrint;
-			} elseif ($reshook > 0) {
-				$formconfirm = $hookmanager->resPrint;
-			}
+		$parameters = array('formConfirm' => $formconfirm, 'lineid'=>$lineid);
+		$reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+		if (empty($reshook)) {
+			$formconfirm .= $hookmanager->resPrint;
+		} elseif ($reshook > 0) {
+			$formconfirm = $hookmanager->resPrint;
 		}
 
 		// Print form confirm
@@ -4059,7 +4064,7 @@ if ($action == 'create') {
 				}
 
 				// Reverse back money or convert to reduction
-				if ($object->type == FactureFournisseur::TYPE_CREDIT_NOTE || $object->type == FactureFournisseur::TYPE_DEPOSIT || $object->type == FactureFournisseur::TYPE_STANDARD) {
+				if ($object->status != FactureFournisseur::STATUS_DRAFT && ($object->type == FactureFournisseur::TYPE_CREDIT_NOTE || $object->type == FactureFournisseur::TYPE_DEPOSIT || $object->type == FactureFournisseur::TYPE_STANDARD)) {
 					// For credit note only
 					if ($object->type == FactureFournisseur::TYPE_CREDIT_NOTE && $object->statut == 1 && $object->paye == 0) {
 						if ($resteapayer == 0) {

+ 2 - 1
htdocs/fourn/facture/list.php

@@ -730,7 +730,8 @@ $sql .= empty($hookmanager->resPrint) ? "" : " HAVING 1=1 ".$hookmanager->resPri
 $nbtotalofrecords = '';
 if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	/* The fast and low memory method to get and count full list converts the sql into a sql count */
-	$sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
+	$sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(DISTINCT f.rowid) as nbtotalofrecords', $sql);
+
 	$sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
 	$sqlforcount = preg_replace('/LEFT JOIN '.MAIN_DB_PREFIX.'paiementfourn_facturefourn as pf ON pf.fk_facturefourn = f.rowid/', '', $sqlforcount);
 

+ 1 - 0
htdocs/hrm/skill_tab.php

@@ -283,6 +283,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 	$sql_skill .=" FROM ".MAIN_DB_PREFIX."hrm_skillrank AS sr";
 	$sql_skill .=" JOIN ".MAIN_DB_PREFIX."hrm_skill AS s ON sr.fk_skill = s.rowid";
 	$sql_skill .= " AND sr.fk_object = ".((int) $id);
+	$sql_skill .= " AND sr.objecttype = '".$db->escape($objecttype)."'";
 	$result = $db->query($sql_skill);
 	$numSkills = $db->num_rows($result);
 	for ($i=0; $i < $numSkills; $i++) {

+ 7 - 0
htdocs/imports/import.php

@@ -1924,6 +1924,13 @@ if ($step == 5 && $datatoimport) {
 						$nbok++;
 					}
 				}
+
+				$reshook = $hookmanager->executeHooks('AfterImportInsert', $parameters);
+				if ($reshook < 0) {
+					$arrayoferrors[$sourcelinenb][] = [
+						'lib' => implode("<br>", array_merge([$hookmanager->error], $hookmanager->errors))
+					];
+				}
 			}
 			// Close file
 			$obj->import_close_file();

+ 2 - 2
htdocs/includes/odtphp/odf.php

@@ -53,8 +53,8 @@ class Odf
 	public $userdefined=array();
 
 	const PIXEL_TO_CM = 0.026458333;
-	const FIND_TAGS_REGEX = '/<([A-Za-z0-9]+)(?:\s([A-Za-z]+(?:\-[A-Za-z]+)?(?:=(?:".*?")|(?:[0-9]+))))*(?:(?:\s\/>)|(?:>(.*)<\/\1>))/s';
-	const FIND_ENCODED_TAGS_REGEX = '/&lt;([A-Za-z]+)(?:\s([A-Za-z]+(?:\-[A-Za-z]+)?(?:=(?:".*?")|(?:[0-9]+))))*(?:(?:\s\/&gt;)|(?:&gt;(.*)&lt;\/\1&gt;))/';
+	const FIND_TAGS_REGEX = '/<([A-Za-z0-9]+)(?:\s([A-Za-z]+(?:\-[A-Za-z]+)?(?:=(?:".*?")|(?:[0-9]+))))*(?:(?:\s\/>)|(?:>(((?!<\1(\s.*)?>).)*)<\/\1>))/s';
+	const FIND_ENCODED_TAGS_REGEX = '/&lt;([A-Za-z]+)(?:\s([A-Za-z]+(?:\-[A-Za-z]+)?(?:=(?:".*?")|(?:[0-9]+))))*(?:(?:\s\/&gt;)|(?:&gt;(((?!&lt;\1(\s.*)?&gt;).)*)&lt;\/\1&gt;))/';
 
 
 	/**

+ 3 - 3
htdocs/includes/tecnickcom/tcpdf/include/barcodes/pdf417.php

@@ -878,7 +878,7 @@ class PDF417 {
 				$txtarr = array(); // array of characters and sub-mode switching characters
 				$codelen = strlen($code);
 				for ($i = 0; $i < $codelen; ++$i) {
-					$chval = ord($code{$i});
+					$chval = ord($code[$i]);
 					if (($k = array_search($chval, $this->textsubmodes[$submode])) !== false) {
 						// we are on the same sub-mode
 						$txtarr[] = $k;
@@ -888,7 +888,7 @@ class PDF417 {
 							// search new sub-mode
 							if (($s != $submode) AND (($k = array_search($chval, $this->textsubmodes[$s])) !== false)) {
 								// $s is the new submode
-								if (((($i + 1) == $codelen) OR ((($i + 1) < $codelen) AND (array_search(ord($code{($i + 1)}), $this->textsubmodes[$submode]) !== false))) AND (($s == 3) OR (($s == 0) AND ($submode == 1)))) {
+								if (((($i + 1) == $codelen) OR ((($i + 1) < $codelen) AND (array_search(ord($code[($i + 1)]), $this->textsubmodes[$submode]) !== false))) AND (($s == 3) OR (($s == 0) AND ($submode == 1)))) {
 									// shift (temporary change only for this char)
 									if ($s == 3) {
 										// shift to puntuaction
@@ -952,7 +952,7 @@ class PDF417 {
 						$cw = array_merge($cw, $cw6);
 					} else {
 						for ($i = 0; $i < $sublen; ++$i) {
-							$cw[] = ord($code{$i});
+							$cw[] = ord($code[$i]);
 						}
 					}
 					$code = $rest;

+ 171 - 0
htdocs/install/mysql/migration/12.0.0-13.0.0.sql

@@ -332,6 +332,8 @@ ALTER TABLE llx_product_warehouse_properties MODIFY COLUMN desiredstock float;
 
 ALTER TABLE llx_product ADD COLUMN fk_state integer DEFAULT NULL AFTER fk_country;
 
+ALTER TABLE llx_product CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+
 ALTER TABLE llx_projet ADD COLUMN email_msgid varchar(255);
 ALTER TABLE llx_ticket ADD COLUMN email_msgid varchar(255);
 ALTER TABLE llx_actioncomm ADD COLUMN reply_to varchar(255);
@@ -599,3 +601,172 @@ insert into llx_c_actioncomm (id, code, type, libelle, module, active, position)
 
 ALTER TABLE llx_export_model MODIFY COLUMN type varchar(64);
 
+
+-- ALL tms thanks to regis (commit 111f73bd863c7120a9c13a8fc9ae227a02079cb0)
+
+ALTER TABLE llx_accounting_account CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_accounting_bookkeeping CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_accounting_bookkeeping_tmp CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_accounting_fiscalyear CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_actioncomm_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_actioncomm CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_adherent_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_adherent CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_adherent_type_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_adherent_type CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_advtargetemailing CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_asset_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_asset CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_asset_type_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_asset_type CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_bank_account_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_bank_account CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_bank CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_blockedlog_authority CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_blockedlog CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_bom_bom_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_bom_bomline_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_bom_bom CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_bordereau_cheque CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_boxes_def CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_budget_lines CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_budget CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_categories_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_categorie CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_c_email_senderprofile CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_c_email_templates CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_c_field_list CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_chargesociales CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_commandedet_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_commande_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_commande_fournisseurdet_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_commande_fournisseur_dispatch_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_commande_fournisseur_dispatch CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_commande_fournisseur_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_commande_fournisseur_log CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_commande_fournisseur CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_commande CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_comment CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_const CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_contratdet_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_contratdet_log CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_contratdet CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_contrat_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_contrat CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_cronjob CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_c_shipment_mode CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_deplacement CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_don_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_don CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_element_resources CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_emailcollector_emailcollectoraction CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_emailcollector_emailcollectorfilter CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_emailcollector_emailcollector CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_entrepot_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_entrepot CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_establishment CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_events CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_expeditiondet_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_expedition_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_expedition CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_expensereport_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_expensereport_ik CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_expensereport_rules CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_expensereport CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_facturedet_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_facturedet_rec_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_facture_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_facture_fourn_det_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_facture_fourn_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_facture_fourn CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_facture_rec_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_facture_rec CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_facture CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_fichinterdet_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_fichinter_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_fichinter CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_holiday_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_holiday CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_inventorydet CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_inventorydet CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_inventory CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_inventory CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_loan_schedule CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_loan CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_localtax CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_mailing_cibles CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_mailing CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_mailing_unsubscribe CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_menu CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_mrp_mo_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_mrp_mo CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_mrp_production CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_notify_def CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_notify CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_online_signatures CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_opensurvey_comments CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_opensurvey_user_studs CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_paiementcharge CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_paiementfourn CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_paiement CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_payment_donation CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_payment_expensereport CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_payment_loan CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_payment_salary_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_payment_salary CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_payment_various CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_pos_cash_fence CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_printing CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_batch CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_customer_price CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_fournisseur_price_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_fournisseur_price CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_lot_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_lot CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_price_by_qty CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_price CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_stock_entrepot CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_product_stock CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_projet_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_projet CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_projet_task_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_projet_task CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_propaldet_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_propal_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_propal_merge_pdf_product CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_propal CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_reception_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_reception CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_resource_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_resource CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_societe_account CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_societe_address CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_societe_contacts CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_societe_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_societe_prices CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_societe_remise CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_societe_remise_supplier CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_societe_rib CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_societe CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_socpeople_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_socpeople CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_stock_mouvement CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_subscription CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_supplier_proposaldet_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_supplier_proposal_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_supplier_proposal CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_ticket_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_ticket CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_tva CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_user_employment CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_user_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_usergroup_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_usergroup CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_user_rib CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_user CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_website_extrafields CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_website_page CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_website CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE llx_zapier_hook CHANGE COLUMN tms tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels