diff --git a/.github/workflows/ci-on-release.yml b/.github/workflows/ci-on-release.yml new file mode 100644 index 0000000000000..958b8865b58a0 --- /dev/null +++ b/.github/workflows/ci-on-release.yml @@ -0,0 +1,24 @@ +name: "CI-RELEASE" + +on: + release: + types: [published] + +jobs: + trigger-docker: + runs-on: ubuntu-latest + + steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.RELEASE_DOCKER_ID }} + private-key: ${{ secrets.RELEASE_DOCKER_SECRET }} + + - uses: peter-evans/repository-dispatch@v4 + with: + token: ${{ steps.generate-token.outputs.token }} + repository: Dolibarr/dolibarr-docker + event-type: new-release + client-payload: '{"version": "${{ github.event.release.tag_name }}"}' diff --git a/.github/workflows/phan.yml b/.github/workflows/phan.yml index 20aa67b3598bc..41baf3e698402 100644 --- a/.github/workflows/phan.yml +++ b/.github/workflows/phan.yml @@ -36,7 +36,7 @@ jobs: with: php-version: 8.2 coverage: none # disable xdebug, pcov - tools: cs2pr,phan + tools: cs2pr,phan:5.5.2 - name: Run Phan analysis run: | # shellcheck disable=2086 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 950e65c97d582..160c98b1821e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -71,14 +71,14 @@ repos: - id: actionlint # Beautify shell scripts - - repo: https://github.com/lovesegfault/beautysh.git - rev: v6.2.1 - hooks: - - id: beautysh - exclude: | - (?x)^(dev/setup/git/hooks/pre-commit - )$ - args: [--tab] + #- repo: https://github.com/lovesegfault/beautysh.git + # rev: v6.2.1 + # hooks: + # - id: beautysh + # exclude: | + # (?x)^(dev/setup/git/hooks/pre-commit + # )$ + # args: [--tab] # Run local script # diff --git a/.travis.yml b/.travis.yml index 2bda2e8105345..79578484b3e94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -148,6 +148,7 @@ install: #sudo apt install composer composer -V sudo composer -n config -g vendor-dir htdocs/includes + sudo composer -n config audit.block-insecure false sudo chmod -R a+rwx /home/travis/.config/composer echo diff --git a/ChangeLog b/ChangeLog index b4f470fbf52fc..4acd205783322 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1584,6 +1584,93 @@ The following changes may create regressions for some external modules, but were * The load of hook context productdao has been removed before calling loadvirtualstock. Modules must use the context of main parent page or 'all' for all cases. +***** ChangeLog for 18.0.9 compared to 18.0.8 ***** +100 files changed, 2283 insertions(+), 591 deletions(-) + +FIX: product ref was not printed on supplier recurring invoice (#37535) +FIX: show export full documents checkbox on change format in accountancy export (#37468) +FIX: remove unused var +FIX: API Warehouse : Error 401 when getting warehouse by id (backport from 22) +FIX: - Fix doc preview in comm card +FIX: Entity on group ticket insertion (#37370) +FIX: display of DLC/DLUO in tooltip (#37164) +FIX: Backport fix on v20 for result page of compta (/compta/resultat/index.php) (#37127) +FIX: Backport fix on v20 for result page of compta (/compta/resultat/index.php). The expense report were not included when the module was activated +FIX(API, thirdparties): get fixed amount discounts (#37068) +FIX: #GHSA-w5j3-8fcr-h87w (#36868) +FIX(ticket): check on TICKET_IMAGE_PUBLIC_INTERFACE (#36833) +FIX: warning accountancy export from external module (#36832) +FIX: php8.1 warning in syslog message +FIX: TakePos sometimes thirdpartyid = undefined +FIX: remove stock correctly when reception is deleted (like 82e092f) +FIX: finished regex in product import (#36770) +FIX: re-create API temp dir after purging temp files +FIX: undefined variables on create invoice card from order (backport from v19) +FIX: Missing Product ref in Bom stats +FIX: fix #36401 (for v17.0) doesn't work in v18.0+ because of variable renaming +FIX: propal shipping and availability update (v18+) +FIX: PR#36401 fixed a missing GETPOSTISSET() but the check involves a variable ($taskid) that was renamed ($tmptaskid) in 18.0 +SEC: FIX: #36430 permissions not checked on other tabs of HRM evaluation card +FIX: Remove + * + * PHP CLI rewrite of makepack-dolibarr.pl + * + * Environment variables 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' + */ + +// ============================================================================ +// Helper functions +// ============================================================================ + +/** + * Return ANSI-colored text + * + * @param string $text Text to colorize + * @param string $color Color name (red, green, yellow, blue, magenta, cyan, white) + * @return string Colored text with reset suffix + */ +function colorText(string $text, string $color): string +{ + $colors = [ + 'reset' => "\033[0m", + 'red' => "\033[31m", + 'green' => "\033[32m", + 'yellow' => "\033[33m", + 'blue' => "\033[34m", + 'magenta' => "\033[35m", + 'cyan' => "\033[36m", + 'white' => "\033[37m", + ]; + $code = $colors[$color] ?? $colors['reset']; + return $code . $text . "\033[0m"; +} + +/** + * Execute a shell command and return stdout + * + * @param string $cmd Shell command to execute + * @return string Command output + */ +function run(string $cmd): string +{ + return shell_exec($cmd) ?? ''; +} + +/** + * Read a line from STDIN + * + * @param string $message Prompt message to display + * @return string User input (trimmed) + */ +function prompt(string $message = ''): string +{ + if ($message !== '') { + echo $message; + } + return trim(fgets(STDIN) ?: ''); +} + + +// ============================================================================ +// Configuration +// ============================================================================ + +// Change this to defined target for option 98 and 99 +$PROJECT = 'dolibarr'; +$PUBLISHBETARC = (getenv('DESTIASSOLOGIN') ?: '') . '@vmprod1.dolibarr.org:/home/dolibarr/asso.dolibarr.org/dolibarr_documents/website/www.dolibarr.org/files'; +$PUBLISHSTABLE = (getenv('DESTISFLOGIN') ?: '') . '@frs.sourceforge.net:/home/frs/project/dolibarr'; + +// Due to implicit origin on git commands +$GITREMOTENAME = getenv('GITREMOTENAME') ?: ''; + +$LISTETARGET = ['TGZ', 'ZIP', 'RPM_GENERIC', 'RPM_FEDORA', 'RPM_MANDRIVA', 'RPM_OPENSUSE', 'DEB', 'EXEDOLIWAMP', 'SNAPSHOT']; + +$REQUIREMENTPUBLISH = [ + 'SF' => 'git ssh rsync', + 'ASSO' => 'git ssh rsync', +]; + +$REQUIREMENTTARGET = [ + 'TGZ' => 'tar', + 'ZIP' => '7z', + 'XZ' => 'xz', + 'RPM_GENERIC' => 'rpmbuild', + 'RPM_FEDORA' => 'rpmbuild', + 'RPM_MANDRIVA' => 'rpmbuild', + 'RPM_OPENSUSE' => 'rpmbuild', + 'DEB' => 'dpkg', + 'FLATPACK' => 'flatpack', + 'EXEDOLIWAMP' => 'ISCC.exe', + 'SNAPSHOT' => 'tar', +]; + +$ALTERNATEPATH = [ + '7z' => '7-ZIP', + 'makensis.exe' => 'NSIS', +]; + +$RPMSUBVERSION = 'auto'; +$RPMDIR = ''; +if (is_dir('/usr/src/redhat')) { $RPMDIR = '/usr/src/redhat'; } // redhat +if (is_dir('/usr/src/packages')) { $RPMDIR = '/usr/src/packages'; } // opensuse +if (is_dir('/usr/src/RPM')) { $RPMDIR = '/usr/src/RPM'; } // mandrake + +$VERSION = '4.0'; + + +// ============================================================================ +// MAIN +// ============================================================================ + +// Detect script directory and name +$scriptPath = realpath($argv[0]) ?: $argv[0]; +$DIR = dirname($scriptPath); +$PROG = pathinfo($scriptPath, PATHINFO_FILENAME); +$Extension = pathinfo($scriptPath, PATHINFO_EXTENSION); +$SOURCE = dirname($DIR); +$DESTI = $SOURCE . '/build'; + +if ($SOURCE[0] !== '/' && !preg_match('/^[a-z]:/i', $SOURCE)) { + echo "Error: Launch the script $PROG.$Extension with its full path from /.\n"; + echo "$PROG.$Extension aborted.\n"; + sleep(2); + exit(1); +} + +// Check environment variables +$ENVDESTIBETARC = getenv('DESTIBETARC') ?: ''; +$ENVDESTISTABLE = getenv('DESTISTABLE') ?: ''; + +if (!$ENVDESTIBETARC || !$ENVDESTISTABLE) { + echo "Error: Missing environment variables.\n"; + echo "You must define the environment variable DESTIBETARC and DESTISTABLE to point to the\ndirectories where you want to save the generated packages.\n"; + echo "$PROG.$Extension aborted.\n"; + echo "\n"; + echo "You can set them with\n"; + echo "On Linux:\n"; + echo "export DESTIBETARC='/tmp'; export DESTISTABLE='/tmp';\n"; + echo "On Windows:\n"; + echo "set DESTIBETARC=c:/tmp\n"; + echo "set DESTISTABLE=c:/tmp\n"; + echo "\n"; + echo "Example: DESTIBETARC='/media/HDDATA1_LD/Mes Sites/Web/Dolibarr/dolibarr.org/files/lastbuild'\n"; + echo "Example: DESTISTABLE='/media/HDDATA1_LD/Mes Sites/Web/Dolibarr/dolibarr.org/files/stable'\n"; + sleep(2); + exit(1); +} + +if (!is_dir($ENVDESTIBETARC) || !is_dir($ENVDESTISTABLE)) { + echo "Error: Directory of environment variable DESTIBETARC ($ENVDESTIBETARC) or DESTISTABLE ($ENVDESTISTABLE) does not exist.\n"; + echo "$PROG.$Extension aborted.\n"; + sleep(2); + exit(1); +} + +if (!$GITREMOTENAME) { + echo "Error: environment variable GITREMOTENAME does not exist. You can set it to 'origin' or any other git remote name.\n"; + echo "$PROG.$Extension aborted.\n"; + sleep(2); + exit(1); +} + +// Detect OS type +$OS = ''; +$CR = ''; +$PROGPATH = ''; + +if (stripos(PHP_OS, 'linux') !== false || (is_dir('/etc') && is_dir('/var') && stripos(PHP_OS, 'cygwin') === false)) { + $OS = 'linux'; + $CR = ''; +} elseif (is_dir('/etc') && is_dir('/Users')) { + $OS = 'macosx'; + $CR = ''; +} elseif (stripos(PHP_OS, 'cygwin') !== false || stripos(PHP_OS, 'win') !== false || stripos(PHP_OS, 'msys') !== false) { + $OS = 'windows'; + $CR = "\r"; +} + +if (!$OS) { + echo "Error: Can't detect your OS.\n"; + echo "Can't continue.\n"; + echo "$PROG.$Extension aborted.\n"; + sleep(2); + exit(1); +} + +// Define buildroot +$TEMP = ''; +if ($OS === 'linux' || $OS === 'macosx') { + $TEMP = getenv('TEMP') ?: (getenv('TMP') ?: '/tmp'); +} +if ($OS === 'windows') { + $TEMP = getenv('TEMP') ?: (getenv('TMP') ?: 'c:/temp'); + $PROGPATH = getenv('ProgramFiles') ?: ''; +} + +if (!$TEMP || !is_dir($TEMP)) { + echo "Error: A temporary directory can not be find.\n"; + echo "Check that TEMP or TMP environment variable is set correctly.\n"; + echo "$PROG.$Extension aborted.\n"; + sleep(2); + exit(2); +} + +$BUILDROOT = $TEMP . '/buildroot'; + + +// Get version $MAJOR, $MINOR and $BUILD +$filefuncPath = $SOURCE . '/htdocs/filefunc.inc.php'; +$filefuncContent = file_get_contents($filefuncPath); +if ($filefuncContent === false) { + echo "Error: Can't open descriptor file $filefuncPath\n"; + exit(1); +} + +$PROJVERSION = ''; +if (preg_match("/define\('DOL_VERSION',\s*'([\d\.a-z\-]+)'\)/i", $filefuncContent, $matches)) { + $PROJVERSION = $matches[1]; +} + +$versionParts = explode('.', $PROJVERSION, 3); +$MAJOR = $versionParts[0] ?? ''; +$MINOR = $versionParts[1] ?? ''; +$BUILD = $versionParts[2] ?? ''; + +if ($MINOR === '') { + echo "Error can't detect version into $filefuncPath\n"; + exit(1); +} + + +// Set vars for packaging +$FILENAME = $PROJECT; +$FILENAMESNAPSHOT = "$PROJECT-snapshot"; +$FILENAMETGZ = "$PROJECT-$MAJOR.$MINOR.$BUILD"; +$FILENAMEZIP = "$PROJECT-$MAJOR.$MINOR.$BUILD"; +$FILENAMEXZ = "$PROJECT-$MAJOR.$MINOR.$BUILD"; +$FILENAMEDEB = 'see later'; +$FILENAMEEXEDOLIWAMP = "DoliWamp-$MAJOR.$MINOR.$BUILD"; + +// For RPM +$ARCH = 'noarch'; +$newbuild = $BUILD; +$newbuild = preg_replace('/(dev|alpha)/i', '0.1.a', $newbuild); // dev (fedora) +$newbuild = preg_replace('/beta(.?)/i', '0.2.beta', $newbuild); // beta (fedora) +$newbuild = preg_replace('/rc(.?)/i', '0.3.rc', $newbuild); // rc (fedora) +if (strpos($newbuild, '-') === false) { + $newbuild .= '-0.4'; // finale (fedora) +} +$REL1 = preg_replace('/-.*$/', '', $newbuild); +if ($RPMSUBVERSION === 'auto') { + $RPMSUBVERSION = preg_replace('/^.*-/', '', $newbuild); +} +$FILENAMETGZ2 = "$PROJECT-$MAJOR.$MINOR.$REL1"; +$FILENAMERPM = $FILENAMETGZ2 . '-' . $RPMSUBVERSION . '.' . $ARCH . '.rpm'; +$FILENAMERPMSRC = $FILENAMETGZ2 . '-' . $RPMSUBVERSION . '.src.rpm'; + +// For Deb +$newbuild = $BUILD; +$newbuild = preg_replace('/(dev|alpha)/i', '1', $newbuild); // dev +$newbuild = preg_replace('/beta(.?)/i', '2', $newbuild); // beta +$newbuild = preg_replace('/rc(.?)/i', '3', $newbuild); // rc +if (strpos($newbuild, '-') === false) { + $newbuild .= '-4'; // finale +} +// now newbuild is 0-1 or 0-4 for example +$build = preg_replace('/-.*$/', '', $newbuild); +// now build is 0 for example +$FILENAMEDEBNATIVE = "{$PROJECT}_{$MAJOR}.{$MINOR}.{$build}"; +$FILENAMEDEB = "{$PROJECT}_{$MAJOR}.{$MINOR}.{$newbuild}"; +$FILENAMEDEBSHORT = "{$PROJECT}_{$MAJOR}.{$MINOR}.{$build}"; + + +// Parse command line arguments +$copyalreadydone = 0; +$batch = 0; +$target = ''; +$PREFIX = ''; + +for ($i = 1; $i < $argc; $i++) { + if (preg_match('/^-*target=(\w+)/i', $argv[$i], $m)) { + $target = $m[1]; + $batch = 1; + } + if (preg_match('/^-*desti=(.+)/i', $argv[$i], $m)) { + $DESTI = $m[1]; + } + if (preg_match('/^-*prefix=(.+)/i', $argv[$i], $m)) { + $PREFIX = $m[1]; + $FILENAMESNAPSHOT .= '-' . $PREFIX; + } +} + +// Force output dir if env vars are defined +if ($ENVDESTIBETARC && preg_match('/[a-z]/i', $BUILD)) { + $DESTI = $ENVDESTIBETARC; +} +if ($ENVDESTISTABLE && preg_match('/^[0-9]+$/', $BUILD)) { + $DESTI = $ENVDESTISTABLE; +} + +// Force target site for publishing if env vars are defined +$envPublishBetarc = getenv('PUBLISHBETARC') ?: ''; +$envPublishStable = getenv('PUBLISHSTABLE') ?: ''; +if ($envPublishBetarc && preg_match('/[a-z]/i', $BUILD)) { + $PUBLISHBETARC = $envPublishBetarc; +} +if ($envPublishStable && preg_match('/^[0-9]+$/', $BUILD)) { + $PUBLISHSTABLE = $envPublishStable; +} + +echo "Makepack version $VERSION\n"; +echo "Building/publishing package name: $PROJECT\n"; +echo "Building/publishing package version: $MAJOR.$MINOR.$BUILD\n"; +echo "Source directory (SOURCE): $SOURCE\n"; +echo "Target directory (DESTI) : $DESTI\n"; + + +// ============================================================================ +// Choose package targets +// ============================================================================ + +$CHOOSEDTARGET = []; +$CHOOSEDPUBLISH = []; + +if ($target) { + $targetUpper = strtoupper($target); + if ($targetUpper === 'ALL') { + foreach ($LISTETARGET as $key) { + if ($key !== 'SNAPSHOT' && $key !== 'SF' && $key !== 'ASSO') { + $CHOOSEDTARGET[$key] = 1; + } + } + } + if ($targetUpper !== 'ALL' && $targetUpper !== 'SF' && $targetUpper !== 'ASSO') { + $CHOOSEDTARGET[$targetUpper] = 1; + } + if ($targetUpper === 'SF') { + $CHOOSEDPUBLISH['SF'] = 1; + } + if ($targetUpper === 'ASSO') { + $CHOOSEDPUBLISH['ASSO'] = 1; + } +} else { + $found = false; + $NUM_SCRIPT = ''; + + while (!$found) { + $cpt = 0; + printf(" %2d - %-14s (%s)\n", $cpt, 'ALL (1..10)', 'Need ' . implode(',', array_values($REQUIREMENTTARGET))); + $cpt++; + printf(" %2d - %-14s\n", $cpt, 'Generate check file'); + foreach ($LISTETARGET as $tgt) { + $cpt++; + printf(" %2d - %-14s (%s)\n", $cpt, $tgt, 'Need ' . $REQUIREMENTTARGET[$tgt]); + } + $cpt = 98; + printf(" %2d - %-14s (%s)\n", $cpt, 'ASSO (publish)', 'Need ' . $REQUIREMENTPUBLISH['ASSO']); + $cpt = 99; + printf(" %2d - %-14s (%s)\n", $cpt, 'SF (publish)', 'Need ' . $REQUIREMENTPUBLISH['SF']); + + $NUM_SCRIPT = prompt("Choose one target number or several separated with space (0 - $cpt): "); + + if (!preg_match('/^[0-9\s]+$/', $NUM_SCRIPT)) { + echo "This is not a valid package number list.\n"; + } else { + $found = true; + } + } + + echo "\n"; + + if ($NUM_SCRIPT === '98') { + $CHOOSEDPUBLISH['ASSO'] = 1; + } elseif ($NUM_SCRIPT === '99') { + $CHOOSEDPUBLISH['SF'] = 1; + } elseif ($NUM_SCRIPT === '0') { + $CHOOSEDTARGET['-CHKSUM'] = 1; + foreach ($LISTETARGET as $key) { + if ($key !== 'SNAPSHOT' && $key !== 'ASSO' && $key !== 'SF') { + $CHOOSEDTARGET[$key] = 1; + } + } + } elseif ($NUM_SCRIPT === '1') { + $CHOOSEDTARGET['-CHKSUM'] = 1; + } else { + foreach (preg_split('/\s+/', $NUM_SCRIPT, -1, PREG_SPLIT_NO_EMPTY) as $num) { + $idx = (int) $num - 2; + if (isset($LISTETARGET[$idx])) { + $CHOOSEDTARGET[$LISTETARGET[$idx]] = 1; + } + } + } +} + + +// ============================================================================ +// Test if requirements are ok +// ============================================================================ + +$atleastonerpm = 0; +ksort($CHOOSEDTARGET); + +foreach ($CHOOSEDTARGET as $tgt => $val) { + if (preg_match('/RPM/i', $tgt)) { + if ($atleastonerpm && $DESTI === "$SOURCE/build") { + echo "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(1); + } + $atleastonerpm = 1; + } + + if (!isset($REQUIREMENTTARGET[$tgt])) { + continue; + } + + foreach (preg_split('/[,\s]+/', $REQUIREMENTTARGET[$tgt], -1, PREG_SPLIT_NO_EMPTY) as $req) { + echo "Test requirement for target $tgt: Search '$req'... "; + + $newreq = $req; + $newparam = ''; + if ($newreq === 'zip') { $newparam .= '-h'; } + if ($newreq === 'xz') { $newparam .= '-h'; } + + $cmd = "\"$newreq\" $newparam 2>&1"; + echo "Test command $cmd... "; + + $outputLines = []; + exec($cmd, $outputLines, $coderetour); + $ret = implode("\n", $outputLines); + + if ($coderetour !== 0 && (($coderetour === 1 && $OS === 'windows' && !preg_match('/Usage/i', $ret)) || ($coderetour === 127 && $OS !== 'windows')) && $PROGPATH) { + // Not found, try in PROGPATH + $altPath = $ALTERNATEPATH[$req] ?? ''; + $outputLines = []; + exec("\"$PROGPATH/$altPath/$req\" 2>&1", $outputLines, $coderetour); + $ret = implode("\n", $outputLines); + $REQUIREMENTTARGET[$tgt] = "$PROGPATH/$altPath/$req"; + } + + if ($coderetour !== 0 && (($coderetour === 1 && $OS === 'windows' && !preg_match('/Usage/i', $ret)) || ($coderetour === 127 && $OS !== 'windows'))) { + // Not found error + echo "Not found\nCan't build target $tgt. Requirement '$req' not found in PATH\n"; + $CHOOSEDTARGET[$tgt] = -1; + break; + } else { + echo " Found $req\n"; + } + } +} + +echo "\n"; + + +// ============================================================================ +// Check if there is at least one target to build +// ============================================================================ + +$nboftargetok = 0; +$nboftargetneedbuildroot = 0; +$nbofpublishneedtag = 0; +$nbofpublishneedchangelog = 0; + +ksort($CHOOSEDTARGET); +foreach ($CHOOSEDTARGET as $tgt => $val) { + if ($tgt === '-CHKSUM') { $nbofpublishneedchangelog++; } + if ($val < 0) { continue; } + if ($tgt !== 'EXE' && $tgt !== 'EXEDOLIWAMP' && $tgt !== '-CHKSUM') { + $nboftargetneedbuildroot++; + } + $nboftargetok++; +} + +ksort($CHOOSEDPUBLISH); +foreach ($CHOOSEDPUBLISH as $tgt => $val) { + if ($val < 0) { continue; } + if ($tgt === 'ASSO') { $nbofpublishneedchangelog++; } + if ($tgt === 'SF') { $nbofpublishneedchangelog++; $nbofpublishneedtag++; } + $nboftargetok++; +} + + +if ($nboftargetok) { + // ======================================================================== + // Check Changelog + // ======================================================================== + + if ($nbofpublishneedchangelog) { + $TMPBUILDTOCHECKCHANGELOG = preg_replace('/\-rc\d*/', '', $BUILD); + $TMPBUILDTOCHECKCHANGELOG = preg_replace('/\-beta\d*/', '', $TMPBUILDTOCHECKCHANGELOG); + + echo "\nCheck if ChangeLog is ok for version $MAJOR.$MINOR.$TMPBUILDTOCHECKCHANGELOG\n"; + $ret = run("grep \"ChangeLog for $MAJOR.$MINOR.$TMPBUILDTOCHECKCHANGELOG\" \"$SOURCE/ChangeLog\" 2>&1"); + + if (!trim($ret)) { + echo colorText("Error: The ChangeLogFile was not updated. Run the following command before building package for $MAJOR.$MINOR.$BUILD:\n", 'yellow'); + } else { + echo "ChangeLog for $MAJOR.$MINOR.$BUILD was found into '$SOURCE/ChangeLog'. But you can regenerate it with command:\n"; + } + + if (!$BUILD || $BUILD === '0-rc') { + // For a major version + echo 'cd ~/git/dolibarr_' . $MAJOR . '.' . $MINOR . '; git log `git rev-list --boundary ' . $MAJOR . '.' . $MINOR . '..origin/develop | grep ^- | cut -c2- | head -n 1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa'; + } else { + // For a maintenance release + echo 'cd ~/git/dolibarr_' . $MAJOR . '.' . $MINOR . '; git log ' . $MAJOR . '.' . $MINOR . '.' . ($BUILD - 1) . '.. | grep -v "Merge branch" | grep -v "Merge pull" | grep "^ " | sed -e "s/^[0-9a-z]* *//" | grep -e \'^FIX\|NEW\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa'; + } + echo "\n"; + + if (!trim($ret)) { + $WAITKEY = prompt("\nPress F to force and continue anyway (or other key to stop)... "); + if ($WAITKEY !== 'F') { + echo "Canceled.\n"; + exit(0); + } + } + } + + + // ======================================================================== + // Build xml check file + // ======================================================================== + + if (isset($CHOOSEDTARGET['-CHKSUM']) && $CHOOSEDTARGET['-CHKSUM'] > 0) { + echo "Go to directory $SOURCE\n"; + $olddir = getcwd(); + chdir($SOURCE); + + echo "Clean $SOURCE/htdocs/includes/autoload.php\n"; + run("rm -f $SOURCE/htdocs/includes/autoload.php"); + + $ret = run("git ls-files . --exclude-standard --others"); + if (trim($ret)) { + echo "Some files exists in source directory and are not indexed neither excluded in .gitignore.\n"; + echo $ret; + echo "Canceled.\n"; + exit(0); + } + + echo "Create xml check file with md5 checksum with command php $SOURCE/build/generate_filelist_xml.php release=$MAJOR.$MINOR.$BUILD\n"; + $outputLines = []; + exec("php $SOURCE/build/generate_filelist_xml.php release=$MAJOR.$MINOR.$BUILD", $outputLines, $retcode); + $ret = implode("\n", $outputLines); + if ($retcode !== 0) { + echo "Error running generate_filelist_xml.php please check\n"; + echo $ret; + echo "Canceled.\n"; + exit(0); + } + echo $ret . "\n"; + + // Copy to final dir + $NEWDESTI = $DESTI; + if (!is_dir("$NEWDESTI/signatures")) { + mkdir("$NEWDESTI/signatures", 0777, true); + } + echo "Copy \"$SOURCE/htdocs/install/filelist-$MAJOR.$MINOR.$BUILD.xml\" to $NEWDESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml\n"; + copy("$SOURCE/htdocs/install/filelist-$MAJOR.$MINOR.$BUILD.xml", "$NEWDESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml"); + } + + + // ======================================================================== + // Update GIT tag if required + // ======================================================================== + + if ($nbofpublishneedtag) { + echo "Go to directory $SOURCE\n"; + $olddir = getcwd(); + chdir($SOURCE); + + echo 'Run git tag -a -m "' . $MAJOR . '.' . $MINOR . '.' . $BUILD . '" "' . $MAJOR . '.' . $MINOR . '.' . $BUILD . '"' . "\n"; + $ret = run("git tag -a -m \"$MAJOR.$MINOR.$BUILD\" \"$MAJOR.$MINOR.$BUILD\" 2>&1"); + + if (preg_match('/(already exists|existe déjà)/', $ret)) { + $QUESTIONOVERWRITETAG = prompt("WARNING: Tag $MAJOR.$MINOR.$BUILD already exists. Overwrite (y/N) ? "); + if (preg_match('/[oy]/i', $QUESTIONOVERWRITETAG)) { + echo 'Run git tag -a -f -m "' . $MAJOR . '.' . $MINOR . '.' . $BUILD . '" "' . $MAJOR . '.' . $MINOR . '.' . $BUILD . '"' . "\n"; + run("git tag -a -f -m \"$MAJOR.$MINOR.$BUILD\" \"$MAJOR.$MINOR.$BUILD\""); + echo "Run git push $GITREMOTENAME -f --tags\n"; + run("git push $GITREMOTENAME -f --tags"); + } + } else { + echo "Run git push $GITREMOTENAME --tags\n"; + run("git push $GITREMOTENAME --tags"); + } + + chdir($olddir); + } + + + // ======================================================================== + // Update buildroot if required + // ======================================================================== + + if ($nboftargetneedbuildroot) { + if (!$copyalreadydone) { + echo "Creation of a buildroot used for all packages\n"; + + echo "Delete directory $BUILDROOT\n"; + run("rm -fr \"$BUILDROOT\""); + + @mkdir($BUILDROOT, 0777, true); + @mkdir("$BUILDROOT/$PROJECT", 0777, true); + echo "Copy $SOURCE into $BUILDROOT/$PROJECT\n"; + run("cp -pr \"$SOURCE\" \"$BUILDROOT/$PROJECT\""); + } + + echo "Clean $BUILDROOT\n"; + run("rm -f $BUILDROOT/$PROJECT/.buildpath"); + run("rm -fr $BUILDROOT/$PROJECT/.cache"); + run("rm -fr $BUILDROOT/$PROJECT/.codeclimate"); + run("rm -fr $BUILDROOT/$PROJECT/.externalToolBuilders"); + run("rm -fr $BUILDROOT/$PROJECT/.git*"); + run("rm -fr $BUILDROOT/$PROJECT/.project"); + run("rm -fr $BUILDROOT/$PROJECT/.pydevproject"); + run("rm -fr $BUILDROOT/$PROJECT/.settings"); + run("rm -fr $BUILDROOT/$PROJECT/.scrutinizer.yml"); + run("rm -fr $BUILDROOT/$PROJECT/.stickler.yml"); + run("rm -fr $BUILDROOT/$PROJECT/.travis.yml"); + run("rm -fr $BUILDROOT/$PROJECT/.tx"); + run("rm -f $BUILDROOT/$PROJECT/build.xml"); + run("rm -f $BUILDROOT/$PROJECT/phpstan.neon"); + run("rm -f $BUILDROOT/$PROJECT/pom.xml"); + run("rm -f $BUILDROOT/$PROJECT/README-*.md"); + + run("rm -fr $BUILDROOT/$PROJECT/build/html"); + run("rm -f $BUILDROOT/$PROJECT/build/Doli*-*"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr_*.deb"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr_*.dsc"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr_*.tar.gz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr_*.tar.xz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.deb"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.rpm"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.tar"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.tar.gz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.tar.xz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.tgz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.xz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.zip"); + run("rm -f $BUILDROOT/$PROJECT/build/doxygen/doxygen_warnings.log"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/cache.manifest"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.mysql"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.nova*"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.old"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.pgsql"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf*sav*"); + + run("rm -f $BUILDROOT/$PROJECT/htdocs/install/mssql/README"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/install/mysql/README"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/install/pgsql/README"); + + run("rm -fr $BUILDROOT/$PROJECT/htdocs/install/mssql"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/install/sqlite3"); + + run("rm -fr $BUILDROOT/$PROJECT/node_modules"); + + run("rm -fr $BUILDROOT/$PROJECT/dev/ansible"); + run("rm -fr $BUILDROOT/$PROJECT/dev/codesniffer"); + run("rm -fr $BUILDROOT/$PROJECT/dev/codetemplates"); + run("rm -fr $BUILDROOT/$PROJECT/dev/examples/ldap"); + run("rm -fr $BUILDROOT/$PROJECT/dev/examples/zapier"); + run("rm -fr $BUILDROOT/$PROJECT/dev/initdata"); + run("rm -fr $BUILDROOT/$PROJECT/dev/initdemo"); + run("rm -fr $BUILDROOT/$PROJECT/dev/resources/dbmodel"); + run("rm -fr $BUILDROOT/$PROJECT/dev/resources/iso-normes"); + run("rm -fr $BUILDROOT/$PROJECT/dev/resources/licence"); + run("rm -fr $BUILDROOT/$PROJECT/dev/mail"); + run("rm -fr $BUILDROOT/$PROJECT/dev/multitail"); + run("rm -fr $BUILDROOT/$PROJECT/dev/phpcheckstyle"); + run("rm -fr $BUILDROOT/$PROJECT/dev/phpunit"); + run("rm -fr $BUILDROOT/$PROJECT/dev/security"); + run("rm -fr $BUILDROOT/$PROJECT/dev/spec"); + run("rm -fr $BUILDROOT/$PROJECT/dev/test"); + run("rm -fr $BUILDROOT/$PROJECT/dev/uml"); + run("rm -fr $BUILDROOT/$PROJECT/dev/vagrant"); + run("rm -fr $BUILDROOT/$PROJECT/dev/xdebug"); + run("rm -f $BUILDROOT/$PROJECT/dev/dolibarr_changes.txt"); + run("rm -f $BUILDROOT/$PROJECT/dev/README"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot2.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot3.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot4.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot5.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot6.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot7.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot8.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot9.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot10.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot11.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot12.png"); + + // Security to avoid to package data files + echo "Remove documents dir\n"; + run("rm -fr $BUILDROOT/$PROJECT/document"); + run("rm -fr $BUILDROOT/$PROJECT/documents"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/document"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/documents"); + + echo "Remove subdir of custom dir\n"; + echo "find $BUILDROOT/$PROJECT/htdocs/custom/* -type d -exec rm -fr {} \\;\n"; + run("find $BUILDROOT/$PROJECT/htdocs/custom/* -type d -exec rm -fr {} \\; >/dev/null 2>&1"); + echo "find $BUILDROOT/$PROJECT/htdocs/custom/* -type l -exec rm -fr {} \\;\n"; + run("find $BUILDROOT/$PROJECT/htdocs/custom/* -type l -exec rm -fr {} \\; >/dev/null 2>&1"); + + // Remove known external modules to avoid any error when packaging from env where external modules are tested + run("rm -fr $BUILDROOT/$PROJECT/htdocs/abricot*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/accountingexport*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/allscreens*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/ancotec*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/cabinetmed*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/calling*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/bootstrap*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/dolimed*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/dolimod*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/factory*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/forceproject*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/lead*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/management*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/multicompany*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/ndf*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/nltechno*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/nomenclature*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/of/"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/oscim*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/pos*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/teclib*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/timesheet*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/webmail*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/theme/common/fontawesome-5/svgs"); + + // Remove other test files + run("rm -fr $BUILDROOT/$PROJECT/htdocs/public/test"); + run("rm -fr $BUILDROOT/$PROJECT/test"); + run("rm -fr $BUILDROOT/$PROJECT/Thumbs.db $BUILDROOT/$PROJECT/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/*/*/Thumbs.db"); + run("rm -f $BUILDROOT/$PROJECT/.cvsignore $BUILDROOT/$PROJECT/*/.cvsignore $BUILDROOT/$PROJECT/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/*/*/.cvsignore"); + run("rm -f $BUILDROOT/$PROJECT/.gitignore $BUILDROOT/$PROJECT/*/.gitignore $BUILDROOT/$PROJECT/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/*/*/.gitignore"); + + // Remove files installed by composer + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/geoip/sample*.*"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/bin"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/ckeditor/adapters"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/ckeditor/samples"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/_source"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/composer"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/doctrine"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/jquery/plugins/multiselect/MIT-LICENSE.txt"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/jquery/plugins/select2/release.sh"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/doc"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/example"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/test"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mobiledetect/mobiledetectlib/.gitmodules"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mobiledetect/mobiledetectlib/docs"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/.github"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/docs"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/samples"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/scripts"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/src"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/test"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nusoap/samples"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/php-iban/docs"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/tests"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/stripe/tests"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/stripe/LICENSE"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/examples"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/dejavu-fonts-ttf-*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/freefont-*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/ae_fonts_*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/utils"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/tools"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/vendor"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/webmozart"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/autoload.php"); + } + + + // ======================================================================== + // Build package for each target + // ======================================================================== + + ksort($CHOOSEDTARGET); + foreach ($CHOOSEDTARGET as $tgt => $val) { + if ($val < 0) { continue; } + if ($tgt === '-CHKSUM') { continue; } + + echo "\nBuild package for target $tgt\n"; + + + // --- SNAPSHOT --- + if ($tgt === 'SNAPSHOT') { + $NEWDESTI = $DESTI; + + echo "Remove target $FILENAMESNAPSHOT.tgz...\n"; + @unlink("$NEWDESTI/$FILENAMESNAPSHOT.tgz"); + + run("rm -fr $BUILDROOT/$FILENAMESNAPSHOT"); + echo "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMESNAPSHOT\n"; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMESNAPSHOT\""; + run($cmd); + + echo "Compress $BUILDROOT into $FILENAMESNAPSHOT.tgz...\n"; + $cmd = "tar --exclude doli*.tgz --exclude doli*.deb --exclude doli*.exe --exclude doli*.xz --exclude doli*.zip --exclude doli*.rpm --exclude .cache --exclude .settings --exclude conf.php --exclude conf.php.mysql --exclude conf.php.old --exclude conf.php.postgres --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$FILENAMESNAPSHOT.tgz\" $FILENAMESNAPSHOT"; + echo $cmd . "\n"; + run($cmd); + + // Move to final dir + echo "Move $FILENAMESNAPSHOT.tgz to $NEWDESTI/$FILENAMESNAPSHOT.tgz\n"; + run("mv \"$FILENAMESNAPSHOT.tgz\" \"$NEWDESTI/$FILENAMESNAPSHOT.tgz\""); + continue; + } + + + // --- TGZ --- + if ($tgt === 'TGZ') { + $NEWDESTI = $DESTI; + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/standard'); + if (is_dir($DESTI . '/standard')) { $NEWDESTI = $DESTI . '/standard'; } + } + + echo "Remove target $FILENAMETGZ.tgz...\n"; + @unlink("$NEWDESTI/$FILENAMETGZ.tgz"); + + run("rm -fr $BUILDROOT/$FILENAMETGZ"); + echo "Copy $BUILDROOT/$PROJECT/ to $BUILDROOT/$FILENAMETGZ\n"; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT/\" \"$BUILDROOT/$FILENAMETGZ\""; + run($cmd); + + run("rm -fr $BUILDROOT/$FILENAMETGZ/build/exe"); + run("rm -fr $BUILDROOT/$FILENAMETGZ/htdocs/includes/ckeditor/_source"); + + echo "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"; + echo "$cmd\n"; + run($cmd); + + // Move to final dir + echo "Move $BUILDROOT/$FILENAMETGZ.tgz to $NEWDESTI/$FILENAMETGZ.tgz\n"; + run("mv \"$BUILDROOT/$FILENAMETGZ.tgz\" \"$NEWDESTI/$FILENAMETGZ.tgz\""); + continue; + } + + + // --- XZ --- + if ($tgt === 'XZ') { + $NEWDESTI = $DESTI; + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/standard'); + if (is_dir($DESTI . '/standard')) { $NEWDESTI = $DESTI . '/standard'; } + } + + echo "Remove target $FILENAMEXZ.xz...\n"; + @unlink("$NEWDESTI/$FILENAMEXZ.xz"); + + run("rm -fr $BUILDROOT/$FILENAMEXZ"); + echo "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMEXZ\n"; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMEXZ\""; + run($cmd); + + run("rm -fr $BUILDROOT/$FILENAMEXZ/build/exe"); + run("rm -fr $BUILDROOT/$FILENAMEXZ/htdocs/includes/ckeditor/_source"); + + echo "Compress $FILENAMEXZ into $FILENAMEXZ.xz...\n"; + + echo "Go to directory $BUILDROOT\n"; + $olddir = getcwd(); + chdir($BUILDROOT); + $cmd = "xz -9 -r $BUILDROOT/$FILENAMEXZ.xz *"; + echo $cmd . "\n"; + run($cmd); + chdir($olddir); + + // Move to final dir + echo "Move $FILENAMEXZ.xz to $NEWDESTI/$FILENAMEXZ.xz\n"; + run("mv \"$BUILDROOT/$FILENAMEXZ.xz\" \"$NEWDESTI/$FILENAMEXZ.xz\""); + continue; + } + + + // --- ZIP --- + if ($tgt === 'ZIP') { + $NEWDESTI = $DESTI; + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/standard'); + if (is_dir($DESTI . '/standard')) { $NEWDESTI = $DESTI . '/standard'; } + } + + echo "Remove target $FILENAMEZIP.zip...\n"; + @unlink("$NEWDESTI/$FILENAMEZIP.zip"); + + run("rm -fr $BUILDROOT/$FILENAMEZIP"); + echo "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMEZIP\n"; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMEZIP\""; + run($cmd); + + run("rm -fr $BUILDROOT/$FILENAMEZIP/build/exe"); + run("rm -fr $BUILDROOT/$FILENAMEZIP/htdocs/includes/ckeditor/_source"); + + echo "Compress $FILENAMEZIP into $FILENAMEZIP.zip...\n"; + + echo "Go to directory $BUILDROOT\n"; + $olddir = getcwd(); + chdir($BUILDROOT); + $cmd = "7z a -r -tzip -xr@\"$BUILDROOT/$FILENAMEZIP/build/zip/zip_exclude.txt\" -mx $BUILDROOT/$FILENAMEZIP.zip $FILENAMEZIP/*"; + echo $cmd . "\n"; + run($cmd); + chdir($olddir); + + // Move to final dir + echo "Move $FILENAMEZIP.zip to $NEWDESTI/$FILENAMEZIP.zip\n"; + run("mv \"$BUILDROOT/$FILENAMEZIP.zip\" \"$NEWDESTI/$FILENAMEZIP.zip\""); + continue; + } + + + // --- RPM --- + if (preg_match('/RPM/', $tgt)) { + $NEWDESTI = $DESTI; + $subdir = 'package_rpm_generic'; + if (preg_match('/FEDO/i', $tgt)) { $subdir = 'package_rpm_redhat-fedora'; } + if (preg_match('/MAND/i', $tgt)) { $subdir = 'package_rpm_mandriva'; } + if (preg_match('/OPEN/i', $tgt)) { $subdir = 'package_rpm_opensuse'; } + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/' . $subdir); + if (is_dir($DESTI . '/' . $subdir)) { $NEWDESTI = $DESTI . '/' . $subdir; } + } + + if ($RPMDIR === '') { $RPMDIR = (getenv('HOME') ?: '') . '/rpmbuild'; } + + echo "Version is $MAJOR.$MINOR.$REL1-$RPMSUBVERSION\n"; + + echo "Remove target $FILENAMERPM...\n"; + @unlink("$NEWDESTI/$FILENAMERPM"); + echo "Remove target $FILENAMERPMSRC...\n"; + @unlink("$NEWDESTI/$FILENAMERPMSRC"); + + echo "Create directory $BUILDROOT/$FILENAMETGZ2\n"; + run("rm -fr $BUILDROOT/$FILENAMETGZ2"); + + echo "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMETGZ2\n"; + $cmd = "cp -pr '$BUILDROOT/$PROJECT' '$BUILDROOT/$FILENAMETGZ2'"; + run($cmd); + + echo "Set permissions on files/dir\n"; + run("chmod -R 755 $BUILDROOT/$FILENAMETGZ2"); + $cmd = "find $BUILDROOT/$FILENAMETGZ2 -type f -exec chmod 644 {} \\; "; + run($cmd); + + // Build tgz + echo "Compress $FILENAMETGZ2 into $FILENAMETGZ2.tgz...\n"; + run("tar --exclude-from \"$SOURCE/build/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" -czvf \"$BUILDROOT/$FILENAMETGZ2.tgz\" $FILENAMETGZ2"); + + if (!is_dir($RPMDIR . '/SOURCES')) { @mkdir($RPMDIR . '/SOURCES', 0777, true); } + echo "Move $BUILDROOT/$FILENAMETGZ2.tgz to $RPMDIR/SOURCES/$FILENAMETGZ2.tgz\n"; + $cmd = "mv $BUILDROOT/$FILENAMETGZ2.tgz $RPMDIR/SOURCES/$FILENAMETGZ2.tgz"; + run($cmd); + + $BUILDFIC = $FILENAME . '.spec'; + $BUILDFICSRC = $FILENAME . '_generic.spec'; + if (preg_match('/FEDO/i', $tgt)) { $BUILDFICSRC = $FILENAME . '_fedora.spec'; } + if (preg_match('/MAND/i', $tgt)) { $BUILDFICSRC = $FILENAME . '_mandriva.spec'; } + if (preg_match('/OPEN/i', $tgt)) { $BUILDFICSRC = $FILENAME . '_opensuse.spec'; } + + $day = str_pad(date('j'), 2, ' ', STR_PAD_LEFT); + $datestring = date('D M ') . $day . date(' Y'); + $changelogstring = "* $datestring Laurent Destailleur (eldy) $MAJOR.$MINOR.$REL1-$RPMSUBVERSION\n- Upstream release\n"; + + echo "Generate file $BUILDROOT/$BUILDFIC from $SOURCE/build/rpm/$BUILDFICSRC\n"; + $specContent = file_get_contents("$SOURCE/build/rpm/$BUILDFICSRC"); + if ($specContent === false) { + echo "Error: Can't read $SOURCE/build/rpm/$BUILDFICSRC\n"; + exit(1); + } + $specContent = str_replace('__FILENAMETGZ__', $FILENAMETGZ, $specContent); + $specContent = str_replace('__VERSION__', "$MAJOR.$MINOR.$REL1", $specContent); + $specContent = str_replace('__RELEASE__', $RPMSUBVERSION, $specContent); + $specContent = str_replace('__CHANGELOGSTRING__', $changelogstring, $specContent); + file_put_contents("$BUILDROOT/$BUILDFIC", $specContent); + + echo "Copy patch file to $RPMDIR/SOURCES\n"; + run("cp \"$SOURCE/build/rpm/dolibarr-forrpm.patch\" \"$RPMDIR/SOURCES\""); + run("chmod 644 $RPMDIR/SOURCES/dolibarr-forrpm.patch"); + + echo "Launch RPM build (rpmbuild --clean -ba $BUILDROOT/$BUILDFIC)\n"; + run("rpmbuild --clean -ba $BUILDROOT/$BUILDFIC"); + + // Move to final dir + echo "Move $RPMDIR/RPMS/$ARCH/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.{$ARCH}.rpm into $NEWDESTI/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.{$ARCH}.rpm\n"; + $cmd = "mv $RPMDIR/RPMS/$ARCH/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.{$ARCH}.rpm \"$NEWDESTI/\""; + run($cmd); + echo "Move $RPMDIR/SRPMS/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.src.rpm into $NEWDESTI/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.src.rpm\n"; + $cmd = "mv $RPMDIR/SRPMS/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.src.rpm \"$NEWDESTI/\""; + run($cmd); + echo "Move $RPMDIR/SOURCES/$FILENAMETGZ2.tgz into $NEWDESTI/$FILENAMETGZ2.tgz\n"; + // This line was commented out in original: $cmd="mv ..."; $ret=`$cmd`; + continue; + } + + + // --- DEB --- + if ($tgt === 'DEB') { + $NEWDESTI = $DESTI; + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/package_debian-ubuntu'); + if (is_dir($DESTI . '/package_debian-ubuntu')) { $NEWDESTI = $DESTI . '/package_debian-ubuntu'; } + } + + $olddir = getcwd(); + + echo "Remove target ${FILENAMEDEB}_all.deb...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}_all.deb"); + echo "Remove target ${FILENAMEDEB}.dsc...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}.dsc"); + echo "Remove target ${FILENAMEDEB}.tar.gz...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}.tar.gz"); + echo "Remove target ${FILENAMEDEB}.changes...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}.changes"); + echo "Remove target ${FILENAMEDEB}.debian.tar.gz...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}.debian.tar.gz"); + echo "Remove target ${FILENAMEDEB}.debian.tar.xz...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}.debian.tar.xz"); + echo "Remove target ${FILENAMEDEBNATIVE}.orig.tar.gz...\n"; + @unlink("$NEWDESTI/${FILENAMEDEBNATIVE}.orig.tar.gz"); + + run("rm -fr $BUILDROOT/$PROJECT.tmp"); + run("rm -fr $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build"); + + echo "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$PROJECT.tmp\n"; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$PROJECT.tmp\""; + run($cmd); + $cmd = "cp -pr \"$BUILDROOT/$PROJECT/build/debian/apache/.htaccess\" \"$BUILDROOT/$PROJECT.tmp/build/debian/apache/.htaccess\""; + run($cmd); + + echo "Remove other files\n"; + run("rm -f $BUILDROOT/$PROJECT.tmp/README-FR.md"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/README"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/README-FR"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/aps"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/dmg"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/pad/README"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/tgz/README"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/debian/po"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/debian/source"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/changelog"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/compat"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/control*"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/copyright"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.config"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.desktop"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.docs"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.install"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.lintian-overrides"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.postrm"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.postinst"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.templates"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.templates.futur"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/rules"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/README.Debian"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/README.howto"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/watch"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/doap"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/exe"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/launchpad"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/live"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/patch"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/perl"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/rpm"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/zip"); + // Remove duplicate license files + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/plugins/scayt/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/plugins/wsc/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/plugins/scayt/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/plugins/wsc/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/php-iban/LICENSE"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/flot/LICENSE.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/ColReorder/License.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/ColVis/License.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/FixedColumns/License.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/Responsive/License.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/license.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/select2/LICENSE"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/mike42/escpos-php/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/mobiledetect/mobiledetectlib/LICENSE.txt"); + + // Remove files we don't need (already removed) + run("rm -fr $BUILDROOT/$PROJECT.tmp/.codeclimate.yml"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/.pre-commit-config.yaml"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/.vscode"); + run("find $BUILDROOT/$PROJECT.tmp/ -type f -name '.editorconfig' -exec rm {} \\;"); + run("find $BUILDROOT/$PROJECT.tmp/ -type f -name '.travis.yml' -exec rm {} \\;"); + + // Rename upstream changelog to match debian rules + run("mv $BUILDROOT/$PROJECT.tmp/ChangeLog $BUILDROOT/$PROJECT.tmp/changelog"); + + // Prepare source package (init debian dir) + echo "Create directory $BUILDROOT/$PROJECT.tmp/debian\n"; + run("mkdir \"$BUILDROOT/$PROJECT.tmp/debian\""); + + echo "Copy $SOURCE/build/debian/xxx to $BUILDROOT/$PROJECT.tmp/debian\n"; + + // Add files for dpkg-source (changelog) + $changelogContent = file_get_contents("$SOURCE/build/debian/changelog"); + if ($changelogContent === false) { + echo "Error: Can't read $SOURCE/build/debian/changelog\n"; + exit(1); + } + $changelogContent = str_replace('__VERSION__', "$MAJOR.$MINOR.$newbuild", $changelogContent); + file_put_contents("$BUILDROOT/$PROJECT.tmp/debian/changelog", $changelogContent); + + // Add files for dpkg-source + run("cp -f \"$SOURCE/build/debian/compat\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/control\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/copyright\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.desktop\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.docs\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.install\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.lintian-overrides\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.xpm\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/rules\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/watch\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -fr \"$SOURCE/build/debian/patches\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -fr \"$SOURCE/build/debian/po\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -fr \"$SOURCE/build/debian/source\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -fr \"$SOURCE/build/debian/apache\" \"$BUILDROOT/$PROJECT.tmp/debian/apache\""); + run("cp -f \"$SOURCE/build/debian/apache/.htaccess\" \"$BUILDROOT/$PROJECT.tmp/debian/apache\""); + run("cp -fr \"$SOURCE/build/debian/lighttpd\" \"$BUILDROOT/$PROJECT.tmp/debian/lighttpd\""); + // Add files also required to build binary package + run("cp -f \"$SOURCE/build/debian/dolibarr.config\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.postinst\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.postrm\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.templates\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/install.forced.php.install\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + + echo "Set permissions on files/dir\n"; + run("chmod -R 755 $BUILDROOT/$PROJECT.tmp"); + run("find $BUILDROOT/$PROJECT.tmp -type f -exec chmod 644 {} \\; "); + run("find $BUILDROOT/$PROJECT.tmp/build -name '*.php' -type f -exec chmod 755 {} \\; "); + run("find $BUILDROOT/$PROJECT.tmp/build -name '*.dpatch' -type f -exec chmod 755 {} \\; "); + run("find $BUILDROOT/$PROJECT.tmp/build -name '*.pl' -type f -exec chmod 755 {} \\; "); + run("find $BUILDROOT/$PROJECT.tmp/dev -name '*.php' -type f -exec chmod 755 {} \\; "); + run("chmod 755 $BUILDROOT/$PROJECT.tmp/debian/rules"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/dev/translation/autotranslator.class.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/actions_mymodule.class.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/api_mymodule.class.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/myobject.class.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/core/modules/modMyModule.class.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/mymoduleindex.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/myobject_card.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/myobject_list.php"); + run("chmod -R 755 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/scripts/mymodule.php"); + run("find $BUILDROOT/$PROJECT.tmp/scripts -name '*.php' -type f -exec chmod 755 {} \\; "); + run("find $BUILDROOT/$PROJECT.tmp/scripts -name '*.sh' -type f -exec chmod 755 {} \\; "); + + echo "Rename directory $BUILDROOT/$PROJECT.tmp into $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n"; + $cmd = "mv $BUILDROOT/$PROJECT.tmp $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build"; + run($cmd); + + echo "Go into directory $BUILDROOT\n"; + chdir($BUILDROOT); + + // We need a tarball to be able to build "quilt" debian package + echo "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"; + echo $cmd . "\n"; + run($cmd); + + // Creation of source package + echo "Go into directory $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n"; + chdir("$BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build"); + $cmd = "dpkg-buildpackage -us -uc --compression=gzip"; + echo "Launch DEB build ($cmd)\n"; + $ret = run("$cmd 2>&1"); + echo $ret . "\n"; + + chdir($olddir); + + echo "You can check bin package with lintian --pedantic -E -I \"$NEWDESTI/${FILENAMEDEB}_all.deb\"\n"; + echo "You can check src package with lintian --pedantic -E -I \"$NEWDESTI/${FILENAMEDEB}.dsc\"\n"; + + // Move to final dir + echo "Move *_all.deb *.dsc *.orig.tar.gz *.changes to $NEWDESTI\n"; + run("mv $BUILDROOT/*_all.deb \"$NEWDESTI/\""); + run("mv $BUILDROOT/*.dsc \"$NEWDESTI/\""); + run("mv $BUILDROOT/*.orig.tar.gz \"$NEWDESTI/\""); + run("mv $BUILDROOT/*.debian.tar.gz \"$NEWDESTI/\""); + run("mv $BUILDROOT/*.changes \"$NEWDESTI/\""); + continue; + } + + + // --- EXEDOLIWAMP --- + if ($tgt === 'EXEDOLIWAMP') { + $NEWDESTI = $DESTI; + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/package_windows'); + if (is_dir($DESTI . '/package_windows')) { $NEWDESTI = $DESTI . '/package_windows'; } + } + + echo "Remove target $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe...\n"; + @unlink("$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"); + + if ($OS === 'windows') { + echo "Check that ISCC.exe is in your PATH.\n"; + } else { + echo "Check that in your Wine setup, you have created a Z: drive that point to your / directory.\n"; + } + + $SOURCEBACK = str_replace('/', '\\', $SOURCE); + + echo "Prepare file \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.tmp.iss\" from \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.iss\"\n"; + + $issContent = file_get_contents("$SOURCE/build/exe/doliwamp/doliwamp.iss"); + if ($issContent === false) { + echo "Error: Can't read $SOURCE/build/exe/doliwamp/doliwamp.iss\n"; + exit(1); + } + $issContent = str_replace('__FILENAMEEXEDOLIWAMP__', $FILENAMEEXEDOLIWAMP, $issContent); + file_put_contents("$SOURCE/build/exe/doliwamp/doliwamp.tmp.iss", $issContent); + + echo "Compil exe $FILENAMEEXEDOLIWAMP.exe file from iss file \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.tmp.iss\" on OS $OS\n"; + + $cmd = ''; + if ($OS === 'windows') { + $cmd = "ISCC.exe \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.tmp.iss\""; + } + if ($cmd) { + echo "$cmd\n"; + $ret = run($cmd); + echo "ret=$ret\n"; + } + + // Move to final dir + echo "Move \"$SOURCE/build/$FILENAMEEXEDOLIWAMP.exe\" to $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe\n"; + @rename("$SOURCE/build/$FILENAMEEXEDOLIWAMP.exe", "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"); + + echo "Remove tmp file $SOURCE/build/exe/doliwamp/doliwamp.tmp.iss\n"; + @unlink("$SOURCE/build/exe/doliwamp/doliwamp.tmp.iss"); + + continue; + } + } + + + // ======================================================================== + // Publish package for each target + // ======================================================================== + + ksort($CHOOSEDPUBLISH); + foreach ($CHOOSEDPUBLISH as $tgt => $val) { + if ($val < 0) { continue; } + + echo "\nList of files to publish (BUILD=$BUILD)\n"; + + if ($tgt === 'ASSO' && preg_match('/[a-z]/i', $BUILD)) { + // Not stable + $filestoscansf = [ + "$DESTI/$FILENAMERPM" => 'Dolibarr installer for Fedora-Redhat-Mandriva-Opensuse (DoliRpm)', + "$DESTI/${FILENAMEDEB}_all.deb" => 'Dolibarr installer for Debian-Ubuntu (DoliDeb)', + "$DESTI/$FILENAMEEXEDOLIWAMP.exe" => 'Dolibarr installer for Windows (DoliWamp)', + "$DESTI/$FILENAMETGZ.tgz" => 'Dolibarr ERP-CRM', + "$DESTI/$FILENAMETGZ.zip" => 'Dolibarr ERP-CRM', + ]; + $filestoscanstableasso = [ + "$DESTI/$FILENAMERPM" => '', + "$DESTI/${FILENAMEDEB}_all.deb" => '', + "$DESTI/$FILENAMEEXEDOLIWAMP.exe" => '', + "$DESTI/$FILENAMETGZ.tgz" => '', + "$DESTI/$FILENAMETGZ.zip" => '', + ]; + } else { + $filestoscansf = [ + "$DESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml" => 'none', + "$DESTI/package_rpm_generic/$FILENAMERPM" => 'Dolibarr installer for Fedora-Redhat-Mandriva-Opensuse (DoliRpm)', + "$DESTI/package_rpm_generic/$FILENAMERPMSRC" => 'none', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_all.deb" => 'Dolibarr installer for Debian-Ubuntu (DoliDeb)', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_amd64.changes" => 'none', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.dsc" => 'none', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.gz" => 'none', + "$DESTI/package_debian-ubuntu/${FILENAMEDEBSHORT}.orig.tar.gz" => 'none', + "$DESTI/package_windows/$FILENAMEEXEDOLIWAMP.exe" => 'Dolibarr installer for Windows (DoliWamp)', + "$DESTI/standard/$FILENAMETGZ.tgz" => 'Dolibarr ERP-CRM', + "$DESTI/standard/$FILENAMETGZ.zip" => 'Dolibarr ERP-CRM', + ]; + $filestoscanstableasso = [ + "$DESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml" => 'signatures', + "$DESTI/package_rpm_generic/$FILENAMERPM" => 'package_rpm_generic', + "$DESTI/package_rpm_generic/$FILENAMERPMSRC" => 'package_rpm_generic', + "$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.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', + "$DESTI/standard/$FILENAMETGZ.zip" => 'standard', + ]; + } + + ksort($filestoscansf); + foreach ($filestoscansf as $file => $label) { + $filesize = @filesize($file); + $filedate = @filemtime($file); + echo $file . ' ' . ($filesize ? '(found)' : '(not found)'); + if ($filesize) { echo ' - ' . $filesize; } + if ($filedate) { echo ' - ' . date('Y-m-d H:i:s', $filedate); } + echo "\n"; + } + + if ($tgt === 'SF' || $tgt === 'ASSO') { + echo "\n"; + + $PUBLISH = ''; + if ($tgt === 'SF') { $PUBLISH = $PUBLISHSTABLE; } + if ($tgt === 'ASSO' && preg_match('/[a-z]/i', $BUILD)) { $PUBLISH = $PUBLISHBETARC . '/lastbuild'; } + if ($tgt === 'ASSO' && preg_match('/^[0-9]+$/', $BUILD)) { $PUBLISH = $PUBLISHBETARC . '/stable'; } + + $NEWPUBLISH = $PUBLISH; + prompt("Publish to target $NEWPUBLISH. Click enter or CTRL+C...\n"); + + echo "Create empty dir /tmp/emptydir. We need it to create target dir using rsync.\n"; + run("mkdir -p \"/tmp/emptydir/\""); + + $filestoscan = $filestoscansf; + ksort($filestoscan); + + foreach ($filestoscan as $file => $label) { + $filesize = @filesize($file); + if (!$filesize) { continue; } + + if ($tgt === 'SF') { + if ($label === 'none') { + continue; + } + $destFolder = "$NEWPUBLISH/$label/$MAJOR.$MINOR.$BUILD"; + } elseif ($tgt === 'ASSO' && preg_match('/stable/', $NEWPUBLISH)) { + $destFolder = "$NEWPUBLISH/" . ($filestoscanstableasso[$file] ?? ''); + } elseif ($tgt === 'ASSO' && !preg_match('/stable/', $NEWPUBLISH)) { + $destFolder = $NEWPUBLISH; + } else { + // No more used + $dirnameonly = preg_replace('/.*\/([^\/]+)\/[^\/]+$/', '$1', $file); + $destFolder = "$NEWPUBLISH/$dirnameonly"; + } + + echo "\n"; + echo "Publish file $file to $destFolder\n"; + + $command = "rsync -s -e 'ssh' --recursive /tmp/emptydir/ \"$destFolder\""; + echo "$command\n"; + run($command); + + $command = "rsync -s -e 'ssh' \"$file\" \"$destFolder\""; + echo "$command\n"; + $ret = run($command); + echo "$ret\n"; + } + } + } +} + + +// ============================================================================ +// Summary +// ============================================================================ + +echo "\n----- Summary -----\n"; + +ksort($CHOOSEDTARGET); +foreach ($CHOOSEDTARGET as $tgt => $val) { + if ($tgt === '-CHKSUM') { + echo "Checksum was generated\n"; + continue; + } + if ($val < 0) { + echo "Package $tgt not built (bad requirement).\n"; + } else { + echo "Package $tgt built successfully in $DESTI\n"; + } +} + +if (!$batch) { + prompt("\nPress key to finish..."); +} + +exit(0); diff --git a/htdocs/accountancy/bookkeeping/export.php b/htdocs/accountancy/bookkeeping/export.php index a991a548ebea9..91276dfce1a2d 100644 --- a/htdocs/accountancy/bookkeeping/export.php +++ b/htdocs/accountancy/bookkeeping/export.php @@ -1,7 +1,7 @@ * Copyright (C) 2013-2016 Florian Henry - * Copyright (C) 2013-2024 Alexandre Spangaro + * Copyright (C) 2013-2026 Alexandre Spangaro * Copyright (C) 2022 Lionel Vessiller * Copyright (C) 2016-2017 Laurent Destailleur * Copyright (C) 2018-2024 Frédéric France @@ -845,19 +845,39 @@ } // add documents in an archive for some accountancy export format - if (getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV') == AccountancyExport::$EXPORT_TYPE_QUADRATUS - || getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV') == AccountancyExport::$EXPORT_TYPE_FEC - || getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV') == AccountancyExport::$EXPORT_TYPE_FEC2 - ) { - $form_question['notifiedexportfull'] = array( - 'name' => 'notifiedexportfull', - 'type' => 'checkbox', - 'label' => $langs->trans('NotifiedExportFull'), - 'value' => 'false', - ); - } + $exportTypesWithDocs = array( + AccountancyExport::$EXPORT_TYPE_QUADRATUS, + AccountancyExport::$EXPORT_TYPE_FEC, + AccountancyExport::$EXPORT_TYPE_FEC2 + ); + + $form_question['notifiedexportfull'] = array( + 'name' => 'notifiedexportfull', + 'type' => 'checkbox', + 'label' => $langs->trans('NotifiedExportFull'), + 'value' => 'false', + ); + + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").'...', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 500, 700); + + $formconfirm .= ''; } // Print form confirm diff --git a/htdocs/accountancy/bookkeeping/list.php b/htdocs/accountancy/bookkeeping/list.php index 20b5256d17222..f939864d3345a 100644 --- a/htdocs/accountancy/bookkeeping/list.php +++ b/htdocs/accountancy/bookkeeping/list.php @@ -1,7 +1,7 @@ * Copyright (C) 2013-2016 Florian Henry - * Copyright (C) 2013-2024 Alexandre Spangaro + * Copyright (C) 2013-2025 Alexandre Spangaro * Copyright (C) 2022 Lionel Vessiller * Copyright (C) 2016-2017 Laurent Destailleur * Copyright (C) 2018-2024 Frédéric France @@ -49,7 +49,7 @@ */ // Load translation files required by the page -$langs->loadLangs(array("accountancy", "compta")); +$langs->loadLangs(array("accountancy", "categories", "compta")); // Get Parameters $socid = GETPOSTINT('socid'); @@ -372,7 +372,12 @@ $listofaccountsforgroup2[] = "'".$db->escape($tmpval['account_number'])."'"; } } - $filter['t.search_accounting_code_in'] = implode(',', $listofaccountsforgroup2); + if (!empty($listofaccountsforgroup2)) { + $filter['t.search_accounting_code_in'] = implode(',', $listofaccountsforgroup2); + } else { + $filter['t.search_accounting_code_in'] = "''"; + setEventMessages($langs->trans("ThisCategoryHasNoItems"), null, 'warnings'); + } $param .= '&search_account_category='.urlencode((string) ($search_account_category)); } if (!empty($search_accountancy_code)) { diff --git a/htdocs/accountancy/bookkeeping/listbyaccount.php b/htdocs/accountancy/bookkeeping/listbyaccount.php index 3c6da12d0f6db..6375d45e7b93d 100644 --- a/htdocs/accountancy/bookkeeping/listbyaccount.php +++ b/htdocs/accountancy/bookkeeping/listbyaccount.php @@ -47,7 +47,7 @@ */ // Load translation files required by the page -$langs->loadLangs(array("accountancy", "compta")); +$langs->loadLangs(array("accountancy", "categories", "compta")); $action = GETPOST('action', 'aZ09'); $socid = GETPOSTINT('socid'); @@ -328,7 +328,12 @@ $listofaccountsforgroup2[] = "'".$db->escape($tmpval['account_number'])."'"; } } - $filter['t.search_accounting_code_in'] = implode(',', $listofaccountsforgroup2); + if (!empty($listofaccountsforgroup2)) { + $filter['t.search_accounting_code_in'] = implode(',', $listofaccountsforgroup2); + } else { + $filter['t.search_accounting_code_in'] = "''"; + setEventMessages($langs->trans("ThisCategoryHasNoItems"), null, 'warnings'); + } $param .= '&search_account_category='.urlencode((string) ($search_account_category)); } if (!empty($search_accountancy_code_start)) { diff --git a/htdocs/accountancy/class/accountancyexport.class.php b/htdocs/accountancy/class/accountancyexport.class.php index 9a48d06df4069..ab0c39f301b56 100644 --- a/htdocs/accountancy/class/accountancyexport.class.php +++ b/htdocs/accountancy/class/accountancyexport.class.php @@ -226,7 +226,7 @@ public static function getFormatCode($type) ); global $hookmanager; - $code = $formatcode[$type]; + $code = $formatcode[$type] ?? ''; $parameters = array('type' => $type); $reshook = $hookmanager->executeHooks('getFormatCode', $parameters, $code); diff --git a/htdocs/accountancy/index.php b/htdocs/accountancy/index.php index 6206bdd5032a2..a7e6a5c033891 100644 --- a/htdocs/accountancy/index.php +++ b/htdocs/accountancy/index.php @@ -131,12 +131,14 @@ print load_fiche_titre($langs->trans("AccountancyArea"), empty($resultboxes['selectboxlist']) ? '' : $resultboxes['selectboxlist'], 'accountancy', 0, '', '', $showtutorial); + /* if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) { $messagewarning = $langs->trans("SorryThisModuleIsNotCompatibleWithTheExperimentalFeatureOfSituationInvoices"); $messagewarning .= ' '.$langs->trans("WarningExperimentalFeatureInvoiceSituationNeedToUpgradeToProgressiveMode", 'https://partners.dolibarr.org'); print info_admin($messagewarning); print "
"; } + */ if (!$helpisexpanded && empty($resultboxes['boxlista']) && empty($resultboxes['boxlistb'])) { print '

'.$langs->trans("ClickOnUseTutorialForHelp", $langs->transnoentities("ShowTutorial"))."
\n"; diff --git a/htdocs/accountancy/journal/bankjournal.php b/htdocs/accountancy/journal/bankjournal.php index b133e0399e470..05cdfdf861a27 100644 --- a/htdocs/accountancy/journal/bankjournal.php +++ b/htdocs/accountancy/journal/bankjournal.php @@ -671,7 +671,7 @@ $accountingaccountpayment->fetch(0, getDolGlobalString('SALARIES_ACCOUNTING_ACCOUNT_PAYMENT'), true); $accountingaccountexpensereport = new AccountingAccount($db); - $accountingaccountexpensereport->fetch(0, $conf->global->ACCOUNTING_ACCOUNT_EXPENSEREPORT, true); + $accountingaccountexpensereport->fetch(0, getDolGlobalString('ACCOUNTING_ACCOUNT_EXPENSEREPORT'), true); $accountingaccountsuspense = new AccountingAccount($db); $accountingaccountsuspense->fetch(0, getDolGlobalString('ACCOUNTING_ACCOUNT_SUSPENSE'), true); @@ -819,43 +819,85 @@ } elseif (in_array($tabtype[$key], array('sc', 'payment_sc'))) { // If payment is payment of social contribution $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(0, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'payment_vat') { $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(0, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'payment_donation') { $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(0, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'member') { $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(0, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'payment_loan') { $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(0, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'payment_various') { $bookkeeping->subledger_account = $k; $bookkeeping->subledger_label = $tabcompany[$key]['name']; - $accountingaccount->fetch(0, $tabpay[$key]["account_various"], true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$tabpay[$key]["account_various"]])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $tabpay[$key]["account_various"], true); + $conf->cache['accountingaccountincurrententity'][$tabpay[$key]["account_various"]] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$tabpay[$key]["account_various"]]; + } $bookkeeping->numero_compte = $tabpay[$key]["account_various"]; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'banktransfert') { $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(0, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } else { diff --git a/htdocs/admin/payment.php b/htdocs/admin/payment.php index f60bed8f0c9bd..fe1111aed8b4c 100644 --- a/htdocs/admin/payment.php +++ b/htdocs/admin/payment.php @@ -270,12 +270,14 @@ print "\n"; // Allow payments on different thirdparties bills but same parent company +/* print ''; print $langs->trans("PaymentOnDifferentThirdBills"); print ''; print $form->selectyesno("FACTURE_PAYMENTS_ON_DIFFERENT_THIRDPARTIES_BILLS", getDolGlobalInt('FACTURE_PAYMENTS_ON_DIFFERENT_THIRDPARTIES_BILLS'), 1); print ''; print "\n"; +*/ // Allow to group payments by mod in rapports print ''; diff --git a/htdocs/api/class/api.class.php b/htdocs/api/class/api.class.php index 818a4c8d923d2..1b76fb9b9d489 100644 --- a/htdocs/api/class/api.class.php +++ b/htdocs/api/class/api.class.php @@ -56,7 +56,20 @@ public function __construct($db, $cachedir = '', $refreshCache = false) Defaults::$cacheDirectory = $cachedir; $this->db = $db; + $production_mode = getDolGlobalBool('API_PRODUCTION_MODE'); + + if ($production_mode) { + // Create the directory Defaults::$cacheDirectory if it does not exist. If dir does not exist, using production_mode generates an error 500. + include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + if (!dol_is_dir(Defaults::$cacheDirectory)) { + dol_mkdir(Defaults::$cacheDirectory, DOL_DATA_ROOT); + } + if (getDolGlobalString('MAIN_API_DEBUG')) { + dol_syslog("Debug API construct::cacheDirectory=".Defaults::$cacheDirectory, LOG_DEBUG, 0, '_api'); + } + } + $this->r = new Restler($production_mode, $refreshCache); $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root)); diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index cd78a874ec48f..c32881491c8c5 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -97,6 +97,23 @@ public function index($modulepart, $original_file = '') throw new RestException(403); } + if (DolibarrApiAccess::$user->socid > 0) { + if ($sqlprotectagainstexternals) { + $resql = $this->db->query($sqlprotectagainstexternals); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($resql); + if (DolibarrApiAccess::$user->socid != $obj->fk_soc) { + throw new RestException(403, 'Not allowed to download documents with such a ref'); + } + $i++; + } + } + } + } + $filename = basename($original_file); $original_file_osencoded = dol_osencode($original_file); // New file name encoded in OS encoding charset @@ -174,6 +191,23 @@ public function builddoc($modulepart, $original_file = '', $doctemplate = '', $l throw new RestException(403); } + if (DolibarrApiAccess::$user->socid > 0) { + if ($sqlprotectagainstexternals) { + $resql = $this->db->query($sqlprotectagainstexternals); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($resql); + if (DolibarrApiAccess::$user->socid != $obj->fk_soc) { + throw new RestException(403, 'Not allowed to download documents with such a ref'); + } + $i++; + } + } + } + } + // --- Generates the document $hidedetails = !getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 0 : 1; $hidedesc = !getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 0 : 1; @@ -1010,6 +1044,23 @@ public function delete($modulepart, $original_file) throw new RestException(403); } + if (DolibarrApiAccess::$user->socid > 0) { + if ($sqlprotectagainstexternals) { + $resql = $this->db->query($sqlprotectagainstexternals); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($resql); + if (DolibarrApiAccess::$user->socid != $obj->fk_soc) { + throw new RestException(403, 'Not allowed to download documents with such a ref'); + } + $i++; + } + } + } + } + $filename = basename($original_file); $original_file_osencoded = dol_osencode($original_file); // New file name encoded in OS encoding charset diff --git a/htdocs/asset/class/assetdepreciationoptions.class.php b/htdocs/asset/class/assetdepreciationoptions.class.php index a8d3e44fe4176..90b4712d68ac7 100644 --- a/htdocs/asset/class/assetdepreciationoptions.class.php +++ b/htdocs/asset/class/assetdepreciationoptions.class.php @@ -303,7 +303,7 @@ public function setDeprecationOptionsFromPost($class_type = 0) $deprecation_options[$mode_key][$field_key] = $field_value; // Validation of fields values - if (getDolGlobalInt('MAIN_FEATURE_LEVEL') >= 1 || getDolGlobalString('MAIN_ACTIVATE_VALIDATION_RESULT')) { + if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 1 || getDolGlobalString('MAIN_ACTIVATE_VALIDATION_RESULT')) { if (!$error && !empty($field_info['validate']) && is_callable(array($this, 'validateField'))) { if (!$this->validateField($mode_info['fields'], $field_key, $value)) { $error++; diff --git a/htdocs/comm/action/card.php b/htdocs/comm/action/card.php index 9c002bda905f4..8d939a0ce6821 100644 --- a/htdocs/comm/action/card.php +++ b/htdocs/comm/action/card.php @@ -603,6 +603,8 @@ $object->error .= '
- ' . $langs->trans('ErrorResourceUseInEvent', $obj->r_ref, $obj->ac_label . ' [' . $obj->ac_id . ']'); } $object->errors[] = $object->error; + + setEventMessages($object->error, null, 'errors'); } $db->free($resql); } @@ -617,8 +619,10 @@ unset($_SESSION['assignedtoresource']); // Category association - $categories = GETPOST('categories', 'array'); - $object->setCategories($categories); + if (!$error) { + $categories = GETPOST('categories', 'array'); + $object->setCategories($categories); + } unset($_SESSION['assignedtouser']); @@ -627,7 +631,7 @@ } // Create reminders - if ($addreminder == 'on') { + if (!$error && $addreminder == 'on') { $actionCommReminder = new ActionCommReminder($db); $dateremind = dol_time_plus_duree($datep, -1 * $offsetvalue, $offsetunit); @@ -698,7 +702,7 @@ $donotclearsession = 1; } - if ($eventisrecurring) { + if (!$error && $eventisrecurring) { $dayoffset = 0; $monthoffset = 0; // We set first date of recurrence and offsets diff --git a/htdocs/comm/card.php b/htdocs/comm/card.php index 429c9d5b38eca..df35900a1a5bf 100644 --- a/htdocs/comm/card.php +++ b/htdocs/comm/card.php @@ -947,7 +947,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $propal_static->element, $relativepath, 0); + print $formfile->showPreview($file_list, $propal_static->element, $relativepath, 0, 'entity=' . $objp->entity); } print ''; if ($propal_static->fk_project > 0) { @@ -1066,7 +1066,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $commande_static->element, $relativepath, 0, $param); + print $formfile->showPreview($file_list, $commande_static->element, $relativepath, 0, 'entity=' . $objp->entity); } print ''; if ($commande_static->fk_project > 0) { @@ -1099,6 +1099,8 @@ * Latest shipments */ if (isModEnabled("shipping") && $user->hasRight('expedition', 'lire')) { + $param = ''; + $sql = 'SELECT e.rowid as id'; $sql .= ', e.ref, e.entity, e.fk_projet'; $sql .= ', e.date_creation'; @@ -1167,7 +1169,8 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $sendingstatic->table_element, $relativepath, 0, $param); + + print $formfile->showPreview($file_list, $sendingstatic->element, $relativepath, 0, $param); } print ''; if ($sendingstatic->fk_project > 0) { @@ -1281,7 +1284,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $contrat->element, $relativepath, 0); + print $formfile->showPreview($file_list, $contrat->element, $relativepath, 0, 'entity=' . $objp->entity); } } // $filename = dol_sanitizeFileName($objp->ref); @@ -1381,7 +1384,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $fichinter_static->element, $relativepath, 0); + print $formfile->showPreview($file_list, $fichinter_static->element, $relativepath, 0, 'entity=' . $objp->entity); } print ''; if ($fichinter_static->fk_project > 0) { @@ -1610,7 +1613,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $facturestatic->element, $relativepath, 0); + print $formfile->showPreview($file_list, $facturestatic->element, $relativepath, 0, 'entity=' . $objp->entity); } print ''; if ($facturestatic->fk_project > 0) { diff --git a/htdocs/comm/propal/class/propal.class.php b/htdocs/comm/propal/class/propal.class.php index c96d0d1389af8..66bcc96ab16dc 100644 --- a/htdocs/comm/propal/class/propal.class.php +++ b/htdocs/comm/propal/class/propal.class.php @@ -18,8 +18,8 @@ * Copyright (C) 2022 ATM Consulting * Copyright (C) 2022 OpenDSI * Copyright (C) 2022 Gauthier VERDOL - * Copyright (C) 2023 William Mead - * Copyright (C) 2024 MDW + * Copyright (C) 2024 MDW + * Copyright (C) 2025 William Mead * * 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 @@ -270,12 +270,12 @@ class Propal extends CommonObject public $address; /** - * @var int availability ID + * @var ?int availability ID can be null in db */ public $availability_id; /** - * @var int availability ID + * @var ?int availability ID * @deprecated * @see $availability_id */ @@ -1834,11 +1834,14 @@ public function update(User $user, $notrigger = 0) $sql .= " fk_statut = ".(isset($this->status) ? (int) $this->status : "null").","; $sql .= " fk_user_author = ".(!empty($this->user_author_id) ? (int) $this->user_author_id : "null").","; $sql .= " fk_user_valid = ".(!empty($this->user_validation_id) ? (int) $this->user_validation_id : "null").","; + $sql .= " fk_projet = ".(!empty($this->fk_project) ? (int) $this->fk_project : "null").","; $sql .= " fk_cond_reglement = ".(!empty($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null").","; $sql .= " deposit_percent = ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").","; $sql .= " fk_mode_reglement = ".(!empty($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null").","; $sql .= " fk_input_reason = ".(!empty($this->demand_reason_id) ? (int) $this->demand_reason_id : "null").","; + $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").","; + $sql .= " fk_availability=".(isset($this->availability_id) ? (int) $this->availability_id : "null").","; $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").","; $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").","; $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").","; diff --git a/htdocs/comm/propal/list.php b/htdocs/comm/propal/list.php index 79e32cc27d7ae..49aaf5658fb25 100644 --- a/htdocs/comm/propal/list.php +++ b/htdocs/comm/propal/list.php @@ -761,7 +761,7 @@ $sql .= " AND p.note_public LIKE '%".$db->escape($db->escapeforlike($search_note_public))."%'"; } if ($search_import_key) { - $sql .= natural_search("s.import_key", $search_import_key); + $sql .= natural_search("p.import_key", $search_import_key); } // Search on user if ($search_user > 0) { diff --git a/htdocs/comm/remx.php b/htdocs/comm/remx.php index 27158f694e162..5a98cf3beb2c0 100644 --- a/htdocs/comm/remx.php +++ b/htdocs/comm/remx.php @@ -93,7 +93,7 @@ $error++; setEventMessages($langs->trans("ErrorFailedToLoadDiscount"), null, 'errors'); } - if (!$error && price2num((float) $amount_ttc_1 + (float) $amount_ttc_2) != $discount->amount_ttc) { + if (!$error && price2num((float) $amount_ttc_1 + (float) $amount_ttc_2, 'MT') != $discount->amount_ttc) { $error++; setEventMessages($langs->trans("TotalOfTwoDiscountMustEqualsOriginal"), null, 'errors'); } diff --git a/htdocs/commande/list.php b/htdocs/commande/list.php index 96a420adfec68..98c84cf55f03d 100644 --- a/htdocs/commande/list.php +++ b/htdocs/commande/list.php @@ -1103,7 +1103,7 @@ $sql .= " AND c.fk_input_reason = ".((int) $search_fk_input_reason); } if ($search_import_key) { - $sql .= natural_search("s.import_key", $search_import_key); + $sql .= natural_search("c.import_key", $search_import_key); } // Search on user if ($search_user > 0) { diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index 5dab10183e6cb..108da039fa757 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -2125,6 +2125,9 @@ $newlang = GETPOST('lang_id', 'aZ09'); } if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + if (empty($object->thirdparty)) { + $object->fetch_thirdparty(); + } $newlang = $object->thirdparty->default_lang; } if (!empty($newlang)) { @@ -6026,7 +6029,7 @@ function js_recalculate_revenuestamp(){ } } - // For situation invoice with excess received + // For situation invoice if ($object->status > Facture::STATUS_DRAFT && $object->isSituationInvoice() && ($object->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits) > 0 diff --git a/htdocs/compta/facture/class/facture.class.php b/htdocs/compta/facture/class/facture.class.php index bdd2c697745ab..f6fac3811c46b 100644 --- a/htdocs/compta/facture/class/facture.class.php +++ b/htdocs/compta/facture/class/facture.class.php @@ -1419,6 +1419,9 @@ public function createFromOrder($object, User $user) $this->date = dol_now(); $this->source = 0; + // Avoid updating the row ranks + $this->context['createfromclone'] = 1; + $num = count($object->lines); for ($i = 0; $i < $num; $i++) { $line = new FactureLigne($this->db); diff --git a/htdocs/compta/facture/list.php b/htdocs/compta/facture/list.php index 68e674e16325a..debcd32ec6d4a 100644 --- a/htdocs/compta/facture/list.php +++ b/htdocs/compta/facture/list.php @@ -898,7 +898,7 @@ $sql .= ' AND f.fk_fac_rec_source = ' . (int) $search_fk_fac_rec_source; } if ($search_import_key) { - $sql .= natural_search("s.import_key", $search_import_key); + $sql .= natural_search("f.import_key", $search_import_key); } // Search on user if ($search_user > 0) { diff --git a/htdocs/compta/paiement.php b/htdocs/compta/paiement.php index 07f5dd234e932..cb5a165164f8c 100644 --- a/htdocs/compta/paiement.php +++ b/htdocs/compta/paiement.php @@ -172,7 +172,7 @@ } } - $formquestion[$i++] = array('type' => 'hidden', 'name' => $key, 'value' => GETPOSTINT($key)); + $formquestion[$i++] = array('type' => 'hidden', 'name' => $key, 'value' => GETPOST($key)); } } diff --git a/htdocs/compta/resultat/clientfourn.php b/htdocs/compta/resultat/clientfourn.php index 68a1e921bf904..25272deb1e6e2 100644 --- a/htdocs/compta/resultat/clientfourn.php +++ b/htdocs/compta/resultat/clientfourn.php @@ -309,16 +309,19 @@ if ($showaccountdetail == 'no') { $sql .= ", f.thirdparty_code as name"; } - $sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as f"; - $sql .= ", ".MAIN_DB_PREFIX."accounting_account as aa"; - $sql .= " WHERE f.numero_compte = aa.account_number"; + $sql .= " FROM ".$db->prefix()."accounting_bookkeeping as f"; + $sql .= " INNER JOIN ".$db->prefix()."accounting_account as aa"; + $sql .= " ON aa.account_number = f.numero_compte"; + $sql .= " AND aa.entity = f.entity"; // Security prevents duplicate. + $sql .= " WHERE 1=1"; $sql .= " AND ".$predefinedgroupwhere; - $sql .= " AND fk_pcg_version = '".$db->escape($charofaccountstring)."'"; + $sql .= " AND aa.fk_pcg_version = '".$db->escape($charofaccountstring)."'"; $sql .= " AND f.entity = ".$conf->entity; if (!empty($date_start) && !empty($date_end)) { - $sql .= " AND f.doc_date >= '".$db->idate($date_start)."' AND f.doc_date <= '".$db->idate($date_end)."'"; + $sql .= " AND f.doc_date >= '".$db->idate($date_start)."'"; + $sql .= " AND f.doc_date <= '".$db->idate($date_end)."'"; } - $sql .= " GROUP BY pcg_type"; + $sql .= " GROUP BY aa.pcg_type"; if ($showaccountdetail == 'no') { $sql .= ", name, socid"; // group by "accounting group" (INCOME/EXPENSE), then "customer". } diff --git a/htdocs/compta/resultat/index.php b/htdocs/compta/resultat/index.php index fbc2401a17955..c1830074dd27b 100644 --- a/htdocs/compta/resultat/index.php +++ b/htdocs/compta/resultat/index.php @@ -54,7 +54,7 @@ $nbofyear = 4; -// Change this to test different cases of setup +// Change this to test different cases of setup. //$conf->global->SOCIETE_FISCAL_MONTH_START = 7; diff --git a/htdocs/contrat/services_list.php b/htdocs/contrat/services_list.php index 1f2c59b5ee439..41a4369a32181 100644 --- a/htdocs/contrat/services_list.php +++ b/htdocs/contrat/services_list.php @@ -383,28 +383,28 @@ } if (!empty($filter_opouvertureprevue) && $filter_opouvertureprevue != -1 && $filter_opouvertureprevue != ' BETWEEN ' && $filter_dateouvertureprevue_start != '') { - $sql .= " AND cd.date_ouverture_prevue ".$filter_opouvertureprevue." '".$db->idate($filter_dateouvertureprevue_start)."'"; + $sql .= " AND cd.date_ouverture_prevue ".preg_replace('/[^<>]/', '', $filter_opouvertureprevue)." '".$db->idate($filter_dateouvertureprevue_start)."'"; } if (!empty($filter_opouvertureprevue) && $filter_opouvertureprevue == ' BETWEEN ') { - $sql .= " AND cd.date_ouverture_prevue ".$filter_opouvertureprevue." '".$db->idate($filter_dateouvertureprevue_start)."' AND '".$db->idate($filter_dateouvertureprevue_end)."'"; + $sql .= " AND cd.date_ouverture_prevue ".$db->sanitize($filter_opouvertureprevue)." '".$db->idate($filter_dateouvertureprevue_start)."' AND '".$db->idate($filter_dateouvertureprevue_end)."'"; } if (!empty($filter_op1) && $filter_op1 != -1 && $filter_op1 != ' BETWEEN ' && $filter_date1_start != '') { - $sql .= " AND cd.date_ouverture ".$filter_op1." '".$db->idate($filter_date1_start)."'"; + $sql .= " AND cd.date_ouverture ".preg_replace('/[^<>]/', '', $filter_op1)." '".$db->idate($filter_date1_start)."'"; } if (!empty($filter_op1) && $filter_op1 == ' BETWEEN ') { - $sql .= " AND cd.date_ouverture ".$filter_op1." '".$db->idate($filter_date1_start)."' AND '".$db->idate($filter_date1_end)."'"; + $sql .= " AND cd.date_ouverture ".$db->sanitize($filter_op1)." '".$db->idate($filter_date1_start)."' AND '".$db->idate($filter_date1_end)."'"; } if (!empty($filter_op2) && $filter_op2 != -1 && $filter_op2 != ' BETWEEN ' && $filter_date2_start != '') { - $sql .= " AND cd.date_fin_validite ".$filter_op2." '".$db->idate($filter_date2_start)."'"; + $sql .= " AND cd.date_fin_validite ".preg_replace('/[^<>]/', '', $filter_op2)." '".$db->idate($filter_date2_start)."'"; } if (!empty($filter_op2) && $filter_op2 == ' BETWEEN ') { - $sql .= " AND cd.date_fin_validite ".$filter_op2." '".$db->idate($filter_date2_start)."' AND '".$db->idate($filter_date2_end)."'"; + $sql .= " AND cd.date_fin_validite ".$db->sanitize($filter_op2)." '".$db->idate($filter_date2_start)."' AND '".$db->idate($filter_date2_end)."'"; } if (!empty($filter_opcloture) && $filter_opcloture != ' BETWEEN ' && $filter_opcloture != -1 && $filter_datecloture_start != '') { - $sql .= " AND cd.date_cloture ".$filter_opcloture." '".$db->idate($filter_datecloture_start)."'"; + $sql .= " AND cd.date_cloture ".preg_replace('/[^<>]/', '', $filter_opcloture)." '".$db->idate($filter_datecloture_start)."'"; } if (!empty($filter_opcloture) && $filter_opcloture == ' BETWEEN ') { - $sql .= " AND cd.date_cloture ".$filter_opcloture." '".$db->idate($filter_datecloture_start)."' AND '".$db->idate($filter_datecloture_end)."'"; + $sql .= " AND cd.date_cloture ".$db->sanitize($filter_opcloture)." '".$db->idate($filter_datecloture_start)."' AND '".$db->idate($filter_datecloture_end)."'"; } // Add where from extra fields include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; diff --git a/htdocs/core/actions_addupdatedelete.inc.php b/htdocs/core/actions_addupdatedelete.inc.php index 78ca677f9facf..6bf15d2ab034c 100644 --- a/htdocs/core/actions_addupdatedelete.inc.php +++ b/htdocs/core/actions_addupdatedelete.inc.php @@ -426,7 +426,7 @@ $object->oldcopy = dol_clone($object, 2); // @phan-suppress-current-line PhanTypeMismatchProperty - $attribute = GETPOST('attribute', 'alphanohtml'); + $attribute = GETPOST('attribute', 'aZ09'); $error = 0; diff --git a/htdocs/core/actions_setmoduleoptions.inc.php b/htdocs/core/actions_setmoduleoptions.inc.php index 8439660f6c86d..4a56c7fc750c1 100644 --- a/htdocs/core/actions_setmoduleoptions.inc.php +++ b/htdocs/core/actions_setmoduleoptions.inc.php @@ -171,7 +171,54 @@ } } - if ($upload_dir) { + // Restrict uploads for ODT template setup pages. + if (preg_match('/_ADDON_PDF_ODT_PATH$/', $keyforuploaddir) && !empty($_FILES['uploadfile'])) { + $allowedext = array('odt', 'ods'); + $allowedmimes = array( + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/zip', + 'application/x-zip-compressed', + 'application/octet-stream' + ); + $files = $_FILES['uploadfile']; + if (!is_array($files['name'])) { + foreach ($files as $key => &$val) { + $val = array($val); + } + unset($val); + } + foreach ($files['name'] as $idx => $filename) { + if (empty($filename)) { + continue; + } + + $extension = strtolower(pathinfo((string) $filename, PATHINFO_EXTENSION)); + if (!in_array($extension, $allowedext, true)) { + $error++; + setEventMessages($langs->trans("ErrorFileNotUploaded").' (Only .odt/.ods templates are allowed)', null, 'errors'); + dol_syslog(__FILE__." ODT template upload refused on extension filename=".$filename, LOG_WARNING); + break; + } + + $detectedmime = ''; + if (!empty($files['tmp_name'][$idx]) && function_exists('finfo_open')) { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + if ($finfo) { + $detectedmime = (string) finfo_file($finfo, $files['tmp_name'][$idx]); + finfo_close($finfo); + } + } + if (!empty($detectedmime) && !in_array(strtolower($detectedmime), $allowedmimes, true)) { + $error++; + setEventMessages($langs->trans("ErrorFileNotUploaded").' (Invalid MIME type for ODT/ODS template)', null, 'errors'); + dol_syslog(__FILE__." ODT template upload refused on mime filename=".$filename." mime=".$detectedmime, LOG_WARNING); + break; + } + } + } + + if ($upload_dir && !$error) { $result = dol_add_file_process($upload_dir, 1, 1, 'uploadfile', ''); if ($result <= 0) { $error++; diff --git a/htdocs/core/ajax/saveinplace.php b/htdocs/core/ajax/saveinplace.php index 9d94c28aac152..5207099ea9ea3 100644 --- a/htdocs/core/ajax/saveinplace.php +++ b/htdocs/core/ajax/saveinplace.php @@ -111,7 +111,6 @@ $value = ($type == 'ckeditor' ? GETPOST('value', '', 2) : GETPOST('value', 'alpha', 2)); $loadmethod = GETPOST('loadmethod', 'alpha', 2); $savemethod = GETPOST('savemethod', 'alpha', 2); - $savemethodname = (!empty($savemethod) ? $savemethod : 'setValueFrom'); $newelement = $element; $subelement = null; @@ -236,11 +235,7 @@ } } - if (!$error) { - if ((isset($object) && !is_object($object)) || empty($savemethod)) { - $object = new GenericObject($db); - } - + if (!$error && isset($object) && is_object($object)) { // Specific for add_object_linked() // TODO add a function for variable treatment $object->ext_fk_element = $newvalue; @@ -248,7 +243,7 @@ $object->fk_element = $fk_element; $object->element = $element; - $ret = $object->$savemethodname($field, $newvalue, $table_element, $fk_element, $format); + $ret = $object->setValueFrom($field, $newvalue, $object->table_element, $fk_element, $format); if ($ret > 0) { if ($type == 'numeric') { $value = price($newvalue); diff --git a/htdocs/core/class/cleadstatus.class.php b/htdocs/core/class/cleadstatus.class.php index 7f6c0b9d0da46..37ab913fa5f29 100644 --- a/htdocs/core/class/cleadstatus.class.php +++ b/htdocs/core/class/cleadstatus.class.php @@ -57,6 +57,9 @@ class CLeadStatus extends CommonDict */ public $percent; + /** + * @var array|string,position:int,notnull?:int,visible:int<-5,5>|string,alwayseditable?:int<0,1>,noteditable?:int<0,1>,default?:string,index?:int,foreignkey?:string,searchall?:int<0,1>,isameasure?:int<0,1>,css?:string,csslist?:string,help?:string,showoncombobox?:int<0,4>,disabled?:int<0,1>,arrayofkeyval?:array,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>,showonheader?:int<0,1>}> + */ public $fields = array( 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'position' => 1, 'notnull' => 1, 'visible' => 0, 'noteditable' => 1, 'index' => 1, 'css' => 'left', 'comment' => "Id"), 'label' => array('type' => 'varchar(128)', 'label' => 'Label', 'enabled' => 1, 'position' => 20, 'notnull' => 1, 'visible' => 1, 'index' => 1, 'searchall' => 1, 'showoncombobox' => 1, 'comment' => "Label of status"), diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 513de0d50af36..0a7022c6ae50f 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -1733,9 +1733,16 @@ public function getExtrafieldsInHtml($object, $outputlangs, $params = array()) foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) { // Enable extrafield ? $enabled = 0; + if (!empty($extrafields->attributes[$object->table_element]['enabled'][$key])) { + $enabled = (int) dol_eval((string) $extrafields->attributes[$object->table_element]['enabled'][$key], 1, 1, '2'); + } + $disableOnEmpty = 0; - if (!empty($extrafields->attributes[$object->table_element]['printable'][$key])) { - $printable = intval($extrafields->attributes[$object->table_element]['printable'][$key]); + $printable = 0; + if (isset($extrafields->attributes[$object->table_element]['printable'][$key])) { + $printable = (int) $extrafields->attributes[$object->table_element]['printable'][$key]; + } + if ($enabled && !empty($printable)) { if (in_array($printable, $params['printableEnable']) || in_array($printable, $params['printableEnableNotEmpty'])) { $enabled = 1; } @@ -1745,7 +1752,7 @@ public function getExtrafieldsInHtml($object, $outputlangs, $params = array()) } } - if (empty($enabled)) { + if (empty($enabled) || empty($printable)) { continue; } diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index a0489c7665306..1635e00c42991 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -491,7 +491,7 @@ abstract class CommonObject public $fk_delivery_address; /** - * @var int Shipping method ID + * @var ?int Shipping method ID * @see setShippingMethod() */ public $shipping_method_id; @@ -4787,6 +4787,8 @@ public function setStatut($status, $elementId = null, $elementType = '', $trigke $this->context = array_merge($this->context, array('newstatus' => $status)); if ($trigkey) { + $this->oldcopy = dol_clone($this); + // Call trigger $result = $this->call_trigger($trigkey, $user); if ($result < 0) { @@ -6700,10 +6702,17 @@ public function insertExtraFields($trigger = '', $userused = null) switch ($attributeType) { case 'int': + case 'duration': + case 'stars': if (!is_numeric($value) && $value != '') { $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel); return -1; - } elseif ($value == '') { + } elseif ($value === '' || $value === false || $value === null) { + $new_array_options[$key] = null; + } + break; + case 'boolean': + if ($value === '' || $value === false || $value === null) { $new_array_options[$key] = null; } break; @@ -6794,6 +6803,10 @@ public function insertExtraFields($trigger = '', $userused = null) $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt'); break; case 'link': + if ($value === '' || $value === false || $value === null) { + $new_array_options[$key] = null; + break; + } $param_list = array_keys($attributeParam['options']); // 0 : ObjectName // 1 : classPath @@ -7870,7 +7883,11 @@ function handlemultiinputdisabling(htmlname){ } if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { list($parentName, $parentField) = explode('|', $InfoFieldList[3]); - $keyList .= ', '.$parentField; + if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList .= ', main.'.$parentField; + } else { + $keyList .= ', '.$parentField; + } } $filter_categorie = false; @@ -8094,10 +8111,6 @@ function handlemultiinputdisabling(htmlname){ $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid'); - if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { - list($parentName, $parentField) = explode('|', $InfoFieldList[3]); - $keyList .= ', '.$parentField; - } if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) { if (strpos($InfoFieldList[4], 'extra.') !== false) { $keyList = 'main.'.$InfoFieldList[2].' as rowid'; @@ -8105,6 +8118,14 @@ function handlemultiinputdisabling(htmlname){ $keyList = $InfoFieldList[2].' as rowid'; } } + if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { + list($parentName, $parentField) = explode('|', $InfoFieldList[3]); + if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList .= ', main.'.$parentField; + } else { + $keyList .= ', '.$parentField; + } + } $filter_categorie = false; if (count($InfoFieldList) > 5) { diff --git a/htdocs/core/class/extrafields.class.php b/htdocs/core/class/extrafields.class.php index c1e0c0fc5e6d9..77f53bcf85da5 100644 --- a/htdocs/core/class/extrafields.class.php +++ b/htdocs/core/class/extrafields.class.php @@ -1421,10 +1421,6 @@ public function showInputField($key, $value, $moreparam = '', $keysuffix = '', $ $parentField = ''; $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid'); - if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { - list($parentName, $parentField) = explode('|', $InfoFieldList[3]); - $keyList .= ', '.$parentField; - } if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) { if (strpos($InfoFieldList[4], 'extra.') !== false) { $keyList = 'main.'.$InfoFieldList[2].' as rowid'; @@ -1432,6 +1428,14 @@ public function showInputField($key, $value, $moreparam = '', $keysuffix = '', $ $keyList = $InfoFieldList[2].' as rowid'; } } + if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { + list($parentName, $parentField) = explode('|', $InfoFieldList[3]); + if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList .= ', main.'.$parentField; + } else { + $keyList .= ', '.$parentField; + } + } $filter_categorie = false; if (count($InfoFieldList) > 5) { @@ -1647,10 +1651,6 @@ public function showInputField($key, $value, $moreparam = '', $keysuffix = '', $ $parentField = ''; $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid'); - if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { - list($parentName, $parentField) = explode('|', $InfoFieldList[3]); - $keyList .= ', '.$parentField; - } if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) { if (strpos($InfoFieldList[4], 'extra.') !== false) { $keyList = 'main.'.$InfoFieldList[2].' as rowid'; @@ -1658,6 +1658,14 @@ public function showInputField($key, $value, $moreparam = '', $keysuffix = '', $ $keyList = $InfoFieldList[2].' as rowid'; } } + if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { + list($parentName, $parentField) = explode('|', $InfoFieldList[3]); + if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList .= ', main.'.$parentField; + } else { + $keyList .= ', '.$parentField; + } + } $filter_categorie = false; if (count($InfoFieldList) > 5) { diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 1859abaf113ad..b552f778db3a9 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -15,7 +15,7 @@ * Copyright (C) 2012-2016 Marcos García * Copyright (C) 2012 Cedric Salvador * Copyright (C) 2012-2015 Raphaël Doursenaud - * Copyright (C) 2014-2023 Alexandre Spangaro + * Copyright (C) 2014-2026 Alexandre Spangaro * Copyright (C) 2018-2022 Ferran Marcet * Copyright (C) 2018-2024 Frédéric France * Copyright (C) 2018 Nicolas ZABOURI @@ -6879,7 +6879,7 @@ public function load_cache_vatrates($country_code) if (!empty($user) && $user->admin && preg_match('/\'(..)\'/', $country_code, $reg)) { $langs->load("errors"); $new_country_code = $reg[1]; - $country_id = dol_getIdFromCode($this->db, $new_country_code, 'c_pays', 'code', 'rowid'); + $country_id = dol_getIdFromCode($this->db, $new_country_code, 'c_country', 'code', 'rowid'); $this->error .= '
'.$langs->trans("ErrorFixThisHere", DOL_URL_ROOT.'/admin/dict.php?id=10'.($country_id > 0 ? '&countryidforinsert='.$country_id : '')); } $this->error .= ''; diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 693b7b674e8d8..c546462f144e2 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -1069,6 +1069,9 @@ public function get_form($addfileaction = 'addfile', $removefileaction = 'remove if (!dol_textishtml($this->substit['__SENDEREMAIL_SIGNATURE__'])) { $this->substit['__SENDEREMAIL_SIGNATURE__'] = dol_nl2br($this->substit['__SENDEREMAIL_SIGNATURE__']); } + if (!dol_textishtml($this->substit['__LINES__'])) { + $this->substit['__LINES__'] = dol_nl2br($this->substit['__LINES__']); + } if (!dol_textishtml($this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__'])) { $this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__'] = dol_nl2br($this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__']); } diff --git a/htdocs/core/class/notify.class.php b/htdocs/core/class/notify.class.php index c79b4b321caed..89bf13da263dd 100644 --- a/htdocs/core/class/notify.class.php +++ b/htdocs/core/class/notify.class.php @@ -668,7 +668,10 @@ public function send($notifcode, $object, $filename_list = array(), $mimetype_li $application = (preg_match('/^\+/', $applicationcustom) ? $application : '').$applicationcustom; } - $from = getDolGlobalString('NOTIFICATION_EMAIL_FROM', getDolGlobalString('MAIN_MAIL_EMAIL_FROM')); + $from = getDolGlobalString('NOTIFICATION_EMAIL_FROM'); + if (empty($from)) { + $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM'); + } $object_type = ''; $link = ''; diff --git a/htdocs/core/class/timespent.class.php b/htdocs/core/class/timespent.class.php index e5f10fb3c8bd1..d6d7911f990a2 100644 --- a/htdocs/core/class/timespent.class.php +++ b/htdocs/core/class/timespent.class.php @@ -701,7 +701,7 @@ public function getTooltipContentArray($params) if (isset($this->status)) { $datas['picto'] .= ' '.$this->getLibStatut(5); } - $datas['ref'] .= '
'.$langs->trans('Ref').': '.$this->ref; + $datas['ref'] = '
'.$langs->trans('Ref').': '.$this->ref; return $datas; } diff --git a/htdocs/core/customreports.php b/htdocs/core/customreports.php index 99d752fa6732a..7005671bff49b 100644 --- a/htdocs/core/customreports.php +++ b/htdocs/core/customreports.php @@ -432,19 +432,20 @@ if (is_array($search_groupby) && count($search_groupby)) { $fieldtocount = ''; foreach ($search_groupby as $gkey => $gval) { - $gvalwithoutprefix = preg_replace('/^[a-z]+\./', '', $gval); + $gvalwithoutprefix = preg_replace('/^[a-z]+\./i', '', $gval); + $gvalsanitized = preg_replace('/[^a-z0-9\._\-]+/i', '', $gval); - if (preg_match('/\-year$/', $search_groupby[$gkey])) { - $tmpval = preg_replace('/\-year$/', '', $search_groupby[$gkey]); + if (preg_match('/\-year$/', $gvalsanitized)) { + $tmpval = preg_replace('/\-year$/', '', $gvalsanitized); $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y')"; - } elseif (preg_match('/\-month$/', $search_groupby[$gkey])) { - $tmpval = preg_replace('/\-month$/', '', $search_groupby[$gkey]); + } elseif (preg_match('/\-month$/', $gvalsanitized)) { + $tmpval = preg_replace('/\-month$/', '', $gvalsanitized); $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y-%m')"; - } elseif (preg_match('/\-day$/', $search_groupby[$gkey])) { - $tmpval = preg_replace('/\-day$/', '', $search_groupby[$gkey]); + } elseif (preg_match('/\-day$/', $gvalsanitized)) { + $tmpval = preg_replace('/\-day$/', '', $gvalsanitized); $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y-%m-%d')"; } else { - $fieldtocount = $search_groupby[$gkey]; + $fieldtocount = $gvalsanitized; } $sql = "SELECT DISTINCT ".$fieldtocount." as val"; diff --git a/htdocs/core/js/lib_head.js.php b/htdocs/core/js/lib_head.js.php index 7c2bff01268f3..d6de78638bb98 100644 --- a/htdocs/core/js/lib_head.js.php +++ b/htdocs/core/js/lib_head.js.php @@ -924,12 +924,15 @@ function confirmConstantAction(action, url, code, input, box, entity, yesButton, }) .addClass( "ui-widget ui-widget-content ui-corner-left dolibarrcombobox" ); - input.data("ui-autocomplete")._renderItem = function( ul, item ) { - return $("
  • ") - .data( "ui-autocomplete-item", item ) // jQuery UI > 1.10.0 - .append( "" + item.label + "" ) - .appendTo( ul ); - }; + const widgetInstance = input.data("ui-autocomplete"); + if (widgetInstance) { + widgetInstance._renderItem = function( ul, item ) { + return $("
  • ") + .data( "ui-autocomplete-item", item ) // jQuery UI > 1.10.0 + .append( "" + item.label + "" ) + .appendTo( ul ); + }; + } this.button = $( "" ) .attr( "tabIndex", -1 ) diff --git a/htdocs/core/lib/ajax.lib.php b/htdocs/core/lib/ajax.lib.php index 933a642251224..7d333c0f2bf9f 100644 --- a/htdocs/core/lib/ajax.lib.php +++ b/htdocs/core/lib/ajax.lib.php @@ -278,12 +278,16 @@ function ajax_autocompleter($selected, $htmlname, $url, $urloption = '', $minLen $("#search_'.$htmlnamejquery.'").trigger("change"); // We have changed value of the combo select, we must be sure to trigger all js hook binded on this event. This is required to trigger other javascript change method binded on original field by other code. } ,delay: 500 - }).data("'.$dataforrenderITem.'")._renderItem = function( ul, item ) { - return $("
  • ") - .data( "'.$dataforitem.'", item ) // jQuery UI > 1.10.0 - .append( \'\' + item.label + "" ) - .appendTo(ul); - }; + }); + const widgetData = $("input#search_'.$htmlnamejquery.'").data("'.$dataforrenderITem.'"); + if (widgetData) { + widgetData._renderItem = function( ul, item ) { + return $("
  • ") + .data( "'.$dataforitem.'", item ) // jQuery UI > 1.10.0 + .append( \'\' + item.label + "" ) + .appendTo(ul); + }; + } });'; $script .= ''; diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index 8d82ea46f9fee..c0cb5f7340fc6 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -2942,9 +2942,15 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, $accessallowed = ($user->admin && basename($original_file) == $original_file && preg_match('/^dolibarr.*\.(log|json)$/', basename($original_file))); $original_file = $dolibarr_main_data_root.'/'.$original_file; } elseif ($modulepart == 'doctemplates' && !empty($dolibarr_main_data_root)) { - // Wrapping for doctemplates $accessallowed = $user->admin; - $original_file = $dolibarr_main_data_root.'/doctemplates/'.$original_file; + $relative_file = $original_file; + $ent = ($entity > 0 ? $entity : $conf->entity); + $path_with_entity = $dolibarr_main_data_root . '/' . $ent . '/doctemplates/' . $relative_file; + if ($ent > 1 && file_exists(dol_osencode($path_with_entity))) { + $original_file = $path_with_entity; + } else { + $original_file = $dolibarr_main_data_root . '/doctemplates/' . $relative_file; + } } elseif ($modulepart == 'doctemplateswebsite' && !empty($dolibarr_main_data_root)) { // Wrapping for doctemplates of websites $accessallowed = ($fuser->hasRight('website', 'write') && preg_match('/\.jpg$/i', basename($original_file))); diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index eaf87871a6764..014a238a35017 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -1173,8 +1173,8 @@ function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto') $m = array(); if ($hourTime === 'getpost') { $hour = GETPOSTINT($prefix . 'hour'); - $minute = GETPOSTINT($prefix . 'minute'); - $second = GETPOSTINT($prefix . 'second'); + $minute = GETPOSTINT($prefix . 'min'); + $second = GETPOSTINT($prefix . 'sec'); } elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $hourTime, $m)) { $hour = intval($m[1]); $minute = intval($m[2]); @@ -1913,7 +1913,7 @@ function dolSlugify($stringtoslugify) /** * Returns text escaped for inclusion into javascript code * - * @param string $stringtoescape String to escape + * @param int|string $stringtoescape String to escape * @param int<0,3> $mode 0=Escape also ' and " into ', 1=Escape ' but not " for usage into 'string', 2=Escape " but not ' for usage into "string", 3=Escape ' and " with \ * @param int $noescapebackslashn 0=Escape also \n. 1=Do not escape \n. * @return string Escaped string. Both ' and " are escaped into ' if they are escaped. @@ -1942,7 +1942,7 @@ function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0) $substitjs["'"] = "\\'"; $substitjs['"'] = "\\\""; } - return strtr($stringtoescape, $substitjs); + return strtr((string) $stringtoescape, $substitjs); } /** @@ -15220,8 +15220,8 @@ function buildParamDate($prefix, $timestamp = null, $hourTime = '', $gm = 'auto' if ($hourTime === 'getpost' || ($timestamp !== null && dol_print_date($timestamp, '%H:%M:%S') !== '00:00:00')) { $TParam = array_merge($TParam, array( $prefix . 'hour' => intval(dol_print_date($timestamp, '%H')), - $prefix . 'minute' => intval(dol_print_date($timestamp, '%M')), - $prefix . 'second' => intval(dol_print_date($timestamp, '%S')) + $prefix . 'min' => intval(dol_print_date($timestamp, '%M')), + $prefix . 'sec' => intval(dol_print_date($timestamp, '%S')) )); } diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index 55a5649dcbb05..29cc7049a5ff9 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -29,14 +29,14 @@ * - you can set MAIN_SECURITY_ANTI_SSRF_SERVER_IP to set static ip of server * - common local lookup ips like 127.*.*.* are automatically added * - * @param string $url URL to call. + * @param string $url URL to call. * @param 'POST'|'GET'|'HEAD'|'PUT'|'PATCH'|'PUTALREADYFORMATED'|'POSTALREADYFORMATED'|'PATCHALREADYFORMATED'|'DELETE' $postorget 'POST', 'GET', 'HEAD', 'PUT', 'PATCH', 'PUTALREADYFORMATED', 'POSTALREADYFORMATED', 'PATCHALREADYFORMATED', 'DELETE' - * @param string $param Parameters of URL (x=value1&y=value2) or may be a formatted content with $postorget='PUTALREADYFORMATED' - * @param int<0,1> $followlocation 0=Do not follow, 1=Follow location. - * @param string[] $addheaders Array of string to add into header. Example: ('Accept: application/xrds+xml', ....) - * @param string[] $allowedschemes List of schemes that are allowed ('http' + 'https' only by default) - * @param int<0,2> $localurl 0=Only external URL are possible, 1=Only local URL, 2=Both external and local URL are allowed. - * @param int<-1,1> $ssl_verifypeer -1=Auto (no ssl check on dev, check on prod), 0=No ssl check, 1=Always ssl check + * @param string|array $param Parameters of URL (x=value1&y=value2) or may be a formatted content with $postorget='PUTALREADYFORMATED' + * @param int<0,1> $followlocation 0=Do not follow, 1=Follow location. + * @param string[] $addheaders Array of string to add into header. Example: ('Accept: application/xrds+xml', ....) + * @param string[] $allowedschemes List of schemes that are allowed ('http' + 'https' only by default) + * @param int<0,2> $localurl 0=Only external URL are possible, 1=Only local URL, 2=Both external and local URL are allowed. + * @param int<-1,1> $ssl_verifypeer -1=Auto (no ssl check on dev, check on prod), 0=No ssl check, 1=Always ssl check * @return array{http_code:int,content:string,curl_error_no:int,curl_error_msg:string} Returns an associative array containing the response from the server array('http_code'=>http response code, 'content'=>response, 'curl_error_no'=>errno, 'curl_error_msg'=>errmsg...) */ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = 1, $addheaders = array(), $allowedschemes = array('http', 'https'), $localurl = 0, $ssl_verifypeer = -1) @@ -49,7 +49,7 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = $PROXY_USER = !getDolGlobalString('MAIN_PROXY_USER') ? 0 : $conf->global->MAIN_PROXY_USER; $PROXY_PASS = !getDolGlobalString('MAIN_PROXY_PASS') ? 0 : $conf->global->MAIN_PROXY_PASS; - dol_syslog("getURLContent postorget=".$postorget." URL=".$url." param=".$param); + dol_syslog("getURLContent postorget=".$postorget." URL=".$url." json_encode(param)=".json_encode($param)); if (!function_exists('curl_init')) { return array('http_code' => 500, 'content' => '', 'curl_error_no' => 1, 'curl_error_msg' => 'PHP curl library must be installed'); diff --git a/htdocs/core/lib/security.lib.php b/htdocs/core/lib/security.lib.php index ecc83bde06dc6..458af07d5abbb 100644 --- a/htdocs/core/lib/security.lib.php +++ b/htdocs/core/lib/security.lib.php @@ -992,14 +992,14 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl $checkonentitydone = 0; // Array to define rules of checks to do - $check = array('adherent', 'banque', 'bom', 'don', 'mrp', 'user', 'usergroup', 'payment', 'payment_supplier', 'payment_sc', 'product', 'produit', 'service', 'produit|service', 'categorie', 'resource', 'expensereport', 'holiday', 'salaries', 'website', 'recruitment', 'chargesociales', 'knowledgemanagement'); // Test on entity only (Objects with no link to company) + $check = array('adherent', 'banque', 'bom', 'don', 'mrp', 'user', 'usergroup', 'payment', 'payment_supplier', 'payment_sc', 'product', 'produit', 'service', 'produit|service', 'categorie', 'resource', 'expensereport', 'holiday', 'salaries', 'website', 'recruitment', 'chargesociales', 'knowledgemanagement', 'stock'); // Test on entity only (Objects with no link to company) $checksoc = array('societe'); // Test for object Societe $checkparentsoc = array('agenda', 'contact', 'contrat'); // Test on entity + link to third party on field $dbt_keyfield. Allowed if link is empty (Ex: contacts...). $checkproject = array('projet', 'project'); // Test for project object $checktask = array('projet_task', 'project_task'); // Test for task object $checkhierarchy = array('expensereport', 'holiday', 'hrm'); // check permission among the hierarchy of user $checkuser = array('bookmark'); // check permission among the fk_user (must be myself or null) - $nocheck = array('barcode', 'stock'); // No test + $nocheck = array('barcode'); // No test //$checkdefault = 'all other not already defined'; // Test on entity + link to third party on field $dbt_keyfield. Not allowed if link is empty (Ex: invoice, orders...). diff --git a/htdocs/core/lib/ticket.lib.php b/htdocs/core/lib/ticket.lib.php index 2ed7811961e46..7a5f4cd3c327d 100644 --- a/htdocs/core/lib/ticket.lib.php +++ b/htdocs/core/lib/ticket.lib.php @@ -3,6 +3,7 @@ * Copyright (C) 2016 Christophe Battarel * Copyright (C) 2019-2024 Frédéric France * Copyright (C) 2024 MDW + * Copyright (C) 2025 Benjamin Falière * * 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 @@ -274,7 +275,7 @@ function llxHeaderTicket($title, $head = "", $disablejs = 0, $disablehead = 0, $ print ''; } - if (getDolGlobalInt('TICKET_IMAGE_PUBLIC_INTERFACE')) { + if (getDolGlobalString('TICKET_IMAGE_PUBLIC_INTERFACE')) { print '
    '; print ''; print '
    '; diff --git a/htdocs/core/menus/init_menu_auguria.sql b/htdocs/core/menus/init_menu_auguria.sql index 98acc50b55c26..f8f7296c4a767 100644 --- a/htdocs/core/menus/init_menu_auguria.sql +++ b/htdocs/core/menus/init_menu_auguria.sql @@ -76,7 +76,7 @@ insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, left -- Home - Menu users and groups insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity, prefix) values ('', '1', __HANDLER__, 'left', 400__+MAX_llx_menu__, 'home', 'users', 1__+MAX_llx_menu__, '/user/home.php?mainmenu=home&leftmenu=users', 'MenuUsersAndGroups', 0, 'users', '', '', 2, 4, __ENTITY__, ''); -insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '1', __HANDLER__, 'left', 401__+MAX_llx_menu__, 'home', 'users', 400__+MAX_llx_menu__, '', 'User', 1, 'users', '$user->hasRight("user", "user", "read") || $user->admin)', '', 2, 5, __ENTITY__); +insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '1', __HANDLER__, 'left', 401__+MAX_llx_menu__, 'home', 'users', 400__+MAX_llx_menu__, '', 'User', 1, 'users', '($user->hasRight("user", "user", "read") || $user->admin)', '', 2, 5, __ENTITY__); insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '1', __HANDLER__, 'left', 402__+MAX_llx_menu__, 'home', 'users', 401__+MAX_llx_menu__, '/user/card.php?mainmenu=home&leftmenu=users&action=create', 'NewUser', 2, 'users', '($user->hasRight("user","user","write") || $user->admin)', '', 2, 6, __ENTITY__); insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '1', __HANDLER__, 'left', 403__+MAX_llx_menu__, 'home', 'users', 401__+MAX_llx_menu__, '/user/list.php?mainmenu=home&leftmenu=users', 'ListOfUsers', 2, 'users', '($user->hasRight("user","user","read") || $user->admin)', '', 2, 7, __ENTITY__); diff --git a/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php b/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php index 27a295e61e49d..d344a938ed95c 100644 --- a/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php +++ b/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php @@ -151,7 +151,7 @@ public function info($langs) $texte .= getDolGlobalString('CONTRACT_ADDON_PDF_ODT_PATH'); $texte .= ''; $texte .= '
    '; - $texte .= ''; + $texte .= ''; $texte .= '
    '; // Scan directories @@ -159,7 +159,7 @@ public function info($langs) if (getDolGlobalString('CONTRACT_ADDON_PDF_ODT_PATH')) { $texte .= $langs->trans("NumberOfModelFilesFound").': '; //$texte.=$nbofiles?'':''; - $texte .= count($listoffiles); + $texte .= $nbofiles; //$texte.=$nbofiles?'':''; $texte .= ''; } diff --git a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php index a437216e158c0..ccb373dea44fc 100644 --- a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php @@ -388,6 +388,19 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $this->atleastonediscount++; } + // determine category of operation + $is_deposit = false; + if (preg_match('/^\((.*)\)$/', $object->lines[$i]->desc, $reg)) { + if ($reg[1] == 'DEPOSIT') { + $is_deposit = true; + } + } + // If DEPOSIT, this line is completely ignored for calculations. + if ($is_deposit) { + continue; + } + + // determine category of operation if ($categoryOfOperation < 2) { $lineProductType = $object->lines[$i]->product_type; diff --git a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php index 5545a7b1636c7..96db1f14e936a 100644 --- a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php @@ -414,6 +414,19 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $this->atleastonediscount++; } + // Do not take into account lines of the type “deposit.” + $is_deposit = false; + if (preg_match('/^\((.*)\)$/', $object->lines[$i]->desc, $reg)) { + if ($reg[1] == 'DEPOSIT') { + $is_deposit = true; + } + } + // If DEPOSIT, this line is completely ignored for calculations. + if ($is_deposit) { + continue; + } + + // determine category of operation if ($categoryOfOperation < 2) { $lineProductType = $object->lines[$i]->product_type; @@ -1761,7 +1774,8 @@ protected function drawTotalTable(&$pdf, $object, $deja_regle, $posy, $outputlan } } if ($total_line_remise > 0) { - if (!getDolGlobalString('MAIN_HIDE_AMOUNT_DISCOUNT')) { + // Show discount except on credit note type invoices + if (!getDolGlobalString('MAIN_HIDE_AMOUNT_DISCOUNT') && $object->type != 2) { $pdf->SetFillColor(255, 255, 255); $pdf->SetXY($col1x, $tab2_top + $tab2_hl); $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalDiscount").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalDiscount") : ''), 0, 'L', 1); @@ -1769,8 +1783,8 @@ protected function drawTotalTable(&$pdf, $object, $deja_regle, $posy, $outputlan $pdf->MultiCell($largcol2, $tab2_hl, price($total_line_remise_print, 0, $outputlangs), 0, 'R', 1); $index++; } - // Show total NET before discount - if (!getDolGlobalString('MAIN_HIDE_AMOUNT_BEFORE_DISCOUNT')) { + // Show total NET before discount except on credit note type invoices + if (!getDolGlobalString('MAIN_HIDE_AMOUNT_BEFORE_DISCOUNT') && $object->type != 2) { $pdf->SetFillColor(255, 255, 255); $pdf->SetXY($col1x, $tab2_top); $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalHTBeforeDiscount").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalHTBeforeDiscount") : ''), 0, 'L', 1); diff --git a/htdocs/core/modules/modProduct.class.php b/htdocs/core/modules/modProduct.class.php index 51dc37ad76759..48f3c49d104e6 100644 --- a/htdocs/core/modules/modProduct.class.php +++ b/htdocs/core/modules/modProduct.class.php @@ -604,7 +604,8 @@ public function __construct($db) 'p.recuperableonly' => '^[0|1]$', ); - if (isModEnabled('stock')) {//if Stock module enabled + // Complete if Stock module enabled + if (isModEnabled('stock')) { $this->import_fields_array[$r] = array_merge($this->import_fields_array[$r], array( 'p.fk_default_warehouse' => 'DefaultWarehouse', 'p.tobatch' => 'ManageLotSerial', @@ -931,7 +932,11 @@ public function __construct($db) // End add extra fields $this->import_fieldshidden_array[$r] = array('extra.fk_object' => 'lastrowid-'.MAIN_DB_PREFIX.'product_price'); // aliastable.field => ('user->id' or 'lastrowid-'.tableparent) - $this->import_regex_array[$r] = array('pr.datec' => '^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$', 'pr.recuperableonly' => '^[0|1]$'); + $this->import_regex_array[$r] = array( + 'pr.datec' => '^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$', + 'pr.recuperableonly' => '^[0|1]$' + ); + $this->import_convertvalue_array[$r] = array( 'pr.fk_product' => array('rule' => 'fetchidfromref', 'classfile' => '/product/class/product.class.php', 'class' => 'Product', 'method' => 'fetch', 'element' => 'Product') ); diff --git a/htdocs/core/modules/propale/doc/pdf_cyan_mpprotection.modules.php b/htdocs/core/modules/propale/doc/pdf_cyan_mpprotection.modules.php new file mode 100644 index 0000000000000..d5a00b56f716e --- /dev/null +++ b/htdocs/core/modules/propale/doc/pdf_cyan_mpprotection.modules.php @@ -0,0 +1,2176 @@ + + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2008 Raphael Bertrand + * Copyright (C) 2010-2015 Juanjo Menent + * Copyright (C) 2012 Christophe Battarel + * Copyright (C) 2012 Cedric Salvador + * Copyright (C) 2015 Marcos García + * Copyright (C) 2017 Ferran Marcet + * Copyright (C) 2021-2024 Anthony Berton + * Copyright (C) 2018-2024 Frédéric France + * Copyright (C) 2024 MDW + * Copyright (C) 2024 Nick Fragoulis + * Copyright (C) 2024 Alexandre Spangaro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * or see https://www.gnu.org/ + */ + +/** + * \file htdocs/core/modules/propale/doc/pdf_cyan_mpprotection.modules.php + * \ingroup propale + * \brief File of Class to generate PDF proposal with Cyan MP Protection template + */ +require_once DOL_DOCUMENT_ROOT.'/core/modules/propale/modules_propale.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; + + +/** + * Class to generate PDF proposal Cyan MP Protection + */ +class pdf_cyan_mpprotection extends ModelePDFPropales +{ + /** + * @var DoliDB Database handler + */ + public $db; + + /** + * @var string model name + */ + public $name; + + /** + * @var string model description (short text) + */ + public $description; + + /** + * @var int Save the name of generated file as the main doc when generating a doc with this template + */ + public $update_main_doc_field; + + /** + * @var string document type + */ + public $type; + + /** + * Dolibarr version of the loaded document + * @var string Version, possible values are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated' or a version string like 'x.y.z'''|'development'|'dolibarr'|'experimental' + */ + public $version = 'dolibarr'; + + /** + * @var array Array of document table columns + */ + public $cols; + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + global $langs, $mysoc; + + // Translations + $langs->loadLangs(array("main", "bills")); + + $this->db = $db; + $this->name = "cyan_mpprotection"; + $this->description = $langs->trans('DocModelCyanDescription').' (MP Protection)'; + $this->update_main_doc_field = 1; // Save the name of generated file as the main doc when generating a doc with this template + + // Dimension page + $this->type = 'pdf'; + $formatarray = pdf_getFormat(); + $this->page_largeur = $formatarray['width']; + $this->page_hauteur = $formatarray['height']; + $this->format = array($this->page_largeur, $this->page_hauteur); + $this->marge_gauche = getDolGlobalInt('MAIN_PDF_MARGIN_LEFT', 10); + $this->marge_droite = getDolGlobalInt('MAIN_PDF_MARGIN_RIGHT', 10); + $this->marge_haute = getDolGlobalInt('MAIN_PDF_MARGIN_TOP', 10); + $this->marge_basse = getDolGlobalInt('MAIN_PDF_MARGIN_BOTTOM', 10); + $this->corner_radius = getDolGlobalInt('MAIN_PDF_FRAME_CORNER_RADIUS', 0); + $this->option_logo = 1; // Display logo + $this->option_tva = 1; // Manage the vat option FACTURE_TVAOPTION + $this->option_modereg = 1; // Display payment mode + $this->option_condreg = 1; // Display payment terms + $this->option_multilang = 1; // Available in several languages + $this->option_escompte = 0; // Displays if there has been a discount + $this->option_credit_note = 0; // Support credit notes + $this->option_freetext = 1; // Support add of a personalised text + $this->option_draft_watermark = 1; // Support add of a watermark on drafts + $this->watermark = ''; + + // Define position of columns + $this->posxdesc = $this->marge_gauche + 1; // used for notes and other stuff + + + $this->tabTitleHeight = 5; // default height + + // Use new system for position of columns, view $this->defineColumnField() + + $this->tva = array(); + $this->tva_array = array(); + $this->localtax1 = array(); + $this->localtax2 = array(); + $this->atleastoneratenotnull = 0; + $this->atleastonediscount = 0; + + if ($mysoc === null) { + dol_syslog(get_class($this).'::__construct() Global $mysoc should not be null.'. getCallerInfoString(), LOG_ERR); + return; + } + + // Get source company + $this->emetteur = $mysoc; + if (empty($this->emetteur->country_code)) { + $this->emetteur->country_code = substr($langs->defaultlang, -2); // By default, if was not defined + } + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps + /** + * Function to build pdf onto disk + * + * @param Propal $object Object to generate + * @param Translate $outputlangs Lang output object + * @param string $srctemplatepath Full path of source filename for generator using a template file + * @param int<0,1> $hidedetails Do not show line details + * @param int<0,1> $hidedesc Do not show desc + * @param int<0,1> $hideref Do not show ref + * @return int<-1,1> 1 if OK, <=0 if KO + */ + public function write_file($object, $outputlangs, $srctemplatepath = '', $hidedetails = 0, $hidedesc = 0, $hideref = 0) + { + // phpcs:enable + global $user, $langs, $conf, $mysoc, $db, $hookmanager, $nblines; + + dol_syslog("write_file outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null')); + + if (!is_object($outputlangs)) { + $outputlangs = $langs; + } + // For backward compatibility with FPDF, force output charset to ISO, because FPDF expect text to be encoded in ISO + if (getDolGlobalString('MAIN_USE_FPDF')) { + $outputlangs->charset_output = 'ISO-8859-1'; + } + + // Load translation files required by page + $langfiles = array("main", "dict", "companies", "bills", "products", "propal", "compta"); + $outputlangs->loadLangs($langfiles); + + // Show Draft Watermark + if ($object->status == $object::STATUS_DRAFT && getDolGlobalString('PROPALE_DRAFT_WATERMARK')) { + $this->watermark = getDolGlobalString('PROPALE_DRAFT_WATERMARK'); + } + + global $outputlangsbis; + $outputlangsbis = null; + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && $outputlangs->defaultlang != getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) { + $outputlangsbis = new Translate('', $conf); + $outputlangsbis->setDefaultLang(getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')); + $outputlangsbis->loadLangs($langfiles); + } + + $nblines = count($object->lines); + + $hidetop = getDolGlobalInt('MAIN_PDF_DISABLE_COL_HEAD_TITLE'); + + // Loop on each lines to detect if there is at least one image to show + $realpatharray = array(); + $this->atleastonephoto = false; + if (getDolGlobalString('MAIN_GENERATE_PROPOSALS_WITH_PICTURE')) { + $objphoto = new Product($this->db); + + for ($i = 0; $i < $nblines; $i++) { + if (empty($object->lines[$i]->fk_product)) { + continue; + } + + $objphoto->fetch($object->lines[$i]->fk_product); + //var_dump($objphoto->ref);exit; + $pdir = array(); + if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) { + $pdir[0] = get_exdir($objphoto->id, 2, 0, 0, $objphoto, 'product').$objphoto->id."/photos/"; + $pdir[1] = get_exdir(0, 0, 0, 0, $objphoto, 'product').dol_sanitizeFileName($objphoto->ref).'/'; + } else { + $pdir[0] = get_exdir(0, 0, 0, 0, $objphoto, 'product'); // default + $pdir[1] = get_exdir($objphoto->id, 2, 0, 0, $objphoto, 'product').$objphoto->id."/photos/"; // alternative + } + + $realpath = ''; + $arephoto = false; + foreach ($pdir as $midir) { + if (!$arephoto) { + if ($conf->entity != $objphoto->entity) { + $dir = $conf->product->multidir_output[$objphoto->entity].'/'.$midir; //Check repertories of current entities + } else { + $dir = $conf->product->dir_output.'/'.$midir; //Check repertory of the current product + } + + foreach ($objphoto->liste_photos($dir, 1) as $key => $obj) { + if (!getDolGlobalInt('CAT_HIGH_QUALITY_IMAGES')) { // If CAT_HIGH_QUALITY_IMAGES not defined, we use thumb if defined and then original photo + if ($obj['photo_vignette']) { + $filename = $obj['photo_vignette']; + } else { + $filename = $obj['photo']; + } + } else { + $filename = $obj['photo']; + } + + $realpath = $dir.$filename; + $arephoto = true; + $this->atleastonephoto = true; + } + } + } + + if ($realpath && $arephoto) { + $realpatharray[$i] = $realpath; + } + } + } + + if (count($realpatharray) == 0) { + $this->posxpicture = $this->posxtva; + } + + if ($conf->propal->multidir_output[$conf->entity]) { + $object->fetch_thirdparty(); + + $deja_regle = 0; + + // Definition of $dir and $file + if ($object->specimen) { + $dir = $conf->propal->multidir_output[$conf->entity]; + $file = $dir."/SPECIMEN.pdf"; + } else { + $objectref = dol_sanitizeFileName($object->ref); + $dir = $conf->propal->multidir_output[$object->entity]."/".$objectref; + $file = $dir."/".$objectref.".pdf"; + } + + if (!file_exists($dir)) { + if (dol_mkdir($dir) < 0) { + $this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir); + return 0; + } + } + + if (file_exists($dir)) { + // Add pdfgeneration hook + if (!is_object($hookmanager)) { + include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; + $hookmanager = new HookManager($this->db); + } + $hookmanager->initHooks(array('pdfgeneration')); + $parameters = array('file' => $file, 'object' => $object, 'outputlangs' => $outputlangs); + global $action; + $reshook = $hookmanager->executeHooks('beforePDFCreation', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks + + // Set nblines with the new content of lines after hook + $nblines = count($object->lines); + //$nbpayments = count($object->getListOfPayments()); + + // Create pdf instance + $pdf = pdf_getInstance($this->format); + $default_font_size = pdf_getPDFFontSize($outputlangs); // Must be after pdf_getInstance + $pdf->SetAutoPageBreak(1, 0); + + if (class_exists('TCPDF')) { + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + } + $pdf->SetFont(pdf_getPDFFont($outputlangs)); + // Set path to the background PDF File + if (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); + } + + $pdf->Open(); + $pagenb = 0; + $pdf->SetDrawColor(128, 128, 128); + + $pdf->SetTitle($outputlangs->convToOutputCharset($object->ref)); + $pdf->SetSubject($outputlangs->transnoentities("PdfCommercialProposalTitle")); + $pdf->SetCreator("Dolibarr ".DOL_VERSION); + $pdf->SetAuthor($outputlangs->convToOutputCharset($user->getFullName($outputlangs))); + $pdf->SetKeyWords($outputlangs->convToOutputCharset($object->ref)." ".$outputlangs->transnoentities("PdfCommercialProposalTitle")." ".$outputlangs->convToOutputCharset($object->thirdparty->name)); + if (getDolGlobalString('MAIN_DISABLE_PDF_COMPRESSION')) { + $pdf->SetCompression(false); + } + + // @phan-suppress-next-line PhanPluginSuspiciousParamOrder + $pdf->SetMargins($this->marge_gauche, $this->marge_haute, $this->marge_droite); // Left, Top, Right + + // Set $this->atleastonediscount if you have at least one discount + for ($i = 0; $i < $nblines; $i++) { + if ($object->lines[$i]->remise_percent) { + $this->atleastonediscount++; + } + } + + + // New page + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + $pagenb++; + + $heightforinfotot = 40; // Height reserved to output the info and total part + $heightforsignature = !getDolGlobalString('PROPAL_DISABLE_SIGNATURE') ? (pdfGetHeightForHtmlContent($pdf, $outputlangs->transnoentities("ProposalCustomerSignature")) + 10) : 0; + $heightforfreetext = getDolGlobalInt('MAIN_PDF_FREETEXT_HEIGHT', 5); // Height reserved to output the free text on last page + $heightforfooter = $this->marge_basse + (!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS') ? 12 : 22); // Height reserved to output the footer (value include bottom margin) + //print $heightforinfotot + $heightforsignature + $heightforfreetext + $heightforfooter;exit; + + $top_shift = $this->_pagehead($pdf, $object, 1, $outputlangs, $outputlangsbis); + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->MultiCell(0, 3, ''); // Set interline to 3 + $pdf->SetTextColor(0, 0, 0); + + + $tab_top = 90 + $top_shift; + $tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 + $top_shift : 10); + if (!$hidetop && getDolGlobalInt('MAIN_PDF_ENABLE_COL_HEAD_TITLE_REPEAT')) { + // TODO : make this hidden conf the default behavior for each PDF when each PDF managed this new Display + $tab_top_newpage+= $this->tabTitleHeight; + } + + $nexY = $tab_top; + + // Incoterm + $height_incoterms = 0; + if (isModEnabled('incoterm')) { + $desc_incoterms = $object->getIncotermsForPDF(); + if ($desc_incoterms) { + $tab_top -= 2; + + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top - 1, dol_htmlentitiesbr($desc_incoterms), 0, 1); + $nexY = max($pdf->GetY(), $nexY); + $height_incoterms = $nexY - $tab_top; + + // Rect takes a length in 3rd parameter + $pdf->SetDrawColor(192, 192, 192); + $pdf->RoundedRect($this->marge_gauche, $tab_top - 1, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $height_incoterms + 3, $this->corner_radius, '1234', 'D'); + + $tab_top = $nexY + 6; + $height_incoterms += 4; + } + } + + // Displays notes + $notetoshow = empty($object->note_public) ? '' : $object->note_public; + if (getDolGlobalString('MAIN_ADD_SALE_REP_SIGNATURE_IN_NOTE')) { + // Get first sale rep + if (is_object($object->thirdparty)) { + $salereparray = $object->thirdparty->getSalesRepresentatives($user); + $salerepobj = new User($this->db); + $salerepobj->fetch($salereparray[0]['id']); + if (!empty($salerepobj->signature)) { + $notetoshow = dol_concatdesc($notetoshow, $salerepobj->signature); + } + } + } + + // Extrafields in note + $extranote = $this->getExtrafieldsInHtml($object, $outputlangs); + if (!empty($extranote)) { + $notetoshow = dol_concatdesc($notetoshow, $extranote); + } + + if (getDolGlobalString('MAIN_ADD_CREATOR_IN_NOTE') && $object->user_author_id > 0) { + $tmpuser = new User($this->db); + $tmpuser->fetch($object->user_author_id); + + $creator_info = $langs->trans("CaseFollowedBy").' '.$tmpuser->getFullName($langs); + if ($tmpuser->email) { + $creator_info .= ', '.$langs->trans("EMail").': '.$tmpuser->email; + } + if ($tmpuser->office_phone) { + $creator_info .= ', '.$langs->trans("Phone").': '.$tmpuser->office_phone; + } + + $notetoshow = dol_concatdesc($notetoshow, $creator_info); + } + + $tab_height = $this->page_hauteur - $tab_top_newpage - $heightforinfotot - $heightforfreetext - $heightforsignature - $heightforfooter; + + $pagenb = $pdf->getPage(); + if ($notetoshow) { + $tab_top -= 2; + + $tab_width = $this->page_largeur - $this->marge_gauche - $this->marge_droite; + $pageposbeforenote = $pagenb; + + $substitutionarray = pdf_getSubstitutionArray($outputlangs, null, $object); + complete_substitutions_array($substitutionarray, $outputlangs, $object); + $notetoshow = make_substitutions($notetoshow, $substitutionarray, $outputlangs); + $notetoshow = convertBackOfficeMediasLinksToPublicLinks($notetoshow); + + $pdf->startTransaction(); + + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top, dol_htmlentitiesbr($notetoshow), 0, 1); + // Description + $pageposafternote = $pdf->getPage(); + $posyafter = $pdf->GetY(); + + if ($pageposafternote > $pageposbeforenote) { + $pdf->rollbackTransaction(true); + + // prepare pages to receive notes + while ($pagenb < $pageposafternote) { + $pdf->AddPage(); + $pagenb++; + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $this->_pagehead($pdf, $object, 0, $outputlangs); + } + // $this->_pagefoot($pdf,$object,$outputlangs,1); + $pdf->setTopMargin($tab_top_newpage); + // The only function to edit the bottom margin of current page to set it. + $pdf->setPageOrientation('', 1, $heightforfooter + $heightforfreetext); + } + + // back to start + $pdf->setPage($pageposbeforenote); + $pdf->setPageOrientation('', 1, $heightforfooter + $heightforfreetext); + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top, dol_htmlentitiesbr($notetoshow), 0, 1); + $pageposafternote = $pdf->getPage(); + + $posyafter = $pdf->GetY(); + + if ($posyafter > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + 20))) { // There is no space left for total+free text + $pdf->AddPage('', '', true); + $pagenb++; + $pageposafternote++; + $pdf->setPage($pageposafternote); + $pdf->setTopMargin($tab_top_newpage); + // The only function to edit the bottom margin of current page to set it. + $pdf->setPageOrientation('', 1, $heightforfooter + $heightforfreetext); + //$posyafter = $tab_top_newpage; + } + + + // apply note frame to previous pages + $i = $pageposbeforenote; + while ($i < $pageposafternote) { + $pdf->setPage($i); + + + $pdf->SetDrawColor(128, 128, 128); + // Draw note frame + if ($i > $pageposbeforenote) { + $height_note = $this->page_hauteur - ($tab_top_newpage + $heightforfooter); + $pdf->RoundedRect($this->marge_gauche, $tab_top_newpage - 1, $tab_width, $height_note + 2, $this->corner_radius, '1234', 'D'); + } else { + $height_note = $this->page_hauteur - ($tab_top + $heightforfooter); + $pdf->RoundedRect($this->marge_gauche, $tab_top - 1, $tab_width, $height_note + 2, $this->corner_radius, '1234', 'D'); + } + + // Add footer + $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it. + $this->_pagefoot($pdf, $object, $outputlangs, 1); + + $i++; + } + + // apply note frame to last page + $pdf->setPage($pageposafternote); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $this->_pagehead($pdf, $object, 0, $outputlangs); + } + $height_note = $posyafter - $tab_top_newpage; + $pdf->RoundedRect($this->marge_gauche, $tab_top_newpage - 1, $tab_width, $height_note + 2, $this->corner_radius, '1234', 'D'); + } else { + // No pagebreak + $pdf->commitTransaction(); + $posyafter = $pdf->GetY(); + $height_note = $posyafter - $tab_top; + $pdf->RoundedRect($this->marge_gauche, $tab_top - 1, $tab_width, $height_note + 2, $this->corner_radius, '1234', 'D'); + + + if ($posyafter > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + 20))) { + // not enough space, need to add page + $pdf->AddPage('', '', true); + $pagenb++; + $pageposafternote++; + $pdf->setPage($pageposafternote); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $this->_pagehead($pdf, $object, 0, $outputlangs); + } + + $posyafter = $tab_top_newpage; + } + } + $tab_height -= $height_note; + $tab_top = $posyafter + 6; + } else { + $height_note = 0; + } + + // Use new auto column system + $this->prepareArrayColumnField($object, $outputlangs, $hidedetails, $hidedesc, $hideref); + + // Table simulation to know the height of the title line + $pdf->startTransaction(); + $this->pdfTabTitles($pdf, $tab_top, $tab_height, $outputlangs, $hidetop); + $pdf->rollbackTransaction(true); + + $nexY = $tab_top + $this->tabTitleHeight; + + // Loop on each lines + $pageposbeforeprintlines = $pdf->getPage(); + $pagenb = $pageposbeforeprintlines; + + for ($i = 0; $i < $nblines; $i++) { + $linePosition = $i + 1; + $curY = $nexY; + + // in First Check line page break and add page if needed + if (isset($object->lines[$i]->pagebreak) && $object->lines[$i]->pagebreak) { + // New page + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + + $pdf->setPage($pdf->getNumPages()); + $nexY = $tab_top_newpage; + } + + $this->resetAfterColsLinePositionsData($nexY, $pdf->getPage()); + + $pdf->SetFont('', '', $default_font_size - 1); // Into loop to work with multipage + $pdf->SetTextColor(0, 0, 0); + + // Define size of image if we need it + $imglinesize = array(); + if (!empty($realpatharray[$i])) { + $imglinesize = pdf_getSizeForImage($realpatharray[$i]); + } + + $pdf->setTopMargin($tab_top_newpage); + $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $pageposbefore = $pdf->getPage(); + $curYBefore = $curY; + + // Allows data in the first page if description is long enough to break in multiples pages + $showpricebeforepagebreak = getDolGlobalInt('MAIN_PDF_DATA_ON_FIRST_PAGE'); + + $posYAfterImage = 0; + + if ($this->getColumnStatus('photo')) { + // We start with Photo of product line + $imageTopMargin = 1; + if (isset($imglinesize['width']) && isset($imglinesize['height']) && ($curY + $imageTopMargin + $imglinesize['height']) > ($this->page_hauteur - $heightforfooter)) { // If photo too high, we moved completely on new page + $pdf->AddPage('', '', true); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + $pdf->setPage($pageposbefore + 1); + $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $curY = $tab_top_newpage; + $showpricebeforepagebreak = 0; + } + + $pdf->setPageOrientation('', 0, $heightforfooter + $heightforfreetext); // The only function to edit the bottom margin of current page to set it. + if (!empty($this->cols['photo']) && isset($imglinesize['width']) && isset($imglinesize['height'])) { + $pdf->Image($realpatharray[$i], $this->getColumnContentXStart('photo'), $curY + $imageTopMargin, $imglinesize['width'], $imglinesize['height'], '', '', '', 2, 300); // Use 300 dpi + // $pdf->Image does not increase value return by getY, so we save it manually + $posYAfterImage = $curY + $imglinesize['height']; + + $this->setAfterColsLinePositionsData('photo', $posYAfterImage, $pdf->getPage()); + } + } + + // restore Page orientation for text + $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + + if ($this->getColumnStatus('desc')) { + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + } + + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $pdf->setPage($pageposbefore); + $pdf->setTopMargin($this->marge_haute); + $curY = $curYBefore; + $pdf->setPageOrientation('', 0, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + + + // We suppose that a too long description or photo were moved completely on next page + if ($afterPosData['page'] > $pageposbefore && (empty($showpricebeforepagebreak) || ($curY + 4) > ($this->page_hauteur - $heightforfooter))) { + $pdf->setPage($afterPosData['page']); + $curY = $tab_top_newpage; + } + + $pdf->SetFont('', '', $default_font_size - 1); // We reposition the default font + + + // # of line + if ($this->getColumnStatus('position')) { + $this->printStdColumnContent($pdf, $curY, 'position', strval($linePosition)); + } + + // VAT Rate + if ($this->getColumnStatus('vat')) { + $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); + } + + // Unit price before discount + if ($this->getColumnStatus('subprice')) { + $up_excl_tax = pdf_getlineupexcltax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'subprice', $up_excl_tax); + } + + // Quantity + // Enough for 6 chars + if ($this->getColumnStatus('qty')) { + $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'qty', $qty); + } + + + // Unit + if ($this->getColumnStatus('unit')) { + $unit = pdf_getlineunit($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'unit', $unit); + } + + // Discount on line + if ($this->getColumnStatus('discount') && $object->lines[$i]->remise_percent) { + $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'discount', $remise_percent); + } + + // Total excl tax line (HT) + if ($this->getColumnStatus('totalexcltax')) { + $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } + + // Total with tax line (TTC) + if ($this->getColumnStatus('totalincltax')) { + $total_incl_tax = pdf_getlinetotalwithtax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'totalincltax', $total_incl_tax); + } + + // Extrafields + if (!empty($object->lines[$i]->array_options)) { + foreach ($object->lines[$i]->array_options as $extrafieldColKey => $extrafieldValue) { + if ($this->getColumnStatus($extrafieldColKey)) { + $extrafieldValue = $this->getExtrafieldContent($object->lines[$i], $extrafieldColKey, $outputlangs); + $this->printStdColumnContent($pdf, $curY, $extrafieldColKey, $extrafieldValue); + + $this->setAfterColsLinePositionsData('options_'.$extrafieldColKey, $pdf->GetY(), $pdf->getPage()); + } + } + } + + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $parameters = array( + 'object' => $object, + 'i' => $i, + 'pdf' => & $pdf, + 'curY' => & $curY, + 'nexY' => & $afterPosData['y'], // for backward module hook compatibility Y will be accessible by $object->getMaxAfterColsLinePositionsData() + 'outputlangs' => $outputlangs, + 'hidedetails' => $hidedetails + ); + $reshook = $hookmanager->executeHooks('printPDFline', $parameters, $this); // Note that $object may have been modified by hook + + + // Collection of totals by value of vat in $this->tva["rate"] = total_tva + if (isModEnabled("multicurrency") && $object->multicurrency_tx != 1) { + $tvaligne = $object->lines[$i]->multicurrency_total_tva; + } else { + $tvaligne = $object->lines[$i]->total_tva; + } + + $localtax1ligne = $object->lines[$i]->total_localtax1; + $localtax2ligne = $object->lines[$i]->total_localtax2; + $localtax1_rate = $object->lines[$i]->localtax1_tx; + $localtax2_rate = $object->lines[$i]->localtax2_tx; + $localtax1_type = $object->lines[$i]->localtax1_type; + $localtax2_type = $object->lines[$i]->localtax2_type; + + // TODO remise_percent is an obsolete field for object parent + /*if ($object->remise_percent) { + $tvaligne -= ($tvaligne * $object->remise_percent) / 100; + } + if ($object->remise_percent) { + $localtax1ligne -= ($localtax1ligne * $object->remise_percent) / 100; + } + if ($object->remise_percent) { + $localtax2ligne -= ($localtax2ligne * $object->remise_percent) / 100; + }*/ + + $vatrate = (string) $object->lines[$i]->tva_tx; + + // Retrieve type from database for backward compatibility with old records + if ((!isset($localtax1_type) || $localtax1_type == '' || !isset($localtax2_type) || $localtax2_type == '') // if tax type not defined + && (!empty($localtax1_rate) || !empty($localtax2_rate))) { // and there is local tax + $localtaxtmp_array = getLocalTaxesFromRate($vatrate, 0, $object->thirdparty, $mysoc); + $localtax1_type = isset($localtaxtmp_array[0]) ? $localtaxtmp_array[0] : ''; + $localtax2_type = isset($localtaxtmp_array[2]) ? $localtaxtmp_array[2] : ''; + } + + // retrieve global local tax + if ($localtax1_type && $localtax1ligne != 0) { + if (empty($this->localtax1[$localtax1_type][$localtax1_rate])) { + $this->localtax1[$localtax1_type][$localtax1_rate] = $localtax1ligne; + } else { + $this->localtax1[$localtax1_type][$localtax1_rate] += $localtax1ligne; + } + } + if ($localtax2_type && $localtax2ligne != 0) { + if (empty($this->localtax2[$localtax2_type][$localtax2_rate])) { + $this->localtax2[$localtax2_type][$localtax2_rate] = $localtax2ligne; + } else { + $this->localtax2[$localtax2_type][$localtax2_rate] += $localtax2ligne; + } + } + + if (($object->lines[$i]->info_bits & 0x01) == 0x01) { + $vatrate .= '*'; + } + + // Fill $this->tva and $this->tva_array + if (!isset($this->tva[$vatrate])) { + $this->tva[$vatrate] = 0; + } + $this->tva[$vatrate] += $tvaligne; + $vatcode = $object->lines[$i]->vat_src_code; + if (empty($this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'])) { + $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] = 0; + } + $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')] = array('vatrate' => $vatrate, 'vatcode' => $vatcode, 'amount' => $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] + $tvaligne); + + + + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $pdf->setPage($afterPosData['page']); + $nexY = $afterPosData['y']; + + // Add line + if (getDolGlobalString('MAIN_PDF_DASH_BETWEEN_LINES') && $i < ($nblines - 1) && $afterPosData['y'] < $this->page_hauteur - $heightforfooter - 5) { + $pdf->SetLineStyle(array('dash' => '1,1', 'color' => array(80, 80, 80))); + //$pdf->SetDrawColor(190,190,200); + $pdf->line($this->marge_gauche, $nexY + 1, $this->page_largeur - $this->marge_droite, $nexY + 1); + $pdf->SetLineStyle(array('dash' => 0)); + } + + $nexY += 2; // Add space between lines + } + + + // Add last page for document footer if there are not enough size left + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + if (isset($afterPosData['y']) && $afterPosData['y'] > $this->page_hauteur - ($heightforfooter + $heightforfreetext + $heightforsignature + $heightforinfotot) ) { + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + $pagenb++; + $pdf->setPage($pagenb); + } + + // Draw table frames and columns borders + $drawTabNumbPage = $pdf->getNumPages(); + for ($i=$pageposbeforeprintlines; $i<=$drawTabNumbPage; $i++) { + $pdf->setPage($i); + // reset page orientation each loop to override it if it was changed + $pdf->setPageOrientation('', 0, 0); // The only function to edit the bottom margin of current page to set it. + + $drawTabHideTop = $hidetop; + $drawTabTop = $tab_top_newpage; + $drawTabBottom = $this->page_hauteur - $heightforfooter; + $hideBottom = 0; // TODO understand why it change to 1 or 0 during process + + if ($i == $pageposbeforeprintlines) { + // first page need to start after notes + $drawTabTop = $tab_top; + } elseif (!$drawTabHideTop) { + if (getDolGlobalInt('MAIN_PDF_ENABLE_COL_HEAD_TITLE_REPEAT')) { + $drawTabTop-= $this->tabTitleHeight; + } else { + $drawTabHideTop = 1; + } + } + + // last page need to include document footer + if ($i == $pdf->getNumPages()) { + // remove document footer height to tab bottom position + $drawTabBottom-= $heightforsignature + $heightforfreetext + $heightforinfotot; + } + + $drawTabHeight = $drawTabBottom - $drawTabTop; + $this->_tableau($pdf, $drawTabTop, $drawTabHeight, 0, $outputlangs, $drawTabHideTop, $hideBottom, $object->multicurrency_code, $outputlangsbis); + + $hideFreeText = $i != $pdf->getNumPages() ? 1 : 0; // Display free text only in last page + $this->_pagefoot($pdf, $object, $outputlangs, $hideFreeText); + + $pdf->setPage($i); // in case of _pagefoot or _tableau change it + + // reset page orientation each loop to override it if it was changed by _pagefoot or _tableau change it + $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it. + + // Don't print head on first page ($pageposbeforeprintlines) because already added previously + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') && $i != $pageposbeforeprintlines) { + $this->_pagehead($pdf, $object, 0, $outputlangs); + } + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + } + + // reset text color before print footers + $pdf->SetTextColor(0, 0, 0); + + $pdf->setPage($pdf->getNumPages()); + + $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforsignature - $heightforfooter + 1; + + // Display infos area + $posy = $this->drawInfoTable($pdf, $object, $bottomlasttab, $outputlangs); + + // Display total zone + $posy = $this->drawTotalTable($pdf, $object, 0, $bottomlasttab, $outputlangs); + + // Customer signature area + if (!getDolGlobalString('PROPAL_DISABLE_SIGNATURE')) { + $posy = $this->drawSignatureArea($pdf, $object, $posy, $outputlangs); + } + + // Add number of pages in footer + if (method_exists($pdf, 'AliasNbPages')) { + $pdf->AliasNbPages(); // @phan-suppress-current-line PhanUndeclaredMethod + } + + // Add terms to sale + if (!empty($mysoc->termsofsale) && getDolGlobalInt('MAIN_PDF_ADD_TERMSOFSALE_PROPAL')) { + $termsofsale = $conf->mycompany->dir_output.'/'.$mysoc->termsofsale; + if (!empty($conf->mycompany->multidir_output[$object->entity])) { + $termsofsale = $conf->mycompany->multidir_output[$object->entity].'/'.$mysoc->termsofsale; + } + if (file_exists($termsofsale) && is_readable($termsofsale)) { + $pagecount = $pdf->setSourceFile($termsofsale); + for ($i = 1; $i <= $pagecount; $i++) { + $tplIdx = $pdf->importPage($i); + if ($tplIdx!==false) { + $s = $pdf->getTemplatesize($tplIdx); + $pdf->AddPage($s['h'] > $s['w'] ? 'P' : 'L'); + $pdf->useTemplate($tplIdx); + } else { + setEventMessages(null, array($termsofsale.' cannot be added, probably protected PDF'), 'warnings'); + } + } + } + } + + //If propal merge product PDF is active + if (getDolGlobalString('PRODUIT_PDF_MERGE_PROPAL')) { + require_once DOL_DOCUMENT_ROOT.'/product/class/propalmergepdfproduct.class.php'; + + $already_merged = array(); + foreach ($object->lines as $line) { + if (!empty($line->fk_product) && !(in_array($line->fk_product, $already_merged))) { + // Find the desired PDF + $filetomerge = new Propalmergepdfproduct($this->db); + + if (getDolGlobalInt('MAIN_MULTILANGS')) { + $filetomerge->fetch_by_product($line->fk_product, $outputlangs->defaultlang); + } else { + $filetomerge->fetch_by_product($line->fk_product); + } + + $already_merged[] = $line->fk_product; + + $product = new Product($this->db); + $product->fetch($line->fk_product); + + if ($product->entity != $conf->entity) { + $entity_product_file = $product->entity; + } else { + $entity_product_file = $conf->entity; + } + + // If PDF is selected and file is not empty + if (count($filetomerge->lines) > 0) { + foreach ($filetomerge->lines as $linefile) { + if (!empty($linefile->id) && !empty($linefile->file_name)) { + if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) { + if (isModEnabled("product")) { + $filetomerge_dir = $conf->product->multidir_output[$entity_product_file].'/'.get_exdir($product->id, 2, 0, 0, $product, 'product').$product->id."/photos"; + } elseif (isModEnabled("service")) { + $filetomerge_dir = $conf->service->multidir_output[$entity_product_file].'/'.get_exdir($product->id, 2, 0, 0, $product, 'product').$product->id."/photos"; + } + } else { + if (isModEnabled("product")) { + $filetomerge_dir = $conf->product->multidir_output[$entity_product_file].'/'.get_exdir(0, 0, 0, 0, $product, 'product'); + } elseif (isModEnabled("service")) { + $filetomerge_dir = $conf->service->multidir_output[$entity_product_file].'/'.get_exdir(0, 0, 0, 0, $product, 'product'); + } + } + + dol_syslog(get_class($this).':: upload_dir='.$filetomerge_dir, LOG_DEBUG); + + $infile = $filetomerge_dir.'/'.$linefile->file_name; + if (file_exists($infile) && is_readable($infile)) { + $pagecount = $pdf->setSourceFile($infile); + for ($i = 1; $i <= $pagecount; $i++) { + $tplIdx = $pdf->importPage($i); + if ($tplIdx !== false) { + $s = $pdf->getTemplatesize($tplIdx); + $pdf->AddPage($s['h'] > $s['w'] ? 'P' : 'L'); + $pdf->useTemplate($tplIdx); + } else { + setEventMessages(null, array($infile.' cannot be added, probably protected PDF'), 'warnings'); + } + } + } + } + } + } + } + } + } + + $pdf->Close(); + + $pdf->Output($file, 'F'); + + //Add pdfgeneration hook + $hookmanager->initHooks(array('pdfgeneration')); + $parameters = array('file' => $file, 'object' => $object, 'outputlangs' => $outputlangs); + global $action; + $reshook = $hookmanager->executeHooks('afterPDFCreation', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks + if ($reshook < 0) { + $this->error = $hookmanager->error; + $this->errors = $hookmanager->errors; + } + + dolChmod($file); + + $this->result = array('fullpath' => $file); + + return 1; // No error + } else { + $this->error = $langs->trans("ErrorCanNotCreateDir", $dir); + return 0; + } + } else { + $this->error = $langs->trans("ErrorConstantNotDefined", "PROP_OUTPUTDIR"); + return 0; + } + } + + /** + * Show miscellaneous information (payment mode, payment term, ...) + * + * @param TCPDF $pdf Object PDF + * @param Propal $object Object to show + * @param int $posy Y + * @param Translate $outputlangs Langs object + * @return int Pos y + */ + public function drawInfoTable(&$pdf, $object, $posy, $outputlangs) + { + global $conf, $mysoc; + $default_font_size = pdf_getPDFFontSize($outputlangs); + + $pdf->SetFont('', '', $default_font_size - 1); + + $diffsizetitle = (!getDolGlobalString('PDF_DIFFSIZE_TITLE') ? 3 : $conf->global->PDF_DIFFSIZE_TITLE); + + // If France, show VAT mention if not applicable + if ($this->emetteur->country_code == 'FR' && empty($mysoc->tva_assuj)) { + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->MultiCell(100, 3, $outputlangs->transnoentities("VATIsNotUsedForInvoice"), 0, 'L', 0); + + $posy = $pdf->GetY() + 4; + } + + $posxval = 52; + if (getDolGlobalString('MAIN_PDF_DELIVERY_DATE_TEXT')) { + $displaydate = "daytext"; + } else { + $displaydate = "day"; + } + + // Show shipping date + if (!empty($object->delivery_date)) { + $outputlangs->load("sendings"); + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("DateDeliveryPlanned").':'; + $pdf->MultiCell(80, 4, $titre, 0, 'L'); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->SetXY($posxval, $posy); + $dlp = dol_print_date($object->delivery_date, $displaydate, false, $outputlangs, true); + $pdf->MultiCell(80, 4, $dlp, 0, 'L'); + + $posy = $pdf->GetY() + 1; + } elseif ($object->availability_code || $object->availability) { // Show availability conditions + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("AvailabilityPeriod").':'; + $pdf->MultiCell(80, 4, $titre, 0, 'L'); + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->SetXY($posxval, $posy); + $lib_availability = ($outputlangs->transnoentities("AvailabilityType".$object->availability_code) != 'AvailabilityType'.$object->availability_code) ? $outputlangs->transnoentities("AvailabilityType".$object->availability_code) : $outputlangs->convToOutputCharset($object->availability); + $lib_availability = str_replace('\n', "\n", $lib_availability); + $pdf->MultiCell(80, 4, $lib_availability, 0, 'L'); + + $posy = $pdf->GetY() + 1; + } + + // Show delivery mode + if (!getDolGlobalString('PROPOSAL_PDF_HIDE_DELIVERYMODE') && $object->shipping_method_id > 0) { + $outputlangs->load("sendings"); + + $shipping_method_id = $object->shipping_method_id; + if (getDolGlobalString('SOCIETE_ASK_FOR_SHIPPING_METHOD') && !empty($this->emetteur->shipping_method_id)) { + $shipping_method_id = $this->emetteur->shipping_method_id; + } + $shipping_method_code = dol_getIdFromCode($this->db, (string) $shipping_method_id, 'c_shipment_mode', 'rowid', 'code'); + $shipping_method_label = dol_getIdFromCode($this->db, (string) $shipping_method_id, 'c_shipment_mode', 'rowid', 'libelle'); + + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("SendingMethod").':'; + $pdf->MultiCell(43, 4, $titre, 0, 'L'); + + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->SetXY($posxval, $posy); + $lib_condition_paiement = ($outputlangs->transnoentities("SendingMethod".strtoupper($shipping_method_code)) != "SendingMethod".strtoupper($shipping_method_code)) ? $outputlangs->trans("SendingMethod".strtoupper($shipping_method_code)) : $shipping_method_label; + $lib_condition_paiement = str_replace('\n', "\n", $lib_condition_paiement); + $pdf->MultiCell(67, 4, $lib_condition_paiement, 0, 'L'); + + $posy = $pdf->GetY() + 1; + } + + // Show payments conditions + if (!getDolGlobalString('PROPOSAL_PDF_HIDE_PAYMENTTERM') && $object->cond_reglement_code) { + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("PaymentConditions").':'; + $pdf->MultiCell(43, 4, $titre, 0, 'L'); + + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->SetXY($posxval, $posy); + $lib_condition_paiement = $outputlangs->transnoentities("PaymentCondition".$object->cond_reglement_code) != 'PaymentCondition'.$object->cond_reglement_code ? $outputlangs->transnoentities("PaymentCondition".$object->cond_reglement_code) : $outputlangs->convToOutputCharset($object->cond_reglement_doc ? $object->cond_reglement_doc : $object->cond_reglement_label); + $lib_condition_paiement = str_replace('\n', "\n", $lib_condition_paiement); + if ($object->deposit_percent > 0) { + $lib_condition_paiement = str_replace('__DEPOSIT_PERCENT__', $object->deposit_percent, $lib_condition_paiement); + } + $pdf->MultiCell(67, 4, $lib_condition_paiement, 0, 'L'); + + $posy = $pdf->GetY() + 3; + } + + if (!getDolGlobalString('PROPOSAL_PDF_HIDE_PAYMENTMODE')) { + // Show payment mode + if ($object->mode_reglement_code + && $object->mode_reglement_code != 'CHQ' + && $object->mode_reglement_code != 'VIR') { + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("PaymentMode").':'; + $pdf->MultiCell(80, 5, $titre, 0, 'L'); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->SetXY($posxval, $posy); + $lib_mode_reg = $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) != 'PaymentType'.$object->mode_reglement_code ? $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) : $outputlangs->convToOutputCharset($object->mode_reglement); + $pdf->MultiCell(80, 5, $lib_mode_reg, 0, 'L'); + + $posy = $pdf->GetY() + 2; + } + + // Show payment mode CHQ + if (empty($object->mode_reglement_code) || $object->mode_reglement_code == 'CHQ') { + // Si mode reglement non force ou si force a CHQ + if (getDolGlobalInt('FACTURE_CHQ_NUMBER')) { + if (getDolGlobalInt('FACTURE_CHQ_NUMBER') > 0) { + $account = new Account($this->db); + $account->fetch(getDolGlobalInt('FACTURE_CHQ_NUMBER')); + + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->transnoentities('PaymentByChequeOrderedTo', $account->owner_name), 0, 'L', 0); + $posy = $pdf->GetY() + 1; + + if (!getDolGlobalString('MAIN_PDF_HIDE_CHQ_ADDRESS')) { + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->convToOutputCharset($account->owner_address), 0, 'L', 0); + $posy = $pdf->GetY() + 2; + } + } + if (getDolGlobalInt('FACTURE_CHQ_NUMBER') == -1) { + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->transnoentities('PaymentByChequeOrderedTo', $this->emetteur->name), 0, 'L', 0); + $posy = $pdf->GetY() + 1; + + if (!getDolGlobalString('MAIN_PDF_HIDE_CHQ_ADDRESS')) { + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->convToOutputCharset($this->emetteur->getFullAddress()), 0, 'L', 0); + $posy = $pdf->GetY() + 2; + } + } + } + } + + // If payment mode not forced or forced to VIR, show payment with BAN + if (empty($object->mode_reglement_code) || $object->mode_reglement_code == 'VIR') { + if ($object->fk_account > 0 || $object->fk_bank > 0 || getDolGlobalInt('FACTURE_RIB_NUMBER')) { + $bankid = ($object->fk_account <= 0 ? getDolGlobalInt('FACTURE_RIB_NUMBER') : $object->fk_account); + if ($object->fk_bank > 0) { + $bankid = $object->fk_bank; // For backward compatibility when object->fk_account is forced with object->fk_bank + } + $account = new Account($this->db); + $account->fetch($bankid); + + $curx = $this->marge_gauche; + $cury = $posy; + + $posy = pdf_bank($pdf, $outputlangs, $curx, $cury, $account, 0, $default_font_size); + + $posy += 2; + } + } + } + + return $posy; + } + + + /** + * Show total to pay + * + * @param TCPDF $pdf Object PDF + * @param Propal $object Object proposal + * @param int $deja_regle Amount already paid (in the currency of invoice) + * @param int $posy Position depart + * @param Translate $outputlangs Object langs + * @param Translate $outputlangsbis Object lang for output bis + * @return int Position pour suite + */ + protected function drawTotalTable(&$pdf, $object, $deja_regle, $posy, $outputlangs, $outputlangsbis = null) + { + global $conf, $mysoc, $hookmanager; + + $default_font_size = pdf_getPDFFontSize($outputlangs); + + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && $outputlangs->defaultlang != getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) { + $outputlangsbis = new Translate('', $conf); + $outputlangsbis->setDefaultLang(getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')); + $outputlangsbis->loadLangs(array("main", "dict", "companies", "bills", "products", "propal")); + $default_font_size--; + } + + $tab2_top = $posy; + $tab2_hl = 4; + $pdf->SetFont('', '', $default_font_size - 1); + + // Total table + $col1x = 120; + $col2x = 170; + if ($this->page_largeur < 210) { // To work with US executive format + $col2x -= 20; + } + $largcol2 = ($this->page_largeur - $this->marge_droite - $col2x); + + $useborder = 0; + $index = 0; + + // Get Total HT + $total_ht = (isModEnabled("multicurrency") && $object->multicurrency_tx != 1 ? $object->multicurrency_total_ht : $object->total_ht); + + // Total remise + $total_line_remise = 0; + foreach ($object->lines as $i => $line) { + $resdiscount = pdfGetLineTotalDiscountAmount($object, $i, $outputlangs, 2); + $total_line_remise += (is_numeric($resdiscount) ? $resdiscount : 0); + // Gestion remise sous forme de ligne négative + if ($line->total_ht < 0) { + $total_line_remise += -$line->total_ht; + } + } + if ($total_line_remise > 0) { + $pdf->SetFillColor(255, 255, 255); + $pdf->SetXY($col1x, $tab2_top + $tab2_hl); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalDiscount").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalDiscount") : ''), 0, 'L', 1); + $pdf->SetXY($col2x, $tab2_top + $tab2_hl); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_line_remise, 0, $outputlangs), 0, 'R', 1); + + $index++; + + // Show total NET before discount + $pdf->SetFillColor(255, 255, 255); + $pdf->SetXY($col1x, $tab2_top); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalHTBeforeDiscount").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalHTBeforeDiscount") : ''), 0, 'L', 1); + $pdf->SetXY($col2x, $tab2_top); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_line_remise + $total_ht, 0, $outputlangs), 0, 'R', 1); + + $index++; + } + + // Total HT + $pdf->SetFillColor(255, 255, 255); + $pdf->SetXY($col1x, $tab2_top+ $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalHT").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalHT") : ''), 0, 'L', 1); + + $total_ht = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? $object->multicurrency_total_ht : $object->total_ht); + $pdf->SetXY($col2x, $tab2_top+ $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_ht + (!empty($object->remise) ? $object->remise : 0), 0, $outputlangs), 0, 'R', 1); + + // Show VAT by rates and total + $pdf->SetFillColor(248, 248, 248); + + $total_ttc = (isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? $object->multicurrency_total_ttc : $object->total_ttc; + + $this->atleastoneratenotnull = 0; + if (!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT')) { + $tvaisnull = (!empty($this->tva) && count($this->tva) == 1 && isset($this->tva['0.000']) && is_float($this->tva['0.000'])); + if (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_IFNULL') && $tvaisnull) { + // Nothing to do + } else { + //Local tax 1 before VAT + //if (getDolGlobalString('FACTURE_LOCAL_TAX1_OPTION') && getDolGlobalString('FACTURE_LOCAL_TAX1_OPTION') == 'localtax1on') + //{ + foreach ($this->localtax1 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('1', '3', '5'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT1", $mysoc->country_code) : ''); + $totalvat .= ' '; + $totalvat .= vatrate((string) abs((float) $tvakey), 1).$tvacompl; + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + //} + //Local tax 2 before VAT + //if (getDolGlobalString('FACTURE_LOCAL_TAX2_OPTION') && getDolGlobalString('FACTURE_LOCAL_TAX2_OPTION') == 'localtax2on') + //{ + foreach ($this->localtax2 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('1', '3', '5'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT2", $mysoc->country_code) : ''); + $totalvat .= ' '; + $totalvat .= vatrate((string) abs((float) $tvakey), 1).$tvacompl; + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + //} + + // VAT + foreach ($this->tva as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + $this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalVAT", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalVAT", $mysoc->country_code) : ''); + $totalvat .= ' '; + $totalvat .= vatrate($tvakey, 1).$tvacompl; + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($tvaval, 0, $outputlangs), 0, 'R', 1); + } + } + + //Local tax 1 after VAT + //if (getDolGlobalString('FACTURE_LOCAL_TAX1_OPTION') && getDolGlobalString('FACTURE_LOCAL_TAX1_OPTION') == 'localtax1on') + //{ + foreach ($this->localtax1 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('2', '4', '6'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT1", $mysoc->country_code) : ''); + $totalvat .= ' '; + + $totalvat .= vatrate((string) abs((float) $tvakey), 1).$tvacompl; + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + //} + //Local tax 2 after VAT + //if (getDolGlobalString('FACTURE_LOCAL_TAX2_OPTION') && getDolGlobalString('FACTURE_LOCAL_TAX2_OPTION') == 'localtax2on') + //{ + foreach ($this->localtax2 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('2', '4', '6'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + // retrieve global local tax + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT2", $mysoc->country_code) : ''); + $totalvat .= ' '; + + if (getDolGlobalString('PDF_LOCALTAX2_LABEL_IS_CODE_OR_RATE') == 'nocodenorate') { + $totalvat .= $tvacompl; + } else { + $totalvat .= vatrate((string) abs((float) $tvakey), 1).$tvacompl; + } + + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + //} + + // Total TTC + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFillColor(224, 224, 224); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalTTC").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalTTC") : ''), $useborder, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_ttc, 0, $outputlangs), $useborder, 'R', 1); + + // Deposit (acompte) line - MP Protection specific + $depositRate = getDolGlobalInt('PROPALE_MPPROTECTION_DEPOSIT_RATE', 40); + if ($depositRate > 0 && $depositRate <= 100) { + $depositAmount = price2num($total_ttc * $depositRate / 100, 'MT'); + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFillColor(255, 255, 255); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("Acompte").' ('.$depositRate.'%)', 0, 'L', 0); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($depositAmount, 0, $outputlangs), 0, 'R', 0); + } + } + } + + $pdf->SetTextColor(0, 0, 0); + + $resteapayer = 0; + /* + $resteapayer = $object->total_ttc - $deja_regle; + if (!empty($object->paye)) $resteapayer=0; + */ + + if ($deja_regle > 0) { + // Already paid + Deposits + $index++; + + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("AlreadyPaid").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("AlreadyPaid") : ''), 0, 'L', 0); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($deja_regle, 0, $outputlangs), 0, 'R', 0); + + /* + if ($object->close_code == 'discount_vat') { + $index++; + $pdf->SetFillColor(255,255,255); + + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("EscompteOfferedShort"), $useborder, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($object->total_ttc - $deja_regle, 0, $outputlangs), $useborder, 'R', 1); + + $resteapayer=0; + } + */ + + $index++; + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFillColor(224, 224, 224); + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("RemainderToPay").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("RemainderToPay") : ''), $useborder, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($resteapayer, 0, $outputlangs), $useborder, 'R', 1); + + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->SetTextColor(0, 0, 0); + } + + $parameters = array('pdf' => &$pdf, 'object' => &$object, 'outputlangs' => $outputlangs, 'index' => &$index, 'posy' => $posy); + + $reshook = $hookmanager->executeHooks('afterPDFTotalTable', $parameters, $this); // Note that $action and $object may have been modified by some hooks + if ($reshook < 0) { + $this->error = $hookmanager->error; + $this->errors = $hookmanager->errors; + } + + $index++; + return ($tab2_top + ($tab2_hl * $index)); + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show table for lines + * + * @param TCPDF $pdf Object PDF + * @param float|int $tab_top Top position of table + * @param float|int $tab_height Height of table (rectangle) + * @param int $nexY Y (not used) + * @param Translate $outputlangs Langs object + * @param int $hidetop 1=Hide top bar of array and title, 0=Hide nothing, -1=Hide only title + * @param int $hidebottom Hide bottom bar of array + * @param string $currency Currency code + * @param Translate $outputlangsbis Langs object bis + * @return void + */ + protected function _tableau(&$pdf, $tab_top, $tab_height, $nexY, $outputlangs, $hidetop = 0, $hidebottom = 0, $currency = '', $outputlangsbis = null) + { + global $conf; + + // Force to disable hidetop and hidebottom + $hidebottom = 0; + if ($hidetop) { + $hidetop = -1; + } + + $currency = !empty($currency) ? $currency : $conf->currency; + $default_font_size = pdf_getPDFFontSize($outputlangs); + + // Amount in (at tab_top - 1) + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - 2); + + if (empty($hidetop)) { + $titre = $outputlangs->transnoentities("AmountInCurrency", $outputlangs->transnoentitiesnoconv("Currency".$currency)); + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && is_object($outputlangsbis)) { + $titre .= ' - '.$outputlangsbis->transnoentities("AmountInCurrency", $outputlangsbis->transnoentitiesnoconv("Currency".$currency)); + } + + $pdf->SetXY($this->page_largeur - $this->marge_droite - ($pdf->GetStringWidth($titre) + 3), $tab_top - 4); + $pdf->MultiCell(($pdf->GetStringWidth($titre) + 3), 2, $titre); + + //$conf->global->MAIN_PDF_TITLE_BACKGROUND_COLOR='230,230,230'; + if (getDolGlobalString('MAIN_PDF_TITLE_BACKGROUND_COLOR')) { + $pdf->RoundedRect($this->marge_gauche, $tab_top, $this->page_largeur - $this->marge_droite - $this->marge_gauche, $this->tabTitleHeight, $this->corner_radius, '1001', 'F', null, explode(',', getDolGlobalString('MAIN_PDF_TITLE_BACKGROUND_COLOR'))); + } + } + + $pdf->SetDrawColor(128, 128, 128); + $pdf->SetFont('', '', $default_font_size - 1); + + // Output Rect + $this->printRoundedRect($pdf, $this->marge_gauche, $tab_top, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $tab_height, $this->corner_radius, $hidetop, $hidebottom, 'D'); // Rect takes a length in 3rd parameter and 4th parameter + + if (getDolGlobalString('MAIN_PDF_TITLE_TEXT_COLOR')) { + $arrayColorTextTitle = explode(',', getDolGlobalString('MAIN_PDF_TITLE_TEXT_COLOR')); + $pdf->SetTextColor($arrayColorTextTitle[0], $arrayColorTextTitle[1], $arrayColorTextTitle[2]); + } + + $this->pdfTabTitles($pdf, $tab_top, $tab_height, $outputlangs, $hidetop); + + if (getDolGlobalString('MAIN_PDF_TITLE_TEXT_COLOR')) { + $pdf->SetTextColor(0, 0, 0); + } + + if (empty($hidetop)) { + $pdf->line($this->marge_gauche, $tab_top + $this->tabTitleHeight, $this->page_largeur - $this->marge_droite, $tab_top + $this->tabTitleHeight); // line takes a position y in 2nd parameter and 4th parameter + } + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show top header of page. + * + * @param TCPDF $pdf Object PDF + * @param Propal $object Object to show + * @param int $showaddress 0=no, 1=yes + * @param Translate $outputlangs Object lang for output + * @param Translate $outputlangsbis Object lang for output bis + * @return float|int Return topshift value + */ + protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $outputlangsbis = null) + { + global $conf, $langs; + + $ltrdirection = 'L'; + if ($outputlangs->trans("DIRECTION") == 'rtl') { + $ltrdirection = 'R'; + } + + // Load traductions files required by page + $outputlangs->loadLangs(array("main", "propal", "companies", "bills")); + + $default_font_size = pdf_getPDFFontSize($outputlangs); + + pdf_pagehead($pdf, $outputlangs, $this->page_hauteur); + + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFont('', 'B', $default_font_size + 3); + + $w = 100; + + $posy = $this->marge_haute; + $posx = $this->page_largeur - $this->marge_droite - $w; + + $pdf->SetXY($this->marge_gauche, $posy); + + // Logo + if (!getDolGlobalInt('PDF_DISABLE_MYCOMPANY_LOGO')) { + if ($this->emetteur->logo) { + $logodir = $conf->mycompany->dir_output; + if (!empty($conf->mycompany->multidir_output[$object->entity])) { + $logodir = $conf->mycompany->multidir_output[$object->entity]; + } + if (!getDolGlobalInt('MAIN_PDF_USE_LARGE_LOGO')) { + $logo = $logodir.'/logos/thumbs/'.$this->emetteur->logo_small; + } else { + $logo = $logodir.'/logos/'.$this->emetteur->logo; + } + if (is_readable($logo)) { + $height = pdf_getHeightForLogo($logo); + $pdf->Image($logo, $this->marge_gauche, $posy, 0, $height); // width=0 (auto) + } else { + $pdf->SetTextColor(200, 0, 0); + $pdf->SetFont('', 'B', $default_font_size - 2); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ErrorLogoFileNotFound", $logo), 0, 'L'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ErrorGoToGlobalSetup"), 0, 'L'); + } + } else { + $text = $this->emetteur->name; + $pdf->MultiCell($w, 4, $outputlangs->convToOutputCharset($text), 0, $ltrdirection); + } + } + + $pdf->SetFont('', 'B', $default_font_size + 3); + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $title = $outputlangs->transnoentities("PdfCommercialProposalTitle"); + $title .= ' '.$outputlangs->convToOutputCharset($object->ref); + if ($object->status == $object::STATUS_DRAFT) { + $pdf->SetTextColor(128, 0, 0); + $title .= ' - '.$outputlangs->transnoentities("NotValidated"); + } + + $pdf->MultiCell($w, 4, $title, '', 'R'); + + $pdf->SetFont('', 'B', $default_font_size); + + /* + $posy += 5; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $textref = $outputlangs->transnoentities("Ref")." : ".$outputlangs->convToOutputCharset($object->ref); + if ($object->status == $object::STATUS_DRAFT) { + $pdf->SetTextColor(128, 0, 0); + $textref .= ' - '.$outputlangs->transnoentities("NotValidated"); + } + $pdf->MultiCell($w, 4, $textref, '', 'R'); + */ + + $posy += 3; + $pdf->SetFont('', '', $default_font_size - 2); + + $ref_customer = $object->ref_customer ?: $object->ref_client; + if ($ref_customer) { + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".dol_trunc($outputlangs->convToOutputCharset($ref_customer), 65), '', 'R'); + } + + if (getDolGlobalString('PDF_SHOW_PROJECT_TITLE')) { + $object->fetchProject(); + if (!empty($object->project->ref)) { + $posy += 3; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("Project")." : ".(empty($object->project->title) ? '' : $object->project->title), '', 'R'); + } + } + + if (getDolGlobalString('PDF_SHOW_PROJECT')) { + $object->fetchProject(); + if (!empty($object->project->ref)) { + $outputlangs->load("projects"); + $posy += 3; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefProject")." : ".(empty($object->project->ref) ? '' : $object->project->ref), '', 'R'); + } + } + + if (getDolGlobalString('MAIN_PDF_DATE_TEXT')) { + $displaydate = "daytext"; + } else { + $displaydate = "day"; + } + + //$posy += 4; + $posy = $pdf->getY(); + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("Date")." : ".dol_print_date($object->date, $displaydate, false, $outputlangs, true), '', 'R'); + + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + + $title = $outputlangs->transnoentities("DateEndPropal"); + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && is_object($outputlangsbis)) { + $title .= ' - '.$outputlangsbis->transnoentities("DateEndPropal"); + } + $pdf->MultiCell($w, 3, $title." : ".dol_print_date($object->fin_validite, $displaydate, false, $outputlangs, true), '', 'R'); + + if (!getDolGlobalString('MAIN_PDF_HIDE_CUSTOMER_CODE') && $object->thirdparty->code_client) { + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CustomerCode")." : ".$outputlangs->transnoentities($object->thirdparty->code_client), '', 'R'); + } + + if (!getDolGlobalString('MAIN_PDF_HIDE_CUSTOMER_ACCOUNTING_CODE') && $object->thirdparty->code_compta_client) { + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CustomerAccountancyCode")." : ".$outputlangs->transnoentities($object->thirdparty->code_compta_client), '', 'R'); + } + + // Get contact + if (getDolGlobalString('DOC_SHOW_FIRST_SALES_REP')) { + $arrayidcontact = $object->getIdContact('internal', 'SALESREPFOLL'); + if (count($arrayidcontact) > 0) { + $usertmp = new User($this->db); + $usertmp->fetch($arrayidcontact[0]); + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R'); + } + } + + $posy += 2; + + $top_shift = 0; + // Show list of linked objects + $current_y = $pdf->getY(); + $posy = pdf_writeLinkedObjects($pdf, $object, $outputlangs, $posx, $posy, $w, 3, 'R', $default_font_size); + if ($current_y < $pdf->getY()) { + $top_shift = $pdf->getY() - $current_y; + } + + if ($showaddress) { + // Sender properties + $carac_emetteur = ''; + // Add internal contact of object if defined + $arrayidcontact = $object->getIdContact('internal', 'SALESREPFOLL'); + if (count($arrayidcontact) > 0) { + $object->fetch_user($arrayidcontact[0]); + $labelbeforecontactname = ($outputlangs->transnoentities("FromContactName") != 'FromContactName' ? $outputlangs->transnoentities("FromContactName") : $outputlangs->transnoentities("Name")); + $carac_emetteur .= ($carac_emetteur ? "\n" : '').$labelbeforecontactname." ".$outputlangs->convToOutputCharset($object->user->getFullName($outputlangs)); + $carac_emetteur .= (getDolGlobalInt('PDF_SHOW_PHONE_AFTER_USER_CONTACT') || getDolGlobalInt('PDF_SHOW_EMAIL_AFTER_USER_CONTACT')) ? ' (' : ''; + $carac_emetteur .= (getDolGlobalInt('PDF_SHOW_PHONE_AFTER_USER_CONTACT') && !empty($object->user->office_phone)) ? $object->user->office_phone : ''; + $carac_emetteur .= (getDolGlobalInt('PDF_SHOW_PHONE_AFTER_USER_CONTACT') && getDolGlobalInt('PDF_SHOW_EMAIL_AFTER_USER_CONTACT')) ? ', ' : ''; + $carac_emetteur .= (getDolGlobalInt('PDF_SHOW_EMAIL_AFTER_USER_CONTACT') && !empty($object->user->email)) ? $object->user->email : ''; + $carac_emetteur .= (getDolGlobalInt('PDF_SHOW_PHONE_AFTER_USER_CONTACT') || getDolGlobalInt('PDF_SHOW_EMAIL_AFTER_USER_CONTACT')) ? ')' : ''; + $carac_emetteur .= "\n"; + } + + $carac_emetteur .= pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, '', 0, 'source', $object); + + // Show sender + $posy = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 40 : 42; + $posy += $top_shift; + $posx = $this->marge_gauche; + if (getDolGlobalString('MAIN_INVERT_SENDER_RECIPIENT')) { + $posx = $this->page_largeur - $this->marge_droite - 80; + } + + $hautcadre = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 38 : 40; + $widthrecbox = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 92 : 82; + + // Show sender frame + if (!getDolGlobalString('MAIN_PDF_NO_SENDER_FRAME')) { + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->SetXY($posx, $posy - 5); + $pdf->MultiCell($widthrecbox, 5, $outputlangs->transnoentities("BillFrom"), 0, $ltrdirection); + $pdf->SetXY($posx, $posy); + $pdf->SetFillColor(230, 230, 230); + $pdf->RoundedRect($posx, $posy, $widthrecbox, $hautcadre, $this->corner_radius, '1234', 'F'); + $pdf->SetTextColor(0, 0, 60); + } + + // Show sender name + if (!getDolGlobalString('MAIN_PDF_HIDE_SENDER_NAME')) { + $pdf->SetXY($posx + 2, $posy + 3); + $pdf->SetFont('', 'B', $default_font_size); + $pdf->MultiCell($widthrecbox - 2, 4, $outputlangs->convToOutputCharset($this->emetteur->name), 0, $ltrdirection); + $posy = $pdf->getY(); + } + + // Show sender information + $pdf->SetXY($posx + 2, $posy); + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->MultiCell($widthrecbox - 2, 4, $carac_emetteur, 0, $ltrdirection); + + + // If CUSTOMER contact defined, we use it + $usecontact = false; + $arrayidcontact = $object->getIdContact('external', 'CUSTOMER'); + if (count($arrayidcontact) > 0) { + $usecontact = true; + $result = $object->fetch_contact($arrayidcontact[0]); + } + + // Recipient name + if ($usecontact && ($object->contact->socid != $object->thirdparty->id && (!isset($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT) || getDolGlobalString('MAIN_USE_COMPANY_NAME_OF_CONTACT')))) { + $thirdparty = $object->contact; + } else { + $thirdparty = $object->thirdparty; + } + + $carac_client_name = pdfBuildThirdpartyName($thirdparty, $outputlangs); + + $mode = 'target'; + $carac_client = pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, ($usecontact ? $object->contact : ''), ($usecontact ? 1 : 0), $mode, $object); + + // Show recipient + $widthrecbox = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 92 : 100; + if ($this->page_largeur < 210) { + $widthrecbox = 84; // To work with US executive format + } + $posy = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 40 : 42; + $posy += $top_shift; + $posx = $this->page_largeur - $this->marge_droite - $widthrecbox; + if (getDolGlobalString('MAIN_INVERT_SENDER_RECIPIENT')) { + $posx = $this->marge_gauche; + } + + // Show recipient frame + if (!getDolGlobalString('MAIN_PDF_NO_RECIPENT_FRAME')) { + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->SetXY($posx + 2, $posy - 5); + $pdf->MultiCell($widthrecbox, 5, $outputlangs->transnoentities("BillTo"), 0, $ltrdirection); + $pdf->RoundedRect($posx, $posy, $widthrecbox, $hautcadre, $this->corner_radius, '1234', 'D'); + } + + // Show recipient name + $pdf->SetXY($posx + 2, $posy + 3); + $pdf->SetFont('', 'B', $default_font_size); + // @phan-suppress-next-line PhanPluginSuspiciousParamOrder + $pdf->MultiCell($widthrecbox, 2, $carac_client_name, 0, $ltrdirection); + + $posy = $pdf->getY(); + + // Show recipient information + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->SetXY($posx + 2, $posy); + // @phan-suppress-next-line PhanPluginSuspiciousParamOrder + $pdf->MultiCell($widthrecbox, 4, $carac_client, 0, $ltrdirection); + } + + $pdf->SetTextColor(0, 0, 0); + + return $top_shift; + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show footer of page. Need this->emetteur object + * + * @param TCPDF $pdf PDF + * @param Propal $object Object to show + * @param Translate $outputlangs Object lang for output + * @param int $hidefreetext 1=Hide free text + * @return int Return height of bottom margin including footer text + */ + protected function _pagefoot(&$pdf, $object, $outputlangs, $hidefreetext = 0) + { + $showdetails = getDolGlobalInt('MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS', 0); + return pdf_pagefoot($pdf, $outputlangs, 'PROPOSAL_FREE_TEXT', $this->emetteur, $this->marge_basse, $this->marge_gauche, $this->page_hauteur, $object, $showdetails, $hidefreetext, $this->page_largeur, $this->watermark); + } + + /** + * Show area for the customer to sign + * + * @param TCPDF $pdf Object PDF + * @param Propal $object Object proposal + * @param int $posy Position depart + * @param Translate $outputlangs Object langs + * @return int Position pour suite + */ + protected function drawSignatureArea(&$pdf, $object, $posy, $outputlangs) + { + $default_font_size = pdf_getPDFFontSize($outputlangs); + $tab_top = $posy + 4; + $tab_hl = 4; + + $posx = 120; + $largcol = ($this->page_largeur - $this->marge_droite - $posx); + + // Total HT + $pdf->SetFillColor(255, 255, 255); + $pdf->SetXY($posx, $tab_top); + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->MultiCell($largcol, $tab_hl, $outputlangs->transnoentities("ProposalCustomerSignature"), 0, 'L', 1); + + $pdf->SetXY($posx, $tab_top + $tab_hl + 3); + //$pdf->MultiCell($largcol, $tab_hl * 3, '', 1, 'R'); + $pdf->RoundedRect($posx, $tab_top + $tab_hl + 3, $largcol, $tab_hl * 3, $this->corner_radius, '1234', 'D'); + + + if (getDolGlobalString('MAIN_PDF_PROPAL_USE_ELECTRONIC_SIGNING')) { + // Can be retrieve with getSignatureAppearanceArray() + // Can be also detected by putting the mouse over the area when using evince pdf reader + $pdf->addEmptySignatureAppearance($posx, $tab_top + $tab_hl, $largcol, $tab_hl * 3); + } + + return ($tab_hl * 7); + } + + + /** + * Define Array Column Field + * + * @param Propal $object object proposal + * @param Translate $outputlangs langs + * @param int $hidedetails Do not show line details + * @param int $hidedesc Do not show desc + * @param int $hideref Do not show ref + * @return void + */ + public function defineColumnField($object, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0) + { + global $hookmanager; + + // Default field style for content + $this->defaultContentsFieldsStyle = array( + 'align' => 'R', // R,C,L + 'padding' => array(1, 0.5, 1, 0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ); + + // Default field style for content + $this->defaultTitlesFieldsStyle = array( + 'align' => 'C', // R,C,L + 'padding' => array(0.5, 0, 0.5, 0), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ); + + /* + * For example + $this->cols['theColKey'] = array( + 'rank' => $rank, // int : use for ordering columns + 'width' => 20, // the column width in mm + 'title' => array( + 'textkey' => 'yourLangKey', // if there is no label, yourLangKey will be translated to replace label + 'label' => ' ', // the final label : used fore final generated text + 'align' => 'L', // text alignment : R,C,L + 'padding' => array(0.5,0.5,0.5,0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + 'content' => array( + 'align' => 'L', // text alignment : R,C,L + 'padding' => array(0.5,0.5,0.5,0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + ); + */ + + $rank = 0; // do not use negative rank + $this->cols['position'] = array( + 'rank' => $rank, + 'width' => 10, + 'status' => getDolGlobalInt('PDF_CYAN_ADD_POSITION') ? true : (getDolGlobalInt('PDF_ADD_POSITION') ? true : false), + 'title' => array( + 'textkey' => '#', // use lang key is useful in somme case with module + 'align' => 'C', + // 'textkey' => 'yourLangKey', // if there is no label, yourLangKey will be translated to replace label + // 'label' => ' ', // the final label + 'padding' => array(0.5, 0.5, 0.5, 0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + 'content' => array( + 'align' => 'C', + 'padding' => array(1, 0.5, 1, 1.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + ); + + $rank = 5; // do not use negative rank + $this->cols['desc'] = array( + 'rank' => $rank, + 'width' => false, // only for desc + 'status' => true, + 'title' => array( + 'textkey' => 'Designation', // use lang key is useful in somme case with module + 'align' => 'L', + // 'textkey' => 'yourLangKey', // if there is no label, yourLangKey will be translated to replace label + // 'label' => ' ', // the final label + 'padding' => array(0.5, 0.5, 0.5, 0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + 'content' => array( + 'align' => 'L', + 'padding' => array(1, 0.5, 1, 1.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + ); + + // Image of product + $rank += 10; + $this->cols['photo'] = array( + 'rank' => $rank, + 'width' => getDolGlobalInt('MAIN_DOCUMENTS_WITH_PICTURE_WIDTH', 20), // in mm + 'status' => false, + 'title' => array( + 'textkey' => 'Photo', + 'label' => ' ' + ), + 'content' => array( + 'padding' => array(0, 0, 0, 0), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + 'border-left' => false, // remove left line separator + ); + + if (getDolGlobalString('MAIN_GENERATE_PROPOSALS_WITH_PICTURE') && !empty($this->atleastonephoto)) { + $this->cols['photo']['status'] = true; + $this->cols['photo']['border-left'] = true; + } + + + $rank += 10; + $this->cols['vat'] = array( + 'rank' => $rank, + 'status' => false, + 'width' => 16, // in mm + 'title' => array( + 'textkey' => 'VAT' + ), + 'border-left' => true, // add left line separator + ); + + if (!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT') && !getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_COLUMN')) { + $this->cols['vat']['status'] = true; + } + + $rank += 10; + $this->cols['subprice'] = array( + 'rank' => $rank, + 'width' => 19, // in mm + 'status' => true, + 'title' => array( + 'textkey' => 'PriceUHT' + ), + 'border-left' => true, // add left line separator + ); + + // Adapt dynamically the width of subprice, if text is too long. + $tmpwidth = 0; + $nblines = count($object->lines); + for ($i = 0; $i < $nblines; $i++) { + $tmpwidth2 = dol_strlen(dol_string_nohtmltag(pdf_getlineupexcltax($object, $i, $outputlangs, $hidedetails))); + $tmpwidth = max($tmpwidth, $tmpwidth2); + } + if ($tmpwidth > 10) { + $this->cols['subprice']['width'] += (2 * ($tmpwidth - 10)); + } + + $rank += 10; + $this->cols['qty'] = array( + 'rank' => $rank, + 'width' => 16, // in mm + 'status' => true, + 'title' => array( + 'textkey' => 'Qty' + ), + 'border-left' => true, // add left line separator + ); + + $rank += 10; + $this->cols['unit'] = array( + 'rank' => $rank, + 'width' => 11, // in mm + 'status' => false, + 'title' => array( + 'textkey' => 'Unit' + ), + 'border-left' => true, // add left line separator + ); + if (getDolGlobalInt('PRODUCT_USE_UNITS')) { + $this->cols['unit']['status'] = true; + } + + $rank += 10; + $this->cols['discount'] = array( + 'rank' => $rank, + 'width' => 13, // in mm + 'status' => false, + 'title' => array( + 'textkey' => 'ReductionShort' + ), + 'border-left' => true, // add left line separator + ); + if ($this->atleastonediscount) { + $this->cols['discount']['status'] = true; + } + + $rank += 1000; // add a big offset to be sure is the last col because default extrafield rank is 100 + $this->cols['totalexcltax'] = array( + 'rank' => $rank, + 'width' => 26, // in mm + 'status' => !getDolGlobalString('PDF_PROPAL_HIDE_PRICE_EXCL_TAX'), + 'title' => array( + 'textkey' => 'TotalHTShort' + ), + 'border-left' => true, // add left line separator + ); + + $rank += 1010; // add a big offset to be sure is the last col because default extrafield rank is 100 + $this->cols['totalincltax'] = array( + 'rank' => $rank, + 'width' => 26, // in mm + 'status' => getDolGlobalBool('PDF_PROPAL_SHOW_PRICE_INCL_TAX'), + 'title' => array( + 'textkey' => 'TotalTTCShort' + ), + 'border-left' => true, // add left line separator + ); + + // Add extrafields cols + if (!empty($object->lines)) { + $line = reset($object->lines); + $this->defineColumnExtrafield($line, $outputlangs, $hidedetails); + } + + $parameters = array( + 'object' => $object, + 'outputlangs' => $outputlangs, + 'hidedetails' => $hidedetails, + 'hidedesc' => $hidedesc, + 'hideref' => $hideref + ); + + $reshook = $hookmanager->executeHooks('defineColumnField', $parameters, $this); // Note that $object may have been modified by hook + if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); + } elseif (empty($reshook)) { + // @phan-suppress-next-line PhanPluginSuspiciousParamOrderInternal + $this->cols = array_replace($this->cols, $hookmanager->resArray); // array_replace is used to preserve keys + } else { + $this->cols = $hookmanager->resArray; + } + } +} diff --git a/htdocs/core/tpl/admin_extrafields_add.tpl.php b/htdocs/core/tpl/admin_extrafields_add.tpl.php index 25452bca8db95..b5684d3ff969f 100644 --- a/htdocs/core/tpl/admin_extrafields_add.tpl.php +++ b/htdocs/core/tpl/admin_extrafields_add.tpl.php @@ -195,8 +195,8 @@ function init_typeoffields(type) textwithpicto('', $langs->trans("ExtrafieldParamHelpselect"), 1, 0, '', 0, 2, 'helpvalue1')?> - textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATUREES_LEVEL') > 0 ? '
    '.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue2')?>
    - textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATUREES_LEVEL') > 0 ? '
    '.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue3')?>
    + textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0 ? '
    '.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue2')?>
    + textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0 ? '
    '.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue3')?>
    textwithpicto('', $langs->trans("ExtrafieldParamHelplink").'

    '.$langs->trans("Examples").':
    '.$listofexamplesforlink, 1, 0, '', 0, 2, 'helpvalue4')?>
    textwithpicto('', $langs->trans("ExtrafieldParamHelpPassword"), 1, 0, '', 0, 2, 'helpvalue5')?> textwithpicto('', $langs->trans("ExtrafieldParamHelpSeparator"), 1, 0, '', 0, 2, 'helpvalue6')?> diff --git a/htdocs/core/tpl/admin_extrafields_edit.tpl.php b/htdocs/core/tpl/admin_extrafields_edit.tpl.php index 78e0e8a2da0fb..fadf9b9b3adaf 100644 --- a/htdocs/core/tpl/admin_extrafields_edit.tpl.php +++ b/htdocs/core/tpl/admin_extrafields_edit.tpl.php @@ -257,8 +257,8 @@ function init_typeoffields(type) textwithpicto('', $langs->trans("ExtrafieldParamHelpselect"), 1, 0, '', 0, 2, 'helpvalue1')?> - textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATUREES_LEVEL') > 0 ? '
    '.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue2')?>
    - textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATUREES_LEVEL') > 0 ? '
    '.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue3')?>
    + textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0 ? '
    '.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue2')?>
    + textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0 ? '
    '.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue3')?>
    textwithpicto('', $langs->trans("ExtrafieldParamHelplink").'

    '.$langs->trans("Examples").':
    '.$listofexamplesforlink, 1, 0, '', 0, 2, 'helpvalue4')?>
    textwithpicto('', $langs->trans("ExtrafieldParamHelpPassword"), 1, 0, '', 0, 2, 'helpvalue5')?> textwithpicto('', $langs->trans("ExtrafieldParamHelpSeparator"), 1, 0, '', 0, 2, 'helpvalue6')?> diff --git a/htdocs/document.php b/htdocs/document.php index a758ab79b8e00..aa6251cab1727 100644 --- a/htdocs/document.php +++ b/htdocs/document.php @@ -242,7 +242,7 @@ function llxFooter($comment = '', $zone = 'private', $disabledoutputofmessages = } // Check security and set return info with full path of file -$check_access = dol_check_secure_access_document($modulepart, $original_file, $entity, $user, ''); +$check_access = dol_check_secure_access_document($modulepart, $original_file, (int) $entity, $user, '', 'read'); $accessallowed = $check_access['accessallowed']; $sqlprotectagainstexternals = $check_access['sqlprotectagainstexternals']; $fullpath_original_file = $check_access['original_file']; // $fullpath_original_file is now a full path name diff --git a/htdocs/expedition/card.php b/htdocs/expedition/card.php index e86df3200ed1d..a804bac3b9b3d 100644 --- a/htdocs/expedition/card.php +++ b/htdocs/expedition/card.php @@ -1144,7 +1144,7 @@ // Document model include_once DOL_DOCUMENT_ROOT.'/core/modules/expedition/modules_expedition.php'; $list = ModelePdfExpedition::liste_modeles($db); - if (is_countable($list) && count($list) > 1) { + if (is_array($list) && count($list) > 1) { print "".$langs->trans("DefaultModel").""; print ''; print img_picto('', 'pdf', 'class="pictofixedwidth"'); diff --git a/htdocs/expedition/list.php b/htdocs/expedition/list.php index d6bd0df20bbf9..59d22552d38a2 100644 --- a/htdocs/expedition/list.php +++ b/htdocs/expedition/list.php @@ -317,7 +317,7 @@ } if ($objecttmp->id > 0) { - $res = $objecttmp->add_object_linked($objecttmp->origin, $id_sending); + $res = $objecttmp->add_object_linked($objecttmp->origin_type, $id_sending); if ($res == 0) { $errors[] = $expd->ref.' : '.$langs->trans($objecttmp->errors[0]); @@ -557,8 +557,10 @@ if ($search_company) { $param .= "&search_company=".urlencode($search_company); } - if ($search_shipping_method_id) { - $param .= "&search_shipping_method_id=".urlencode($search_shipping_method_id); + if ($search_shipping_method_ids) { + foreach ($search_shipping_method_ids as $value) { + $param .= "&search_shipping_method_ids[]=".urlencode($value); + } } if ($search_tracking) { $param .= "&search_tracking=".urlencode($search_tracking); diff --git a/htdocs/fichinter/card.php b/htdocs/fichinter/card.php index 5375c3a1831ad..ae0e59426b281 100644 --- a/htdocs/fichinter/card.php +++ b/htdocs/fichinter/card.php @@ -5,7 +5,7 @@ * Copyright (C) 2011-2020 Juanjo Menent * Copyright (C) 2013 Florian Henry * Copyright (C) 2014-2018 Ferran Marcet - * Copyright (C) 2014-2022 Charlene Benke + * Copyright (C) 2014-2025 Charlene Benke * Copyright (C) 2015-2016 Abbes Bahfir * Copyright (C) 2018-2022 Philippe Grand * Copyright (C) 2020-2024 Frédéric France @@ -13,6 +13,7 @@ * Copyright (C) 2023-2024 William Mead * Copyright (C) 2024 MDW * Copyright (C) 2024 Alexandre Spangaro + * Copyright (C) 2025 Pierre Ardoin * * 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 @@ -1224,6 +1225,7 @@ $formquestion[] = [ 'type' => 'select', 'name' => 'signed_status', + 'select_show_empty' => 0, 'label' => ''.$langs->trans('SignStatus').'', 'values' => $object->getSignedStatusLocalisedArray() ]; diff --git a/htdocs/fourn/class/fournisseur.commande.class.php b/htdocs/fourn/class/fournisseur.commande.class.php index 8134844ed7d04..218efa8d0a360 100644 --- a/htdocs/fourn/class/fournisseur.commande.class.php +++ b/htdocs/fourn/class/fournisseur.commande.class.php @@ -726,7 +726,7 @@ public function fetch_lines($only_product = 0) $objsearchpackage = $this->db->fetch_object($resqlsearchpackage); if ($objsearchpackage) { $line->fk_fournprice = $objsearchpackage->rowid; - $line->packaging = $objsearchpackage->packaging; + $line->packaging = (float) $objsearchpackage->packaging; } } else { $this->error = $this->db->lasterror(); @@ -2130,12 +2130,12 @@ public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txloca // Predefine quantity according to packaging if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) { $prod = new Product($this->db); - $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc)); + $prod->get_buyprice($fk_prod_fourn_price, (float) $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc)); if ($qty < $prod->packaging) { - $qty = $prod->packaging; + $qty = (float) $prod->packaging; } else { - if (!empty($prod->packaging) && (fmod((float) $qty, $prod->packaging) > 0.000001)) { + if (!empty($prod->packaging) && (fmod((float) $qty, (float) $prod->packaging) > 0.000001)) { $coeff = intval((float) $qty / $prod->packaging) + 1; $qty = (float) $prod->packaging * $coeff; setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs'); @@ -3097,7 +3097,7 @@ public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $txtva, $t if ($qty < $this->line->packaging) { $qty = $this->line->packaging; } else { - if (!empty($this->line->packaging) && ($qty % $this->line->packaging) > 0) { + if (!empty($this->line->packaging) && is_numeric($this->line->packaging) && (float) $this->line->packaging > 0 && (fmod((float) $qty, (float) $this->line->packaging) > 0)) { $coeff = intval($qty / $this->line->packaging) + 1; $qty = $this->line->packaging * $coeff; setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs'); diff --git a/htdocs/fourn/class/fournisseur.facture-rec.class.php b/htdocs/fourn/class/fournisseur.facture-rec.class.php index c34c74545ee75..501580ce1a032 100644 --- a/htdocs/fourn/class/fournisseur.facture-rec.class.php +++ b/htdocs/fourn/class/fournisseur.facture-rec.class.php @@ -797,7 +797,7 @@ public function fetch_lines() */ $sql = 'SELECT l.rowid,'; - $sql .= ' l.fk_facture_fourn, l.fk_parent_line, l.fk_product, l.ref, l.label, l.description as line_desc,'; + $sql .= ' l.fk_facture_fourn, l.fk_parent_line, l.fk_product, l.ref as ref_supplier, l.label, l.description as line_desc,'; $sql .= ' l.pu_ht, l.pu_ttc, l.qty, l.remise_percent, l.fk_remise_except, l.vat_src_code, l.tva_tx,'; $sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type,'; $sql .= ' l.total_ht, l.total_tva, l.total_ttc, total_localtax1, total_localtax2,'; @@ -826,11 +826,16 @@ public function fetch_lines() $line->fk_facture_fourn = $objp->fk_facture_fourn; $line->fk_parent = $objp->fk_parent_line; $line->fk_product = $objp->fk_product; - $line->ref_supplier = $objp->ref; + $line->ref = $objp->product_ref; // Ref of product + $line->product_ref = $objp->product_ref; // Ref of product + $line->product_label = $objp->product_label; + $line->product_desc = $objp->product_desc; + $line->ref_supplier = $objp->ref_supplier; $line->label = $objp->label; $line->description = $objp->line_desc; $line->desc = $objp->line_desc; $line->pu_ht = $objp->pu_ht; + $line->subprice = $objp->pu_ht; $line->pu_ttc = $objp->pu_ttc; $line->qty = $objp->qty; $line->remise_percent = $objp->remise_percent; @@ -982,152 +987,146 @@ public function addline($fk_product, $ref, $label, $desc, $pu_ht, $pu_ttc, $qty, return -1; } - if ($this->suspended == self::STATUS_NOTSUSPENDED) { - $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); + $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); - // Clean vat code - $reg = array(); - $vat_src_code = ''; - if (preg_match('/\((.*)\)/', (string) $txtva, $reg)) { - $vat_src_code = $reg[1]; - $txtva = preg_replace('/\s*\(.*\)/', '', (string) $txtva); // Remove code into vatrate. - } - - // Clean parameters - $fk_product = empty($fk_product) ? 0 : $fk_product; - $label = empty($label) ? '' : $label; - $remise_percent = empty($remise_percent) ? 0 : price2num($remise_percent); - $qty = price2num($qty); - $pu_ht = price2num($pu_ht); - $pu_ttc = price2num($pu_ttc); - if (!preg_match('/\((.*)\)/', $txtva)) { - $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5' - } - $txlocaltax1 = price2num($txlocaltax1); - $txlocaltax2 = price2num($txlocaltax2); - $txtva = !empty($txtva) ? $txtva : 0; - $txlocaltax1 = !empty($txlocaltax1) ? $txlocaltax1 : 0; - $txlocaltax2 = !empty($txlocaltax2) ? $txlocaltax2 : 0; - $info_bits = !empty($info_bits) ? $info_bits : 0; - $info_bits = !empty($info_bits) ? $info_bits : 0; - $pu = $price_base_type == 'HT' ? $pu_ht : $pu_ttc; - - // Calcul du total TTC et de la TVA pour la ligne a partir de qty, pu, remise_percent et txtva - // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker - // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. - - $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise); - $total_ht = $tabprice[0]; - $total_tva = $tabprice[1]; - $total_ttc = $tabprice[2]; - $total_localtax1 = $tabprice[9]; - $total_localtax2 = $tabprice[10]; - $pu_ht = $tabprice[3]; - - // MultiCurrency - $multicurrency_total_ht = $tabprice[16]; - $multicurrency_total_tva = $tabprice[17]; - $multicurrency_total_ttc = $tabprice[18]; - $pu_ht_devise = $tabprice[19]; - - $this->db->begin(); - $product_type = $type; - if ($fk_product) { - $product = new Product($this->db); - $result = $product->fetch($fk_product); - if ($result < 0) { - return -1; - } - $product_type = $product->type; - if (empty($label)) { - $label = $product->label; - } - } - - $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'facture_fourn_det_rec ('; - $sql .= 'fk_facture_fourn'; - $sql .= ', fk_product'; - $sql .= ', ref'; - $sql .= ', label'; - $sql .= ', description'; - $sql .= ', pu_ht'; - $sql .= ', pu_ttc'; - $sql .= ', qty'; - $sql .= ', remise_percent'; - $sql .= ', fk_remise_except'; - $sql .= ', vat_src_code'; - $sql .= ', tva_tx'; - $sql .= ', localtax1_tx'; - $sql .= ', localtax1_type'; - $sql .= ', localtax2_tx'; - $sql .= ', localtax2_type'; - $sql .= ', total_ht'; - $sql .= ', total_tva'; - $sql .= ', total_localtax1'; - $sql .= ', total_localtax2'; - $sql .= ', total_ttc'; - $sql .= ', product_type'; - $sql .= ', date_start'; - $sql .= ', date_end'; - $sql .= ', info_bits'; - $sql .= ', special_code'; - $sql .= ', rang'; - $sql .= ', fk_unit'; - $sql .= ', fk_user_author'; - $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc'; - $sql .= ') VALUES ('; - $sql .= ' ' . (int) $facid; // source supplier invoice id - $sql .= ', ' . (!empty($fk_product) ? "'" . $this->db->escape($fk_product) . "'" : 'null'); - $sql .= ', ' . (!empty($ref) ? "'" . $this->db->escape($ref) . "'" : 'null'); - $sql .= ', ' . (!empty($label) ? "'" . $this->db->escape($label) . "'" : 'null'); - $sql .= ", '" . $this->db->escape($desc) . "'"; - $sql .= ', ' . price2num($pu_ht); - $sql .= ', ' . price2num($pu_ttc); - $sql .= ', ' . price2num($qty); - $sql .= ', ' . price2num($remise_percent); - $sql .= ', null'; - $sql .= ", '" . $this->db->escape($vat_src_code) . "'"; - $sql .= ', ' . price2num($txtva); - $sql .= ', ' . price2num($txlocaltax1); - $sql .= ", '" . $this->db->escape(isset($localtaxes_type[0]) ? $localtaxes_type[0] : '') . "'"; - $sql .= ', ' . price2num($txlocaltax2); - $sql .= ", '" . $this->db->escape(isset($localtaxes_type[2]) ? $localtaxes_type[2] : '') . "'"; - $sql .= ', ' . price2num($total_ht); - $sql .= ', ' . price2num($total_tva); - $sql .= ', ' . price2num($total_localtax1); - $sql .= ', ' . price2num($total_localtax2); - $sql .= ', ' . price2num($total_ttc); - $sql .= ', ' . (int) $product_type; - $sql .= ', ' . ($date_start > 0 ? (int) $date_start : 'NULL'); - $sql .= ', ' . ($date_end > 0 ? (int) $date_end : 'NULL'); - $sql .= ', ' . (int) $info_bits; - $sql .= ', ' . (int) $special_code; - $sql .= ', ' . (int) $rang; - $sql .= ', ' . ($fk_unit ? (int) $fk_unit : 'NULL'); - $sql .= ', ' . (int) $user->id; - $sql .= ', ' . (int) $this->fk_multicurrency; - $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'"; - $sql .= ', ' . price2num($pu_ht_devise, 'CU'); - $sql .= ', ' . price2num($multicurrency_total_ht, 'CT'); - $sql .= ', ' . price2num($multicurrency_total_tva, 'CT'); - $sql .= ', ' . price2num($multicurrency_total_ttc, 'CT'); - $sql .= ')'; + // Clean vat code + $reg = array(); + $vat_src_code = ''; + if (preg_match('/\((.*)\)/', (string) $txtva, $reg)) { + $vat_src_code = $reg[1]; + $txtva = preg_replace('/\s*\(.*\)/', '', (string) $txtva); // Remove code into vatrate. + } - dol_syslog(get_class($this). '::addline', LOG_DEBUG); - if ($this->db->query($sql)) { - $lineId = $this->db->last_insert_id(MAIN_DB_PREFIX. 'facture_fourn_det_rec'); - $this->update_price(); - $this->id = $facid; - $this->db->commit(); - return $lineId; - } else { - $this->db->rollback(); - $this->error = $this->db->lasterror(); + // Clean parameters + $fk_product = empty($fk_product) ? 0 : $fk_product; + $label = empty($label) ? '' : $label; + $remise_percent = empty($remise_percent) ? 0 : price2num($remise_percent); + $qty = price2num($qty); + $pu_ht = price2num($pu_ht); + $pu_ttc = price2num($pu_ttc); + if (!preg_match('/\((.*)\)/', $txtva)) { + $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5' + } + $txlocaltax1 = price2num($txlocaltax1); + $txlocaltax2 = price2num($txlocaltax2); + $txtva = !empty($txtva) ? $txtva : 0; + $txlocaltax1 = !empty($txlocaltax1) ? $txlocaltax1 : 0; + $txlocaltax2 = !empty($txlocaltax2) ? $txlocaltax2 : 0; + $info_bits = !empty($info_bits) ? $info_bits : 0; + $info_bits = !empty($info_bits) ? $info_bits : 0; + $pu = $price_base_type == 'HT' ? $pu_ht : $pu_ttc; + + // Calcul du total TTC et de la TVA pour la ligne a partir de qty, pu, remise_percent et txtva + // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker + // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. + + $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise); + $total_ht = $tabprice[0]; + $total_tva = $tabprice[1]; + $total_ttc = $tabprice[2]; + $total_localtax1 = $tabprice[9]; + $total_localtax2 = $tabprice[10]; + $pu_ht = $tabprice[3]; + + // MultiCurrency + $multicurrency_total_ht = $tabprice[16]; + $multicurrency_total_tva = $tabprice[17]; + $multicurrency_total_ttc = $tabprice[18]; + $pu_ht_devise = $tabprice[19]; + $this->db->begin(); + $product_type = $type; + if ($fk_product) { + $product = new Product($this->db); + $result = $product->fetch($fk_product); + if ($result < 0) { return -1; } + $product_type = $product->type; + if (empty($label)) { + $label = $product->label; + } + } + + $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'facture_fourn_det_rec ('; + $sql .= 'fk_facture_fourn'; + $sql .= ', fk_product'; + $sql .= ', ref'; + $sql .= ', label'; + $sql .= ', description'; + $sql .= ', pu_ht'; + $sql .= ', pu_ttc'; + $sql .= ', qty'; + $sql .= ', remise_percent'; + $sql .= ', fk_remise_except'; + $sql .= ', vat_src_code'; + $sql .= ', tva_tx'; + $sql .= ', localtax1_tx'; + $sql .= ', localtax1_type'; + $sql .= ', localtax2_tx'; + $sql .= ', localtax2_type'; + $sql .= ', total_ht'; + $sql .= ', total_tva'; + $sql .= ', total_localtax1'; + $sql .= ', total_localtax2'; + $sql .= ', total_ttc'; + $sql .= ', product_type'; + $sql .= ', date_start'; + $sql .= ', date_end'; + $sql .= ', info_bits'; + $sql .= ', special_code'; + $sql .= ', rang'; + $sql .= ', fk_unit'; + $sql .= ', fk_user_author'; + $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc'; + $sql .= ') VALUES ('; + $sql .= ' ' . (int) $facid; // source supplier invoice id + $sql .= ', ' . (!empty($fk_product) ? "'" . $this->db->escape($fk_product) . "'" : 'null'); + $sql .= ', ' . (!empty($ref) ? "'" . $this->db->escape($ref) . "'" : 'null'); + $sql .= ', ' . (!empty($label) ? "'" . $this->db->escape($label) . "'" : 'null'); + $sql .= ", '" . $this->db->escape($desc) . "'"; + $sql .= ', ' . price2num($pu_ht); + $sql .= ', ' . price2num($pu_ttc); + $sql .= ', ' . price2num($qty); + $sql .= ', ' . price2num($remise_percent); + $sql .= ', null'; + $sql .= ", '" . $this->db->escape($vat_src_code) . "'"; + $sql .= ', ' . price2num($txtva); + $sql .= ', ' . price2num($txlocaltax1); + $sql .= ", '" . $this->db->escape(isset($localtaxes_type[0]) ? $localtaxes_type[0] : '') . "'"; + $sql .= ', ' . price2num($txlocaltax2); + $sql .= ", '" . $this->db->escape(isset($localtaxes_type[2]) ? $localtaxes_type[2] : '') . "'"; + $sql .= ', ' . price2num($total_ht); + $sql .= ', ' . price2num($total_tva); + $sql .= ', ' . price2num($total_localtax1); + $sql .= ', ' . price2num($total_localtax2); + $sql .= ', ' . price2num($total_ttc); + $sql .= ', ' . (int) $product_type; + $sql .= ', ' . ($date_start > 0 ? (int) $date_start : 'NULL'); + $sql .= ', ' . ($date_end > 0 ? (int) $date_end : 'NULL'); + $sql .= ', ' . (int) $info_bits; + $sql .= ', ' . (int) $special_code; + $sql .= ', ' . (int) $rang; + $sql .= ', ' . ($fk_unit ? (int) $fk_unit : 'NULL'); + $sql .= ', ' . (int) $user->id; + $sql .= ', ' . (int) $this->fk_multicurrency; + $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'"; + $sql .= ', ' . price2num($pu_ht_devise, 'CU'); + $sql .= ', ' . price2num($multicurrency_total_ht, 'CT'); + $sql .= ', ' . price2num($multicurrency_total_tva, 'CT'); + $sql .= ', ' . price2num($multicurrency_total_ttc, 'CT'); + $sql .= ')'; + + dol_syslog(get_class($this). '::addline', LOG_DEBUG); + if ($this->db->query($sql)) { + $lineId = $this->db->last_insert_id(MAIN_DB_PREFIX. 'facture_fourn_det_rec'); + $this->update_price(); + $this->id = $facid; + $this->db->commit(); + return $lineId; } else { - $this->error = 'Recurring Invoice is suspended. adding lines not allowed.'; + $this->db->rollback(); + $this->error = $this->db->lasterror(); return -1; } @@ -1174,113 +1173,110 @@ public function updateline($rowid, $fk_product, $ref, $label, $desc, $pu_ht, $qt return -1; } - if ($this->status == self::STATUS_SUSPENDED) { - // Clean parameters - $fk_product = empty($fk_product) ? 0 : $fk_product; - $label = empty($label) ? '' : $label; - $remise_percent = empty($remise_percent) ? 0 : price2num($remise_percent); - $qty = price2num($qty); - $info_bits = empty($info_bits) ? 0 : $info_bits; - $pu_ht = price2num($pu_ht); - $pu_ttc = price2num($pu_ttc); - $pu_ht_devise = price2num($pu_ht_devise); - - if (!preg_match('/\((.*)\)/', (string) $txtva)) { - $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5' - } + // Clean parameters + $fk_product = empty($fk_product) ? 0 : $fk_product; + $label = empty($label) ? '' : $label; + $remise_percent = empty($remise_percent) ? 0 : price2num($remise_percent); + $qty = price2num($qty); + $info_bits = empty($info_bits) ? 0 : $info_bits; + $pu_ht = price2num($pu_ht); + $pu_ttc = price2num($pu_ttc); + $pu_ht_devise = price2num($pu_ht_devise); + + if (!preg_match('/\((.*)\)/', (string) $txtva)) { + $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5' + } - $txlocaltax1 = empty($txlocaltax1) ? 0 : price2num($txlocaltax1); - $txlocaltax2 = empty($txlocaltax2) ? 0 : price2num($txlocaltax2); - $this->multicurrency_total_ht = empty($this->multicurrency_total_ht) ? 0 : $this->multicurrency_total_ht; - $this->multicurrency_total_tva = empty($this->multicurrency_total_tva) ? 0 : $this->multicurrency_total_tva; - $this->multicurrency_total_ttc = empty($this->multicurrency_total_ttc) ? 0 : $this->multicurrency_total_ttc; + $txlocaltax1 = empty($txlocaltax1) ? 0 : price2num($txlocaltax1); + $txlocaltax2 = empty($txlocaltax2) ? 0 : price2num($txlocaltax2); + $this->multicurrency_total_ht = empty($this->multicurrency_total_ht) ? 0 : $this->multicurrency_total_ht; + $this->multicurrency_total_tva = empty($this->multicurrency_total_tva) ? 0 : $this->multicurrency_total_tva; + $this->multicurrency_total_ttc = empty($this->multicurrency_total_ttc) ? 0 : $this->multicurrency_total_ttc; - $pu = $price_base_type == 'HT' ? $pu_ht : $pu_ttc; + $pu = $price_base_type == 'HT' ? $pu_ht : $pu_ttc; - // Calculate total with, without tax and tax from qty, pu, remise_percent and txtva - // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker - // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. + // Calculate total with, without tax and tax from qty, pu, remise_percent and txtva + // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker + // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. - $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); + $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); - // Clean vat code - $vat_src_code = ''; - $reg = array(); - if (preg_match('/\((.*)\)/', $txtva, $reg)) { - $vat_src_code = $reg[1]; - $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate. - } + // Clean vat code + $vat_src_code = ''; + $reg = array(); + if (preg_match('/\((.*)\)/', $txtva, $reg)) { + $vat_src_code = $reg[1]; + $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate. + } - $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise); - - $total_ht = $tabprice[0]; - $total_tva = $tabprice[1]; - $total_ttc = $tabprice[2]; - $total_localtax1 = $tabprice[9]; - $total_localtax2 = $tabprice[10]; - $pu_ht = $tabprice[3]; - $pu_tva = $tabprice[4]; - $pu_ttc = $tabprice[5]; - - // MultiCurrency - $multicurrency_total_ht = $tabprice[16]; - $multicurrency_total_tva = $tabprice[17]; - $multicurrency_total_ttc = $tabprice[18]; - $pu_ht_devise = $tabprice[19]; - - $product_type = $type; - if ($fk_product) { - $product = new Product($this->db); - $result = $product->fetch($fk_product); - $product_type = $product->type; - } + $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise); + + $total_ht = $tabprice[0]; + $total_tva = $tabprice[1]; + $total_ttc = $tabprice[2]; + $total_localtax1 = $tabprice[9]; + $total_localtax2 = $tabprice[10]; + $pu_ht = $tabprice[3]; + $pu_tva = $tabprice[4]; + $pu_ttc = $tabprice[5]; + + // MultiCurrency + $multicurrency_total_ht = $tabprice[16]; + $multicurrency_total_tva = $tabprice[17]; + $multicurrency_total_ttc = $tabprice[18]; + $pu_ht_devise = $tabprice[19]; + + $product_type = $type; + if ($fk_product) { + $product = new Product($this->db); + $result = $product->fetch($fk_product); + $product_type = $product->type; + } - $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture_fourn_det_rec SET'; - $sql .= ' fk_facture_fourn = ' . ((int) $facid); - $sql .= ', fk_product = ' . ($fk_product > 0 ? ((int) $fk_product) : 'null'); - $sql .= ", ref = '" . $this->db->escape($ref) . "'"; - $sql .= ", label = '" . $this->db->escape($label) . "'"; - $sql .= ", description = '" . $this->db->escape($desc) . "'"; - $sql .= ', pu_ht = ' . price2num($pu_ht); - $sql .= ', qty = ' . price2num($qty); - $sql .= ", remise_percent = '" . price2num($remise_percent) . "'"; - $sql .= ", vat_src_code = '" . $this->db->escape($vat_src_code) . "'"; - $sql .= ', tva_tx = ' . price2num($txtva); - $sql .= ', localtax1_tx = ' . (float) $txlocaltax1; - $sql .= ", localtax1_type = '" . $this->db->escape($localtaxes_type[0]) . "'"; - $sql .= ', localtax2_tx = ' . (float) $txlocaltax2; - $sql .= ", localtax2_type = '" . $this->db->escape($localtaxes_type[2]) . "'"; - $sql .= ", total_ht = '" . price2num($total_ht) . "'"; - $sql .= ", total_tva = '" . price2num($total_tva) . "'"; - $sql .= ", total_localtax1 = '" . price2num($total_localtax1) . "'"; - $sql .= ", total_localtax2 = '" . price2num($total_localtax2) . "'"; - $sql .= ", total_ttc = '" . price2num($total_ttc) . "'"; - $sql .= ', product_type = ' . (int) $product_type; - $sql .= ', date_start = ' . (empty($date_start) ? 'NULL' : (int) $date_start); - $sql .= ', date_end = ' . (empty($date_end) ? 'NULL' : (int) $date_end); - $sql .= ', info_bits = ' . (int) $info_bits; - $sql .= ', special_code = ' . (int) $special_code; - $sql .= ', rang = ' . (int) $rang; - $sql .= ', fk_unit = ' . ($fk_unit ? "'" . $this->db->escape($fk_unit) . "'" : 'null'); - $sql .= ', fk_user_modif = ' . (int) $user; - $sql .= ', multicurrency_subprice = '.price2num($pu_ht_devise); - $sql .= ', multicurrency_total_ht = '.price2num($multicurrency_total_ht); - $sql .= ', multicurrency_total_tva = '.price2num($multicurrency_total_tva); - $sql .= ', multicurrency_total_ttc = '.price2num($multicurrency_total_ttc); - $sql .= ' WHERE rowid = ' . (int) $rowid; - - dol_syslog(get_class($this). '::updateline', LOG_DEBUG); - if ($this->db->query($sql)) { - $this->id = $facid; - $this->update_price(); - return 1; - } else { - $this->error = $this->db->lasterror(); - return -1; - } + $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture_fourn_det_rec SET'; + $sql .= ' fk_facture_fourn = ' . ((int) $facid); + $sql .= ', fk_product = ' . ($fk_product > 0 ? ((int) $fk_product) : 'null'); + $sql .= ", ref = '" . $this->db->escape($ref) . "'"; + $sql .= ", label = '" . $this->db->escape($label) . "'"; + $sql .= ", description = '" . $this->db->escape($desc) . "'"; + $sql .= ', pu_ht = ' . price2num($pu_ht); + $sql .= ', qty = ' . price2num($qty); + $sql .= ", remise_percent = '" . price2num($remise_percent) . "'"; + $sql .= ", vat_src_code = '" . $this->db->escape($vat_src_code) . "'"; + $sql .= ', tva_tx = ' . price2num($txtva); + $sql .= ', localtax1_tx = ' . (float) $txlocaltax1; + $sql .= ", localtax1_type = '" . $this->db->escape($localtaxes_type[0]) . "'"; + $sql .= ', localtax2_tx = ' . (float) $txlocaltax2; + $sql .= ", localtax2_type = '" . $this->db->escape($localtaxes_type[2]) . "'"; + $sql .= ", total_ht = '" . price2num($total_ht) . "'"; + $sql .= ", total_tva = '" . price2num($total_tva) . "'"; + $sql .= ", total_localtax1 = '" . price2num($total_localtax1) . "'"; + $sql .= ", total_localtax2 = '" . price2num($total_localtax2) . "'"; + $sql .= ", total_ttc = '" . price2num($total_ttc) . "'"; + $sql .= ', product_type = ' . (int) $product_type; + $sql .= ', date_start = ' . (empty($date_start) ? 'NULL' : (int) $date_start); + $sql .= ', date_end = ' . (empty($date_end) ? 'NULL' : (int) $date_end); + $sql .= ', info_bits = ' . (int) $info_bits; + $sql .= ', special_code = ' . (int) $special_code; + $sql .= ', rang = ' . (int) $rang; + $sql .= ', fk_unit = ' . ($fk_unit ? "'" . $this->db->escape($fk_unit) . "'" : 'null'); + $sql .= ', fk_user_modif = ' . (int) $user; + $sql .= ', multicurrency_subprice = '.price2num($pu_ht_devise); + $sql .= ', multicurrency_total_ht = '.price2num($multicurrency_total_ht); + $sql .= ', multicurrency_total_tva = '.price2num($multicurrency_total_tva); + $sql .= ', multicurrency_total_ttc = '.price2num($multicurrency_total_ttc); + $sql .= ' WHERE rowid = ' . (int) $rowid; + + dol_syslog(get_class($this). '::updateline', LOG_DEBUG); + if ($this->db->query($sql)) { + $this->id = $facid; + $this->update_price(); + return 1; + } else { + $this->error = $this->db->lasterror(); + return -1; } - return 0; } diff --git a/htdocs/fourn/commande/card.php b/htdocs/fourn/commande/card.php index d4fff20a846f6..47e8476fff4b3 100644 --- a/htdocs/fourn/commande/card.php +++ b/htdocs/fourn/commande/card.php @@ -1404,7 +1404,7 @@ $num = count($lines); for ($i = 0; $i < $num; $i++) { - if (empty($lines[$i]->subprice) || $lines[$i]->qty <= 0 || !in_array($lines[$i]->id, $selectedLines)) { + if (empty($lines[$i]->subprice) || $lines[$i]->qty < 0 || !in_array($lines[$i]->id, $selectedLines)) { continue; } diff --git a/htdocs/fourn/commande/contact.php b/htdocs/fourn/commande/contact.php index 67b436e308ece..430c9c67432ed 100644 --- a/htdocs/fourn/commande/contact.php +++ b/htdocs/fourn/commande/contact.php @@ -5,6 +5,7 @@ * Copyright (C) 2017 Ferran Marcet * Copyright (C) 2023 Christian Foellmann * Copyright (C) 2024 Frédéric France + * Copyright (C) 2026 Serhii Bondarenko * * 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 @@ -187,8 +188,14 @@ print dol_get_fiche_end(); - // Contacts lines - include DOL_DOCUMENT_ROOT.'/core/tpl/contacts.tpl.php'; + // Contacts lines (modules that overwrite templates must declare this into descriptor) + $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl')); + foreach ($dirtpls as $reldir) { + $res = @include dol_buildpath($reldir.'/contacts.tpl.php'); + if ($res) { + break; + } + } } else { // Contact not found print "ErrorRecordNotFound"; diff --git a/htdocs/fourn/facture/card-rec.php b/htdocs/fourn/facture/card-rec.php index 8dc8a12b9dd46..7160d796ee9ec 100644 --- a/htdocs/fourn/facture/card-rec.php +++ b/htdocs/fourn/facture/card-rec.php @@ -1523,14 +1523,6 @@ $object->fetch_lines(); // Show object lines if (!empty($object->lines)) { - $canchangeproduct = 1; - // To set ref for getNomURL function - foreach ($object->lines as $line) { - $line->ref = $line->label; - $line->product_label = $line->label; - $line->subprice = $line->pu_ht; - } - global $canchangeproduct; $canchangeproduct = 0; diff --git a/htdocs/fourn/facture/contact.php b/htdocs/fourn/facture/contact.php index d15646059f35a..3ff6c983bb9ec 100644 --- a/htdocs/fourn/facture/contact.php +++ b/htdocs/fourn/facture/contact.php @@ -5,6 +5,7 @@ * Copyright (C) 2017 Ferran Marcet * Copyright (C) 2021-2024 Frédéric France * Copyright (C) 2023 Christian Foellmann + * Copyright (C) 2026 Serhii Bondarenko * * 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 @@ -269,8 +270,14 @@ //print '
    '; //print '
    '; - // Contacts lines - include DOL_DOCUMENT_ROOT.'/core/tpl/contacts.tpl.php'; + // Contacts lines (modules that overwrite templates must declare this into descriptor) + $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl')); + foreach ($dirtpls as $reldir) { + $res = @include dol_buildpath($reldir.'/contacts.tpl.php'); + if ($res) { + break; + } + } } else { print "ErrorRecordNotFound"; } diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index a661211b24254..4b09dbedb419c 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -775,7 +775,9 @@ public function validate($user = null, $notrigger = 0) if ($checkBalance > 0) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); - if ($balance < 0) { + $daysAsked = num_open_day($this->date_debut, $this->date_fin, 0, 1); + + if (($balance - $daysAsked) < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; } @@ -897,9 +899,9 @@ public function approve($user = null, $notrigger = 0) if ($checkBalance > 0) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); + $daysAsked = num_open_day($this->date_debut, $this->date_fin, 0, 1); - $days = num_between_day($this->date_debut, $this->date_fin); - if ($balance - $days < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { + if (($balance - $daysAsked) < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; } @@ -1024,10 +1026,11 @@ public function update($user = null, $notrigger = 0) $checkBalance = getDictionaryValue('c_holiday_types', 'block_if_negative', $this->fk_type, true); - if ($checkBalance > 0 && $this->status != self::STATUS_DRAFT) { + if ($checkBalance > 0 && $this->statut != self::STATUS_DRAFT && $this->statut != self::STATUS_CANCELED) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); + $daysAsked = num_open_day($this->date_debut, $this->date_fin, 0, 1); - if ($balance < 0) { + if (($balance - $daysAsked) < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; } diff --git a/htdocs/hrm/class/evaluation.class.php b/htdocs/hrm/class/evaluation.class.php index 51b0c78ed3a04..2969446f26d52 100644 --- a/htdocs/hrm/class/evaluation.class.php +++ b/htdocs/hrm/class/evaluation.class.php @@ -225,7 +225,7 @@ public function __construct(DoliDB $db) } if (!$user->hasRight('hrm', 'evaluation', 'readall')) { - $this->fields['fk_user']['type'] .= ':t.rowid:IN:'.$this->db->sanitize(implode(",", $user->getAllChildIds(1))); + $this->fields['fk_user']['type'] .= ' AND (t.rowid:IN:'.$this->db->sanitize(implode(",", $user->getAllChildIds(1))) .')'; } $this->date_eval = dol_now(); diff --git a/htdocs/hrm/evaluation_agenda.php b/htdocs/hrm/evaluation_agenda.php index ec3d106630b94..0e22cce890138 100644 --- a/htdocs/hrm/evaluation_agenda.php +++ b/htdocs/hrm/evaluation_agenda.php @@ -105,8 +105,9 @@ // Security check (enable the most restrictive one) //if ($user->socid > 0) accessforbidden(); //if ($user->socid > 0) $socid = $user->socid; -//$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0); -//restrictedArea($user, $object->module, $object->id, $object->table_element, $object->element, 'fk_soc', 'rowid', $isdraft); +$isdraft = $object->status == Evaluation::STATUS_DRAFT ? 1 : 0; +restrictedArea($user, $object->element, $object, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); + if (!isModEnabled('hrm')) { accessforbidden(); } diff --git a/htdocs/hrm/evaluation_contact.php b/htdocs/hrm/evaluation_contact.php index 48165b9d7a4c6..1e58563f9bfa4 100644 --- a/htdocs/hrm/evaluation_contact.php +++ b/htdocs/hrm/evaluation_contact.php @@ -71,10 +71,8 @@ // Security check (enable the most restrictive one) //if ($user->socid > 0) accessforbidden(); //if ($user->socid > 0) $socid = $user->socid; -//$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0); -//restrictedArea($user, $object->element, $object->id, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); -//if (empty($conf->hrm->enabled)) accessforbidden(); -//if (!$permissiontoread) accessforbidden(); +$isdraft = $object->status == Evaluation::STATUS_DRAFT ? 1 : 0; +restrictedArea($user, $object->element, $object, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); diff --git a/htdocs/hrm/evaluation_document.php b/htdocs/hrm/evaluation_document.php index 8438a700cae3b..c413fc8782d12 100644 --- a/htdocs/hrm/evaluation_document.php +++ b/htdocs/hrm/evaluation_document.php @@ -94,16 +94,12 @@ $permissiontoread = $user->hasRight('hrm', 'evaluation', 'read'); // Security check (enable the most restrictive one) -//if ($user->socid > 0) accessforbidden(); -//if ($user->socid > 0) $socid = $user->socid; -//$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0); -//restrictedArea($user, $object->element, $object->id, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); -if (empty($conf->hrm->enabled)) { - accessforbidden(); -} -if (!$permissiontoread) { - accessforbidden(); -} + +$isdraft = $object->status == Evaluation::STATUS_DRAFT ? 1 : 0; +restrictedArea($user, $object->element, $object, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); + +if (!isModEnabled('hrm')) accessforbidden(); +if (!$permissiontoread) accessforbidden(); /* diff --git a/htdocs/hrm/evaluation_note.php b/htdocs/hrm/evaluation_note.php index 033e314750c20..e60aaf70d168b 100644 --- a/htdocs/hrm/evaluation_note.php +++ b/htdocs/hrm/evaluation_note.php @@ -75,10 +75,10 @@ // Security check (enable the most restrictive one) //if ($user->socid > 0) accessforbidden(); //if ($user->socid > 0) $socid = $user->socid; -//$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0); -//restrictedArea($user, $object->element, $object->id, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); -//if (empty($conf->hrm->enabled)) accessforbidden(); -//if (!$permissiontoread) accessforbidden(); +$isdraft = (($object->status == Evaluation::STATUS_DRAFT) ? 1 : 0); +restrictedArea($user, $object->element, $object, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); +if (empty($conf->hrm->enabled)) accessforbidden(); +if (!$permissiontoread) accessforbidden(); /* diff --git a/htdocs/includes/odtphp/odf.php b/htdocs/includes/odtphp/odf.php index d45f99395ca4f..aa19feac431b2 100644 --- a/htdocs/includes/odtphp/odf.php +++ b/htdocs/includes/odtphp/odf.php @@ -844,7 +844,7 @@ public function exportAsAttachedPDF($name = "") // using windows libreoffice that must be in path // using linux/mac libreoffice that must be in path // Note PHP Config "fastcgi.impersonate=0" must set to 0 - Default is 1 - $command ='soffice --headless -env:UserInstallation=file:'.(getDolGlobalString('MAIN_ODT_ADD_SLASH_FOR_WINDOWS') ? '///' : '').'\''.$conf->user->dir_temp.'/odtaspdf\' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); + $command ='soffice --headless -env:UserInstallation=file:'.escapeshellarg((getDolGlobalString('MAIN_ODT_ADD_SLASH_FOR_WINDOWS') ? '///' : '').dol_sanitizePathName($conf->user->dir_temp).'/odtaspdf').' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); } elseif (preg_match('/unoconv/', getDolGlobalString('MAIN_ODT_AS_PDF'))) { // If issue with unoconv, see https://github.com/dagwieers/unoconv/issues/87 @@ -870,17 +870,19 @@ public function exportAsAttachedPDF($name = "") // - set shell of user to bash instead of nologin. // - set permission to read/write to user on home directory /var/www so user can create the libreoffice , dconf and .cache dir and files then set permission back - $command = getDolGlobalString('MAIN_ODT_AS_PDF').' '.escapeshellcmd($name); + $command = getDolGlobalString('MAIN_ODT_AS_PDF').' '.escapeshellarg($name); //$command = '/usr/bin/unoconv -vvv '.escapeshellcmd($name); } else { // deprecated old method using odt2pdf.sh (native, jodconverter, ...) $tmpname=preg_replace('/\.odt/i', '', $name); if (getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT')) { - $command = getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT').'/scripts/odt2pdf/odt2pdf.sh '.escapeshellcmd($tmpname).' '.(is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF'))?'jodconverter':getDolGlobalString('MAIN_ODT_AS_PDF')); + $command = dol_sanitizePathName(getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT')).'/scripts/odt2pdf/odt2pdf.sh '.escapeshellarg($tmpname).' '.escapeshellarg(is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF')) ? 'jodconverter' : getDolGlobalString('MAIN_ODT_AS_PDF')); } else { dol_syslog(get_class($this).'::exportAsAttachedPDF is used but the constant MAIN_DOL_SCRIPTS_ROOT with path to script directory was not defined.', LOG_WARNING); - $command = '../../scripts/odt2pdf/odt2pdf.sh '.escapeshellcmd($tmpname).' '.(is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF'))?'jodconverter':getDolGlobalString('MAIN_ODT_AS_PDF')); + $paramodt2pdf = (is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF')) ? 'jodconverter' : getDolGlobalString('MAIN_ODT_AS_PDF')); + $paramodt2pdf = dol_sanitizePathName($paramodt2pdf); + $command = '../../scripts/odt2pdf/odt2pdf.sh '.escapeshellarg($tmpname).' '.escapeshellarg($paramodt2pdf); } } @@ -888,6 +890,7 @@ public function exportAsAttachedPDF($name = "") //$command = DOL_DOCUMENT_ROOT.'/includes/odtphp/odt2pdf.sh '.$name.' '.$dirname; dol_syslog(get_class($this).'::exportAsAttachedPDF $execmethod='.$execmethod.' Run command='.$command, LOG_DEBUG); + // TODO Use: // $outputfile = DOL_DATA_ROOT.'/odt2pdf.log'; // $result = $utils->executeCLI($command, $outputfile); and replace test on $execmethod. @@ -895,17 +898,17 @@ public function exportAsAttachedPDF($name = "") // $errorstring will be $result['output'] $retval=0; $output_arr=array(); if ($execmethod == 1) { - exec($command, $output_arr, $retval); + exec(escapeshellcmd($command), $output_arr, $retval); } if ($execmethod == 2) { $outputfile = DOL_DATA_ROOT.'/odt2pdf.log'; - $ok=0; $handle = fopen($outputfile, 'w'); if ($handle) { dol_syslog(get_class($this)."Run command ".$command, LOG_DEBUG); + dol_syslog(get_class($this)."escapeshellcmd(command) = ".escapeshellcmd($command), LOG_DEBUG); fwrite($handle, $command."\n"); - $handlein = popen($command, 'r'); + $handlein = popen(escapeshellcmd($command), 'r'); while (!feof($handlein)) { $read = fgets($handlein); fwrite($handle, $read); @@ -948,7 +951,7 @@ public function exportAsAttachedPDF($name = "") foreach ($output_arr as $line) { $errorstring.= $line."
    "; } - throw new OdfException('ODT to PDF convert fail (option MAIN_ODT_AS_PDF is '.$conf->global->MAIN_ODT_AS_PDF.', command was '.$command.', retval='.$retval.') : ' . $errorstring); + throw new OdfException('ODT to PDF convert fail (option MAIN_ODT_AS_PDF is '.getDolGlobalString('MAIN_ODT_AS_PDF').', command was '.$command.', retval='.$retval.') : ' . $errorstring); } } } diff --git a/htdocs/index.php b/htdocs/index.php index c09d8484342aa..3ba840ba7e029 100644 --- a/htdocs/index.php +++ b/htdocs/index.php @@ -129,11 +129,13 @@ */ // Specific warning to propose to upgrade invoice situation to progressive mode -if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1 && (float) DOL_VERSION >= 22.0) { // @phpstan-ignore-line +/* +if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) { $langs->loadLangs(array("admin")); print info_admin($langs->trans("WarningExperimentalFeatureInvoiceSituationNeedToUpgradeToProgressiveMode", 'https://partners.dolibarr.org')); //print "
    "; } +*/ /* * Show security warnings diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index 030582bda9172..aa61182fa2565 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -359,6 +359,13 @@ function analyseVarsForSqlAndScriptsInjection(&$var, $type, $stopcode = 1) // Include the conf.php and functions.lib.php and security.lib.php. This defined the constants like DOL_DOCUMENT_ROOT, DOL_DATA_ROOT, DOL_URL_ROOT... require_once 'filefunc.inc.php'; +/** + * @var ?string $php_session_save_handler + * @var ?string $dolibarr_main_force_https + * @var ?string $dolibarr_main_restrict_ip + * @var ?string $dolibarr_nocsrfcheck + * @var ?string $dolibarr_main_demo + */ /** * @var Conf $conf @@ -725,31 +732,33 @@ function analyseVarsForSqlAndScriptsInjection(&$var, $type, $stopcode = 1) // Note: There is another CSRF protection into the filefunc.inc.php } -// Disable modules (this must be after session_start and after conf has been loaded) -if (GETPOSTISSET('disablemodules')) { - $_SESSION["disablemodules"] = GETPOST('disablemodules', 'alpha'); -} -if (!empty($_SESSION["disablemodules"])) { - $modulepartkeys = array('css', 'js', 'tabs', 'triggers', 'login', 'substitutions', 'menus', 'theme', 'sms', 'tpl', 'barcode', 'models', 'societe', 'hooks', 'dir', 'syslog', 'tpllinkable', 'contactelement', 'moduleforexternal', 'websitetemplates'); - - $disabled_modules = explode(',', $_SESSION["disablemodules"]); - foreach ($disabled_modules as $module) { - if ($module) { - if (empty($conf->$module)) { - $conf->$module = new stdClass(); // To avoid warnings - } +if (!empty($dolibarr_main_demo)) { + // Disable modules (this must be after session_start and after conf has been loaded) + if (GETPOSTISSET('disablemodules')) { + $_SESSION["disablemodules"] = GETPOST('disablemodules', 'alpha'); + } + if (!empty($_SESSION["disablemodules"])) { + $modulepartkeys = array('css', 'js', 'tabs', 'triggers', 'login', 'substitutions', 'menus', 'theme', 'sms', 'tpl', 'barcode', 'models', 'societe', 'hooks', 'dir', 'syslog', 'tpllinkable', 'contactelement', 'moduleforexternal', 'websitetemplates'); - $conf->$module->enabled = false; // Old usage - unset($conf->modules[$module]); + $disabled_modules = explode(',', $_SESSION["disablemodules"]); + foreach ($disabled_modules as $module) { + if ($module) { + if (empty($conf->$module)) { + $conf->$module = new stdClass(); // To avoid warnings + } - foreach ($modulepartkeys as $modulepartkey) { - unset($conf->modules_parts[$modulepartkey][$module]); - } - if ($module == 'fournisseur') { // Special case - $conf->supplier_order->enabled = 0; // Old usage - $conf->supplier_invoice->enabled = 0; // Old usage - unset($conf->modules['supplier_order']); - unset($conf->modules['supplier_invoice']); + $conf->$module->enabled = false; // Old usage + unset($conf->modules[$module]); + + foreach ($modulepartkeys as $modulepartkey) { + unset($conf->modules_parts[$modulepartkey][$module]); + } + if ($module == 'fournisseur') { // Special case + $conf->supplier_order->enabled = 0; // Old usage + $conf->supplier_invoice->enabled = 0; // Old usage + unset($conf->modules['supplier_order']); + unset($conf->modules['supplier_invoice']); + } } } } diff --git a/htdocs/modulebuilder/template/class/myobject.class.php b/htdocs/modulebuilder/template/class/myobject.class.php index 6aa8cfdbf4cc2..493e828a8dfc7 100644 --- a/htdocs/modulebuilder/template/class/myobject.class.php +++ b/htdocs/modulebuilder/template/class/myobject.class.php @@ -777,7 +777,7 @@ public function getTooltipContentArray($params) $datas['ref'] = '
    '.$langs->trans('Ref').': '.$this->ref; } if (property_exists($this, 'label')) { - $datas['ref'] = '
    '.$langs->trans('Label').': '.$this->label; + $datas['label'] = '
    '.$langs->trans('Label').': '.$this->label; } return $datas; diff --git a/htdocs/product/class/api_products.class.php b/htdocs/product/class/api_products.class.php index 06c41e0b82abe..fdd2b2bc85472 100644 --- a/htdocs/product/class/api_products.class.php +++ b/htdocs/product/class/api_products.class.php @@ -412,8 +412,8 @@ public function put($id, $request_data = null) $result = $this->product->update($id, DolibarrApiAccess::$user, 1, 'update', $updatetype); - // If price mode is 1 price per product - if ($result > 0 && getDolGlobalString('PRODUCT_PRICE_UNIQ')) { + // If price mode is 1 price per product or price by client + if ($result > 0 && (getDolGlobalString('PRODUCT_PRICE_UNIQ') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES'))) { // We update price only if it was changed $pricemodified = false; if ($this->product->price_base_type != $oldproduct->price_base_type) { diff --git a/htdocs/product/inventory/card.php b/htdocs/product/inventory/card.php index 81c52b976daf8..3b14512ccbf07 100644 --- a/htdocs/product/inventory/card.php +++ b/htdocs/product/inventory/card.php @@ -54,9 +54,9 @@ $hookmanager->initHooks(array('inventorycard', 'globalcard')); // Note that conf->hooks_modules contains array if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { - $result = restrictedArea($user, 'stock', $id); + $result = restrictedArea($user, 'stock', $id, 'inventory&stock'); } else { - $result = restrictedArea($user, 'stock', $id, '', 'inventory_advance'); + $result = restrictedArea($user, 'stock', $id, 'inventory&stock', 'inventory_advance'); } // Initialize a technical objects diff --git a/htdocs/product/inventory/inventory.php b/htdocs/product/inventory/inventory.php index 4d8601912696a..805eee53f3673 100644 --- a/htdocs/product/inventory/inventory.php +++ b/htdocs/product/inventory/inventory.php @@ -71,9 +71,9 @@ $totalRealValuation = 0; $hookmanager->initHooks(array('inventorycard')); // Note that conf->hooks_modules contains array if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { - $result = restrictedArea($user, 'stock', $id); + $result = restrictedArea($user, 'stock', $id, 'inventory&stock'); } else { - $result = restrictedArea($user, 'stock', $id, '', 'inventory_advance'); + $result = restrictedArea($user, 'stock', $id, 'inventory&stock', 'inventory_advance'); } // Initialize a technical objects @@ -122,6 +122,9 @@ $paramwithsearch .= '&limit='.((int) $limit); } +// Sort by warehouse/product or product/warehouse +$sortfield .= ',' . ($sortfield == 'e.ref' ? 'p.ref' : 'e.ref') . ',id.batch,id.rowid'; +$sortorder .= ',' . $sortorder.",ASC,ASC"; if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { $permissiontoadd = $user->hasRight('stock', 'creer'); @@ -291,8 +294,10 @@ $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,'; $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated'; $sql .= ' FROM '.MAIN_DB_PREFIX.'inventorydet as id'; + $sql .= ' LEFT JOIN ' . $db->prefix() . 'product as p ON id.fk_product = p.rowid'; + $sql .= ' LEFT JOIN ' . $db->prefix() . 'entrepot as e ON id.fk_warehouse = e.rowid'; $sql .= ' WHERE id.fk_inventory = '.((int) $object->id); - $sql .= $db->order('id.rowid', 'ASC'); + $sql .= $db->order($sortfield, $sortorder); $sql .= $db->plimit($limit, $offset); $db->begin(); @@ -1037,10 +1042,6 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,sele print ''; } -// Sort by warehouse/product or product/warehouse -$sortfield .= ',' . ($sortfield == 'e.ref' ? 'p.ref' : 'e.ref'); -$sortorder .= ',' . $sortorder; - // Request to show lines of inventory (prefilled after start/validate step) $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,'; $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.fk_movement, id.pmp_real, id.pmp_expected'; @@ -1123,7 +1124,7 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,sele if (isModEnabled('productbatch') && $product_static->hasbatch()) { $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->detail_batch[$obj->batch]->qty ?? 0; } else { - $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->real ?? 0; + $valuetoshow = !empty($product_static->stock_warehouse[$obj->fk_warehouse]->real) ? $product_static->stock_warehouse[$obj->fk_warehouse]->real : 0; } } print price2num($valuetoshow, 'MS'); @@ -1298,10 +1299,11 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,sele success: function(result){ window.location.href = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page + 1).$paramwithsearch.'"; }}); + return false; }); - $(".paginationprevious:last").click(function(e){ + $(".paginationprevious:last").click(function(e){ var form = $("#formrecord"); var actionURL = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page).$paramwithsearch.'"; $.ajax({ @@ -1311,9 +1313,10 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,sele success: function(result){ window.location.href = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page - 1).$paramwithsearch.'"; }}); - }); + return false; + }); - $("#idbuttonmakemovementandclose").click(function(e){ + $("#idbuttonmakemovementandclose").click(function(e){ var form = $("#formrecord"); var actionURL = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page).$paramwithsearch.'"; $.ajax({ @@ -1323,7 +1326,8 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,sele success: function(result){ window.location.href = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page - 1).$paramwithsearch.'&action=record"; }}); - }); + return false; + }); }); '; diff --git a/htdocs/product/stats/bom.php b/htdocs/product/stats/bom.php index d52198a2d91cc..215472d88f2da 100644 --- a/htdocs/product/stats/bom.php +++ b/htdocs/product/stats/bom.php @@ -244,7 +244,7 @@ $bomtmp->ref = $objp->ref; $product = new Product($db); if (!empty($objp->fk_product)) { - if (!array_key_exists($product->id, $product_cache)) { + if (!array_key_exists($objp->fk_product, $product_cache)) { $resultFetch = $product->fetch($objp->fk_product); if ($resultFetch < 0) { setEventMessages($product->error, $product->errors, 'errors'); diff --git a/htdocs/product/stock/card.php b/htdocs/product/stock/card.php index f462ae9a03db0..c9006b3c379f8 100644 --- a/htdocs/product/stock/card.php +++ b/htdocs/product/stock/card.php @@ -85,8 +85,7 @@ $hookmanager->initHooks(array('warehousecard', 'stocklist', 'globalcard')); // Security check -//$result=restrictedArea($user,'stock', $id, 'entrepot&stock'); -$result = restrictedArea($user, 'stock'); +$result=restrictedArea($user, 'stock', $id, 'entrepot&stock'); $object = new Entrepot($db); $extrafields = new ExtraFields($db); diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 45668188f936b..adc9af41781cd 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -542,6 +542,7 @@ public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = // Test if there is already a record for couple (warehouse / product), so later we will make an update or create. $alreadyarecord = 0; + $fk_product_stock = 0; if (!$error) { $sql = "SELECT rowid, reel FROM ".$this->db->prefix()."product_stock"; $sql .= " WHERE fk_entrepot = ".((int) $entrepot_id)." AND fk_product = ".((int) $fk_product); // This is a unique key diff --git a/htdocs/projet/activity/permonth.php b/htdocs/projet/activity/permonth.php index 804b4afc8f592..aeca89c863fdb 100644 --- a/htdocs/projet/activity/permonth.php +++ b/htdocs/projet/activity/permonth.php @@ -597,6 +597,11 @@ print ''; } print ''; +// TASK fields +$search_options_pattern = 'search_task_options_'; +$extrafieldsobjectkey = 'projet_task'; +$extrafieldsobjectprefix = 'efpt.'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_input.tpl.php'; if (!empty($arrayfields['t.planned_workload']['checked'])) { print ''; } @@ -625,6 +630,10 @@ print ''.$langs->trans("ThirdParty").''; } print ''.$langs->trans("Task").''; +// TASK fields +$extrafieldsobjectkey = 'projet_task'; +$extrafieldsobjectprefix = 'efpt.'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php'; if (!empty($arrayfields['t.planned_workload']['checked'])) { print ''.$form->textwithpicto($langs->trans("PlannedWorkloadShort"), $langs->trans("PlannedWorkload")).''; } diff --git a/htdocs/projet/activity/perweek.php b/htdocs/projet/activity/perweek.php index b69946d83ddb5..6cd82c40a79c6 100644 --- a/htdocs/projet/activity/perweek.php +++ b/htdocs/projet/activity/perweek.php @@ -318,7 +318,7 @@ } } - if (!$updateoftaskdone && GETPOSTISSET($taskid.'progress')) { // Check to update progress if no update were done on task. + if (!$updateoftaskdone && GETPOSTISSET($tmptaskid.'progress')) { // Check to update progress if no update were done on task. $object->fetch($tmptaskid); //var_dump($object->progress); //var_dump(GETPOST($tmptaskid . 'progress', 'int')); exit; @@ -422,7 +422,7 @@ $extrafieldsobjectkey = 'projet_task'; include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; -$tasksarray = $taskstatic->getTasksArray(null, null, ($project->id ? $project->id : 0), $socid, 0, $search_project_ref, $onlyopenedproject, $morewherefilter, ($search_usertoprocessid ? $search_usertoprocessid : 0), 0, $extrafields, 0, $search_array_options_task, 1);// We want to see all tasks of open project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. +$tasksarray = $taskstatic->getTasksArray(null, null, ($project->id ? $project->id : 0), $socid, 0, $search_project_ref, $onlyopenedproject, $morewherefilter, ($search_usertoprocessid ? $search_usertoprocessid : 0), 0, $extrafields, 0, array(), 1);// We want to see all tasks of open project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. $tasksarraywithoutfilter = array(); if ($morewherefilter) { // Get all task without any filter, so we can show total of time spent for not visible tasks $tasksarraywithoutfilter = $taskstatic->getTasksArray(null, null, ($project->id ? $project->id : 0), $socid, 0, '', $onlyopenedproject, '', ($search_usertoprocessid ? $search_usertoprocessid : 0)); // We want to see all tasks of open project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. diff --git a/htdocs/projet/class/project.class.php b/htdocs/projet/class/project.class.php index 5114564141b69..fa8652f1ca80b 100644 --- a/htdocs/projet/class/project.class.php +++ b/htdocs/projet/class/project.class.php @@ -2048,7 +2048,7 @@ public function shiftTaskDate($old_project_dt_start) public function update_element($tableName, $elementSelectId) { // phpcs:enable - $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName; + $sql = "UPDATE ".MAIN_DB_PREFIX.$this->db->sanitize($tableName); if ($tableName == "actioncomm") { $sql .= " SET fk_project=".$this->id; @@ -2084,14 +2084,14 @@ public function update_element($tableName, $elementSelectId) public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet') { // phpcs:enable - $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName; + $sql = "UPDATE ".MAIN_DB_PREFIX.$this->db->sanitize($tableName); if ($tableName == "actioncomm") { - $sql .= " SET fk_project=NULL"; - $sql .= " WHERE id=".((int) $elementSelectId); + $sql .= " SET fk_project = NULL"; + $sql .= " WHERE id = ".((int) $elementSelectId); } else { - $sql .= " SET ".$projectfield."=NULL"; - $sql .= " WHERE rowid=".((int) $elementSelectId); + $sql .= " SET ".$this->db->sanitize($projectfield)." = NULL"; + $sql .= " WHERE rowid = ".((int) $elementSelectId); } dol_syslog(get_class($this)."::remove_element", LOG_DEBUG); diff --git a/htdocs/projet/element.php b/htdocs/projet/element.php index 60cf4e489dc38..3fb33d7f7969a 100644 --- a/htdocs/projet/element.php +++ b/htdocs/projet/element.php @@ -737,7 +737,7 @@ } if ($action == "addelement") { - $tablename = GETPOST("tablename"); + $tablename = GETPOST("tablename", "aZ09"); $elementselectid = GETPOST("elementselect"); $result = $object->update_element($tablename, $elementselectid); if ($result < 0) { diff --git a/htdocs/projet/tasks/time.php b/htdocs/projet/tasks/time.php index 92822e76c21e8..93ef6997fd446 100644 --- a/htdocs/projet/tasks/time.php +++ b/htdocs/projet/tasks/time.php @@ -332,10 +332,12 @@ $object->timespent_duration = GETPOSTINT("new_durationhour") * 60 * 60; // We store duration in seconds $object->timespent_duration += (GETPOSTINT("new_durationmin") ? GETPOSTINT('new_durationmin') : 0) * 60; // We store duration in seconds if (GETPOST("timelinehour") != '' && GETPOST("timelinehour") >= 0) { // If hour was entered - $object->timespent_date = dol_mktime(GETPOSTINT("timelinehour"), GETPOSTINT("timelinemin"), 0, GETPOSTINT("timelinemonth"), GETPOSTINT("timelineday"), GETPOSTINT("timelineyear")); + $object->timespent_date = dol_mktime(12, 0, 0, GETPOSTINT("timelinemonth"), GETPOSTINT("timelineday"), GETPOSTINT("timelineyear")); + $object->timespent_datehour = dol_mktime(GETPOSTINT("timelinehour"), GETPOSTINT("timelinemin"), 0, GETPOSTINT("timelinemonth"), GETPOSTINT("timelineday"), GETPOSTINT("timelineyear")); $object->timespent_withhour = 1; } elseif (!empty($timespent_date)) { $object->timespent_date = $timespent_date; + $object->timespent_datehour = $timespent_date; $object->timespent_withhour = 0; } $object->timespent_fk_user = GETPOSTINT("userid_line"); diff --git a/htdocs/reception/class/reception.class.php b/htdocs/reception/class/reception.class.php index 235cfa1b08d4e..9e971b86c6f20 100644 --- a/htdocs/reception/class/reception.class.php +++ b/htdocs/reception/class/reception.class.php @@ -13,6 +13,7 @@ * Copyright (C) 2018 Quentin Vial-Gouteyron * Copyright (C) 2022-2024 Frédéric France * Copyright (C) 2024 MDW + * Copyright (C) 2026 Mathieu Moulin * * 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 diff --git a/htdocs/recruitment/class/api_recruitments.class.php b/htdocs/recruitment/class/api_recruitments.class.php index 1aaff616496e2..b36c3d67b7405 100644 --- a/htdocs/recruitment/class/api_recruitments.class.php +++ b/htdocs/recruitment/class/api_recruitments.class.php @@ -418,11 +418,11 @@ public function postCandidature($request_data = null) foreach ($request_data as $field => $value) { if ($field === 'caller') { // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller - $this->jobposition->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); + $this->candidature->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); continue; } - $this->jobposition->$field = $this->_checkValForAPI($field, $value, $this->jobposition); + $this->candidature->$field = $this->_checkValForAPI($field, $value, $this->candidature); } // Clean data diff --git a/htdocs/societe/class/api_thirdparties.class.php b/htdocs/societe/class/api_thirdparties.class.php index d42054361b611..6e6d7b1bf637f 100644 --- a/htdocs/societe/class/api_thirdparties.class.php +++ b/htdocs/societe/class/api_thirdparties.class.php @@ -5,7 +5,8 @@ * Copyright (C) 2020-2024 Frédéric France * Copyright (C) 2023 Alexandre Janniaux * Copyright (C) 2024 MDW - * Copyright (C) 2024 Jon Bendtsen + * Copyright (C) 2024 Jon Bendtsen + * Copyright (C) 2026 Benjamin Falière * * 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 @@ -995,8 +996,9 @@ public function getFixedAmountDiscounts($id, $filter = "none", $sortfield = "f.t $sql = "SELECT f.ref, f.type as factype, re.fk_facture_source, re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc, re.description, re.fk_facture, re.fk_facture_line"; - $sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re, ".MAIN_DB_PREFIX."facture as f"; - $sql .= " WHERE f.rowid = re.fk_facture_source AND re.fk_soc = ".((int) $id); + $sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as f ON f.rowid = re.fk_facture_source"; + $sql .= " WHERE re.fk_soc = ".((int) $id); if ($filter == "available") { $sql .= " AND re.fk_facture IS NULL AND re.fk_facture_line IS NULL"; } diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index 7fd6fb6829a9a..d3b2536d4ff2e 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -1490,7 +1490,7 @@ public function update($id, User $user, $call_trigger = 1, $allowmodcodeclient = $this->supplier_order_min_amount = price2num($this->supplier_order_min_amount); $this->tva_assuj = (is_numeric($this->tva_assuj)) ? (int) trim((string) $this->tva_assuj) : 0; - $this->tva_intra = dol_sanitizeFileName($this->tva_intra, ''); + $this->tva_intra = trim($this->tva_intra); $this->vat_reverse_charge = empty($this->vat_reverse_charge) ? 0 : 1; if (empty($this->status)) { $this->status = 0; @@ -5005,6 +5005,8 @@ public function getOutstandingOrders($mode = 'customer') */ public function getOutstandingBills($mode = 'customer', $late = 0) { + include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; + $table = 'facture'; if ($mode == 'supplier') { $table = 'facture_fourn'; diff --git a/htdocs/societe/price.php b/htdocs/societe/price.php index 1a0890de567c8..296bac76e09f1 100644 --- a/htdocs/societe/price.php +++ b/htdocs/societe/price.php @@ -32,11 +32,6 @@ // Load Dolibarr environment require '../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -46,9 +41,14 @@ * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php'; +$prodcustprice = null; if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) { - require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php'; $prodcustprice = new ProductCustomerPrice($db); } @@ -104,7 +104,7 @@ $action = 'add_customer_price'; } - if (!$error) { + if (!$error && $prodcustprice !== null) { $update_child_soc = GETPOST('updatechildprice'); // add price by customer @@ -187,7 +187,7 @@ } } - if ($action == 'delete_customer_price' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) { + if ($action == 'delete_customer_price' && $prodcustprice !== null && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) { // Delete price by customer $prodcustprice->id = GETPOSTINT('lineid'); $result = $prodcustprice->delete($user); @@ -200,7 +200,7 @@ $action = ''; } - if ($action == 'update_customer_price_confirm' && !$cancel && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) { + if ($action == 'update_customer_price_confirm' && !$cancel && $prodcustprice !== null && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) { $prodcustprice->fetch(GETPOSTINT('lineid')); $update_child_soc = GETPOST('updatechildprice'); @@ -713,7 +713,7 @@ $nbtotalofrecords = $prodcustprice->fetchAll('', '', 0, 0, $filter); } - $result = $prodcustprice->fetchAll($sortorder, $sortfield, $conf->liste_limit, $offset, $filter); + $result = $prodcustprice->fetchAll($sortorder, $sortfield, $limit, $offset, $filter); if ($result < 0) { setEventMessages($prodcustprice->error, $prodcustprice->errors, 'errors'); } diff --git a/htdocs/supplier_proposal/contact.php b/htdocs/supplier_proposal/contact.php index 32fa86a340f6e..8e4aa7e0a6ef8 100644 --- a/htdocs/supplier_proposal/contact.php +++ b/htdocs/supplier_proposal/contact.php @@ -6,6 +6,7 @@ * Copyright (C) 2023 Christian Foellmann * Copyright (C) 2024 Alexandre Spangaro * Copyright (C) 2024 Frédéric France + * Copyright (C) 2026 Serhii Bondarenko * * 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 @@ -189,8 +190,14 @@ print dol_get_fiche_end(); - // Contacts lines - include DOL_DOCUMENT_ROOT.'/core/tpl/contacts.tpl.php'; + // Contacts lines (modules that overwrite templates must declare this into descriptor) + $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl')); + foreach ($dirtpls as $reldir) { + $res = @include dol_buildpath($reldir.'/contacts.tpl.php'); + if ($res) { + break; + } + } } else { // Contact not found print "ErrorRecordNotFound"; diff --git a/htdocs/takepos/index.php b/htdocs/takepos/index.php index 828059e3633ed..fd9bc1416a39e 100644 --- a/htdocs/takepos/index.php +++ b/htdocs/takepos/index.php @@ -107,6 +107,9 @@ $term = empty($_SESSION['takeposterminal']) ? 1 : $_SESSION['takeposterminal']; +$socid = getDolGlobalInt('CASHDESK_ID_THIRDPARTY' . $term); + + /* $constforcompanyid = 'CASHDESK_ID_THIRDPARTY'.$_SESSION["takeposterminal"]; $soc = new Societe($db); @@ -359,8 +362,15 @@ function LoadProducts(position, issubcat) { if (maxproduct >= 1) { limit = maxproduct - 1; } + + // Get socid + let socid = jQuery('#thirdpartyid').val(); + if ((socid === undefined || socid === "") && parseInt("") > 0) { + socid = parseInt(""); + } + // Only show products for sale (tosell=1) - $.getJSON('/takepos/ajax/ajax.php?action=getProducts&token=&thirdpartyid=' + jQuery('#thirdpartyid').val() + '&category='+currentcat+'&tosell=1&limit='+limit+'&offset=0', function(data) { + $.getJSON('/takepos/ajax/ajax.php?action=getProducts&token=&thirdpartyid=' + socid + '&category='+currentcat+'&tosell=1&limit='+limit+'&offset=0', function(data) { console.log("Call ajax.php (in LoadProducts) to get Products of category "+currentcat+" then loop on result to fill image thumbs"); console.log(data); @@ -479,10 +489,18 @@ function MoreProducts(moreorless) { if (maxproduct >= 1) { limit = maxproduct-1; } + var nb_cat_shown = $('.div5 div.wrapper2[data-iscat=1]').length; - var offset = * pageproducts - nb_cat_shown; + var offset = * pageproducts - nb_cat_shown; + + // Get socid + let socid = jQuery('#thirdpartyid').val(); + if ((socid === undefined || socid === "") && parseInt("") > 0) { + socid = parseInt(""); + } + // Only show products for sale (tosell=1) - $.getJSON('/takepos/ajax/ajax.php?action=getProducts&token=&thirdpartyid=' + jQuery('#thirdpartyid').val() + '&category='+currentcat+'&tosell=1&limit='+limit+'&offset='+offset, function(data) { + $.getJSON('/takepos/ajax/ajax.php?action=getProducts&token=&thirdpartyid=' + socid + '&category='+currentcat+'&tosell=1&limit='+limit+'&offset='+offset, function(data) { console.log("Call ajax.php (in MoreProducts) to get Products of category "+currentcat); if (typeof (data[0]) == "undefined" && moreorless=="more"){ // Return if no more pages @@ -749,7 +767,14 @@ function Search2(keyCodeForEnter, moreorless) { pageproducts = 0; jQuery(".wrapper2 .catwatermark").hide(); var nbsearchresults = 0; - $.getJSON('/takepos/ajax/ajax.php?action=search&token=&term=' + search_term + '&thirdpartyid=' + jQuery('#thirdpartyid').val() + '&search_start=' + search_start + '&search_limit=' + search_limit, function (data) { + + // Only show products for sale (tosell=1) + let socid = jQuery('#thirdpartyid').val(); + if ((socid === undefined || socid === "") && parseInt("") > 0) { + socid = parseInt(""); + } + + $.getJSON('/takepos/ajax/ajax.php?action=search&token=&term=' + search_term + '&thirdpartyid=' + socid + '&search_start=' + search_start + '&search_limit=' + search_limit, function (data) { for (i = 0; i < ; i++) { if (typeof (data[i]) == "undefined") { $("#prowatermark" + i).html(""); diff --git a/htdocs/ticket/card.php b/htdocs/ticket/card.php index a94cd7ab6237c..deea4e588f8c3 100644 --- a/htdocs/ticket/card.php +++ b/htdocs/ticket/card.php @@ -615,8 +615,9 @@ // Reopen ticket if ($object->fetch(GETPOSTINT('id'), GETPOST('track_id', 'alpha')) >= 0) { $new_status = GETPOSTINT('new_status'); - //$old_status = $object->status; - $res = $object->setStatut($new_status); + + $res = $object->setStatut($new_status, null, '', 'TICKET_MODIFY'); + if ($res) { $url = 'card.php?track_id=' . $object->track_id; header("Location: " . $url); @@ -719,6 +720,11 @@ print load_fiche_titre($langs->trans('NewTicket'), '', 'ticket'); $formticket->trackid = ''; // TODO Use a unique key 'tic' to avoid conflict in upload file feature + + if (GETPOST("mode", "aZ09") == 'init' && empty($_POST)) { + $formticket->clear_attached_files(); + } + $formticket->withfromsocid = $socid ? $socid : $user->socid; $formticket->withfromcontactid = $contactid ? $contactid : ''; $formticket->withtitletopic = 1; diff --git a/htdocs/ticket/class/ticket.class.php b/htdocs/ticket/class/ticket.class.php index 13a477d0ef9b8..fe9e0284c0e33 100644 --- a/htdocs/ticket/class/ticket.class.php +++ b/htdocs/ticket/class/ticket.class.php @@ -1707,7 +1707,7 @@ public function markAsRead($user, $notrigger = 0) $this->status = Ticket::STATUS_READ; $sql = "UPDATE ".MAIN_DB_PREFIX."ticket"; - $sql .= " SET fk_statut = ".Ticket::STATUS_READ.", date_read = '".$this->db->idate(dol_now())."'"; + $sql .= " SET fk_statut = ".((int) $this->status) .", date_read = '".$this->db->idate(dol_now())."'"; $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog(get_class($this)."::markAsRead"); @@ -1732,7 +1732,9 @@ public function markAsRead($user, $notrigger = 0) $this->status = $this->oldcopy->status; $this->db->rollback(); + $this->error = implode(',', $this->errors); + dol_syslog(get_class($this)."::markAsRead ".$this->error, LOG_ERR); return -1; } @@ -2000,8 +2002,11 @@ public function close(User $user, $mode = 0) if ($this->status != Ticket::STATUS_CLOSED && $this->status != Ticket::STATUS_CANCELED) { // not closed $this->db->begin(); + $this->oldcopy = dol_clone($this); + $this->status = ($mode ? Ticket::STATUS_CANCELED : Ticket::STATUS_CLOSED); + $sql = "UPDATE ".MAIN_DB_PREFIX."ticket"; - $sql .= " SET fk_statut=".($mode ? Ticket::STATUS_CANCELED : Ticket::STATUS_CLOSED).", progress=100, date_close='".$this->db->idate(dol_now())."'"; + $sql .= " SET fk_statut = ".((int) $this->status).", progress=100, date_close='".$this->db->idate(dol_now())."'"; $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog(get_class($this)."::close mode=".$mode); @@ -3007,9 +3012,11 @@ public function newMessage($user, &$action, $private = 1, $public_area = 0) if ((getDolGlobalInt('TICKET_SET_STATUS_ON_ANSWER', -1) < 0 && ($object->status < self::STATUS_IN_PROGRESS && !$user->socid && !$private)) || ($object->status > self::STATUS_IN_PROGRESS && $public_area)) { - $object->setStatut($object::STATUS_IN_PROGRESS); + // Set status + $object->setStatut($object::STATUS_IN_PROGRESS, null, '', 'TICKET_MODIFY'); } elseif (getDolGlobalInt('TICKET_SET_STATUS_ON_ANSWER', -1) >= 0 && empty($user->socid) && empty($private)) { - $object->setStatut(getDolGlobalInt('TICKET_SET_STATUS_ON_ANSWER')); + // Set status + $object->setStatut(getDolGlobalInt('TICKET_SET_STATUS_ON_ANSWER'), null, '', 'TICKET_MODIFY'); } return 1; diff --git a/htdocs/ticket/list.php b/htdocs/ticket/list.php index e2ab49441dfdc..c9cd63ad07892 100644 --- a/htdocs/ticket/list.php +++ b/htdocs/ticket/list.php @@ -297,7 +297,9 @@ $result = $objecttmp->fetch($toselectid); if ($result > 0) { if ($objecttmp->status == Ticket::STATUS_CLOSED || $objecttmp->status == Ticket::STATUS_CANCELED) { - $result = $objecttmp->setStatut(Ticket::STATUS_ASSIGNED); + // Set status + $result = $objecttmp->setStatut(Ticket::STATUS_ASSIGNED, null, '', 'TICKET_MODIFY'); + if ($result < 0) { setEventMessages($objecttmp->error, $objecttmp->errors, 'errors'); $error++; @@ -776,7 +778,7 @@ print ''; } -$url = DOL_URL_ROOT.'/ticket/card.php?action=create'.($socid ? '&socid='.$socid : '').($projectid ? '&origin=projet_project&originid='.$projectid : ''); +$url = DOL_URL_ROOT.'/ticket/card.php?action=create&mode=init'.($socid ? '&socid='.$socid : '').($projectid ? '&origin=projet_project&originid='.$projectid : ''); if (!empty($socid)) { $url .= '&socid='.$socid; } diff --git a/htdocs/user/class/api_users.class.php b/htdocs/user/class/api_users.class.php index 7038d521180a5..8042101a27fef 100644 --- a/htdocs/user/class/api_users.class.php +++ b/htdocs/user/class/api_users.class.php @@ -311,12 +311,12 @@ public function getInfo($includepermissions = 0) * @phpstan-param ?array $request_data * @return int * - * @throws RestException 401 Not allowed + * @throws RestException 403 Not allowed */ public function post($request_data = null) { // Check user authorization - if (!DolibarrApiAccess::$user->hasRight('user', 'creer') && empty(DolibarrApiAccess::$user->admin)) { + if (!DolibarrApiAccess::$user->hasRight('user', 'user', 'creer') && empty(DolibarrApiAccess::$user->admin)) { throw new RestException(403, "User creation not allowed for login ".DolibarrApiAccess::$user->login); } diff --git a/htdocs/webportal/class/html.formwebportal.class.php b/htdocs/webportal/class/html.formwebportal.class.php index 202aed83dfeb0..4fe58d11bdcf0 100644 --- a/htdocs/webportal/class/html.formwebportal.class.php +++ b/htdocs/webportal/class/html.formwebportal.class.php @@ -830,7 +830,11 @@ public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = } if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { list($parentName, $parentField) = explode('|', $InfoFieldList[3]); - $keyList .= ', ' . $parentField; + if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList .= ', main.'.$parentField; + } else { + $keyList .= ', '.$parentField; + } } $filter_categorie = false; diff --git a/htdocs/website/class/websitepage.class.php b/htdocs/website/class/websitepage.class.php index 2b0ae10fdfed0..f8c9a49e45ad8 100644 --- a/htdocs/website/class/websitepage.class.php +++ b/htdocs/website/class/websitepage.class.php @@ -264,6 +264,18 @@ public function create(User $user, $notrigger = 0) // Remove spaces and be sure we have main language only $this->lang = preg_replace('/[_-].*$/', '', trim($this->lang)); // en_US or en-US -> en + // Test if page contains dynamic PHP content + if (!$user->hasRight('website', 'writephp')) { + // Check there is no PHP content into the imported file (must be only HTML + JS) + $phpcontent = dolKeepOnlyPhpCode($this->content); + + if ($phpcontent) { + $this->error = 'Error: you try to create a page with PHP content without having permissions for that.'; + $this->errors[] = $this->error; + return -1; + } + } + return $this->createCommon($user, $notrigger); } diff --git a/htdocs/website/index.php b/htdocs/website/index.php index dc0b9f5f402cf..787aae0a22259 100644 --- a/htdocs/website/index.php +++ b/htdocs/website/index.php @@ -869,6 +869,7 @@ // Remove comments $tmp['content'] = removeHtmlComment($tmp['content']); + /* disable this, moved into the create() method // Check there is no PHP content into the imported file (must be only HTML + JS) $phpcontent = dolKeepOnlyPhpCode($tmp['content']); if ($phpcontent) { @@ -876,6 +877,7 @@ setEventMessages('Error getting '.$urltograb.': file that include PHP content is not allowed', null, 'errors'); $action = 'createcontainer'; } + */ } if (!$error) { @@ -1252,6 +1254,7 @@ $pageid = 0; if (!$error) { + // Create page. This also check there is no PHP content if user has no pemrissions for that. $pageid = $objectpage->create($user); if ($pageid <= 0) { $error++;