diff --git a/AGENTS.md b/.agents/AGENTS.md similarity index 82% rename from AGENTS.md rename to .agents/AGENTS.md index d8d5f3cc6eb..a1d66837061 100644 --- a/AGENTS.md +++ b/.agents/AGENTS.md @@ -15,7 +15,7 @@ Every modification must respect: - ❌ Do not break compatibility of PHP functions and methods - ❌ Do not introduce external dependencies without validation - ❌ Separate page actions in the `/* Actions */` section of the PHP code and the rendering part in the `/* Views */` section -- ❌ Never commit directly to `main` or `master` branch +- ❌ Never commit directly to `develop` or version branch matching regex `^\d+\.\d+$` - ❌ Never use PHP native curl functions to call a GET or POST URL, but use instead the Dolibarr function getURLContent() - ✅ Use Dolibarr hooks whenever possible - ✅ Respect existing naming conventions @@ -53,7 +53,6 @@ Before writing any code, the agent **must**: - PHP >= 7.3 (minimum support); PHP 8.1+ recommended for new external modules - ⚠️ When writing a **bug fix**, always target the lowest compatible PHP version of the branch being patched — do not use PHP 8.x syntax on a fix targeting v19 or v20 -- Use `declare(strict_types=1)` when targeting PHP 8.1+ - Respect PSR-12, but **indentations must use Tabs, not Spaces** - Write short, readable, and testable functions - Avoid side effects @@ -66,8 +65,7 @@ Before writing any code, the agent **must**: - Use Dolibarr database functions exclusively — never use PDO or MySQLi directly - In pages: use global `$db` - In classes: use `$this->db` -- ✅ Always escape user inputs -- ✅ SQL forged by PHP must escape fields with `db->escape()`, `db->sanitize()`, or by casting values to `(int)` or `(float)` +- ✅ SQL forged by PHP must escaped fields with `db->escape()`, `db->sanitize()`, or by casting values to `(int)` or `(float)` - ✅ Always use `$db->query()` followed by `$db->fetch_object()` or `$db->fetch_array()` to retrieve results - ✅ SQL scripts for table and index creation must be placed in `htdocs/install/mysql/tables/` (see existing files for examples) - ❌ Never run SQL queries inside loops (avoid N+1 problem — use JOINs or batch queries instead) @@ -94,7 +92,8 @@ Before writing any code, the agent **must**: - Never hardcode user-facing strings — always use `$langs->trans('Key')` - Language files must be placed in `mymodule/langs/en_US/` (and other locales as needed) -- Language key names must be in English and PascalCase (e.g., `MyModuleLabel`, not `MonLibellé`) +- All code comments and variables or functions names must be in English. +- Language key names must use PascalCase (e.g., `MyModuleLabel`, not `monLibelléModule`) - Load the language file at the top of the page: `$langs->load('mymodule@mymodule')` --- @@ -103,12 +102,13 @@ Before writing any code, the agent **must**: Before any modification, verify: - Creation / edition / deletion workflows -- User rights enforcement (`$user->rights->module->action`) -- Multi-entity compatibility (`$conf->entity`) +- User rights enforcement (`$user->hasRights("module", "permission")` or `$user->hasRights("module", "objectname", "permission")`) +- Multi-entity compatibility (add ` AND entity IN ('.getDolEntity("tablename").')`) If possible: -- Add a PHPUnit test file in `htdocs/modulebuilder/template/test/phpunit/` -- Reference the `phpunit.xml` file at the root of the project to run the test suite +- If doing an external module, add a PHPUnit test file in `yourmoduledir/test/phpunit/` +- If modifying the Dolibarr code project, add a PHPUnit test file into `test/phpunit/` and add the entry into file `test/phpunit/AllTests.php`. + --- @@ -123,8 +123,9 @@ If possible: ## 🔒 Security -- Always validate inputs (`GET`, `POST`) via `GETPOST()` with a type parameter -- Prevent SQL injection (use `db->escape()`) and XSS (use `dol_escape_htmltag()`) +- Always validate user inputs (`GET`, `POST`) via `GETPOST()` with a type parameter +- Prevent SQL injection (use `db->escape()` or cast into `(int)` or `(float)`) +- Prevent XSS injection by escaping HTML output (use `dolPrintHTML()`, `dolPrintHTMLForAttribute()`) - Always include Dolibarr CSRF tokens in POST forms: `` --- @@ -153,7 +154,7 @@ If possible: - `develop` branch for both fixes and new features - ❌ Never commit directly to `main` or `develop` without a reviewed PR - Commit message format: `TYPE: #issueNumber Short description` - - Types: `NEW`, `FIX`, `CLOSE` + - Types: `NEW`, `FIX` or `CLOSE` - Example: `FIX: #1234 Correct VAT calculation on credit notes` - Update the `ChangeLog` file with a summary of significant changes - When fixing a bug, apply the patch on the **oldest affected branch first**, @@ -177,6 +178,7 @@ If possible: - Change the global architecture of existing modules - Delete code without justification and a comment explaining why - Add external dependencies (Composer packages, JS libraries) without prior validation +- Modify the `ChangeLog` file (this file will be updated by the maintainer during the release process) --- diff --git a/.agents/skills/add-unit-test/SKILL.md b/.agents/skills/add-unit-test/SKILL.md new file mode 100644 index 00000000000..ffb14fb96ed --- /dev/null +++ b/.agents/skills/add-unit-test/SKILL.md @@ -0,0 +1,95 @@ +--- +name: add-unit-test +description: Add a unit test to test functions or methods. +license: MIT +user-invocable: true +allowed-tools: + - read_file + - write_file + - grep +--- + + +# Skill: Add a Unit Test for Dolibarr + +## When to use this skill + +Use this skill whenever the user asks to create, modify, or complete a **PHP unit test** for the Dolibarr ERP/CRM project. + +The goal is to produce a unit test that follows Dolibarr conventions and integrates cleanly into the existing PHPUnit test suite. + + +## Inputs + +The user request should contain, when available: + +- a name of the function to make the unit test for +- or the method of the class name to make the unit test for + + +## General Rules + +- Follow the coding style already used in files into directory test/phpunit/ +- Modify the minimum amount of existing code. +- Prefer adding new test methods instead of modifying existing ones. +- Tests must be deterministic and independent. +- Avoid dependencies on external services. +- Clean up every object created during the test. + +## Test Location + +Locate the most appropriate existing PHPUnit test file in test/phpunit. + +If no suitable test exists, create one in: + +``` +test/phpunit/ +``` + +using the naming convention: + +``` +FeatureTest.php +``` + + +## Naming + +A test method should describe the expected behavior. + +Examples: + +```php +public function testCreateObject() +public function testDeleteObject() +public function testFetchReturnsExpectedValues() +public function testInvalidInputThrowsException() +``` + +## Assertions + +Prefer specific assertions. + +Good: + +```php +$this->assertTrue($result); +$this->assertFalse($result); +$this->assertEquals($expected, $actual); +$this->assertCount(3, $array); +$this->assertNull($value); +$this->assertNotNull($value); +``` + +Avoid generic assertions when a more precise one exists. + + +## Output + +When generating code: + +- provide only the relevant PHP code +- preserve the existing file formatting +- do not rewrite unrelated methods +- explain briefly what is being tested + diff --git a/.agents/skills/code-review/SKILL.md b/.agents/skills/code-review/SKILL.md new file mode 100644 index 00000000000..b82913625c4 --- /dev/null +++ b/.agents/skills/code-review/SKILL.md @@ -0,0 +1,51 @@ +--- +name: code-review +description: Review code and fix bad practices +license: MIT +user-invocable: true +allowed-tools: + - read_file + - write_file + - grep +--- + + +# Skill: Review code for Dolibarr practices and fix it + +## When to use this skill + +Use this skill whenever the user asks to review code to fix bad practices or to update code to match good practices. + + +## Inputs + +The user request should contain, when available: + +- a module name +- or a directory name +- or a file name + + +## General Rules + +- Follow the coding style already used in files in module builder template in htdocs/modulebuilder/templates +- Modify the minimum amount of existing code. + + +## Rules + +- Use PSR-12 coding style except tabulation that must use TAB characters and not SPACES. +- Remove all spaces at end of lines +- Rewrite in english all code comments that are not in english +- Scan files for security vulnerabilities + + +## Output + +When generating code: + +- provide only the relevant PHP code +- preserve the existing file formatting +- do not rewrite unrelated methods +- explain briefly what is being tested + diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 57628ed8ac5..6c8ac975245 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -22,6 +22,8 @@ body: label: Dolibarr Version description: Affected Dolibarr version(s) placeholder: 19.0, develop, ... + validations: + required: true - type: input id: environment-php @@ -40,6 +42,8 @@ body: attributes: label: Steps to reproduce the behavior and expected behavior description: Verbose description + validations: + required: true - type: textarea id: files diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 5d97ee6a5be..5bc0de505fa 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -28,8 +28,3 @@ body: label: Suggested implementation description: Verbose description - - type: textarea - id: suggested-steps - attributes: - label: Suggested steps - description: List of tasks to achieve goal diff --git a/.github/scripts/get_changed_php.sh b/.github/scripts/get_changed_php.sh index 192de878900..43d64101c74 100755 --- a/.github/scripts/get_changed_php.sh +++ b/.github/scripts/get_changed_php.sh @@ -1,5 +1,7 @@ #!/bin/bash -# Copyright (C) 2025 MDW +# Copyright (C) 2025-2026 MDW + +# shellcheck disable=2129,2128,2034,2016 set -euo pipefail @@ -41,22 +43,33 @@ repo="${GITHUB_REPOSITORY##*/}" # Extract text after the last '/' page=1 per_page=100 changed_php_files=() +changed_phan_files=() changed_lang_files=() +# Get phan path configuration +phan_directory_list=$(php -r '$config = require("dev/tools/phan/config.php"); echo "^".implode("|",$config["directory_list"]);') +phan_exclude_directory=$(php -r '$config = require("dev/tools/phan/config.php"); echo "^".implode("|",$config["exclude_analysis_directory_list"]);') + +phan_exclude_file_regex=$(php -r '$config = require("dev/tools/phan/config.php"); echo $config["exclude_file_regex"];') +phan_exclude_file_regex=${phan_exclude_file_regex#@} +phan_exclude_file_regex=${phan_exclude_file_regex%@} + # Loop through all pages to gather changed files while true; do response=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ "https://api.github.com/repos/${owner}/${repo}/pulls/${pr_number}/files?per_page=${per_page}&page=${page}") - phan_exclude_file_regex=$(php -r '$config = require("dev/tools/phan/config.php"); echo $config["exclude_file_regex"];') # Filter for files ending with .php and add them to the list - mapfile -t files < <(echo "$response" | jq -r '.[] | select((.filename | test("\\.php$")) and (.filename | test("^dev/") | not)) | .filename' | grep -vP "$phan_exclude_file_regex") + mapfile -t files < <(echo "$response" | jq -r '.[] | select((.filename | test("\\.php$")) and (.filename | test("^dev/") | not)) | .filename') changed_php_files+=("${files[@]}") + mapfile -t files < <(echo "$response" | jq -r '.[] | select((.filename | test("\\.php$")) and (.filename | test("'"$phan_directory_list"'"))) | .filename' | grep -vP "$phan_exclude_file_regex") + changed_phan_files+=("${files[@]}") + mapfile -t files < <(echo "$response" | jq -r '.[] | select(.filename | test("\\.lang$")) | .filename') - changed_lang_files+=("${files[@]}") + changed_lang_files+=("${files[@]}") # Check if we have reached the last page (less than per_page results) count=$(echo "$response" | jq 'length') @@ -69,10 +82,12 @@ done # Build a space-separated string of changed PHP and lang files # This does not cope with files that have spaces. + # But such files do not exist in the project (at least not for the # files we are filtering). all_changed_files=$(IFS=" " ; echo "${changed_php_files[*]}") all_changed_lang=$(IFS=" " ; echo "${changed_lang_files[*]}") +phan_changed_files=$(IFS=" " ; echo "${changed_phan_files[*]}") forbidden_files="" @@ -85,21 +100,31 @@ forbidden_files="" #fi -# Determine changed files flag -if [ -z "$all_changed_files" ]; then +# Determine changed files flags +if [ -z "${all_changed_files}" ]; then any_changed="false" else any_changed="true" fi +if [ -z "${phan_changed_files}" ]; then + phan_changed="false" +else + phan_changed="true" +fi + # Set outputs for GitHub Actions if GITHUB_OUTPUT is available if [ -n "${GITHUB_OUTPUT:-}" ]; then echo "any_changed=${any_changed}" >> "$GITHUB_OUTPUT" + echo "phan_changed=${phan_changed}" >> "$GITHUB_OUTPUT" echo "all_changed_files=${all_changed_files}" >> "$GITHUB_OUTPUT" + echo "phan_changed_files=${phan_changed_files[*]}" >> "$GITHUB_OUTPUT" echo "forbidden_files=${forbidden_files}" >> "$GITHUB_OUTPUT" else # Otherwise, print the outputs echo "any_changed=${any_changed}" + echo "phan_changed=${phan_changed}" + echo "phan_changed_files=${phan_changed_files[*]}" echo "all_changed_files=${all_changed_files}" echo "forbidden_files=${forbidden_files}" fi diff --git a/.github/workflows/phan.yml b/.github/workflows/phan.yml index 3547d3e1df5..9acdbfa17b7 100644 --- a/.github/workflows/phan.yml +++ b/.github/workflows/phan.yml @@ -61,9 +61,10 @@ jobs: # - the merge from branch contains 'phan_full', or, # - there are changes in PHP files. # Note: --output-mode=github does not provide file:line, so using checkstyle and cs2pr - if: ${{ ! cancelled() && (github.ref_name == 'develop' || github.ref_name == 'refs/heads/develop' || endsWith(github.ref_name, '.0') || contains(github.head_ref, 'phan_full') || steps.changed-php.outputs.any_changed == 'true') }} + if: ${{ ! cancelled() && (github.ref_name == 'develop' || github.ref_name == 'refs/heads/develop' || endsWith(github.ref_name, '.0') || contains(github.head_ref, 'phan_full') || steps.changed-php.outputs.phan_changed == 'true') }} env: ALL_CHANGED_FILES: ${{ steps.changed-php.outputs.all_changed_files }} + PHAN_CHANGED_FILES: ${{ steps.changed-php.outputs.phan_changed_files }} # shellcheck disable=2086 FILE_CHANGED_LIST: /tmp/phan-changed.lst run: | @@ -73,7 +74,7 @@ jobs: phan $PHAN_QUICK -k "$PHAN_CONFIG" -B "$PHAN_BASELINE" --analyze-twice --minimum-target-php-version "$PHAN_MIN_PHP" --output-mode=checkstyle -o _phan.xml else echo -n "" > "$FILE_CHANGED_LIST" - for f in $ALL_CHANGED_FILES; do echo "$f" >> "$FILE_CHANGED_LIST"; done + for f in $PHAN_CHANGED_FILES; do echo "$f" >> "$FILE_CHANGED_LIST"; done # Must exclude same files as in phan configuration grep -v -E '^htdocs/(custom/|.*/canvas/.*/tpl/.*.tpl.php|admin/tools/ui/|includes/(nusoap/|restler/|stripe/)|conf/conf.php)$' "$FILE_CHANGED_LIST"> "$FILE_CHANGED_LIST".tmp || true mv "$FILE_CHANGED_LIST".tmp "$FILE_CHANGED_LIST" diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 6b788114546..b597b678c68 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -21,6 +21,7 @@ env: gh_event: ${{ inputs.gh_event || github.event_name }} CACHE_KEY_PART: ${{ ( inputs.gh_event == 'pull_request' || github.event_name == 'pull_request' ) && format('{0}-{1}', github.base_ref, github.head_ref) || github.ref_name }} GITHUB_JSON: ${{ toJSON(github) }} # Helps in debugging Github Action + DUMMY_FILE: htdocs/theme/md/progress.inc.php # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job @@ -113,7 +114,7 @@ jobs: fi echo phpstan analyse --no-progress --error-format=checkstyle --memory-limit 7G -a dev/build/phpstan/bootstrap_action.php ${ALL_CHANGED_FILES} - phpstan analyse --no-progress --error-format=checkstyle --memory-limit 7G -a dev/build/phpstan/bootstrap_action.php ${ALL_CHANGED_FILES} | tee _stan.xml | cs2pr --graceful-warnings + phpstan analyse --no-progress --error-format=checkstyle --memory-limit 7G -a dev/build/phpstan/bootstrap_action.php "${{ env.DUMMY_FILE }}" ${ALL_CHANGED_FILES} | tee _stan.xml | cs2pr --graceful-warnings fi # continue-on-error: true diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 8098dc970c9..3ab7757a732 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -157,7 +157,7 @@ jobs: ls -l ~/.cache/pre-commit/ - name: Convert Raw Log to Annotations - uses: mdeweerd/logToCheckStyle@v2025.11.2 + uses: mdeweerd/logToCheckStyle@v2026.7.1 if: ${{ failure() }} with: in: ${{ env.RAW_LOG }} diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 5215886e9ab..a1a42ceecaa 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -192,7 +192,7 @@ jobs: for /f "tokens=2 delims==" %%A in ('doskey /m:err') do EXIT /B %%A - name: Convert Raw Log to Annotations - uses: mdeweerd/logToCheckStyle@v2025.11.2 + uses: mdeweerd/logToCheckStyle@v2026.7.1 if: ${{ failure() }} with: in: ${{ env.PHPUNIT_LOG }} diff --git a/.mailmap b/.mailmap index 7fb0a8c7f30..32d2219ba0d 100644 --- a/.mailmap +++ b/.mailmap @@ -4,11 +4,13 @@ # # This files allows to have several profiles (the second part) matched to the same name/email (the first part). # -Laurent Destailleur Laurent Destailleur -Laurent Destailleur eldy -Laurent Destailleur Laurent Destailleur -Laurent Destailleur eldy10 -Laurent Destailleur Laurent Destailleur +Laurent Destailleur Laurent Destailleur +Laurent Destailleur eldy +Laurent Destailleur Laurent Destailleur +Laurent Destailleur eldy10 +Laurent Destailleur Laurent Destailleur +Laurent Destailleur Laurent Destailleur +Laurent Destailleur Laurent Destailleur Regis Houssin Regis Houssin Regis Houssin Régis Houssin Juanjo Menent simnandez @@ -18,12 +20,15 @@ Juanjo Menent juanjo Juanjo Menent juanjo Marcos García de La Fuente Marcos García Marcos García de La Fuente Marcos García +Mohamed DAOUD Mohamed DAOUD +Mohamed DAOUD Mohamed DAOUD Florian Henry fhenry Florian Henry Florian HENRY Florian Henry FHenry Florian Henry Florian HENRY Florian Henry FHenry Florian Henry HENRY Florian +Lucas Marcouiller Hystepik Alexandre Spangaro aspangaro Alexandre Spangaro Spangaro Alexandre Alexandre Spangaro Alexandre SPANGARO diff --git a/ChangeLog b/ChangeLog index 33c35923532..0ba4d32234f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -159,7 +159,6 @@ NEW: Add mass action to cancel invoices using credit notes (#37852) NEW: Add personal_data flag in extrafields admin UI - next step of #8612 (#38133) NEW: Direct installation for community and free modules on DoliStore (#38357) NEW: Display thirdparty name with ref in supplier orders linked objects (#36952) -NEW: Show bom net needs treeview by line position. (#37459) NEW: Show name_alias in parentheses in thirdparty dropdown lists (#37997) NEW: Thirdparty card +tab with events where thirdparty is attendee/booth (#37743) NEW: Update ticket read date when status changes (#37470) @@ -178,7 +177,6 @@ For developers: NEW: Use another hash algorithm v2 based on sha256/hmac for immutable logs (#37725) NEW: Replace MyObject MyModule occurrences (#38370) NEW: ModuleBuilder - Allow selecting which optional tabs (contact, note, document, agenda) are generated for an object (#38570) -FIX: ModuleBuilder - Generated list page derives the email trackid from the object name instead of the leftover 'xxxx' placeholder (#32460) NEW: add hooks in reception card (#37214) NEW: add new hook in BonPrelevement::EnregDestinataireSEPA() function (#37419) NEW: Add hook selectForFormsListUrl in Form::selectForForms (#37447) @@ -243,6 +241,7 @@ FIX: missing "blob:" in the assistant for CSP editor. FIX: option MEMBER_SEARCH_MEMBER_PUBLIC_FORM_CREATE FIX: product price.php: preserve default_vat_code and tva_npr when auto-creating initial product_price row (#38034) FIX: propagate fk_warehouse from BOM/MO to production lines in createProduction() and processBOM() (#38147) +FIX: #39068 Product search combo: ignore supplier price warning text when filtering FIX: selected default value ko on select_produits_fournisseurs_list() FIX: Set default warehouse on order create. (#37815) FIX: Site root missing in $backtopage. (#37804) diff --git a/dev/build/generate_filelist_xml.php b/dev/build/generate_filelist_xml.php index 7c1fb1d5a30..f73bdadd47b 100755 --- a/dev/build/generate_filelist_xml.php +++ b/dev/build/generate_filelist_xml.php @@ -115,7 +115,7 @@ while ($i < $argc) { $includeconstants[$tmp[0]][$tmp[1]] = $tmp[2]; } if (!empty($result["buildzip"])) { - $buildzip = 1; + $buildzip = $result["buildzip"]; } $i++; } @@ -134,7 +134,7 @@ $savrelease = $release; $tmpver = explode('-', $release, 2); if ($tmpver[0] == 'auto') { $release = DOL_VERSION; - if (!empty($tmpver[1]) && $tmpver[0] == 'auto') { + if (!empty($tmpver[1])) { $release .= '-'.$tmpver[1]; } } @@ -419,12 +419,12 @@ foreach ($arrayofunalterablefiles as $entry) { exit(1); } if ($newdir != $dir) { - if ($needtoclose) { - if ($release) { - fputs($fp, ' '."\n"); - } - $needtoclose = 0; + //if ($needtoclose) { + if ($release) { + fputs($fp, ' '."\n"); } + $needtoclose = 0; + //} if ($release) { fputs($fp, ' '."\n"); } diff --git a/dev/build/makepack-dolibarr.php b/dev/build/makepack-dolibarr.php index 026b0235140..7ebbd98fbe0 100755 --- a/dev/build/makepack-dolibarr.php +++ b/dev/build/makepack-dolibarr.php @@ -230,6 +230,8 @@ if (preg_match("/define\('DOL_VERSION',\s*'([\d\.a-z\-]+)'\)/i", $filefuncConten $PROJVERSION = $matches[1]; } if (empty($PROJVERSION)) { + $DOL_MAJOR_VERSION = 'notfound'; + $DOL_MINOR_VERSION = 'notfound'; if (preg_match("/define\('DOL_MAJOR_VERSION',\s*'([\d\.a-z\-]+)'\)/i", $filefuncContent, $matches)) { $DOL_MAJOR_VERSION = $matches[1]; } @@ -269,7 +271,7 @@ if (strpos($newbuild, '-') === false) { $newbuild .= '-0.4'; // finale (fedora) } $REL1 = preg_replace('/-.*$/', '', $newbuild); -if ($RPMSUBVERSION === 'auto') { +if ($RPMSUBVERSION === 'auto') { // @phpstan-ignore-line $RPMSUBVERSION = preg_replace('/^.*-/', '', $newbuild); } $FILENAMETGZ2 = "$PROJECT-$MAJOR.$MINOR.$REL1"; @@ -314,10 +316,10 @@ for ($i = 1; $i < $argc; $i++) { } // Force output dir if env vars are defined -if ($ENVDESTIBETARC && preg_match('/[a-z]/i', $BUILD)) { +if ($ENVDESTIBETARC && preg_match('/[a-z]/i', $BUILD)) { // @phpstan-ignore-line $DESTI = $ENVDESTIBETARC; } -if ($ENVDESTISTABLE && preg_match('/^[0-9]+$/', $BUILD)) { +if ($ENVDESTISTABLE && preg_match('/^[0-9]+$/', $BUILD)) { // @phpstan-ignore-line $DESTI = $ENVDESTISTABLE; } @@ -349,12 +351,12 @@ if ($target) { $targetUpper = strtoupper($target); if ($targetUpper === 'ALL') { foreach ($LISTETARGET as $key) { - if ($key !== 'SNAPSHOT' && $key !== 'SF' && $key !== 'ASSO') { + if ($key !== 'SNAPSHOT' && $key !== 'SF' && $key !== 'ASSO') { // @phpstan-ignore-line $CHOOSEDTARGET[$key] = 1; } } } - if ($targetUpper !== 'ALL' && $targetUpper !== 'SF' && $targetUpper !== 'ASSO') { + if ($targetUpper !== 'ALL' && $targetUpper !== 'SF' && $targetUpper !== 'ASSO') { // @phpstan-ignore-line $CHOOSEDTARGET[$targetUpper] = 1; } if ($targetUpper === 'SF') { @@ -399,7 +401,7 @@ if ($target) { } elseif ($NUM_SCRIPT === '0') { $CHOOSEDTARGET['-CHKSUM'] = 1; foreach ($LISTETARGET as $key) { - if ($key !== 'SNAPSHOT' && $key !== 'ASSO' && $key !== 'SF') { + if ($key !== 'SNAPSHOT' && $key !== 'ASSO' && $key !== 'SF') { // @phpstan-ignore-line $CHOOSEDTARGET[$key] = 1; } } @@ -495,7 +497,7 @@ foreach ($CHOOSEDTARGET as $tgt => $val) { ksort($CHOOSEDPUBLISH); foreach ($CHOOSEDPUBLISH as $tgt => $val) { - if ($val < 0) { continue; } + if ($val < 0) { continue; } // @phpstan-ignore-line if ($tgt === 'ASSO') { $nbofpublishneedchangelog++; } if ($tgt === 'SF') { $nbofpublishneedchangelog++; $nbofpublishneedtag++; } $nboftargetok++; @@ -621,7 +623,7 @@ if ($nboftargetok) { // ======================================================================== if ($nboftargetneedbuildroot) { - if (!$copyalreadydone) { + if (!$copyalreadydone) { // @phpstan-ignore-line echo "Creation of a buildroot used for all packages\n"; echo "Delete directory $BUILDROOT\n"; @@ -1297,7 +1299,7 @@ if ($nboftargetok) { ksort($CHOOSEDPUBLISH); foreach ($CHOOSEDPUBLISH as $tgt => $val) { - if ($val < 0) { continue; } + if ($val < 0) { continue; } // @phpstan-ignore-line echo "\nList of files to publish (BUILD=$BUILD)\n"; diff --git a/dev/build/makepack-howto.md b/dev/build/makepack-howto.md index 8eca0e0807d..1cee5b9190d 100644 --- a/dev/build/makepack-howto.md +++ b/dev/build/makepack-howto.md @@ -53,7 +53,7 @@ Prerequisites to build autoexe DoliWamp package from Windows: ``` -## Actions to do a BETA +## Actions to do a FREEZE / BETA This section describes steps made by Dolibarr packaging team to make a beta version of Dolibarr, step by step. @@ -84,7 +84,7 @@ git log x.y.z-1.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* // - Recopy the content of the output file into the file ChangeLog. - Note: To know number of lines changes: git diff --shortstat A B + Note: To know number of changed lines: git diff --shortstat A B - Update version number with x.y.z-w in file htdocs/filefunc.inc.php diff --git a/dev/build/phpstan/phpstan-baseline.neon b/dev/build/phpstan/phpstan-baseline.neon index 0968e6da4ca..7ab66d19e39 100644 --- a/dev/build/phpstan/phpstan-baseline.neon +++ b/dev/build/phpstan/phpstan-baseline.neon @@ -1230,12 +1230,6 @@ parameters: count: 1 path: ../../../htdocs/comm/action/index.php - - - message: '#^Parameter \#16 \$usergroupid of function print_actions_filter expects array\\|int, string given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/comm/action/index.php - - message: '#^Ternary operator condition is always true\.$#' identifier: ternary.alwaysTrue @@ -3216,12 +3210,6 @@ parameters: count: 1 path: ../../../htdocs/core/class/openid.class.php - - - message: '#^Parameter \#1 \$content of method SimpleOpenID\:\:HTML2OpenIDServer\(\) expects string, array\ given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/core/class/openid.class.php - - message: '#^Call to function is_object\(\) with XMLParser will always evaluate to true\.$#' identifier: function.alreadyNarrowedType @@ -5088,12 +5076,6 @@ parameters: count: 1 path: ../../../htdocs/fourn/paiement/card.php - - - message: '#^Variable \$object might not be defined\.$#' - identifier: variable.undefined - count: 2 - path: ../../../htdocs/fourn/paiement/document.php - - message: '#^Ternary operator condition is always false\.$#' identifier: ternary.alwaysFalse @@ -5640,12 +5622,6 @@ parameters: count: 1 path: ../../../htdocs/loan/class/loanschedule.class.php - - - message: '#^Variable \$object might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/loan/payment/card.php - - message: '#^Negated boolean expression is always true\.$#' identifier: booleanNot.alwaysTrue @@ -6222,12 +6198,6 @@ parameters: count: 4 path: ../../../htdocs/multicurrency/multicurrency_rate.php - - - message: '#^Variable \$id might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/opensurvey/card.php - - message: '#^Left side of && is always true\.$#' identifier: booleanAnd.leftAlwaysTrue @@ -6252,12 +6222,6 @@ parameters: count: 1 path: ../../../htdocs/opensurvey/results.php - - - message: '#^Variable \$expiredate might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/opensurvey/results.php - - message: '#^Left side of && is always false\.$#' identifier: booleanAnd.leftAlwaysFalse @@ -6378,12 +6342,6 @@ parameters: count: 1 path: ../../../htdocs/paypal/lib/paypal.lib.php - - - message: '#^Parameter \#3 \$value of function curl_setopt expects 0\|2, bool given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/paypal/lib/paypal.lib.php - - message: '#^Parameter \#3 \$value of function curl_setopt expects bool, int given\.$#' identifier: argument.type @@ -6798,12 +6756,6 @@ parameters: count: 1 path: ../../../htdocs/product/stock/stocktransfer/stocktransfer_card.php - - - message: '#^Variable \$result might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/product/stock/stocktransfer/stocktransfer_contact.php - - message: '#^Right side of && is always true\.$#' identifier: booleanAnd.rightAlwaysTrue @@ -6894,12 +6846,6 @@ parameters: count: 1 path: ../../../htdocs/projet/card.php - - - message: '#^Variable \$morecss might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/projet/card.php - - message: '#^Call to function method_exists\(\) with \$this\(Project\) and ''getLibStatut'' will always evaluate to true\.$#' identifier: function.alreadyNarrowedType @@ -7476,18 +7422,6 @@ parameters: count: 1 path: ../../../htdocs/public/onlinesign/newonlinesign.php - - - message: '#^Result of && is always false\.$#' - identifier: booleanAnd.alwaysFalse - count: 1 - path: ../../../htdocs/public/onlinesign/newonlinesign.php - - - - message: '#^Variable \$mesg might not be defined\.$#' - identifier: variable.undefined - count: 2 - path: ../../../htdocs/public/onlinesign/newonlinesign.php - - message: '#^Left side of && is always true\.$#' identifier: booleanAnd.leftAlwaysTrue @@ -7944,12 +7878,6 @@ parameters: count: 1 path: ../../../htdocs/reception/card.php - - - message: '#^Variable \$reception might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/reception/card.php - - message: '#^Call to function method_exists\(\) with \$this\(Reception\) and ''getLibStatut'' will always evaluate to true\.$#' identifier: function.alreadyNarrowedType @@ -8238,24 +8166,6 @@ parameters: count: 1 path: ../../../htdocs/societe/canvas/company/tpl/card_view.tpl.php - - - message: '#^Variable \$objcanvas might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/societe/canvas/company/tpl/card_view.tpl.php - - - - message: '#^Variable \$objcanvas might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/societe/canvas/individual/tpl/card_view.tpl.php - - - - message: '#^Variable \$socid might not be defined\.$#' - identifier: variable.undefined - count: 3 - path: ../../../htdocs/societe/canvas/individual/tpl/card_view.tpl.php - - message: '#^Call to function is_numeric\(\) with 0\|1\|2\|3\|4\|5\|6\|7\|8\|9 will always evaluate to true\.$#' identifier: function.alreadyNarrowedType diff --git a/dev/dolibarr_changes.txt b/dev/dolibarr_changes.txt index 4aec331a2e7..6d590dd7563 100644 --- a/dev/dolibarr_changes.txt +++ b/dev/dolibarr_changes.txt @@ -467,3 +467,13 @@ Edit CSS to restore line removed between 4.0.5 and 4.0.6. It generates this bug: JS JCROP: ---------- * Remove analytics tag into file index.html + + +PHP MIKE42 ESCPOS: +------------------ +* In htdocs/includes/mike42/escpos-php/src/Mike42/Escpos/GdEscposImage.php +Replace: + if (!is_resource($im)) { +With: + if (!($im instanceof \GdImage) && !is_resource($im)) { +PHP 8.0+ returns GdImage objects from imagecreate*; the upstream guard with is_resource() alone fails on PHP 8 and throws 'Failed to load image' on receipts that contain an image. diff --git a/dev/setup/codesniffer/Sniffs/Dolibarr/CheckIsModEnabledArgumentSniff.php b/dev/setup/codesniffer/Sniffs/Dolibarr/CheckIsModEnabledArgumentSniff.php index 0f5c435a9a8..28d260f60e2 100644 --- a/dev/setup/codesniffer/Sniffs/Dolibarr/CheckIsModEnabledArgumentSniff.php +++ b/dev/setup/codesniffer/Sniffs/Dolibarr/CheckIsModEnabledArgumentSniff.php @@ -1,5 +1,6 @@ + * Copyright (C) 2026 MDW * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,7 +26,7 @@ use PHP_CodeSniffer\Files\File; */ class CheckIsModEnabledArgumentSniff implements Sniff { - // Nom de la fonction cible + // Target function name protected $targetFunction = 'ismodenabled'; protected $deprecatedModulesNames = [ @@ -87,7 +88,7 @@ class CheckIsModEnabledArgumentSniff implements Sniff return; } - // check value of argument + // Check argument value $argContent = str_replace(["'", '"'], '', $tokens[$firstArgTokenPtr]['content']); $argCode = $tokens[$firstArgTokenPtr]['code']; diff --git a/dev/setup/codesniffer/Sniffs/Dolibarr/LanguageOfCommentsSniff.php b/dev/setup/codesniffer/Sniffs/Dolibarr/LanguageOfCommentsSniff.php index 2b656adad0b..1fedd27091d 100644 --- a/dev/setup/codesniffer/Sniffs/Dolibarr/LanguageOfCommentsSniff.php +++ b/dev/setup/codesniffer/Sniffs/Dolibarr/LanguageOfCommentsSniff.php @@ -1,5 +1,6 @@ + * Copyright (C) 2026 MDW * * 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 @@ -14,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + namespace codesniffer\Sniffs\Dolibarr; use PHP_CodeSniffer\Sniffs\Sniff; @@ -108,7 +110,14 @@ class LanguageOfCommentsSniff implements Sniff return; } - if (strpos($content, 'Copyright') === false && preg_match('/[àâäéèêëîïôùûüçœÀÂÄÉÈÊËÎÏÔÙÛÜÇŒ]/u', $content)) { + // Avoid matching special characters directly preceded or followed with []'" + // Avoid matching special characters in a "word/identifier" like string with '_'. + // Implementation: + // - Start with word (\b); + // - Require that this word does not contain '_' + // - Match all word characters up to the accented character. + // - Require that the character is not preceded or following by specific delimieters + if (strpos($content, 'Copyright') === false && preg_match('/\b(?![\p{L}_]*_[\p{L}_]*\b)([\p{L}_]*)(?\/\'"\[])[àâäéèêëîïôùûüçœÀÂÄÉÈÊËÎÏÔÙÛÜÇŒ]+(?![\'"\[]\/)(?![<>\/\'"\[])/u', $content)) { $error = "The comment appears to be in French (accent detected in: '%s'). Please write in English."; $data = [trim($content)]; $phpcsFile->addWarning($error, $stackPtr, 'FrenchDetected', $data); diff --git a/dev/tools/codespell/codespell-lines-ignore.txt b/dev/tools/codespell/codespell-lines-ignore.txt index 79f375d950a..3c99a539b3b 100644 --- a/dev/tools/codespell/codespell-lines-ignore.txt +++ b/dev/tools/codespell/codespell-lines-ignore.txt @@ -1,16 +1,16 @@ $objMod->dictionaries = $objMod->{"dictionnaries"}; // For backward compatibility if (empty($objMod->dictionaries) && !empty($objMod->{"dictionnaries"})) { print 'id.'&token='.newToken().'">'; + $dateemail = dol_stringtotime((string) $overview[0]->udate, 'gmt'); $reponsesadd = str_split($obj->reponses); $sql .= " SET reponses = '".$db->escape($reponsesadd)."'"; $sql .= " SET reponses = '0".$db->escape($obj->reponses)."'"; $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0); + dol_syslog("msgid=".$overview[0]->message_id." date=".dol_print_date($overview[0]->udate, 'dayrfc', 'gmt')." from=".$overview[0]->from." to=".$overview[0]->to." subject=".$overview[0]->subject); print ''; - $dateemail = dol_stringtotime((string) $overview[0]->udate, 'gmt'); $pice = ''; $typea = ($data[$j]->typea == 'birth') ? $picb : $pice; GETPOSTINT("mouvement"), - dol_syslog("msgid=".$overview[0]->message_id." date=".dol_print_date($overview[0]->udate, 'dayrfc', 'gmt')." from=".$overview[0]->from." to=".$overview[0]->to." subject=".$overview[0]->subject); jQuery("#mouvement option").removeAttr("selected").change(); jQuery("#mouvement option[value=0]").attr("selected","selected").trigger("change"); jQuery("#mouvement option[value=1]").attr("selected","selected").trigger("change"); @@ -46,6 +46,7 @@ $sql2 .= " SET reponses = '".$db->escape($newcar)."'"; $this->category->childs = array(); $tmpcrit = preg_replace('/^.*(:[!<>=\s]+:|:in:|:notin:|:like:|:notlike:)/', '\1', $tmpcrit); // the condition after the name of the field + $user->rights->accounting->mouvements = new stdClass(); // mise a jour des reponses utilisateurs dans la base if ($user->hasRight('stock', 'mouvement', 'lire')) { if (empty($reyear) || empty($remonth) || empty($reday)) { @@ -66,6 +67,7 @@ $head[$tab][1] = $langs->trans("Referers"); $out .= "".$langs->trans("Referer").": ".(isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT) : '')."
\n"; $reday = GETPOSTINT('reday'); + $savpermexport = $user->hasRight('accounting', 'mouvements', 'export'); $sql .= " (cs.periode IS NOT NULL AND cs.periode between '".$db->idate(dol_get_first_day($year))."' AND '".$db->idate(dol_get_last_day($year))."')"; $sql .= " OR (cs.periode IS NULL AND cs.date_ech between '".$db->idate(dol_get_first_day($year))."' AND '".$db->idate(dol_get_last_day($year))."')"; $sql .= ", cs.libelle as label, cs.fk_type, cs.amount, cs.fk_projet as fk_project, cs.paye, cs.periode as period, cs.import_key"; @@ -78,10 +80,14 @@ $this->periode = $this->date_creation + 3600 * 24 * 30; $title = $langs->trans('Product')." ".$shortlabel." - ".$langs->trans('Referers'); $title = $langs->trans('Service')." ".$shortlabel." - ".$langs->trans('Referers'); + $user->rights->accounting->mouvements->export = $savpermexport; + $user->rights->accounting->mouvements->export = 0; + $user->rights->accounting->mouvements->export = 1; if (!empty($arrayfields['cs.periode']['checked'])) { if ($newamount == 0 || empty($this->date_ech) || (empty($this->period) && empty($this->periode))) { if ($user->hasRight('stock', 'mouvement', 'creer')) { if (GETPOSTISSET("reday") && GETPOSTISSET("remonth") && GETPOSTISSET("reyear")) { + if (empty($user->rights->accounting->mouvements)) { * @param float|string $selectedrate Force preselected vat rate. Can be '8.5' or '8.5 (NOO)' for example. Use '' for no forcing. $datepaid = dol_mktime(12, 0, 0, GETPOSTINT("remonth"), GETPOSTINT("reday"), GETPOSTINT("reyear")); $datepaye = dol_mktime(12, 0, 0, GETPOSTINT("remonth"), GETPOSTINT("reday"), GETPOSTINT("reyear")); @@ -179,3 +185,5 @@ print $langs->trans("Developpers").':'; print_liste_field_titre("PeriodEndDate", $_SERVER["PHP_SELF"], "cs.periode", "", $param, '', $sortfield, $sortorder, 'center '); select#date_startday, select#date_startmonth, select#date_endday, select#date_endmonth, select#reday, select#remonth select#date_startday, select#date_startmonth, select#date_endday, select#date_endmonth, select#reday, select#remonth, + if (empty($user->rights->accounting->mouvements)) { + $user->rights->accounting->mouvements = new stdClass(); diff --git a/dev/tools/phan/baseline.txt b/dev/tools/phan/baseline.txt index 3eddf4abafb..374872ef75c 100644 --- a/dev/tools/phan/baseline.txt +++ b/dev/tools/phan/baseline.txt @@ -11,7 +11,7 @@ return [ // # Issue statistics: // PhanUndeclaredProperty : 370+ occurrences // PhanTypeMismatchArgument : 25+ occurrences - // PhanUndeclaredGlobalVariable : 25+ occurrences + // PhanUndeclaredGlobalVariable : 20+ occurrences // PhanTypeMismatchProperty : 15+ occurrences // PhanTypeInvalidDimOffset : 10+ occurrences // PhanTypeMismatchDimFetch : 10+ occurrences @@ -103,7 +103,6 @@ return [ 'htdocs/core/tpl/objectline_view.tpl.php' => ['PhanUndeclaredProperty'], 'htdocs/core/tpl/passwordreset.tpl.php' => ['PhanUndeclaredGlobalVariable'], 'htdocs/core/tpl/resource_view.tpl.php' => ['PhanUndeclaredProperty'], - 'htdocs/core/triggers/interface_99_modAgenda_ActionsAuto.class.php' => ['PhanUndeclaredProperty'], 'htdocs/delivery/class/delivery.class.php' => ['PhanUndeclaredProperty'], 'htdocs/emailcollector/class/emailcollector.class.php' => ['PhanUndeclaredProperty'], 'htdocs/eventorganization/class/conferenceorboothattendee.class.php' => ['PhanUndeclaredMethod', 'PhanUndeclaredProperty'], @@ -139,7 +138,6 @@ return [ 'htdocs/loan/payment/payment.php' => ['PhanUndeclaredProperty'], 'htdocs/mrp/tpl/linkedobjectblock.tpl.php' => ['PhanUndeclaredProperty'], 'htdocs/multicurrency/class/multicurrency.class.php' => ['PhanTypeExpectedObjectPropAccess'], - 'htdocs/opensurvey/results.php' => ['PhanUndeclaredGlobalVariable'], 'htdocs/partnership/class/partnership.class.php' => ['PhanUndeclaredProperty'], 'htdocs/partnership/core/modules/partnership/mod_partnership_advanced.php' => ['PhanUndeclaredProperty'], 'htdocs/partnership/partnership_card.php' => ['PhanUndeclaredGlobalVariable'], @@ -161,7 +159,6 @@ return [ 'htdocs/product/stock/stocktransfer/class/stocktransferline.class.php' => ['PhanUndeclaredProperty'], 'htdocs/product/stock/tpl/stockcorrection.tpl.php' => ['PhanUndeclaredProperty'], 'htdocs/projet/admin/project.php' => ['PhanTypeMismatchArgumentProbablyReal'], - 'htdocs/projet/card.php' => ['PhanUndeclaredGlobalVariable'], 'htdocs/projet/tasks.php' => ['PhanTypeMismatchArgument'], 'htdocs/projet/tasks/time.php' => ['PhanTypeInvalidDimOffset', 'PhanUndeclaredProperty'], 'htdocs/projet/tasks/tpl/linkedobjectblock.tpl.php' => ['PhanUndeclaredProperty'], @@ -171,7 +168,7 @@ return [ 'htdocs/public/project/viewandvote.php' => ['PhanUndeclaredGlobalVariable'], 'htdocs/public/recruitment/view.php' => ['PhanUndeclaredGlobalVariable'], 'htdocs/public/webportal/tpl/menu.tpl.php' => ['PhanUndeclaredProperty'], - 'htdocs/reception/card.php' => ['PhanUndeclaredGlobalVariable', 'PhanUndeclaredProperty'], + 'htdocs/reception/card.php' => ['PhanUndeclaredProperty'], 'htdocs/reception/class/reception.class.php' => ['PhanUndeclaredProperty'], 'htdocs/reception/list.php' => ['PhanUndeclaredProperty'], 'htdocs/recruitment/class/recruitmentcandidature.class.php' => ['PhanUndeclaredProperty'], diff --git a/dev/tools/rector/src/Renaming/GlobalToFunction.php b/dev/tools/rector/src/Renaming/GlobalToFunction.php index 16c4b658c29..cabacc2359c 100644 --- a/dev/tools/rector/src/Renaming/GlobalToFunction.php +++ b/dev/tools/rector/src/Renaming/GlobalToFunction.php @@ -1,4 +1,6 @@ + */ namespace Dolibarr\Rector\Renaming; @@ -230,7 +232,7 @@ class GlobalToFunction extends AbstractRector // If a && b and a or b is conf->global, we replace a or/and b if ($node instanceof BooleanAnd) { - // Transformation sur le nœud gauche + // Transformation on the left node $changedone = 0; if ($this->isGlobalVar($node->left)) { $constName = $this->getConstName($node->left); diff --git a/htdocs/accountancy/admin/categories.php b/htdocs/accountancy/admin/categories.php index 163efdfb22c..0f1229fa10f 100644 --- a/htdocs/accountancy/admin/categories.php +++ b/htdocs/accountancy/admin/categories.php @@ -238,7 +238,7 @@ if ($useNewSystem) { $sql .= " FROM ".MAIN_DB_PREFIX."accounting_category_account as aca"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON aa.rowid = aca.fk_accounting_account"; $sql .= " WHERE aca.fk_accounting_category = ".((int) $id); - $sql .= " AND aa.entity = ".$conf->entity; + $sql .= " AND aa.entity = ".((int) $conf->entity); // Search filters if (!empty($search_account)) { diff --git a/htdocs/accountancy/admin/categories_list.php b/htdocs/accountancy/admin/categories_list.php index 848ccc1f0f6..85c19429c0f 100644 --- a/htdocs/accountancy/admin/categories_list.php +++ b/htdocs/accountancy/admin/categories_list.php @@ -261,7 +261,7 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) { // List of values if ($tabrowid[$id] && !in_array($tabrowid[$id], $listfieldinsert)) { - $sql .= $newid.","; + $sql .= ((int) $newid).","; } $i = 0; foreach ($listfieldinsert as $f => $value) { diff --git a/htdocs/accountancy/admin/journals_list.php b/htdocs/accountancy/admin/journals_list.php index 1a8d8a2318c..4c9b0370b55 100644 --- a/htdocs/accountancy/admin/journals_list.php +++ b/htdocs/accountancy/admin/journals_list.php @@ -225,7 +225,7 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) { // List of values if ($tabrowid[$id] && !in_array($tabrowid[$id], $listfieldinsert)) { - $sql .= $newid.","; + $sql .= ((int) $newid).","; } $i = 0; foreach ($listfieldinsert as $f => $value) { @@ -239,7 +239,7 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) { } $i++; } - $sql .= ",1,".$conf->entity.")"; + $sql .= ",1,".((int) $conf->entity).")"; dol_syslog("actionadd", LOG_DEBUG); $result = $db->query($sql); @@ -326,7 +326,7 @@ if ($action == $acts[0]) { } elseif ($code) { $sql = "UPDATE ".$db->sanitize($tabname[$id])." SET active = 1 WHERE code = '".$db->escape($code)."'"; } - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); $result = $db->query($sql); if (!$result) { @@ -348,7 +348,7 @@ if ($action == $acts[1]) { } elseif ($code) { $sql = "UPDATE ".$db->sanitize($tabname[$id])." SET active = 0 WHERE code='".$db->escape($code)."'"; } - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); $result = $db->query($sql); if (!$result) { diff --git a/htdocs/accountancy/admin/productaccount.php b/htdocs/accountancy/admin/productaccount.php index 8b3fca6793a..418cb375bd8 100644 --- a/htdocs/accountancy/admin/productaccount.php +++ b/htdocs/accountancy/admin/productaccount.php @@ -349,7 +349,7 @@ if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) { $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "accounting_account as aa ON aa.account_number = p." . $db->sanitize($accountancy_field_name) . " AND aa.fk_pcg_version = '" . $db->escape($pcgvercode) . "'"; } if (!empty($searchCategoryProductList)) { - $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX."categorie_product as cp ON p.rowid = cp.fk_product"; // We'll need this table joined to the select in order to filter by categ + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."categorie_product as cp ON p.rowid = cp.fk_product"; // We'll need this table joined to the select in order to filter by categ } $sql .= ' WHERE p.entity IN ('.getEntity('product').')'; if (strlen(trim($search_current_account))) { @@ -483,13 +483,12 @@ if ($resql) { print load_fiche_titre($langs->trans("ProductsBinding"), '', 'title_accountancy'); - print ''.$langs->trans("InitAccountancyDesc").'
'; - print '
'; + print '
'.$langs->trans("InitAccountancyDesc").'

'; // Select usage print ''; print ''; - print ''; + print ''; print "\n"; print ''; print ''; print ''; // Action column diff --git a/htdocs/accountancy/admin/report_list.php b/htdocs/accountancy/admin/report_list.php index 91463a27082..74846999790 100644 --- a/htdocs/accountancy/admin/report_list.php +++ b/htdocs/accountancy/admin/report_list.php @@ -212,7 +212,7 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) { // List of values if ($tabrowid[$id] && !in_array($tabrowid[$id], $listfieldinsert)) { - $sql .= $newid.","; + $sql .= ((int) $newid).","; } $i = 0; foreach ($listfieldinsert as $f => $value) { diff --git a/htdocs/accountancy/bookkeeping/balance.php b/htdocs/accountancy/bookkeeping/balance.php index 61838a78c70..6935141cc3b 100644 --- a/htdocs/accountancy/bookkeeping/balance.php +++ b/htdocs/accountancy/bookkeeping/balance.php @@ -3,7 +3,7 @@ * Copyright (C) 2016 Florian Henry * Copyright (C) 2016-2026 Alexandre Spangaro * Copyright (C) 2018-2025 Frédéric France - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -102,7 +102,7 @@ $form = new Form($db); if (empty($search_date_start) && empty($search_date_end) && !GETPOSTISSET('formfilteraction')) { $sql = "SELECT date_start, date_end"; - $sql .=" FROM ".MAIN_DB_PREFIX."accounting_fiscalyear "; + $sql .= " FROM ".MAIN_DB_PREFIX."accounting_fiscalyear "; if (getDolGlobalInt('ACCOUNTANCY_FISCALYEAR_DEFAULT')) { $sql .= " WHERE rowid = " . getDolGlobalInt('ACCOUNTANCY_FISCALYEAR_DEFAULT'); } else { @@ -375,7 +375,7 @@ if ($action != 'export') { $newcardbutton .= dolGetButtonTitle($langs->trans('ExportToPdf'), '', 'fa fa-file-pdf paddingleft', $_SERVER['PHP_SELF'] . '&token=' . newToken(), 'exportpdfbutton', $permissiontoexport, array('morecss' => 'marginleftonly')); $newcardbutton .= dolGetButtonTitleSeparator(); - $newcardbutton .= dolGetButtonTitle($langs->trans('NewAccountingMvt'), '', 'fa fa-plus-circle paddingleft', DOL_URL_ROOT.'/accountancy/bookkeeping/card.php?action=create'.(!empty($type)?'&type=sub':'').'&backtopage='.urlencode($_SERVER['PHP_SELF']), '', $permissiontoadd); + $newcardbutton .= dolGetButtonTitle($langs->trans('NewAccountingMvt'), '', 'fa fa-plus-circle paddingleft', DOL_URL_ROOT.'/accountancy/bookkeeping/card.php?action=create'.(!empty($type) ? '&type=sub' : '').'&backtopage='.urlencode($_SERVER['PHP_SELF']), '', $permissiontoadd); } if ($contextpage != $_SERVER["PHP_SELF"]) { $param .= '&contextpage='.urlencode($contextpage); @@ -515,7 +515,7 @@ if ($action != 'export') { if (getDolGlobalString('ACCOUNTANCY_SHOW_OPENING_BALANCE')) { $sql = "SELECT t.numero_compte, (SUM(t.debit) - SUM(t.credit)) as opening_balance"; $sql .= " FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping as t"; - $sql .= " WHERE t.entity = " . $conf->entity; // Never do sharing into accounting features + $sql .= " WHERE t.entity = " . ((int) $conf->entity); // Never do sharing into accounting features $sql .= " AND t.doc_date < '" . $db->idate($search_date_start) . "'"; $sql .= " GROUP BY t.numero_compte"; diff --git a/htdocs/accountancy/bookkeeping/export.php b/htdocs/accountancy/bookkeeping/export.php index 0310f8a502c..6af1271787a 100644 --- a/htdocs/accountancy/bookkeeping/export.php +++ b/htdocs/accountancy/bookkeeping/export.php @@ -167,7 +167,7 @@ $hookmanager->initHooks(array('bookkeepingexport')); $formaccounting = new FormAccounting($db); $form = new Form($db); -if (!in_array($action, array('export_file', 'delmouv', 'delmouvconfirm')) && !GETPOSTISSET('begin') && !GETPOSTISSET('formfilteraction') && GETPOSTINT('page') == 0 && !GETPOSTINT('noreset') && $user->hasRight('accounting', 'mouvements', 'export')) { +if (!in_array($action, array('export_file', 'export_fileconfirm', 'delmouv', 'delmouvconfirm')) && !GETPOSTISSET('begin') && !GETPOSTISSET('formfilteraction') && GETPOSTINT('page') == 0 && !GETPOSTINT('noreset') && $user->hasRight('accounting', 'mouvements', 'export')) { if (empty($search_date_start) && empty($search_date_end) && !GETPOSTISSET('restore_lastsearch_values') && !GETPOST('search_accountancy_code_start')) { $sql = "SELECT date_start, date_end"; $sql .= " FROM ".MAIN_DB_PREFIX."accounting_fiscalyear "; @@ -680,10 +680,10 @@ if ($action == 'export_fileconfirm' && $user->hasRight('accounting', 'mouvements $now = dol_now(); $sanitizedsetfields = ''; - if (!empty($notifiedexportdate) && empty($movement->date_export)) { + if (!empty($notifiedexportdate) && (empty($movement->date_export) || getDolGlobalString('ACCOUNTING_REEXPORT'))) { $sanitizedsetfields .= ($sanitizedsetfields ? "," : "")." date_export = '".$db->idate($now)."'"; } - if (!empty($notifiedvalidationdate) && empty($movement->date_validation)) { + if (!empty($notifiedvalidationdate) && (empty($movement->date_validation) || getDolGlobalString('ACCOUNTING_REEXPORT'))) { $sanitizedsetfields .= ($sanitizedsetfields ? "," : "")." date_validated = '".$db->idate($now)."'"; } diff --git a/htdocs/accountancy/class/accountancycategory.class.php b/htdocs/accountancy/class/accountancycategory.class.php index 1e5048ca286..0119832c2e1 100644 --- a/htdocs/accountancy/class/accountancycategory.class.php +++ b/htdocs/accountancy/class/accountancycategory.class.php @@ -444,7 +444,7 @@ class AccountancyCategory // extends CommonObject $sql .= " FROM ".$this->db->prefix()."accounting_account as t"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."accounting_system as asy ON t.fk_pcg_version = asy.pcg_version AND asy.rowid = ".((int) $pcgver); $sql .= " WHERE t.fk_accounting_category = ".((int) $id); - $sql .= " AND t.entity = ".$conf->entity; + $sql .= " AND t.entity = ".((int) $conf->entity); $this->lines_display = array(); @@ -483,7 +483,7 @@ class AccountancyCategory // extends CommonObject $sql .= " WHERE (aa.fk_accounting_category <> ".((int) $id)." OR aa.fk_accounting_category IS NULL)"; $sql .= " AND asy.rowid = ".((int) getDolGlobalInt('CHARTOFACCOUNTS')); $sql .= " AND aa.active = 1"; - $sql .= " AND aa.entity = ".$conf->entity; + $sql .= " AND aa.entity = ".((int) $conf->entity); $sql .= " GROUP BY aa.account_number, aa.label"; $sql .= " ORDER BY aa.account_number, aa.label"; @@ -529,7 +529,7 @@ class AccountancyCategory // extends CommonObject $sql .= " INNER JOIN ".$this->db->prefix()."accounting_system as asy ON aa.fk_pcg_version = asy.pcg_version"; $sql .= " AND asy.rowid = ".((int) getDolGlobalInt('CHARTOFACCOUNTS')); $sql .= " AND aa.active = 1"; - $sql .= " AND aa.entity = ".$conf->entity; + $sql .= " AND aa.entity = ".((int) $conf->entity); $sql .= " ORDER BY LENGTH(aa.account_number) DESC;"; // LENGTH is ok with mysql and postgresql $this->db->begin(); @@ -623,7 +623,7 @@ class AccountancyCategory // extends CommonObject /** * Function to set the property ->sdc (and ->sdcperaccount) that is the result of an accounting account from the ledger with a direction and a period * - * @param int|array $cpt Accounting account or array of accounting account + * @param string|int|array $cpt Accounting account or array of accounting account * @param int $date_start Date start * @param int $date_end Date end * @param int<0,1> $sens Sens of the account: 0: credit - debit (use this by default), 1: debit - credit @@ -734,10 +734,10 @@ class AccountancyCategory // extends CommonObject $sql .= " WHERE t.fk_accounting_category IN (SELECT c.rowid"; $sql .= " FROM ".$this->db->prefix().$this->table_element." as c"; $sql .= " WHERE c.active = 1"; - $sql .= " AND c.entity = ".$conf->entity; + $sql .= " AND c.entity = ".((int) $conf->entity); $sql .= " AND (c.fk_country = ".((int) $mysoc->country_id)." OR c.fk_country = 0)"; $sql .= " AND cat.rowid = t.fk_accounting_category"; - $sql .= " AND t.entity = ".$conf->entity; + $sql .= " AND t.entity = ".((int) $conf->entity); if ($catid > 0) { $sql .= " AND cat.rowid = ".((int) $catid); } @@ -796,7 +796,7 @@ class AccountancyCategory // extends CommonObject $sql .= " FROM ".$this->db->prefix().$this->table_element." as c"; $sql .= " WHERE c.active = " . (int) $active; $sql .= " AND c.fk_report=".((int) $id_report); - $sql .= " AND c.entity = ".$conf->entity; + $sql .= " AND c.entity = ".((int) $conf->entity); if ($categorytype >= 0) { $sql .= " AND c.category_type = 1"; } @@ -868,7 +868,7 @@ class AccountancyCategory // extends CommonObject $sql = "SELECT t.rowid, t.account_number, t.label as account_label"; $sql .= " FROM ".$this->db->prefix()."accounting_account as t"; $sql .= " WHERE t.fk_accounting_category = ".((int) $cat_id); - $sql .= " AND t.entity = ".$conf->entity; + $sql .= " AND t.entity = ".((int) $conf->entity); $sql .= " AND t.active = 1"; $sql .= " AND t.fk_pcg_version = '".$this->db->escape($pcgvercode)."'"; $sql .= " ORDER BY t.account_number"; @@ -876,7 +876,7 @@ class AccountancyCategory // extends CommonObject $sql = "SELECT t.rowid, t.account_number, t.label as account_label"; $sql .= " FROM ".$this->db->prefix()."accounting_account as t"; $sql .= " WHERE ".$sanitizedpredefinedgroupwhere; - $sql .= " AND t.entity = ".$conf->entity; + $sql .= " AND t.entity = ".((int) $conf->entity); $sql .= ' AND t.active = 1'; $sql .= " AND t.fk_pcg_version = '".$this->db->escape($pcgvercode)."'"; $sql .= " ORDER BY t.account_number"; @@ -989,7 +989,7 @@ class AccountancyCategory // extends CommonObject $sql .= " FROM ".MAIN_DB_PREFIX."accounting_category_account as aca"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON aa.rowid = aca.fk_accounting_account"; $sql .= " WHERE aca.fk_accounting_category = ".((int) $this->id); - $sql .= " AND aa.entity = ".$conf->entity; + $sql .= " AND aa.entity = ".((int) $conf->entity); $sql .= " ORDER BY aa.account_number ASC"; dol_syslog(get_class($this)."::getAccountsForCategory", LOG_DEBUG); @@ -1033,7 +1033,7 @@ class AccountancyCategory // extends CommonObject $sql .= " FROM ".MAIN_DB_PREFIX."accounting_category_account as aca"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."c_accounting_category as ac ON ac.rowid = aca.fk_accounting_category"; $sql .= " WHERE aca.fk_accounting_account = ".((int) $fkAccountingAccount); - $sql .= " AND ac.entity = ".$conf->entity; + $sql .= " AND ac.entity = ".((int) $conf->entity); $sql .= " ORDER BY ac.position ASC"; dol_syslog(get_class($this)."::getCategoriesForAccount", LOG_DEBUG); @@ -1109,7 +1109,7 @@ class AccountancyCategory // extends CommonObject $sql .= " FROM ".MAIN_DB_PREFIX."accounting_category_account"; $sql .= " WHERE fk_accounting_category = ".((int) $this->id); $sql .= " )"; - $sql .= " AND aa.entity = ".$conf->entity; + $sql .= " AND aa.entity = ".((int) $conf->entity); $sql .= " AND aa.active = 1"; $sql .= " AND aa.fk_pcg_version = '".$this->db->escape($pcgvercode)."'"; $sql .= " ORDER BY aa.account_number ASC"; diff --git a/htdocs/accountancy/class/accountingaccount.class.php b/htdocs/accountancy/class/accountingaccount.class.php index 7bef70f9873..8c25c240d8d 100644 --- a/htdocs/accountancy/class/accountingaccount.class.php +++ b/htdocs/accountancy/class/accountingaccount.class.php @@ -5,7 +5,7 @@ * Copyright (C) 2014 Juanjo Menent * Copyright (C) 2015 Ari Elbaz (elarifr) * Copyright (C) 2018-2025 Frédéric France - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -195,7 +195,7 @@ class AccountingAccount extends CommonObject $sql .= " a.rowid = ".(int) $rowid; } elseif ($account_number) { $sql .= " a.account_number = '".$this->db->escape($account_number)."'"; - $sql .= " AND a.entity = ".$conf->entity; + $sql .= " AND a.entity = ".((int) $conf->entity); } if (!empty($limittocurrentchart)) { $sql .= ' AND a.fk_pcg_version IN (SELECT pcg_version FROM '.$this->db->prefix().'accounting_system WHERE rowid = '.((int) getDolGlobalInt('CHARTOFACCOUNTS')).')'; diff --git a/htdocs/accountancy/class/accountingjournal.class.php b/htdocs/accountancy/class/accountingjournal.class.php index 22cd31bf0a7..25aeae90dd2 100644 --- a/htdocs/accountancy/class/accountingjournal.class.php +++ b/htdocs/accountancy/class/accountingjournal.class.php @@ -169,7 +169,7 @@ class AccountingJournal extends CommonObject $sql .= " rowid = ".((int) $rowid); } elseif ($journal_code) { $sql .= " code = '".$this->db->escape($journal_code)."'"; - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); } dol_syslog(get_class($this)."::fetch", LOG_DEBUG); diff --git a/htdocs/accountancy/class/api_accountancy.class.php b/htdocs/accountancy/class/api_accountancy.class.php index 0b201108f5b..17093546ec9 100644 --- a/htdocs/accountancy/class/api_accountancy.class.php +++ b/htdocs/accountancy/class/api_accountancy.class.php @@ -78,7 +78,7 @@ class Accountancy extends DolibarrApi * @param int $alreadyexport [=0] by default export data only if it's not yet exported or 1 already exported (always export data even if 'date_export" is set) * @param int $notnotifiedasexport [=0] by default notified as exported or 1 not notified as exported (when the export is done, notified or not the column 'date_export') * - * @return string + * @return array{modulepart:string,relative_path:string,filename:string,mimetype:string} Generated file metadata * * @url GET exportdata * @@ -271,7 +271,24 @@ class Accountancy extends DolibarrApi throw new RestException(500, 'Error accountancy export : '.implode(',', $accountancyexport->errors)); } else { $this->db->commit(); - exit(); + + $filedata = $accountancyexport->generatedfiledata; + if (empty($filedata['downloadFilePath']) || empty($filedata['downloadFileFullName'])) { + throw new RestException(500, 'Accounting export generated no downloadable file'); + } + + $outputdir = !empty($conf->accounting->multidir_output[$conf->entity]) ? $conf->accounting->multidir_output[$conf->entity] : $conf->accounting->dir_output; + $outputdir = rtrim($outputdir, '/').'/'; + if (strpos($filedata['downloadFilePath'], $outputdir) !== 0) { + throw new RestException(500, 'Accounting export generated a file outside the accounting output directory'); + } + + return array( + 'modulepart' => 'export_compta', + 'relative_path' => substr($filedata['downloadFilePath'], strlen($outputdir)), + 'filename' => basename($filedata['downloadFileFullName']), + 'mimetype' => $filedata['downloadFileMimeType'], + ); } } } diff --git a/htdocs/accountancy/class/bookkeeping.class.php b/htdocs/accountancy/class/bookkeeping.class.php index 096fe121731..3f09411ae60 100644 --- a/htdocs/accountancy/class/bookkeeping.class.php +++ b/htdocs/accountancy/class/bookkeeping.class.php @@ -3,7 +3,7 @@ * Copyright (C) 2015-2026 Alexandre Spangaro * Copyright (C) 2015-2020 Florian Henry * Copyright (C) 2018-2025 Frédéric France - * Copyright (C) 2024-2026 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Jose MARTINEZ * Copyright (C) 2025 Nicolas Barrouillet * @@ -350,7 +350,7 @@ class BookKeeping extends CommonObject if (!empty($this->subledger_account)) { $sql .= " AND subledger_account = '".$this->db->escape($this->subledger_account)."'"; } - $sql .= " AND entity = ".$conf->entity; // Do not use getEntity for accounting features + $sql .= " AND entity = ".((int) $conf->entity); // Do not use getEntity for accounting features // Allow duplicates in incomes or loss statements $accountProfit = getDolGlobalString('ACCOUNTING_RESULT_PROFIT'); @@ -377,7 +377,7 @@ class BookKeeping extends CommonObject $sqlnum .= " AND fk_docdet = ".((int) $this->fk_docdet); } $sqlnum .= " AND doc_ref = '".$this->db->escape($this->doc_ref)."'"; // ref of source object - $sqlnum .= " AND entity = ".$conf->entity; // Do not use getEntity for accounting features + $sqlnum .= " AND entity = ".((int) $conf->entity); // Do not use getEntity for accounting features dol_syslog(get_class($this).":: create sqlnum=".$sqlnum, LOG_DEBUG); $resqlnum = $this->db->query($sqlnum); @@ -461,7 +461,7 @@ class BookKeeping extends CommonObject $sql .= ", ".(!empty($this->journal_label) ? ("'".$this->db->escape($this->journal_label)."'") : "NULL"); $sql .= ", ".((int) $this->piece_num); $sql .= ", '".$this->db->escape($this->ref)."'"; - $sql .= ", ".(!isset($this->entity) ? $conf->entity : $this->entity); + $sql .= ", ".(!isset($this->entity) ? ((int) $conf->entity) : ((int) $this->entity)); $sql .= ")"; $resql = $this->db->query($sql); @@ -793,9 +793,9 @@ class BookKeeping extends CommonObject $sql .= ' '.(!isset($this->numero_compte) ? 'NULL' : "'".$this->db->escape($this->numero_compte)."'").','; $sql .= ' '.(!isset($this->label_compte) ? 'NULL' : "'".$this->db->escape($this->label_compte)."'").','; $sql .= ' '.(!isset($this->label_operation) ? 'NULL' : "'".$this->db->escape($this->label_operation)."'").','; - $sql .= ' '.(!isset($this->debit) ? 'NULL' : $this->debit).','; - $sql .= ' '.(!isset($this->credit) ? 'NULL' : $this->credit).','; - $sql .= ' '.(!isset($this->montant) ? 'NULL' : $this->montant).','; + $sql .= ' '.(!isset($this->debit) ? 'NULL' : ((float) $this->debit)).','; + $sql .= ' '.(!isset($this->credit) ? 'NULL' : ((float) $this->credit)).','; + $sql .= ' '.(!isset($this->montant) ? 'NULL' : ((float) $this->montant)).','; $sql .= ' '.(!isset($this->sens) ? 'NULL' : "'".$this->db->escape($this->sens)."'").','; $sql .= ' '.((int) $user->id).','; $sql .= ' '."'".$this->db->idate($now)."',"; @@ -803,7 +803,7 @@ class BookKeeping extends CommonObject $sql .= ' '.(empty($this->journal_label) ? 'NULL' : "'".$this->db->escape($this->journal_label)."'").','; $sql .= ' '.(empty($this->piece_num) ? 'NULL' : $this->db->escape((string) $this->piece_num)).','; $sql .= ' '.(empty($this->ref) ? "''" : "'".$this->db->escape($this->ref)."'").','; - $sql .= ' '.(!isset($this->entity) ? $conf->entity : $this->entity); + $sql .= ' '.(!isset($this->entity) ? ((int) $conf->entity) : ((int) $this->entity)); $sql .= ')'; $this->db->begin(); @@ -1565,23 +1565,23 @@ class BookKeeping extends CommonObject $sql .= ' doc_date = '.(isDolTms($this->doc_date) ? "'".$this->db->idate($this->doc_date)."'" : 'null').','; $sql .= ' doc_type = '.(isset($this->doc_type) ? "'".$this->db->escape($this->doc_type)."'" : "null").','; $sql .= ' doc_ref = '.(isset($this->doc_ref) ? "'".$this->db->escape($this->doc_ref)."'" : "null").','; - $sql .= ' fk_doc = '.(isset($this->fk_doc) ? $this->fk_doc : "null").','; - $sql .= ' fk_docdet = '.(isset($this->fk_docdet) ? $this->fk_docdet : "null").','; + $sql .= ' fk_doc = '.(isset($this->fk_doc) ? ((int) $this->fk_doc) : "null").','; + $sql .= ' fk_docdet = '.(isset($this->fk_docdet) ? ((int) $this->fk_docdet) : "null").','; $sql .= ' thirdparty_code = '.(isset($this->thirdparty_code) ? "'".$this->db->escape($this->thirdparty_code)."'" : "null").','; $sql .= ' subledger_account = '.(isset($this->subledger_account) ? "'".$this->db->escape($this->subledger_account)."'" : "null").','; $sql .= ' subledger_label = '.(isset($this->subledger_label) ? "'".$this->db->escape($this->subledger_label)."'" : "null").','; $sql .= ' numero_compte = '.(isset($this->numero_compte) ? "'".$this->db->escape($this->numero_compte)."'" : "null").','; $sql .= ' label_compte = '.(isset($this->label_compte) ? "'".$this->db->escape($this->label_compte)."'" : "null").','; $sql .= ' label_operation = '.(isset($this->label_operation) ? "'".$this->db->escape($this->label_operation)."'" : "null").','; - $sql .= ' debit = '.(isset($this->debit) ? $this->debit : "null").','; - $sql .= ' credit = '.(isset($this->credit) ? $this->credit : "null").','; - $sql .= ' montant = '.(isset($this->montant) ? $this->montant : "null").','; + $sql .= ' debit = '.(isset($this->debit) ? ((float) $this->debit) : "null").','; + $sql .= ' credit = '.(isset($this->credit) ? ((float) $this->credit) : "null").','; + $sql .= ' montant = '.(isset($this->montant) ? ((float) $this->montant) : "null").','; $sql .= ' sens = '.(isset($this->sens) ? "'".$this->db->escape($this->sens)."'" : "null").','; - $sql .= ' fk_user_author = '.(isset($this->fk_user_author) ? $this->fk_user_author : "null").','; + $sql .= ' fk_user_author = '.(isset($this->fk_user_author) ? ((int) $this->fk_user_author) : "null").','; $sql .= ' import_key = '.(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").','; $sql .= ' code_journal = '.(isset($this->code_journal) ? "'".$this->db->escape($this->code_journal)."'" : "null").','; $sql .= ' journal_label = '.(isset($this->journal_label) ? "'".$this->db->escape($this->journal_label)."'" : "null").','; - $sql .= ' piece_num = '.(isset($this->piece_num) ? $this->piece_num : "null"); + $sql .= ' piece_num = '.(isset($this->piece_num) ? ((int) $this->piece_num) : "null"); $sql .= ' WHERE rowid='.((int) $this->id); $this->db->begin(); @@ -2291,7 +2291,7 @@ class BookKeeping extends CommonObject $sql .= ' doc_ref, fk_doc, fk_docdet, entity, thirdparty_code, subledger_account, subledger_label,'; $sql .= ' numero_compte, label_compte, label_operation, debit, credit,'; $sql .= ' montant, sens, fk_user_author, import_key, code_journal, journal_label, piece_num, date_creation)'; - $sql .= ' SELECT doc_date, doc_type,' . "'{$ref}',"; + $sql .= ' SELECT doc_date, doc_type,' . "'".$this->db->escape($ref)."',"; $sql .= ' doc_ref, fk_doc, fk_docdet, entity, thirdparty_code, subledger_account, subledger_label,'; $sql .= ' numero_compte, label_compte, label_operation, debit, credit,'; $sql .= ' montant, sens, fk_user_author, import_key, code_journal, journal_label, '.((int) $next_piecenum).", '".$this->db->idate($now)."'"; @@ -2328,7 +2328,7 @@ class BookKeeping extends CommonObject $sql .= ' doc_ref, fk_doc, fk_docdet, thirdparty_code, subledger_account, subledger_label,'; $sql .= ' numero_compte, label_compte, label_operation, debit, credit,'; $sql .= ' montant, sens, fk_user_author, import_key, code_journal, journal_label, piece_num)'; - $sql .= ' SELECT doc_date, doc_type,' . "'{$ref}',"; + $sql .= ' SELECT doc_date, doc_type,' . "'".$this->db->escape($ref)."',"; $sql .= ' doc_ref, fk_doc, fk_docdet, thirdparty_code, subledger_account, subledger_label,'; $sql .= ' numero_compte, label_compte, label_operation, debit, credit,'; $sql .= ' montant, sens, fk_user_author, import_key, code_journal, journal_label, piece_num'; @@ -3651,7 +3651,7 @@ class BookKeeping extends CommonObject $bookKeepingInstance = new BookKeeping($this->db); $pieceNumNext = $bookKeepingInstance->getNextNumMvt(); $cloneId = []; - $sqlRowidClone = "SELECT rowid FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping WHERE piece_num = $pieceNum"; + $sqlRowidClone = "SELECT rowid FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping WHERE piece_num = ".((int) $pieceNum); $resqlRowidClone = $this->db->query($sqlRowidClone); if ($resqlRowidClone) { @@ -3699,7 +3699,7 @@ class BookKeeping extends CommonObject $sql_insert .= ", montant"; $sql_insert .= ")"; $sql_insert .= " VALUES ("; - $sql_insert .= $pieceNumNext; + $sql_insert .= ((int) $pieceNumNext); $sql_insert .= ", '" . $this->db->escape($obj->label_operation) . "'"; $sql_insert .= ", '" . $this->db->escape($obj->numero_compte) . "'"; $sql_insert .= ", '" . $this->db->escape($obj->label_compte) . "'"; diff --git a/htdocs/accountancy/class/bookkeepingtemplate.class.php b/htdocs/accountancy/class/bookkeepingtemplate.class.php index daa7530f1c0..329e29d8d2f 100644 --- a/htdocs/accountancy/class/bookkeepingtemplate.class.php +++ b/htdocs/accountancy/class/bookkeepingtemplate.class.php @@ -163,7 +163,7 @@ class BookkeepingTemplate extends CommonObject /** * Constructor * - * @param DoliDb $db Database handler + * @param DoliDB $db Database handler */ public function __construct(DoliDB $db) { diff --git a/htdocs/accountancy/class/bookkeepingtemplateline.class.php b/htdocs/accountancy/class/bookkeepingtemplateline.class.php index 11fba2a104c..3297d987844 100644 --- a/htdocs/accountancy/class/bookkeepingtemplateline.class.php +++ b/htdocs/accountancy/class/bookkeepingtemplateline.class.php @@ -182,7 +182,7 @@ class BookkeepingTemplateLine extends CommonObject /** * Constructor * - * @param DoliDb $db Database handler + * @param DoliDB $db Database handler */ public function __construct(DoliDB $db) { @@ -247,10 +247,10 @@ class BookkeepingTemplateLine extends CommonObject * @param int $limit limit * @param int $offset Offset * @param array $filter Filter array. Example array('mystringfield'=>'value', 'myintfield'=>4, 'customsql'=>...) - * @param string $filtermode Filter mode (AND or OR) + * @param 'AND'|'OR' $sqlfiltermode Filter mode (AND or OR) * @return BookkeepingTemplateLine[]|int Array of BookkeepingTemplateLine objects if OK, <0 if KO */ - public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND') + public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $sqlfiltermode = 'AND') { dol_syslog(__METHOD__, LOG_DEBUG); @@ -313,7 +313,7 @@ class BookkeepingTemplateLine extends CommonObject } } if (count($sqlwhere) > 0) { - $sql .= " AND (".implode(" ".$filtermode." ", $sqlwhere).")"; + $sql .= " AND (".implode(" ".$sqlfiltermode." ", $sqlwhere).")"; } if (!empty($sortfield)) { diff --git a/htdocs/accountancy/customer/index.php b/htdocs/accountancy/customer/index.php index 2abd0fb89dd..6eca51892b5 100644 --- a/htdocs/accountancy/customer/index.php +++ b/htdocs/accountancy/customer/index.php @@ -5,7 +5,7 @@ * Copyright (C) 2014 Juanjo Menent * Copyright (C) 2015 Jean-François Ferry * Copyright (C) 2024 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * * 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 @@ -156,10 +156,10 @@ if ($action == 'validatehistory' && $user->hasRight('accounting', 'bind', 'write } $alias_societe_perentity = !getDolGlobalString('MAIN_COMPANY_PERENTITY_SHARED') ? "s" : "spe"; $alias_product_perentity = !getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') ? "p" : "ppe"; - $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa ON ".$db->sanitize($alias_product_perentity).".accountancy_code_sell = aa.account_number AND aa.active = 1 AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".$conf->entity; - $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa2 ON ".$db->sanitize($alias_product_perentity).".accountancy_code_sell_intra = aa2.account_number AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".$conf->entity; - $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa3 ON ".$db->sanitize($alias_product_perentity).".accountancy_code_sell_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".$conf->entity; - $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa4 ON ".$db->sanitize($alias_societe_perentity).".accountancy_code_sell = aa4.account_number AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".$conf->entity; + $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa ON ".$db->sanitize($alias_product_perentity).".accountancy_code_sell = aa.account_number AND aa.active = 1 AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".((int) $conf->entity); + $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa2 ON ".$db->sanitize($alias_product_perentity).".accountancy_code_sell_intra = aa2.account_number AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".((int) $conf->entity); + $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa3 ON ".$db->sanitize($alias_product_perentity).".accountancy_code_sell_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".((int) $conf->entity); + $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa4 ON ".$db->sanitize($alias_societe_perentity).".accountancy_code_sell = aa4.account_number AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".((int) $conf->entity); $sql .= " WHERE f.fk_statut > 0 AND l.fk_code_ventilation <= 0"; $sql .= " AND l.product_type <= 2"; $sql .= " AND f.entity IN (".getEntity('invoice', 0).")"; // We don't share object for accountancy @@ -356,8 +356,8 @@ for ($i = 1; $i <= 12; $i++) { if ($j > 12) { $j -= 12; } - $sql .= " SUM(".$db->ifsql("MONTH(f.datef) = ".((string) $j), "fd.total_ht", "0").") AS month".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; - $sql .= " SUM(".$db->ifsql("MONTH(f.datef) = ".((string) $j), "1", "0").") AS nbmonth".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(f.datef) = ".((int) $j), "fd.total_ht", "0").") AS month".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(f.datef) = ".((int) $j), "1", "0").") AS nbmonth".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; } $sql .= " SUM(fd.total_ht) as total, COUNT(fd.rowid) as nb"; $sql .= " FROM ".MAIN_DB_PREFIX."facturedet as fd"; @@ -496,7 +496,7 @@ for ($i = 1; $i <= 12; $i++) { if ($j > 12) { $j -= 12; } - $sql .= " SUM(".$db->ifsql("MONTH(f.datef) = ".((int) $j), "fd.total_ht", "0").") AS month".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(f.datef) = ".((int) $j), "fd.total_ht", "0").") AS month".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; } $sql .= " SUM(fd.total_ht) as total"; $sql .= " FROM ".MAIN_DB_PREFIX."facturedet as fd"; @@ -599,11 +599,14 @@ if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This pa if ($j > 12) { $j -= 12; } - $sql .= " SUM(".$db->ifsql("MONTH(f.datef) = ".((int) $j), "fd.total_ht", "0").") AS month".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(f.datef) = ".((int) $j), "fd.total_ht", "0").") AS month".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; } $sql .= " SUM(fd.total_ht) as total"; $sql .= " FROM ".MAIN_DB_PREFIX."facturedet as fd"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as f ON f.rowid = fd.fk_facture"; + if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) { + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facturedet AS prev_fd ON prev_fd.rowid = fd.fk_prev_id"; + } $sql .= " WHERE f.datef >= '".$db->idate($search_date_start)."'"; $sql .= " AND f.datef <= '".$db->idate($search_date_end)."'"; // Define begin binding date @@ -665,16 +668,16 @@ if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This pa "MONTH(f.datef) = ".((int) $j), " (".$db->ifsql( "fd.total_ht < 0", - " (-1 * (abs(fd.total_ht) - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100))))", // TODO This is bugged, we must use the percent for the invoice and fd.situation_percent is cumulated percent ! - " (fd.total_ht - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100)))" + " (-1 * (abs(fd.total_ht * ((fd.situation_percent - COALESCE(prev_fd.situation_percent, 0)) / fd.situation_percent)) - (fd.buy_price_ht * fd.qty * ((fd.situation_percent - COALESCE(prev_fd.situation_percent, 0)) / 100))))", + " (fd.total_ht * ((fd.situation_percent - COALESCE(prev_fd.situation_percent, 0)) / fd.situation_percent) - (fd.buy_price_ht * fd.qty * ((fd.situation_percent - COALESCE(prev_fd.situation_percent, 0)) / 100)))" ).")", '0' - ).") AS month".str_pad((string) $j, 2, '0', STR_PAD_LEFT).","; + ).") AS month".str_pad((string) ((int) $j), 2, '0', STR_PAD_LEFT).","; } $sql .= " SUM(".$db->ifsql( "fd.total_ht < 0", - " (-1 * (abs(fd.total_ht) - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100))))", // TODO This is bugged, we must use the percent for the invoice and fd.situation_percent is cumulated percent ! - " (fd.total_ht - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100)))" + " (-1 * (abs(fd.total_ht * ((fd.situation_percent - COALESCE(prev_fd.situation_percent, 0)) / fd.situation_percent)) - (fd.buy_price_ht * fd.qty * ((fd.situation_percent - COALESCE(prev_fd.situation_percent, 0)) / 100))))", + " (fd.total_ht * ((fd.situation_percent - COALESCE(prev_fd.situation_percent, 0)) / fd.situation_percent) - (fd.buy_price_ht * fd.qty * ((fd.situation_percent - COALESCE(prev_fd.situation_percent, 0)) / 100)))" ).") as total"; } else { $sql = "SELECT '".$db->escape($langs->trans("Vide"))."' AS marge,"; @@ -691,7 +694,7 @@ if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This pa " (fd.total_ht - (fd.buy_price_ht * fd.qty))" ).")", '0' - ).") AS month".str_pad((string) $j, 2, '0', STR_PAD_LEFT).","; + ).") AS month".str_pad((string) ((int) $j), 2, '0', STR_PAD_LEFT).","; } $sql .= " SUM(".$db->ifsql( "fd.total_ht < 0", diff --git a/htdocs/accountancy/customer/list.php b/htdocs/accountancy/customer/list.php index 69299cda228..68f84af5894 100644 --- a/htdocs/accountancy/customer/list.php +++ b/htdocs/accountancy/customer/list.php @@ -7,7 +7,7 @@ * Copyright (C) 2016 Laurent Destailleur * Copyright (C) 2021 Gauthier VERDOL * Copyright (C) 2024 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -309,10 +309,10 @@ if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) { } $alias_societe_perentity = !getDolGlobalString('MAIN_COMPANY_PERENTITY_SHARED') ? "s" : "spe"; $alias_product_perentity = !getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') ? "p" : "ppe"; -$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON " . $alias_product_perentity . ".accountancy_code_sell = aa.account_number AND aa.active = 1 AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".$conf->entity; -$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_sell_intra = aa2.account_number AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".$conf->entity; -$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_sell_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".$conf->entity; -$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa4 ON " . $alias_societe_perentity . ".accountancy_code_sell = aa4.account_number AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".$conf->entity; +$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON " . $alias_product_perentity . ".accountancy_code_sell = aa.account_number AND aa.active = 1 AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".((int) $conf->entity); +$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_sell_intra = aa2.account_number AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".((int) $conf->entity); +$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_sell_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".((int) $conf->entity); +$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa4 ON " . $alias_societe_perentity . ".accountancy_code_sell = aa4.account_number AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".((int) $conf->entity); // Add table from hooks $parameters = array(); $reshook = $hookmanager->executeHooks('printFieldListFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by hook @@ -541,68 +541,68 @@ if ($result) { } // Line ID if (!empty($arrayfields['l.rowid']['checked'])) { - print ''; + print ''; } // Ref invoice if (!empty($arrayfields['f.ref']['checked'])) { - print ''; + print ''; } // Date if (!empty($arrayfields['f.datef']['checked'])) { - print ''; + print ''; } // Product ref if (!empty($arrayfields['p.ref']['checked'])) { - print ''; + print ''; } // Description if (!empty($arrayfields['l.description']['checked'])) { - print ''; + print ''; } // Amount if (!empty($arrayfields['l.total_ht']['checked'])) { - print ''; + print ''; } // VAT if (!empty($arrayfields['l.tva_tx']['checked'])) { - print ''; + print ''; } // Thirdparty if (!empty($arrayfields['s.nom']['checked'])) { - print ''; + print ''; } // Country if (!empty($arrayfields['co.label']['checked'])) { - print ''; + print ''; } // TVA Intracom if (!empty($arrayfields['s.tva_intra']['checked'])) { - print ''; + print ''; } // Data suggested if (!empty($arrayfields['aa.data_suggest']['checked'])) { @@ -610,7 +610,7 @@ if ($result) { } // Account if (!empty($arrayfields['aa.account_number']['checked'])) { - print ''; + print ''; } // Fields from hook $parameters = array('arrayfields' => $arrayfields); @@ -639,53 +639,53 @@ if ($result) { } // Line ID if (!empty($arrayfields['l.rowid']['checked'])) { - print_liste_field_titre($arrayfields['l.rowid']['label'], $_SERVER["PHP_SELF"], "l.rowid", "", $param, '', $sortfield, $sortorder); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['l.rowid']['label'], $_SERVER["PHP_SELF"], "l.rowid", "", $param, '', $sortfield, $sortorder); + $totalarray['nbfield']++; } // Ref invoice if (!empty($arrayfields['f.ref']['checked'])) { - print_liste_field_titre($arrayfields['f.ref']['label'], $_SERVER["PHP_SELF"], "f.ref", "", $param, '', $sortfield, $sortorder); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['f.ref']['label'], $_SERVER["PHP_SELF"], "f.ref", "", $param, '', $sortfield, $sortorder); + $totalarray['nbfield']++; } // Date if (!empty($arrayfields['f.datef']['checked'])) { - print_liste_field_titre($arrayfields['f.datef']['label'], $_SERVER["PHP_SELF"], "f.datef, f.ref, l.rowid", "", $param, '', $sortfield, $sortorder, 'center '); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['f.datef']['label'], $_SERVER["PHP_SELF"], "f.datef, f.ref, l.rowid", "", $param, '', $sortfield, $sortorder, 'center '); + $totalarray['nbfield']++; } // Product ref if (!empty($arrayfields['p.ref']['checked'])) { - print_liste_field_titre($arrayfields['p.ref']['label'], $_SERVER["PHP_SELF"], "p.ref", "", $param, '', $sortfield, $sortorder); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['p.ref']['label'], $_SERVER["PHP_SELF"], "p.ref", "", $param, '', $sortfield, $sortorder); + $totalarray['nbfield']++; } // product description if (!empty($arrayfields['l.description']['checked'])) { - print_liste_field_titre($arrayfields['l.description']['label'], $_SERVER["PHP_SELF"], "l.description", "", $param, '', $sortfield, $sortorder); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['l.description']['label'], $_SERVER["PHP_SELF"], "l.description", "", $param, '', $sortfield, $sortorder); + $totalarray['nbfield']++; } // Amount if (!empty($arrayfields['l.total_ht']['checked'])) { - print_liste_field_titre($arrayfields['l.total_ht']['label'], $_SERVER["PHP_SELF"], "l.total_ht", "", $param, '', $sortfield, $sortorder, 'right '); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['l.total_ht']['label'], $_SERVER["PHP_SELF"], "l.total_ht", "", $param, '', $sortfield, $sortorder, 'right '); + $totalarray['nbfield']++; } // VAT if (!empty($arrayfields['l.tva_tx']['checked'])) { - print_liste_field_titre($arrayfields['l.tva_tx']['label'], $_SERVER["PHP_SELF"], "l.tva_tx", "", $param, '', $sortfield, $sortorder, 'right '); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['l.tva_tx']['label'], $_SERVER["PHP_SELF"], "l.tva_tx", "", $param, '', $sortfield, $sortorder, 'right '); + $totalarray['nbfield']++; } // Thirdparty if (!empty($arrayfields['s.nom']['checked'])) { - print_liste_field_titre($arrayfields['s.nom']['label'], $_SERVER["PHP_SELF"], "s.nom", "", $param, '', $sortfield, $sortorder); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['s.nom']['label'], $_SERVER["PHP_SELF"], "s.nom", "", $param, '', $sortfield, $sortorder); + $totalarray['nbfield']++; } // Country if (!empty($arrayfields['co.label']['checked'])) { - print_liste_field_titre($arrayfields['co.label']['label'], $_SERVER["PHP_SELF"], "co.label", "", $param, '', $sortfield, $sortorder); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['co.label']['label'], $_SERVER["PHP_SELF"], "co.label", "", $param, '', $sortfield, $sortorder); + $totalarray['nbfield']++; } // TVA Intracom if (!empty($arrayfields['s.tva_intra']['checked'])) { - print_liste_field_titre($arrayfields['s.tva_intra']['label'], $_SERVER["PHP_SELF"], "s.tva_intra", "", $param, '', $sortfield, $sortorder); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['s.tva_intra']['label'], $_SERVER["PHP_SELF"], "s.tva_intra", "", $param, '', $sortfield, $sortorder); + $totalarray['nbfield']++; } // Data suggested if (!empty($arrayfields['aa.data_suggest']['checked'])) { @@ -694,8 +694,8 @@ if ($result) { } // Account if (!empty($arrayfields['aa.account_number']['checked'])) { - print_liste_field_titre($arrayfields['aa.account_number']['label'], $_SERVER["PHP_SELF"], "aa.account_number", "", $param, '', $sortfield, $sortorder); - $totalarray['nbfield']++; + print_liste_field_titre($arrayfields['aa.account_number']['label'], $_SERVER["PHP_SELF"], "aa.account_number", "", $param, '', $sortfield, $sortorder); + $totalarray['nbfield']++; } // Hook fields $parameters = array('arrayfields' => $arrayfields, 'param' => $param, 'sortfield' => $sortfield, 'sortorder' => $sortorder); @@ -703,8 +703,8 @@ if ($result) { print $hookmanager->resPrint; // Action column if (!$conf->main_checkbox_left_column) { - print getTitleFieldOfList($selectedfields, 0, $_SERVER["PHP_SELF"], '', '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ')."\n"; - $totalarray['nbfield']++; + print getTitleFieldOfList($selectedfields, 0, $_SERVER["PHP_SELF"], '', '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ')."\n"; + $totalarray['nbfield']++; } print "\n"; diff --git a/htdocs/accountancy/expensereport/index.php b/htdocs/accountancy/expensereport/index.php index 8b9ec2628e6..c32d004cd78 100644 --- a/htdocs/accountancy/expensereport/index.php +++ b/htdocs/accountancy/expensereport/index.php @@ -4,7 +4,7 @@ * Copyright (C) 2013-2024 Alexandre Spangaro * Copyright (C) 2014 Juanjo Menent * Copyright (C) 2024 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * * 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 @@ -233,7 +233,7 @@ for ($i = 1; $i <= 12; $i++) { $param .= '&search_month='.$tmp['mon'].'&search_year='.$tmp['year']; print ''; } - print $langs->trans('MonthShort'.str_pad((string) $j, 2, '0', STR_PAD_LEFT)); + print $langs->trans('MonthShort'.str_pad((string) ((int) $j), 2, '0', STR_PAD_LEFT)); if (!empty($tmp['mday'])) { print ''; } @@ -248,8 +248,8 @@ for ($i = 1; $i <= 12; $i++) { if ($j > 12) { $j -= 12; } - $sql .= " SUM(".$db->ifsql("MONTH(er.date_debut) = ".((int) $j), "erd.total_ht", "0").") AS month".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; - $sql .= " SUM(".$db->ifsql("MONTH(er.date_debut) = ".((string) $j), "1", "0").") AS nbmonth".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(er.date_debut) = ".((int) $j), "erd.total_ht", "0").") AS month".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(er.date_debut) = ".((int) $j), "1", "0").") AS nbmonth".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; } $sql .= " SUM(erd.total_ht) as total, COUNT(erd.rowid) as nb"; $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_det as erd"; @@ -361,7 +361,7 @@ for ($i = 1; $i <= 12; $i++) { $param .= '&search_date_endday='.$tmp['mday'].'&search_date_endmonth='.$tmp['mon'].'&search_date_endyear='.$tmp['year']; print ''; } - print $langs->trans('MonthShort'.str_pad((string) $j, 2, '0', STR_PAD_LEFT)); + print $langs->trans('MonthShort'.str_pad((string) ((int) $j), 2, '0', STR_PAD_LEFT)); if (!empty($tmp['mday'])) { print ''; } @@ -376,7 +376,7 @@ for ($i = 1; $i <= 12; $i++) { if ($j > 12) { $j -= 12; } - $sql .= " SUM(".$db->ifsql("MONTH(er.date_debut) = ".((int) $j), "erd.total_ht", "0").") AS month".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(er.date_debut) = ".((int) $j), "erd.total_ht", "0").") AS month".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; } $sql .= " ROUND(SUM(erd.total_ht),2) as total"; $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_det as erd"; @@ -450,7 +450,7 @@ if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This pa if ($j > 12) { $j -= 12; } - print ''; + print ''; } print ''; @@ -460,7 +460,7 @@ if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This pa if ($j > 12) { $j -= 12; } - $sql .= " SUM(".$db->ifsql("MONTH(er.date_create) = ".((int) $j), "erd.total_ht", "0").") AS month".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(er.date_create) = ".((int) $j), "erd.total_ht", "0").") AS month".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; } $sql .= " SUM(erd.total_ht) as total"; $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_det as erd"; diff --git a/htdocs/accountancy/expensereport/list.php b/htdocs/accountancy/expensereport/list.php index b7718229767..b9c9be7c065 100644 --- a/htdocs/accountancy/expensereport/list.php +++ b/htdocs/accountancy/expensereport/list.php @@ -6,7 +6,7 @@ * Copyright (C) 2014 Juanjo Menent * Copyright (C) 2016 Laurent Destailleur * Copyright (C) 2024 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * * 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 +274,7 @@ $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as er"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."expensereport_det as erd ON er.rowid = erd.fk_expensereport"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_type_fees as f ON f.id = erd.fk_c_type_fees"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u ON u.rowid = er.fk_user_author"; -$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON f.accountancy_code = aa.account_number AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".$conf->entity; +$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON f.accountancy_code = aa.account_number AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".((int) $conf->entity); // Add table from hooks $parameters = array(); $reshook = $hookmanager->executeHooks('printFieldListFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by hook diff --git a/htdocs/accountancy/journal/treasuryjournal.php b/htdocs/accountancy/journal/treasuryjournal.php index dbef0b6fca4..232ab2960ce 100644 --- a/htdocs/accountancy/journal/treasuryjournal.php +++ b/htdocs/accountancy/journal/treasuryjournal.php @@ -626,7 +626,7 @@ if ($resql) { } else { $sql .= " LEFT JOIN ".$db->prefix()."accounting_bookkeeping as ab ON ab.fk_doc=bu.fk_bank AND ab.fk_docdet=cs.rowid"; } - $sql .= " WHERE cs.entity = ".$conf->entity; // We don't share object for accountancy, we use source object sharing + $sql .= " WHERE cs.entity = ".((int) $conf->entity); // We don't share object for accountancy, we use source object sharing // Not already in bookkeeping if ($in_bookkeeping == 'notyet') { $sql .= " AND ab.rowid IS NULL"; @@ -694,7 +694,7 @@ if ($resql) { $sql .= " LEFT JOIN ".$db->prefix()."accounting_bookkeeping as ab ON ab.fk_doc=bu.fk_bank AND ab.fk_docdet=t.rowid"; } $sql .= " WHERE bu.fk_bank IN (".$db->sanitize(implode(',', $ids)).")"; - // $sql .= " AND t.entity = " . $conf->entity; // TODO when entity is managed in tva + // $sql .= " AND t.entity = " . ((int) $conf->entity); // TODO when entity is managed in tva // Not already in bookkeeping if ($in_bookkeeping == 'notyet') { $sql .= " AND ab.rowid IS NULL"; @@ -827,7 +827,7 @@ if ($resql) { } else { $sql .= " LEFT JOIN ".$db->prefix()."accounting_bookkeeping as ab ON ab.fk_doc=bu.fk_bank AND ab.fk_docdet=l.rowid"; } - $sql .= " WHERE l.entity = ".$conf->entity; // We don't share object for accountancy, we use source object sharing + $sql .= " WHERE l.entity = ".((int) $conf->entity); // We don't share object for accountancy, we use source object sharing // Not already in bookkeeping if ($in_bookkeeping == 'notyet') { $sql .= " AND ab.rowid IS NULL"; diff --git a/htdocs/accountancy/supplier/index.php b/htdocs/accountancy/supplier/index.php index 366c7253f8f..efd583f5a34 100644 --- a/htdocs/accountancy/supplier/index.php +++ b/htdocs/accountancy/supplier/index.php @@ -4,7 +4,7 @@ * Copyright (C) 2013-2022 Alexandre Spangaro * Copyright (C) 2014 Juanjo Menent * Copyright (C) 2024 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * * 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 @@ -155,10 +155,10 @@ if ($action == 'validatehistory' && $user->hasRight('accounting', 'bind', 'write } $alias_societe_perentity = !getDolGlobalString('MAIN_COMPANY_PERENTITY_SHARED') ? "s" : "spe"; $alias_product_perentity = !getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') ? "p" : "ppe"; - $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa ON " . $alias_product_perentity . ".accountancy_code_buy = aa.account_number AND aa.active = 1 AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".$conf->entity; - $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_buy_intra = aa2.account_number AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".$conf->entity; - $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_buy_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".$conf->entity; - $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa4 ON " . $alias_product_perentity . ".accountancy_code_buy = aa4.account_number AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".$conf->entity; + $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa ON " . $alias_product_perentity . ".accountancy_code_buy = aa.account_number AND aa.active = 1 AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".((int) $conf->entity); + $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_buy_intra = aa2.account_number AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".((int) $conf->entity); + $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_buy_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".((int) $conf->entity); + $sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa4 ON " . $alias_product_perentity . ".accountancy_code_buy = aa4.account_number AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".((int) $conf->entity); $sql .= " WHERE f.fk_statut > 0 AND l.fk_code_ventilation <= 0"; $sql .= " AND l.product_type <= 2"; $sql .= " AND f.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy @@ -208,9 +208,9 @@ if ($action == 'validatehistory' && $user->hasRight('accounting', 'bind', 'write $product_static->label = $objp->product_label; $product_static->status = !empty($objp->status) ? $objp->status : 0; $product_static->status_buy = !empty($objp->status_buy) ? $objp->status_buy : 0; - $product_static->accountancy_code_sell = !empty($objp->code_sell)? $objp->code_sell : ""; - $product_static->accountancy_code_sell_intra = !empty($objp->code_sell_intra)? $objp->code_sell_intra : ""; - $product_static->accountancy_code_sell_export = !empty($objp->code_sell_export)? $objp->code_sell_export : ""; + $product_static->accountancy_code_sell = !empty($objp->code_sell) ? $objp->code_sell : ""; + $product_static->accountancy_code_sell_intra = !empty($objp->code_sell_intra) ? $objp->code_sell_intra : ""; + $product_static->accountancy_code_sell_export = !empty($objp->code_sell_export) ? $objp->code_sell_export : ""; $product_static->accountancy_code_buy = !empty($objp->code_buy) ? $objp->code_buy : ""; $product_static->accountancy_code_buy_intra = !empty($objp->code_buy_intra) ? $objp->code_buy_intra : ""; $product_static->accountancy_code_buy_export = !empty($objp->code_buy_export) ? $objp->code_buy_export : ""; @@ -342,7 +342,7 @@ for ($i = 1; $i <= 12; $i++) { $param .= '&search_date_endday='.$tmp['mday'].'&search_date_endmonth='.$tmp['mon'].'&search_date_endyear='.$tmp['year']; print ''; } - print $langs->trans('MonthShort'.str_pad((string) $j, 2, '0', STR_PAD_LEFT)); + print $langs->trans('MonthShort'.str_pad((string) ((int) $j), 2, '0', STR_PAD_LEFT)); if (!empty($tmp['mday'])) { print ''; } @@ -357,8 +357,8 @@ for ($i = 1; $i <= 12; $i++) { if ($j > 12) { $j -= 12; } - $sql .= " SUM(".$db->ifsql("MONTH(ff.datef) = ".((int) $j), "ffd.total_ht", "0").") AS month".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; - $sql .= " SUM(".$db->ifsql("MONTH(ff.datef) = ".((string) $j), "1", "0").") AS nbmonth".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(ff.datef) = ".((int) $j), "ffd.total_ht", "0").") AS month".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(ff.datef) = ".((int) $j), "1", "0").") AS nbmonth".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; } $sql .= " SUM(ffd.total_ht) as total, COUNT(ffd.rowid) as nb"; $sql .= " FROM ".$db->prefix()."facture_fourn_det as ffd"; @@ -474,7 +474,7 @@ for ($i = 1; $i <= 12; $i++) { $param .= '&search_date_endday='.$tmp['mday'].'&search_date_endmonth='.$tmp['mon'].'&search_date_endyear='.$tmp['year']; print ''; } - print $langs->trans('MonthShort'.str_pad((string) $j, 2, '0', STR_PAD_LEFT)); + print $langs->trans('MonthShort'.str_pad((string) ((int) $j), 2, '0', STR_PAD_LEFT)); if (!empty($tmp['mday'])) { print ''; } @@ -489,7 +489,7 @@ for ($i = 1; $i <= 12; $i++) { if ($j > 12) { $j -= 12; } - $sql .= " SUM(".$db->ifsql("MONTH(ff.datef) = ".((int) $j), "ffd.total_ht", "0").") AS month".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(ff.datef) = ".((int) $j), "ffd.total_ht", "0").") AS month".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; } $sql .= " SUM(ffd.total_ht) as total"; $sql .= " FROM ".$db->prefix()."facture_fourn_det as ffd"; @@ -573,7 +573,7 @@ if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This pa if ($j > 12) { $j -= 12; } - print ''; + print ''; } print ''; @@ -583,7 +583,7 @@ if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This pa if ($j > 12) { $j -= 12; } - $sql .= " SUM(".$db->ifsql("MONTH(ff.datef) = ".((int) $j), "ffd.total_ht", "0").") AS month".str_pad((string) $j, 2, "0", STR_PAD_LEFT).","; + $sql .= " SUM(".$db->ifsql("MONTH(ff.datef) = ".((int) $j), "ffd.total_ht", "0").") AS month".str_pad((string) ((int) $j), 2, "0", STR_PAD_LEFT).","; } $sql .= " SUM(ffd.total_ht) as total"; $sql .= " FROM ".$db->prefix()."facture_fourn_det as ffd"; diff --git a/htdocs/accountancy/supplier/list.php b/htdocs/accountancy/supplier/list.php index 62c1fdb167a..1fc9d489260 100644 --- a/htdocs/accountancy/supplier/list.php +++ b/htdocs/accountancy/supplier/list.php @@ -7,7 +7,7 @@ * Copyright (C) 2014 Juanjo Menent s * Copyright (C) 2016 Laurent Destailleur * Copyright (C) 2024 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -313,10 +313,10 @@ if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) { } $alias_societe_perentity = !getDolGlobalString('MAIN_COMPANY_PERENTITY_SHARED') ? "s" : "spe"; $alias_product_perentity = !getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') ? "p" : "ppe"; -$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON " . $alias_product_perentity . ".accountancy_code_buy = aa.account_number AND aa.active = 1 AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".$conf->entity; -$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_buy_intra = aa2.account_number AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".$conf->entity; -$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_buy_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".$conf->entity; -$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa4 ON " . $alias_societe_perentity . ".accountancy_code_buy = aa4.account_number AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".$conf->entity; +$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON " . $alias_product_perentity . ".accountancy_code_buy = aa.account_number AND aa.active = 1 AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".((int) $conf->entity); +$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_buy_intra = aa2.account_number AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".((int) $conf->entity); +$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_buy_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".((int) $conf->entity); +$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa4 ON " . $alias_societe_perentity . ".accountancy_code_buy = aa4.account_number AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".((int) $conf->entity); // Add table from hooks $parameters = array(); $reshook = $hookmanager->executeHooks('printFieldListFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by hook @@ -836,7 +836,8 @@ if ($result) { if ($conf->main_checkbox_left_column) { print ''; + print ''; + print ''; if (!$i) { $totalarray['nbfield']++; } diff --git a/htdocs/accountancy/tpl/export_journal.tpl.php b/htdocs/accountancy/tpl/export_journal.tpl.php index 48e75dd4a48..7978a609c8c 100644 --- a/htdocs/accountancy/tpl/export_journal.tpl.php +++ b/htdocs/accountancy/tpl/export_journal.tpl.php @@ -80,7 +80,17 @@ if ((substr($accountancyexport->getFormatCode($formatexportset), 0, 3) == 'fec') $endaccountingperiod = dol_print_date(dol_get_last_day($tmparray['year'], $tmparray['mon']), 'dayxcard'); $siren = str_replace(" ", "", $siren); - $completefilename = $siren."FEC".$endaccountingperiod.".txt"; + $completefilename = $siren."FEC".$endaccountingperiod; + + // Option: append a timestamp suffix to avoid overwriting a previous export of the same fiscal period. + // The strict FEC naming convention (SIRENFECAAAAMMJJ.txt) is still respected by default; + // this suffix is only added if the admin explicitly enables it, since some import software + // requires strict compliance with the official filename. + if (getDolGlobalInt('ACCOUNTING_EXPORT_FEC_ADD_TIMESTAMP')) { + $completefilename .= "_".dol_print_date(dol_now(), '%Y%m%d%H%M%S'); + } + + $completefilename .= ".txt"; } elseif ($accountancyexport->getFormatCode($formatexportset) == 'ciel' && $type_export == "general_ledger" && getDolGlobalString('ACCOUNTING_EXPORT_XIMPORT_FORCE_FILENAME')) { $completefilename = "XIMPORT.TXT"; } else { diff --git a/htdocs/adherents/admin/member_emails.php b/htdocs/adherents/admin/member_emails.php index 4d05ac5edd3..67699436bea 100644 --- a/htdocs/adherents/admin/member_emails.php +++ b/htdocs/adherents/admin/member_emails.php @@ -10,6 +10,7 @@ * Copyright (C) 2015 Jean-François Ferry * Copyright (C) 2024 Alexandre Spangaro * Copyright (C) 2024-2026 Frédéric France + * Copyright (C) 2026 MDW * * 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 @@ -51,7 +52,7 @@ if (!$user->admin) { } -$oldtypetonewone = array('texte'=>'text', 'chaine'=>'string'); // old type to new ones +$oldtypetonewone = array('texte' => 'text', 'chaine' => 'string'); // old type to new ones $action = GETPOST('action', 'aZ09'); @@ -64,17 +65,17 @@ $helptext .= '__COMPANY__, __ADDRESS__, __ZIP__, __TOWN__, __COUNTRY__, __EMAIL_ // Editing global variables not related to a specific theme $constantes = array( - 'MEMBER_REMINDER_EMAIL' => array('type'=>'yesno', 'label' => $langs->trans('MEMBER_REMINDER_EMAIL', $langs->transnoentities("Module2300Name")), 'help' => $langs->trans('MEMBER_REMINDER_EMAILHelp', $langs->transnoentities("Module2300Name"))), - 'ADHERENT_EMAIL_TEMPLATE_REMIND_EXPIRATION' => array('type'=>'emailtemplate:member', 'label'=>''), - 'ADHERENT_EMAIL_TEMPLATE_AUTOREGISTER' => array('type'=>'emailtemplate:member', 'label'=>''), - 'ADHERENT_EMAIL_TEMPLATE_MEMBER_VALIDATION' => array('type'=>'emailtemplate:member', 'label'=>''), - 'ADHERENT_EMAIL_TEMPLATE_SUBSCRIPTION' => array('type'=>'emailtemplate:member', 'label'=>''), - 'ADHERENT_EMAIL_TEMPLATE_CANCELATION' => array('type'=>'emailtemplate:member', 'label'=>''), - 'ADHERENT_EMAIL_TEMPLATE_EXCLUSION' => array('type'=>'emailtemplate:member', 'label'=>''), - 'ADHERENT_MAIL_FROM' => array('type'=>'string', 'label'=>''), - 'ADHERENT_CC_MAIL_FROM' => array('type'=>'string', 'label'=>''), - 'ADHERENT_AUTOREGISTER_NOTIF_MAIL_SUBJECT' => array('type'=>'string', 'label'=>''), - 'ADHERENT_AUTOREGISTER_NOTIF_MAIL' => array('type'=>'html', 'tooltip' => $helptext, 'label' => '') + 'MEMBER_REMINDER_EMAIL' => array('type' => 'yesno', 'label' => $langs->trans('MEMBER_REMINDER_EMAIL', $langs->transnoentities("Module2300Name")), 'help' => $langs->trans('MEMBER_REMINDER_EMAILHelp', $langs->transnoentities("Module2300Name"))), + 'ADHERENT_EMAIL_TEMPLATE_REMIND_EXPIRATION' => array('type' => 'emailtemplate:member', 'label' => ''), + 'ADHERENT_EMAIL_TEMPLATE_AUTOREGISTER' => array('type' => 'emailtemplate:member', 'label' => ''), + 'ADHERENT_EMAIL_TEMPLATE_MEMBER_VALIDATION' => array('type' => 'emailtemplate:member', 'label' => ''), + 'ADHERENT_EMAIL_TEMPLATE_SUBSCRIPTION' => array('type' => 'emailtemplate:member', 'label' => ''), + 'ADHERENT_EMAIL_TEMPLATE_CANCELATION' => array('type' => 'emailtemplate:member', 'label' => ''), + 'ADHERENT_EMAIL_TEMPLATE_EXCLUSION' => array('type' => 'emailtemplate:member', 'label' => ''), + 'ADHERENT_MAIL_FROM' => array('type' => 'string', 'label' => ''), + 'ADHERENT_CC_MAIL_FROM' => array('type' => 'string', 'label' => ''), + 'ADHERENT_AUTOREGISTER_NOTIF_MAIL_SUBJECT' => array('type' => 'string', 'label' => ''), + 'ADHERENT_AUTOREGISTER_NOTIF_MAIL' => array('type' => 'html', 'tooltip' => $helptext, 'label' => '') ); @@ -186,7 +187,7 @@ foreach ($tableau as $key => $const) { // Loop on each param $sql = "SELECT rowid, ".$db->decrypt('name')." as name, ".$db->decrypt('value')." as value, type, note"; $sql .= " FROM ".MAIN_DB_PREFIX."const"; $sql .= " WHERE ".$db->decrypt('name')." = '".$db->escape($const)."'"; - $sql .= " AND entity IN (0, ".$conf->entity.")"; + $sql .= " AND entity IN (0, ".((int) $conf->entity).")"; $sql .= " ORDER BY name ASC, entity DESC"; $resql = $db->query($sql); diff --git a/htdocs/adherents/card.php b/htdocs/adherents/card.php index 09750d5c2da..ce510e61a80 100644 --- a/htdocs/adherents/card.php +++ b/htdocs/adherents/card.php @@ -8,7 +8,7 @@ * Copyright (C) 2015-2024 Alexandre Spangaro * Copyright (C) 2018-2026 Frédéric France * Copyright (C) 2021 Waël Almoman - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -206,7 +206,7 @@ if (empty($reshook)) { if ($socid != $object->socid) { // If link differs from currently in database $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."adherent"; $sql .= " WHERE socid = ".((int) $socid); - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); $resql = $db->query($sql); if ($resql) { $obj = $db->fetch_object($resql); diff --git a/htdocs/adherents/class/adherent.class.php b/htdocs/adherents/class/adherent.class.php index 05d534a6b14..0b287228811 100644 --- a/htdocs/adherents/class/adherent.class.php +++ b/htdocs/adherents/class/adherent.class.php @@ -15,7 +15,7 @@ * Copyright (C) 2020 Josep Lluís Amador * Copyright (C) 2021 Waël Almoman * Copyright (C) 2021 Philippe Grand - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -505,17 +505,17 @@ class Adherent extends CommonObject } // Send mail confirmation - $from = getDolGlobalString('ADHERENT_MAIL_FROM', $conf->email_from); + $email_from = getDolGlobalString('ADHERENT_MAIL_FROM', $conf->email_from); $trackid = 'mem'.$this->id; // Send email (substitutionarray must be done just before this) include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php'; - $mailfile = new CMailFile($subjecttosend, (string) $this->email, $from, $texttosend, $filename_list, $mimetype_list, $mimefilename_list, $addr_cc, $addr_bcc, $deliveryreceipt, $msgishtml, '', '', $trackid, $moreinheader); + $mailfile = new CMailFile($subjecttosend, (string) $this->email, $email_from, $texttosend, $filename_list, $mimetype_list, $mimefilename_list, $addr_cc, $addr_bcc, $deliveryreceipt, $msgishtml, '', '', $trackid, $moreinheader); if ($mailfile->sendfile()) { return 1; } else { - $this->error = $langs->trans("ErrorFailedToSendMail", $from, (string) $this->email).'. '.$mailfile->error; + $this->error = $langs->trans("ErrorFailedToSendMail", $email_from, (string) $this->email).'. '.$mailfile->error; return -1; } } @@ -697,7 +697,7 @@ class Adherent extends CommonObject $sql .= " '(PROV)'"; $sql .= ", '".$this->db->idate($this->datec)."'"; $sql .= ", ".($this->login ? "'".$this->db->escape($this->login)."'" : "null"); - $sql .= ", ".($user->id > 0 ? $user->id : "null"); // Can be null because member can be created by a guest or a script + $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "null"); // Can be null because member can be created by a guest or a script $sql .= ", null, null, '".$this->db->escape($this->morphy)."'"; $sql .= ", ".((int) $this->typeid); $sql .= ", ".((int) $this->entity); @@ -871,7 +871,7 @@ class Adherent extends CommonObject if ($this->datevalid) { $sql .= ", datevalid = '".$this->db->idate($this->datevalid)."'"; // Must be modified only when validating a member } - $sql .= ", fk_user_mod = ".($user->id > 0 ? $user->id : 'null'); // Can be null because member can be create by a guest + $sql .= ", fk_user_mod = ".($user->id > 0 ? ((int) $user->id) : 'null'); // Can be null because member can be create by a guest $sql .= " WHERE rowid = ".((int) $this->id); // If we change the type of membership, we set also label of new type.. @@ -1503,7 +1503,7 @@ class Adherent extends CommonObject if ($thirdpartyid > 0) { $sql = "UPDATE ".MAIN_DB_PREFIX."adherent SET fk_soc = null"; $sql .= " WHERE fk_soc = ".((int) $thirdpartyid); - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); dol_syslog(get_class($this)."::setThirdPartyId", LOG_DEBUG); $resql = $this->db->query($sql); } @@ -1539,7 +1539,7 @@ class Adherent extends CommonObject $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."adherent"; $sql .= " WHERE login='".$this->db->escape($login)."'"; - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if ($resql) { @@ -1568,7 +1568,7 @@ class Adherent extends CommonObject $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."adherent"; $sql .= " WHERE firstname='".$this->db->escape($firstname)."'"; $sql .= " AND lastname='".$this->db->escape($lastname)."'"; - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if ($resql) { @@ -3333,7 +3333,7 @@ class Adherent extends CommonObject $subject = make_substitutions($arraydefaultmessage->topic, $substitutionarray, $outputlangs); $msg = make_substitutions($arraydefaultmessage->content, $substitutionarray, $outputlangs); - $from = getDolGlobalString('ADHERENT_MAIL_FROM', $conf->email_from); + $email_from = getDolGlobalString('ADHERENT_MAIL_FROM', $conf->email_from); $to = $adherent->email; $cc = getDolGlobalString('ADHERENT_CC_MAIL_FROM'); @@ -3341,7 +3341,7 @@ class Adherent extends CommonObject $moreinheader = 'X-Dolibarr-Info: sendReminderForExpiredSubscription'."\r\n"; include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php'; - $cmail = new CMailFile($subject, $to, $from, $msg, array(), array(), array(), $cc, '', 0, 1, '', '', $trackid, $moreinheader); + $cmail = new CMailFile($subject, $to, $email_from, $msg, array(), array(), array(), $cc, '', 0, 1, '', '', $trackid, $moreinheader); $result = $cmail->sendfile(); if (!$result) { $error++; @@ -3363,9 +3363,9 @@ class Adherent extends CommonObject $extraparams = array(); $actionmsg = ''; - $actionmsg2 = $langs->transnoentities('MailSentByTo', CMailFile::getValidAddress($from, 4, 0, 1), CMailFile::getValidAddress($sendto, 4, 0, 1)); + $actionmsg2 = $langs->transnoentities('MailSentByTo', CMailFile::getValidAddress($email_from, 4, 0, 1), CMailFile::getValidAddress($sendto, 4, 0, 1)); if ($message) { - $actionmsg = $langs->transnoentities('MailFrom').': '.dol_escape_htmltag($from); + $actionmsg = $langs->transnoentities('MailFrom').': '.dol_escape_htmltag($email_from); $actionmsg = dol_concatdesc($actionmsg, $langs->transnoentities('MailTo').': '.dol_escape_htmltag($sendto)); // if ($sendtocc) { // $actionmsg = dol_concatdesc($actionmsg, $langs->transnoentities('Bcc').": ".dol_escape_htmltag($sendtocc)); @@ -3394,7 +3394,7 @@ class Adherent extends CommonObject $actioncomm->userownerid = $user->id; // Owner of action // Fields when action is an email (content should be added into note) $actioncomm->email_msgid = $cmail->msgid; - $actioncomm->email_from = $from; + $actioncomm->email_from = $email_from; $actioncomm->email_sender = ''; $actioncomm->email_to = $to; $actioncomm->email_tocc = $sendtocc; diff --git a/htdocs/adherents/class/subscription.class.php b/htdocs/adherents/class/subscription.class.php index ecfad20791f..75a7a63abb5 100644 --- a/htdocs/adherents/class/subscription.class.php +++ b/htdocs/adherents/class/subscription.class.php @@ -206,7 +206,7 @@ class Subscription extends CommonObject $sql .= " '".$this->db->escape($this->note_public ? $this->note_public : $this->note)."',"; $sql .= " '".$this->db->escape($this->note_private)."',"; $sql .= " ".(empty($this->ref_ext) ? "null" : "'".$this->db->escape($this->ref_ext)."'").","; - $sql .= " ".((int) ($this->user_creation_id > 0 ? $this->user_creation_id : $user->id)); + $sql .= " ".((int) ($this->user_creation_id > 0 ? $this->user_creation_id : ((int) $user->id))); $sql .= ", ".(!empty($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null"); $sql .= ")"; @@ -547,9 +547,9 @@ class Subscription extends CommonObject */ public function info($id) { - $sql = 'SELECT c.rowid, c.datec, c.tms as datem, c.fk_user_creat'; - $sql .= ' FROM '.MAIN_DB_PREFIX.'subscription as c'; - $sql .= ' WHERE c.rowid = '.((int) $id); + $sql = "SELECT c.rowid, c.datec, c.tms as datem, c.fk_user_creat"; + $sql .= " FROM ".MAIN_DB_PREFIX."subscription as c"; + $sql .= " WHERE c.rowid = ".((int) $id); $resql = $this->db->query($sql); if ($resql) { diff --git a/htdocs/adherents/list.php b/htdocs/adherents/list.php index 6c57b720c7e..0c7fb2b12b4 100644 --- a/htdocs/adherents/list.php +++ b/htdocs/adherents/list.php @@ -834,11 +834,11 @@ print ''; print ''; $newcardbutton = ''; -$queryforbutton = $query; -$queryforbutton['mode'] = 'common'; -$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', dolBuildUrl($_SERVER["PHP_SELF"], $queryforbutton), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss' => 'reposition')); -$queryforbutton['mode'] = 'kanban'; -$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', dolBuildUrl($_SERVER["PHP_SELF"], $queryforbutton), '', ($mode == 'kanban' ? 2 : 1), array('morecss' => 'reposition')); +$argsforbutton = $query; +$argsforbutton['mode'] = 'common'; +$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', dolBuildUrl($_SERVER["PHP_SELF"], $argsforbutton), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss' => 'reposition')); +$argsforbutton['mode'] = 'kanban'; +$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', dolBuildUrl($_SERVER["PHP_SELF"], $argsforbutton), '', ($mode == 'kanban' ? 2 : 1), array('morecss' => 'reposition')); $newcardbutton .= dolGetButtonTitle($langs->trans('Statistics'), '', 'fa fa-chart-bar imgforviewmode', dol_buildpath('/adherents/stats/geo.php', 1).'?mode=memberbycountry&objecttype=adherent@adherent'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', ($mode == 'statistics' ? 2 : 1), array('morecss' => 'reposition')); $newcardbutton .= dolGetButtonTitleSeparator(); $newcardbutton .= dolGetButtonTitle($langs->trans('NewMember'), '', 'fa fa-plus-circle', dolBuildUrl(DOL_URL_ROOT.'/adherents/card.php', ['action' => 'create']), '', $user->hasRight('adherent', 'creer')); diff --git a/htdocs/adherents/subscription.php b/htdocs/adherents/subscription.php index c3003137e3e..be9a5148b19 100644 --- a/htdocs/adherents/subscription.php +++ b/htdocs/adherents/subscription.php @@ -123,6 +123,11 @@ if ($id > 0 || !empty($ref)) { // Define variables to determine what the current user can do on the members $permissiontoaddmember = $user->hasRight('adherent', 'creer'); +$permissiontoeditextra = $permissiontoaddmember; +if (GETPOST('attribute', 'aZ09') && isset($extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')])) { + // For action 'update_extras', is there a specific permission set for the attribute to update + $permissiontoeditextra = dol_eval((string) $extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')]); +} // Security check $result = restrictedArea($user, 'adherent', $object->id, '', '', 'socid', 'rowid', 0); @@ -138,6 +143,28 @@ if ($reshook < 0) { setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); } +if (empty($reshook) && $action == 'update_extras' && $permissiontoeditextra) { + $error = 0; + $object->oldcopy = dol_clone($object, 2); // @phan-suppress-current-line PhanTypeMismatchProperty + $attribute_name = GETPOST('attribute', 'aZ09'); + + // Fill array 'array_options' with data from update form + $ret = $extrafields->setOptionalsFromPost(null, $object, $attribute_name); + if ($ret < 0) { + $error++; + } + if (!$error) { + $result = $object->updateExtraField($attribute_name, 'MEMBER_MODIFY'); + if ($result < 0) { + setEventMessages($object->error, $object->errors, 'errors'); + $error++; + } + } + if ($error) { + $action = 'edit_extras'; + } +} + // Create third party from a member if (empty($reshook) && $action == 'confirm_create_thirdparty' && $confirm == 'yes' && $user->hasRight('societe', 'creer')) { if ($result > 0) { diff --git a/htdocs/admin/barcode.php b/htdocs/admin/barcode.php index 0845e3901b6..c836c2f3a83 100644 --- a/htdocs/admin/barcode.php +++ b/htdocs/admin/barcode.php @@ -84,7 +84,7 @@ if ($action == 'setcoder') { $sqlp = "UPDATE ".MAIN_DB_PREFIX."c_barcode_type"; $sqlp .= " SET coder = '".$db->escape($coder)."'"; $sqlp .= " WHERE rowid = ".((int) $code_id); - $sqlp .= " AND entity = ".$conf->entity; + $sqlp .= " AND entity = ".((int) $conf->entity); $resql = $db->query($sqlp); if (!$resql) { @@ -106,7 +106,7 @@ if ($action == 'setcoder') { } elseif ($action == 'updateengine') { $sql = "SELECT rowid, coder"; $sql .= " FROM ".MAIN_DB_PREFIX."c_barcode_type"; - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); $sql .= " ORDER BY code"; $resql = $db->query($sql); @@ -124,7 +124,7 @@ if ($action == 'setcoder') { $sqlp = "UPDATE ".MAIN_DB_PREFIX."c_barcode_type"; $sqlp .= " SET coder = '".$db->escape($coder)."'"; $sqlp .= " WHERE rowid = ".((int) $code_id); - $sqlp .= " AND entity = ".$conf->entity; + $sqlp .= " AND entity = ".((int) $conf->entity); $upsql = $db->query($sqlp); if (!$upsql) { @@ -413,7 +413,7 @@ if (getDolGlobalString('BARCODE_USE_ON_PRODUCT') || getDolGlobalString('BARCODE_ $sql = "SELECT rowid, code as encoding, libelle as label, coder, example"; $sql .= " FROM ".MAIN_DB_PREFIX."c_barcode_type"; - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); $sql .= " ORDER BY code"; dol_syslog("admin/barcode.php", LOG_DEBUG); diff --git a/htdocs/admin/boxes.php b/htdocs/admin/boxes.php index 15a4e25b79c..2bde31978f0 100644 --- a/htdocs/admin/boxes.php +++ b/htdocs/admin/boxes.php @@ -3,7 +3,7 @@ * Copyright (C) 2004-2022 Laurent Destailleur * Copyright (C) 2005-2012 Regis Houssin * Copyright (C) 2015 Jean-François Ferry - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -124,7 +124,7 @@ if ($action == 'add') { $sql = "INSERT INTO ".MAIN_DB_PREFIX."boxes ("; $sql .= "box_id, position, box_order, fk_user, entity"; $sql .= ") VALUES ("; - $sql .= ((int) $boxid['value']).", ".((int) $pos).", '".(($nbboxonleft > $nbboxonright) ? 'B01' : 'A01')."', ".((int) $fk_user).", ".$conf->entity; + $sql .= ((int) $boxid['value']).", ".((int) $pos).", '".(($nbboxonleft > $nbboxonright) ? 'B01' : 'A01')."', ".((int) $fk_user).", ".((int) $conf->entity); $sql .= ")"; dol_syslog("boxes.php activate box", LOG_DEBUG); @@ -159,7 +159,7 @@ if ($action == 'delete') { $db->begin(); $sql = "DELETE FROM ".MAIN_DB_PREFIX."boxes"; - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); $sql .= " AND box_id=".((int) $obj->box_id); $resql = $db->query($sql); @@ -236,7 +236,7 @@ $sql = "SELECT b.rowid, b.box_id, b.position, b.box_order,"; $sql .= " bd.rowid as boxid"; $sql .= " FROM ".MAIN_DB_PREFIX."boxes as b, ".MAIN_DB_PREFIX."boxes_def as bd"; $sql .= " WHERE b.box_id = bd.rowid"; -$sql .= " AND b.entity IN (0,".$conf->entity.")"; +$sql .= " AND b.entity IN (0,".((int) $conf->entity).")"; $sql .= " AND b.fk_user = 0"; $sql .= " ORDER by b.position, b.box_order"; //print $sql; @@ -272,7 +272,7 @@ if ($resql) { // This occurs just after an insert. $sql = "SELECT box_order"; $sql .= " FROM ".MAIN_DB_PREFIX."boxes"; - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); $sql .= " AND LENGTH(box_order) <= 2"; dol_syslog("Execute requests to renumber box order", LOG_DEBUG); @@ -282,21 +282,21 @@ if ($resql) { if (dol_strlen($record['box_order']) == 1) { if (preg_match("/[13579]{1}/", substr($record['box_order'], -1))) { $box_order = "A0".$record['box_order']; - $sql = "UPDATE ".MAIN_DB_PREFIX."boxes SET box_order = '".$db->escape($box_order)."' WHERE entity = ".$conf->entity." AND box_order = '".$db->escape($record['box_order'])."'"; + $sql = "UPDATE ".MAIN_DB_PREFIX."boxes SET box_order = '".$db->escape($box_order)."' WHERE entity = ".((int) $conf->entity)." AND box_order = '".$db->escape($record['box_order'])."'"; $resql = $db->query($sql); } elseif (preg_match("/[02468]{1}/", substr($record['box_order'], -1))) { $box_order = "B0".$record['box_order']; - $sql = "UPDATE ".MAIN_DB_PREFIX."boxes SET box_order = '".$db->escape($box_order)."' WHERE entity = ".$conf->entity." AND box_order = '".$db->escape($record['box_order'])."'"; + $sql = "UPDATE ".MAIN_DB_PREFIX."boxes SET box_order = '".$db->escape($box_order)."' WHERE entity = ".((int) $conf->entity)." AND box_order = '".$db->escape($record['box_order'])."'"; $resql = $db->query($sql); } } elseif (dol_strlen($record['box_order']) == 2) { if (preg_match("/[13579]{1}/", substr($record['box_order'], -1))) { $box_order = "A".$record['box_order']; - $sql = "UPDATE ".MAIN_DB_PREFIX."boxes SET box_order = '".$db->escape($box_order)."' WHERE entity = ".$conf->entity." AND box_order = '".$db->escape($record['box_order'])."'"; + $sql = "UPDATE ".MAIN_DB_PREFIX."boxes SET box_order = '".$db->escape($box_order)."' WHERE entity = ".((int) $conf->entity)." AND box_order = '".$db->escape($record['box_order'])."'"; $resql = $db->query($sql); } elseif (preg_match("/[02468]{1}/", substr($record['box_order'], -1))) { $box_order = "B".$record['box_order']; - $sql = "UPDATE ".MAIN_DB_PREFIX."boxes SET box_order = '".$db->escape($box_order)."' WHERE entity = ".$conf->entity." AND box_order = '".$db->escape($record['box_order'])."'"; + $sql = "UPDATE ".MAIN_DB_PREFIX."boxes SET box_order = '".$db->escape($box_order)."' WHERE entity = ".((int) $conf->entity)." AND box_order = '".$db->escape($record['box_order'])."'"; $resql = $db->query($sql); } } diff --git a/htdocs/admin/company.php b/htdocs/admin/company.php index 758512de5e2..5c5bd9c3531 100644 --- a/htdocs/admin/company.php +++ b/htdocs/admin/company.php @@ -75,6 +75,12 @@ $hookmanager->initHooks(array('admincompany', 'globaladmin')); $object = new Societe($db); +if (!getDolGlobalString('MAIN_INFO_SOCIETE_NOM') || !getDolGlobalString('MAIN_INFO_SOCIETE_COUNTRY') || getDolGlobalString('MAIN_INFO_SOCIETE_SETUP_TODO_WARNING')) { + $setupcompanynotcomplete = 1; +} else { + $setupcompanynotcomplete = 0; +} + /* * Actions @@ -444,8 +450,8 @@ $head = company_admin_prepare_head(); print dol_get_fiche_head($head, 'company', '', -1, ''); -print ''.$langs->trans("CompanyFundationDesc", $langs->transnoentities("Save"))."
\n"; -print "

\n"; +print '
'.$langs->trans("CompanyFundationDesc", $langs->transnoentities("Save"))."
\n"; +print "
\n"; // Edit parameters diff --git a/htdocs/admin/company_socialnetworks.php b/htdocs/admin/company_socialnetworks.php index 18b5493d9eb..5ce5e5bfd83 100644 --- a/htdocs/admin/company_socialnetworks.php +++ b/htdocs/admin/company_socialnetworks.php @@ -103,8 +103,8 @@ $head = company_admin_prepare_head(); print dol_get_fiche_head($head, 'socialnetworks', '', -1, ''); -print ''.$langs->trans("CompanyFundationDesc", $langs->transnoentities("Save"))."
\n"; -print ''.$langs->trans("MoreNetworksAvailableWithModule")."
\n"; +print '
'.$langs->trans("CompanyFundationDesc", $langs->transnoentities("Save"))."
\n"; +print $langs->trans("MoreNetworksAvailableWithModule")."
\n"; print "
\n"; diff --git a/htdocs/admin/const.php b/htdocs/admin/const.php index 4bdc418cf65..28f0482f340 100644 --- a/htdocs/admin/const.php +++ b/htdocs/admin/const.php @@ -4,6 +4,7 @@ * Copyright (C) 2005-2012 Regis Houssin * Copyright (C) 2013 Juanjo Menent * Copyright (C) 2024-2025 Frédéric France + * Copyright (C) 2026 MDW * * 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 @@ -259,7 +260,7 @@ $sql .= ", note"; $sql .= ", tms"; $sql .= ", entity"; $sql .= " FROM ".MAIN_DB_PREFIX."const"; -$sql .= " WHERE entity IN (".$db->sanitize($user->entity.",".$conf->entity).")"; +$sql .= " WHERE entity IN (".$db->sanitize($user->entity.",".((int) $conf->entity)).")"; if ((empty($user->entity)/* || $user->admin */) && $debug) { // empty } elseif (!GETPOST('visible') || GETPOST('visible') != 'all') { diff --git a/htdocs/admin/dict.php b/htdocs/admin/dict.php index 0b92b0fa41e..277d106f3f0 100644 --- a/htdocs/admin/dict.php +++ b/htdocs/admin/dict.php @@ -701,7 +701,7 @@ if (empty($sortfield)) { $tmp1 = explode(',', empty($tabcomplete[$keytable]['sqlsort']) ? '' : $tabcomplete[$keytable]['sqlsort']); $tmp2 = explode(' ', $tmp1[0]); $sortfield = preg_replace('/^.*\./', '', $tmp2[0]); - $sortorder = (!empty($tmp2[1]) ? $tmp2[1] : ''); + $sortorder = (!empty($tmp2[1]) ? $tmp2[1] : ''); // @phan-suppress-current-line SqlInjection //var_dump($sortfield); //var_dump($sortorder); } @@ -984,7 +984,7 @@ if (empty($reshook)) { // List of values if ($tabrowid[$id] && !in_array($tabrowid[$id], $listfieldinsert)) { - $sql .= $newid.","; + $sql .= ((int) $newid).","; } $i = 0; foreach ($listfieldinsert as $f => $value) { @@ -2618,6 +2618,10 @@ if ($id > 0) { $valuetoshow = $langs->trans($valuetoshow ? $valuetoshow : $tmpid); } elseif ($tabname[$id] == 'c_exp_tax_cat') { $valuetoshow = $langs->trans($valuetoshow); + } elseif ($value == 'libelle' && ($tabname[$id] == 'c_stcomm' || $tabname[$id] == 'c_stcommcontact')) { + $key = 'StatusProspect'.$obj->rowid; + $trans = $langs->trans($key); + $valuetoshow = ($trans != $key ? $trans : $obj->{$value}); } elseif ($value == 'label' && $tabname[$id] == 'c_units') { $langs->load('other'); $key = $langs->trans($obj->label); diff --git a/htdocs/admin/external_rss.php b/htdocs/admin/external_rss.php index 17e8602a06a..9b30053bee6 100644 --- a/htdocs/admin/external_rss.php +++ b/htdocs/admin/external_rss.php @@ -27,7 +27,7 @@ /** * \file htdocs/admin/external_rss.php * \ingroup external_rss - * \brief Page to setupe module ExternalRss + * \brief Page to setup the ExternalRss module */ // Load Dolibarr environment @@ -89,7 +89,7 @@ if ($action == 'add' || GETPOST("modify")) { $db->begin(); if (GETPOST("modify")) { - // Supprime boite box_external_rss de definition des boites + // Remove box_external_rss from the definition of boxes /* $sql = "UPDATE ".MAIN_DB_PREFIX."boxes_def"; $sql.= " SET name = '".$db->escape($boxlabel)."'"; $sql.= " WHERE file ='box_external_rss.php' AND note LIKE '".$db->escape(GETPOST("norss"))." %'"; @@ -102,7 +102,7 @@ if ($action == 'add' || GETPOST("modify")) { } */ } else { - // Ajoute boite box_external_rss dans definition des boites + // Add box_external_rss to the definition of boxes $sql = "INSERT INTO ".MAIN_DB_PREFIX."boxes_def (file, note)"; $sql .= " VALUES ('box_external_rss.php', '".$db->escape(GETPOSTINT("norss")." (".GETPOST($external_rss_title)).")')"; if (!$db->query($sql)) { @@ -148,7 +148,7 @@ if (GETPOST("delete")) { $obj = $db->fetch_object($resql); $sql = "DELETE FROM ".MAIN_DB_PREFIX."boxes"; - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); $sql .= " AND box_id = ".((int) $obj->rowid); $resql = $db->query($sql); @@ -203,7 +203,7 @@ $linkback = ''; -// Formulaire ajout +// From to add print '
'; print ''; @@ -358,7 +358,7 @@ llxFooter(); $db->close(); /** - * Check if the given RSS feed if inside the list of boxes/widgets + * Check if the given RSS feed is inside the list of boxes/widgets * * @param int $idrss The id of the RSS feed * @param ModeleBoxes[] $boxlist A list with boxes/widgets diff --git a/htdocs/admin/fediverse.php b/htdocs/admin/fediverse.php index fb9259995cf..90601d4dea3 100644 --- a/htdocs/admin/fediverse.php +++ b/htdocs/admin/fediverse.php @@ -160,7 +160,7 @@ if ($action == 'confirm_delete' && GETPOST('confirm') == 'yes') { $db->begin(); $sql = "DELETE FROM ".MAIN_DB_PREFIX."boxes"; - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); $sql .= " AND box_id = ".((int) $key); $resql1 = $db->query($sql); diff --git a/htdocs/admin/index.php b/htdocs/admin/index.php index d8a3cc2a005..26edbd5dc93 100644 --- a/htdocs/admin/index.php +++ b/htdocs/admin/index.php @@ -167,7 +167,7 @@ print '
'; $arrayofeinvoiceneed = array( 'FR' => array('module' => array('einvoice', 'pdpconnectfr'), 'search' => 'e-invoice'), - 'ES' => array('search' => 'veri factu'), + 'ES' => array('search' => 'verifactu'), 'BE' => array('search' => 'peppol'), 'PL' => array('module' => array('ksef'), 'search' => 'ksef') ); @@ -196,7 +196,7 @@ if ($mysoc->country_code && in_array($mysoc->country_code, array_keys($arrayofei print img_picto('', 'bill', 'class="paddingright valignmiddle double"'); print ' '; print '
'.$langs->transnoentities("EInvoice").''; - if ($modulefound) { + if ($modulefound || getDolGlobalString('MAIN_FAKE_EINVOICE_MODULE_FOUND')) { print '

'.$langs->trans("AnEInvoiceModuleHasBeenEnabled", $mysoc->country_code); } else { print '

'.$langs->trans("SetupDescriptionEInvoice", $mysoc->country_code); diff --git a/htdocs/admin/invoice_situation.php b/htdocs/admin/invoice_situation.php index 091bfcba9e3..d5924171102 100644 --- a/htdocs/admin/invoice_situation.php +++ b/htdocs/admin/invoice_situation.php @@ -69,10 +69,19 @@ $form = new Form($db); $formSetup = new FormSetup($db); -// Setup conf MYMODULE_MYPARAM4 : example of quick define write style -$formSetup->newItem('INVOICE_USE_SITUATION') - ->setAsYesNo() - ->nameText = $langs->trans('UseSituationInvoices'); +// INVOICE_USE_SITUATION is a 3-state flag (0=off, 1=cumulative/legacy, 2=progressive), see admin/invoice.php. +// A yes/no toggle can only write 0 or 1 and deletes the const when turned off (ajax_constantonoff calls +// dolibarr_del_const): mode 2 could not be selected and was silently dropped. Use a 3-value select, which +// writes the literal value via dolibarr_set_const and never deletes the const. +$item = $formSetup->newItem('INVOICE_USE_SITUATION'); +$item->setAsSelect(array( + 0 => $langs->trans('Disabled'), + 1 => $langs->trans('SituationInvoiceModeCumulative'), + 2 => $langs->trans('SituationInvoiceModeProgressive'), +)); +$situationModeHelp = $langs->trans('SituationInvoiceModeHelp') + .'
'.$langs->trans('SituationInvoiceModeWarning').''; +$item->nameText = $langs->trans('SituationInvoiceMode').info_admin($situationModeHelp, 0, 0, 'warning', 'clearboth'); $item = $formSetup->newItem('INVOICE_USE_SITUATION_CREDIT_NOTE') ->setAsYesNo() diff --git a/htdocs/admin/limits.php b/htdocs/admin/limits.php index 8aab7d89509..08a67b285dc 100644 --- a/htdocs/admin/limits.php +++ b/htdocs/admin/limits.php @@ -44,7 +44,9 @@ $langs->loadLangs(array('companies', 'products', 'admin')); $action = GETPOST('action', 'aZ09'); $cancel = GETPOST('cancel', 'alpha'); $currencycode = GETPOST('currencycode', 'alpha'); - +if (empty($action)) { + $action = 'edit'; +} if (isModEnabled('multicompany') && getDolGlobalString('MULTICURRENCY_USE_LIMIT_BY_CURRENCY')) { // When MULTICURRENCY_USE_LIMIT_BY_CURRENCY is on, we use always a defined currency code instead of '' even for default. $currencycode = (!empty($currencycode) ? $currencycode : getDolCurrency()); @@ -118,6 +120,8 @@ if ($action == 'update' && !$cancel) { dolibarr_set_const($db, $mainroundingruletot, $valmainroundingruletot, 'chaine', 0, '', $conf->entity); + setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); + header("Location: ".$_SERVER["PHP_SELF"]."?mainmenu=home&leftmenu=setup".(!empty($currencycode) ? '¤cycode='.$currencycode : '')); exit; } @@ -194,8 +198,10 @@ if ($action == 'edit') { print '
'; print ''; + /* print '   '; print ''; + */ print '
'; print '
'; @@ -243,6 +249,8 @@ if (empty($mysoc->country_code)) { // Show examples print load_fiche_titre($langs->trans("ExamplesWithCurrentSetup"), '', ''); + print '
'; + print ''.$langs->trans("NumberFormatForATotalPrice", '1234.56789').': '.price(price2num(1234.56789, 'MT'), 0, $langs, 1, -1, -1, $currencycode)."
\n"; // Always show vat rates with vat 0 @@ -355,6 +363,8 @@ if (empty($mysoc->country_code)) { print ' - '.$langs->trans("VAT").": ".$vat.'%'; print '   ->   '.$langs->trans("TotalPriceAfterRounding").": ".$tmparray[0].' / '.$tmparray[1].' / '.$tmparray[2]."
\n"; } + + print '
'; } // End of page diff --git a/htdocs/admin/mails_emailing.php b/htdocs/admin/mails_emailing.php index 4f914758326..766cc7739bc 100644 --- a/htdocs/admin/mails_emailing.php +++ b/htdocs/admin/mails_emailing.php @@ -4,7 +4,7 @@ * Copyright (C) 2013 Juanjo Menent * Copyright (C) 2016 Jonathan TISSEAU * Copyright (C) 2024-2025 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * * 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 @@ -22,7 +22,7 @@ /** * \file htdocs/admin/mails.php - * \brief Page to setup emails sending + * \brief Email setup page */ // Load Dolibarr environment @@ -798,7 +798,7 @@ if ($action == 'edit') { print dol_get_fiche_head(array(), '', '', -1); - // Cree l'objet formulaire mail + // Create the mail form object include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; $formmail = new FormMail($db); $formmail->trackid = (($action == 'testhtml') ? "testhtml" : "test"); diff --git a/htdocs/admin/mails_passwordreset.php b/htdocs/admin/mails_passwordreset.php index 511e64074d5..85105506afb 100644 --- a/htdocs/admin/mails_passwordreset.php +++ b/htdocs/admin/mails_passwordreset.php @@ -4,7 +4,7 @@ * Copyright (C) 2013 Juanjo Menent * Copyright (C) 2016 Jonathan TISSEAU * Copyright (C) 2024-2025 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * * 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 @@ -849,7 +849,7 @@ if ($action == 'edit') { print dol_get_fiche_head(array(), '', '', -1); - // Cree l'objet formulaire mail + // Create the email form object include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; $formmail = new FormMail($db); $formmail->fromname = (GETPOSTISSET('fromname') ? GETPOST('fromname', 'restricthtml') : getDolGlobalString('MAIN_MAIL_EMAIL_FROM')); diff --git a/htdocs/admin/mails_templates.php b/htdocs/admin/mails_templates.php index 9861f1b4d9b..75a6e2fadad 100644 --- a/htdocs/admin/mails_templates.php +++ b/htdocs/admin/mails_templates.php @@ -776,6 +776,9 @@ if ($sortorder) { if ($sortfield) { $paramwithsearch .= '&sortfield='.urlencode($sortfield); } +if ($limit) { + $paramwithsearch .= '&limit='.((int) $limit); +} if (GETPOST('from', 'alpha')) { $paramwithsearch .= '&from='.urlencode(GETPOST('from', 'alpha')); } @@ -813,7 +816,6 @@ if ($action != 'create') { if (!empty($user->admin) && (empty($_SESSION['leftmenu']) || $_SESSION['leftmenu'] != 'email_templates')) { print load_fiche_titre($title, '', $titlepicto); } else { - //print load_fiche_titre($title, $newcardbutton, $titlepicto); print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'tools', 0, $newcardbutton, '', $limit, 'limit', 0, 1); } @@ -1173,11 +1175,11 @@ foreach ($fieldlist as $field => $value) { if ($sortfieldtouse == 'type_template') { $sortfieldtouse .= ',lang,position,label'; } - print getTitleFieldOfList($valuetoshow, 0, $_SERVER["PHP_SELF"], $sortfieldtouse, ($page ? 'page='.$page.'&' : ''), $param, '', $sortfield, $sortorder, $css.' '); + print getTitleFieldOfList($valuetoshow, 0, $_SERVER["PHP_SELF"], $sortfieldtouse, ($page ? 'page='.$page.'&' : ''), $paramwithsearch, '', $sortfield, $sortorder, $css.' '); } } -print getTitleFieldOfList($langs->trans("Status"), 0, $_SERVER["PHP_SELF"], "active", ($page ? 'page='.$page.'&' : ''), $param, '', $sortfield, $sortorder, 'center '); +print getTitleFieldOfList($langs->trans("Status"), 0, $_SERVER["PHP_SELF"], "active", ($page ? 'page='.$page.'&' : ''), $paramwithsearch, '', $sortfield, $sortorder, 'center '); // Action column if (!$conf->main_checkbox_left_column) { print getTitleFieldOfList(''); diff --git a/htdocs/admin/mails_ticket.php b/htdocs/admin/mails_ticket.php index 91e57a9475d..5942f3ef25c 100644 --- a/htdocs/admin/mails_ticket.php +++ b/htdocs/admin/mails_ticket.php @@ -4,7 +4,7 @@ * Copyright (C) 2013 Juanjo Menent * Copyright (C) 2016 Jonathan TISSEAU * Copyright (C) 2024-2025 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * * 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 @@ -794,7 +794,7 @@ if ($action == 'edit') { print dol_get_fiche_head(array(), '', '', -1); - // Cree l'objet formulaire mail + // Create the mail form object include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; $formmail = new FormMail($db); $formmail->fromname = (GETPOSTISSET('fromname') ? GETPOST('fromname', 'restricthtml') : getDolGlobalString('MAIN_MAIL_EMAIL_FROM')); diff --git a/htdocs/admin/menus/index.php b/htdocs/admin/menus/index.php index 4ca16a08a84..3d689d7122f 100644 --- a/htdocs/admin/menus/index.php +++ b/htdocs/admin/menus/index.php @@ -3,7 +3,7 @@ * Copyright (C) 2007-2012 Laurent Destailleur * Copyright (C) 2009-2012 Regis Houssin * Copyright (C) 2019-2025 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -100,9 +100,9 @@ if ($action == 'up') { // Menu before $sql = "SELECT m.rowid, m.position"; $sql .= " FROM ".MAIN_DB_PREFIX."menu as m"; - $sql .= " WHERE (m.position < ".($current['order'])." OR (m.position = ".($current['order'])." AND rowid < ".GETPOSTINT("menuId")."))"; + $sql .= " WHERE (m.position < ".((int) $current['order'])." OR (m.position = ".((int) $current['order'])." AND rowid < ".GETPOSTINT("menuId")."))"; $sql .= " AND m.menu_handler='".$db->escape($menu_handler_to_search)."'"; - $sql .= " AND m.entity = ".$conf->entity; + $sql .= " AND m.entity = ".((int) $conf->entity); $sql .= " AND m.type = '".$db->escape($current['type'])."'"; $sql .= " AND m.fk_menu = '".$db->escape((string) $current['fk_menu'])."'"; $sql .= " ORDER BY m.position, m.rowid"; @@ -151,9 +151,9 @@ if ($action == 'up') { // Menu after $sql = "SELECT m.rowid, m.position"; $sql .= " FROM ".MAIN_DB_PREFIX."menu as m"; - $sql .= " WHERE (m.position > ".($current['order'])." OR (m.position = ".($current['order'])." AND rowid > ".GETPOSTINT("menuId")."))"; + $sql .= " WHERE (m.position > ".((int) $current['order'])." OR (m.position = ".((int) $current['order'])." AND rowid > ".GETPOSTINT("menuId")."))"; $sql .= " AND m.menu_handler='".$db->escape($menu_handler_to_search)."'"; - $sql .= " AND m.entity = ".$conf->entity; + $sql .= " AND m.entity = ".((int) $conf->entity); $sql .= " AND m.type = '".$db->escape($current['type'])."'"; $sql .= " AND m.fk_menu = '".$db->escape((string) $current['fk_menu'])."'"; $sql .= " ORDER BY m.position, m.rowid"; @@ -291,7 +291,7 @@ $data[] = array('rowid' => 0, 'fk_menu' => -1, 'title' => 'racine', 'mainmenu' = $sql = "SELECT m.rowid, m.titre, m.langs, m.mainmenu, m.leftmenu, m.fk_menu, m.fk_mainmenu, m.fk_leftmenu, m.position, m.module"; $sql .= " FROM ".MAIN_DB_PREFIX."menu as m"; $sql .= " WHERE menu_handler = '".$db->escape($menu_handler_to_search)."'"; -$sql .= " AND entity = ".$conf->entity; +$sql .= " AND entity = ".((int) $conf->entity); //$sql.= " AND fk_menu >= 0"; $sql .= " ORDER BY m.position, m.rowid"; // Order is position then rowid (because we need a sort criteria when position is same) diff --git a/htdocs/admin/modules.php b/htdocs/admin/modules.php index 839dc44755c..1dc19ff1f1a 100644 --- a/htdocs/admin/modules.php +++ b/htdocs/admin/modules.php @@ -9,7 +9,7 @@ * Copyright (C) 2015 Raphaël Doursenaud * Copyright (C) 2018 Nicolas ZABOURI * Copyright (C) 2021-2026 Frédéric France - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2026 Charlene Benke * * This program is free software; you can redistribute it and/or modify @@ -432,8 +432,7 @@ if ($action == 'install' && $allowonlineinstall) { 'search_nature' => '-1', 'search_version' => '-1' ); - $queryString = http_build_query($searchParams); - $redirectUrl = DOL_URL_ROOT . '/admin/modules.php?' . $queryString; + $redirectUrl = dolBuildUrl(DOL_URL_ROOT . '/admin/modules.php', $searchParams); $message = $langs->trans("SetupIsReadyForUse", $redirectUrl, $langs->transnoentitiesnoconv("Home").' - '.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Modules")); @@ -1255,7 +1254,7 @@ if ($mode == 'common' || $mode == 'commonkanban') { } $urltogo = $_SERVER["PHP_SELF"].'?id='.$objMod->numero.'&token='.newToken().'&module_position='.$module_position.'&action=set&token='.newToken().'&value='.$modName.'&mode='.$mode.$param; - $popupWidth = 500; + $popupWidth = 600; $popupHeight = 300; $codeenabledisable .= ''."\n"; $codeenabledisable .= ''; print img_picto(($objMod->isCoreOrExternalModule() == 'external' ? $langs->trans("ExternalModule").' - ' : '').$langs->trans("ClickToShowDescription"), $imginfo, '', 0, 0, 0, '', 'purple'); print ''; - print ($timestoinit[$modName] > 500 ? img_picto($langs->trans('InitModuleIsSlow'), 'fa-exclamation-circle') : ''); + print($timestoinit[$modName] > 500 ? img_picto($langs->trans('InitModuleIsSlow'), 'fa-exclamation-circle') : ''); print ''; // Version @@ -1492,11 +1491,11 @@ if ($mode == 'marketplace') { '; - $totalnboflines .= $langs->trans("itemFound", $remotestore->numberTotalOfProducts); - $totalnboflines .= ''; + $totalnboflines .= $langs->trans("itemFound", $remotestore->numberTotalOfProducts); + $totalnboflines .= ''; - print $totalnboflines; - print $remotestore->getPagination(); + print $totalnboflines; + print $remotestore->getPagination(); print ''; print ''; diff --git a/htdocs/admin/openinghours.php b/htdocs/admin/openinghours.php index 62957712aca..b3560280d6f 100644 --- a/htdocs/admin/openinghours.php +++ b/htdocs/admin/openinghours.php @@ -24,12 +24,6 @@ // Load Dolibarr environment require '../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; - -$action = GETPOST('action', 'aZ09'); -$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'adminaccoutant'; // To manage different context of search - /** * @var Conf $conf * @var DoliDB $db @@ -37,6 +31,11 @@ $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'ad * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; + +$action = GETPOST('action', 'aZ09'); +$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'adminaccoutant'; // To manage different context of search // Load translation files required by the page $langs->loadLangs(array('admin', 'companies', 'other')); @@ -105,8 +104,8 @@ $head = company_admin_prepare_head(); print dol_get_fiche_head($head, 'openinghours', '', -1, ''); -print ''.$langs->trans("OpeningHoursDesc")."
\n"; -print "

\n"; +print '
'.$langs->trans("OpeningHoursDesc")."
\n"; +print "
\n"; if (empty($action) || $action == 'edit' || $action == 'updateedit') { /** diff --git a/htdocs/admin/perms.php b/htdocs/admin/perms.php index bdffba3dffe..c70ec80e72f 100644 --- a/htdocs/admin/perms.php +++ b/htdocs/admin/perms.php @@ -3,7 +3,7 @@ * Copyright (C) 2004-2009 Laurent Destailleur * Copyright (C) 2005-2013 Regis Houssin * Copyright (C) 2011 Herve Prot - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -58,14 +58,14 @@ if (!$user->admin) { if ($action == 'add') { $sql = "UPDATE ".MAIN_DB_PREFIX."rights_def SET bydefault=1"; $sql .= " WHERE id = ".GETPOSTINT("pid"); - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); $db->query($sql); } if ($action == 'remove') { $sql = "UPDATE ".MAIN_DB_PREFIX."rights_def SET bydefault=0"; $sql .= " WHERE id = ".GETPOSTINT('pid'); - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); $db->query($sql); } diff --git a/htdocs/admin/prelevement.php b/htdocs/admin/prelevement.php index 77de8cab34b..a248badc013 100644 --- a/htdocs/admin/prelevement.php +++ b/htdocs/admin/prelevement.php @@ -5,7 +5,7 @@ * Copyright (C) 2010-2013 Juanjo Menent * Copyright (C) 2019 Markus Welters * Copyright (C) 2024-2026 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * * 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 @@ -483,7 +483,7 @@ if (isModEnabled('notification') ) $sql.= " ".MAIN_DB_PREFIX."c_action_trigger as ad"; $sql.= " WHERE u.rowid = nd.fk_user"; $sql.= " AND nd.fk_action = ad.rowid"; - $sql.= " AND u.entity IN (0,".$conf->entity.")"; + $sql.= " AND u.entity IN (0,".((int) $conf->entity).")"; $resql = $db->query($sql); if ($resql) diff --git a/htdocs/admin/security_file.php b/htdocs/admin/security_file.php index 1e5bdafa65c..dc025221d8a 100644 --- a/htdocs/admin/security_file.php +++ b/htdocs/admin/security_file.php @@ -33,6 +33,8 @@ require '../main.inc.php'; * @var HookManager $hookmanager * @var Translate $langs * @var User $user + * + * @var string $dolibarr_main_restrict_os_commands */ require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; diff --git a/htdocs/admin/sms.php b/htdocs/admin/sms.php index e4c6d73a0a0..e2c5e2da769 100644 --- a/htdocs/admin/sms.php +++ b/htdocs/admin/sms.php @@ -3,6 +3,7 @@ * Copyright (C) 2009 Regis Houssin * Copyright (C) 2013 Juanjo Menent * Copyright (C) 2020-2025 Frédéric France + * Copyright (C) 2026 MDW * * 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 @@ -20,7 +21,7 @@ /** * \file htdocs/admin/sms.php - * \brief Page to setup emails sending + * \brief Page to set up SMS sending */ // Load Dolibarr environment @@ -267,12 +268,12 @@ if ($action == 'edit') { print ''; - // Affichage formulaire de TEST simple + // Display simple test form if ($action == 'test') { print '
'; print load_fiche_titre($langs->trans("DoTestSend")); - // Cree l'objet formulaire mail + // Create SMS form object include_once DOL_DOCUMENT_ROOT.'/core/class/html.formsms.class.php'; $formsms = new FormSms($db); $formsms->fromtype = 'user'; diff --git a/htdocs/admin/subcontractors.php b/htdocs/admin/subcontractors.php index b8a7aa18d83..b0d6f8c35af 100644 --- a/htdocs/admin/subcontractors.php +++ b/htdocs/admin/subcontractors.php @@ -148,13 +148,11 @@ $head = company_admin_prepare_head(); print dol_get_fiche_head($head, 'subcontractors', '', -1, ''); $form = new Form($db); -$formother = new FormOther($db); $formcompany = new FormCompany($db); -$countrynotdefined = ''.$langs->trans("ErrorSetACountryFirst").' ('.$langs->trans("SeeAbove").')'; -print ''.$langs->trans("SubcontractorsDesc")."
\n"; -print "

\n"; +print '
'.$langs->trans("SubcontractorsDesc")."
\n"; +print "
\n"; /** * Edit parameters diff --git a/htdocs/admin/subtotals.php b/htdocs/admin/subtotals.php index fd53f2c5d75..eaf907d9a7e 100644 --- a/htdocs/admin/subtotals.php +++ b/htdocs/admin/subtotals.php @@ -195,12 +195,12 @@ if (empty($conf->use_javascript_ajax)) { print $value_subtotal == 0 ? img_picto($langs->trans("Disabled"), 'switch_off') : img_picto($langs->trans("Enabled"), 'switch_on') . ''; print ''; - print '
'; print ''; print ''; +if ($db->type == 'mysqli') { + $functions = ["mysqli_connect"]; + $name = "MySQLi"; + + print ""; + print ""; + print getResultColumn($name, $activatedExtensions, $loadedExtensions, $functions); + print ""; +} + $functions = ["mb_check_encoding"]; $name = "MBString"; diff --git a/htdocs/admin/tools/export.php b/htdocs/admin/tools/export.php index af4991ac757..4450ac53ff3 100644 --- a/htdocs/admin/tools/export.php +++ b/htdocs/admin/tools/export.php @@ -127,7 +127,8 @@ $lowmemorydump = (int) (GETPOSTISSET("lowmemorydump") ? GETPOSTINT("lowmemorydum // MYSQL if ($what == 'mysql') { $cmddump = GETPOST("mysqldump", 'none'); // Do not sanitize here with 'alpha', will be sanitize later by dol_sanitizePathName and escapeshellarg - $cmddump = dol_sanitizePathName($cmddump); + $cmddump = dol_sanitizePathName($cmddump); // Sanitize path + $cmddump = dol_string_nospecial($cmddump, '', array("|", ";", "<", ">", "&", "+")); // Sanitize command $basenamecmddump = basename(str_replace('\\', '/', $cmddump)); // Add a fallback when we detect something wrong with the path of the dump command @@ -136,21 +137,12 @@ if ($what == 'mysql') { $reg = array(); if (preg_match('/mysqldump(\.exe)?$/', $cmddump, $reg)) { // And if command ends with mysqldump $cmddump = 'mysqldump'.(empty($reg[1]) ? '' : $reg[1]); // Then we try the command with no forced path + $langs->load("errors"); + setEventMessage($langs->trans("WarningFullPathWasNotFoundSoWeUseAnAlternativeValue", $cmddump), 'warnings'); } } } - if (!empty($dolibarr_main_restrict_os_commands)) { - $arrayofallowedcommand = explode(',', $dolibarr_main_restrict_os_commands); - $arrayofallowedcommand = array_map('trim', $arrayofallowedcommand); - dol_syslog("Command are restricted to ".$dolibarr_main_restrict_os_commands.". We check that one of this command is inside ".$cmddump); - if (!in_array($basenamecmddump, $arrayofallowedcommand)) { // the provided command $cmddump must be an allowed command - $langs->load("errors"); - $errormsg = $langs->trans('CommandIsNotInsideAllowedCommands'); - $errormsg .= '
'.$langs->trans('ErrorCheckTheCommandInsideTheAdvancedOptions'); - } - } - if (!$errormsg && $cmddump) { dolibarr_set_const($db, 'SYSTEMTOOLS_MYSQLDUMP', $cmddump, 'chaine', 0, '', 0); } @@ -177,6 +169,7 @@ if ($what == 'mysqlnobin') { if ($what == 'postgresql') { $cmddump = GETPOST("postgresqldump", 'none'); // Do not sanitize here with 'alpha', will be sanitize later by dol_sanitizePathName and escapeshellarg $cmddump = dol_sanitizePathName($cmddump); + $cmddump = dol_string_nospecial($cmddump, '', array("|", ";", "<", ">", "&", "+")); // Sanitize command /* Not required, the command is output on screen but not ran for pgsql if (!empty($dolibarr_main_restrict_os_commands)) diff --git a/htdocs/admin/tools/export_files.php b/htdocs/admin/tools/export_files.php index 4364c6d7ad0..631c7dcb99c 100644 --- a/htdocs/admin/tools/export_files.php +++ b/htdocs/admin/tools/export_files.php @@ -30,11 +30,6 @@ if (! defined('CSRFCHECK_WITH_TOKEN')) { // Load Dolibarr environment require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/utils.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -42,6 +37,10 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/utils.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; $langs->load("admin"); diff --git a/htdocs/admin/tools/listevents.php b/htdocs/admin/tools/listevents.php index fd92cb142ca..8af676aa268 100644 --- a/htdocs/admin/tools/listevents.php +++ b/htdocs/admin/tools/listevents.php @@ -3,7 +3,7 @@ * Copyright (C) 2005-2012 Regis Houssin * Copyright (C) 2015 Bahfir Abbes * Copyright (C) 2018-2025 Frédéric France - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -178,7 +178,7 @@ if ($action == 'confirm_purge' && $confirm == 'yes' && $user->admin) { // Delete events $sql = "DELETE FROM ".MAIN_DB_PREFIX."events"; - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); dol_syslog("listevents purge", LOG_DEBUG); $resql = $db->query($sql); @@ -224,7 +224,7 @@ $sql .= " u.login, u.admin, u.email, u.entity, u.firstname, u.lastname, u.gender $sql .= " FROM ".MAIN_DB_PREFIX."events as e"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u ON u.rowid = e.fk_user"; if ($search_entity > 0) { - $sql .= " WHERE e.entity = ".((int) $search_entity).")"; + $sql .= " WHERE e.entity = ".((int) $search_entity); } else { $sql .= " WHERE e.entity IN (".getEntity('event', (GETPOSTINT('search_current_entity') ? 0 : 1)).")"; } diff --git a/htdocs/admin/tools/listsessions.php b/htdocs/admin/tools/listsessions.php index a5ced26531b..13d1dab89e2 100644 --- a/htdocs/admin/tools/listsessions.php +++ b/htdocs/admin/tools/listsessions.php @@ -157,7 +157,7 @@ if ($savehandler == 'files') { print ''; // Login - print ''; + print ''; // ID print ''; // Login - print ''; + print ''; // ID print ''; // Remote IP - print ''; + print ''; // User Agent - print ''; + print ''; print ''; print "\n"; $i++; diff --git a/htdocs/admin/translation.php b/htdocs/admin/translation.php index 557f23a2c8e..db69903a22f 100644 --- a/htdocs/admin/translation.php +++ b/htdocs/admin/translation.php @@ -278,7 +278,7 @@ if ($transvalue) { } -print ''; +print ''; if ($optioncss != '') { print ''; } diff --git a/htdocs/admin/website.php b/htdocs/admin/website.php index fcc04d8f66d..ef9faadc821 100644 --- a/htdocs/admin/website.php +++ b/htdocs/admin/website.php @@ -205,7 +205,7 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) { // List of values if ($tabrowid[$id] && !in_array($tabrowid[$id], $listfieldinsert)) { - $sql .= $newid.","; + $sql .= ((int) $newid).","; } $i = 0; foreach ($listfieldinsert as $f => $value) { diff --git a/htdocs/ai/css/ai_assistant.css b/htdocs/ai/css/ai_assistant.css index 1bf80b2f659..16c298361b4 100644 --- a/htdocs/ai/css/ai_assistant.css +++ b/htdocs/ai/css/ai_assistant.css @@ -616,6 +616,15 @@ body.ai-fullpage { } /* Input Field */ +#user-input::placeholder { + font-size: 0.95rem; +} +@media (max-width: 768px) { + #user-input::placeholder { + opacity: 0; + } +} + #user-input { flex-grow: 1; padding: 11px 15px; @@ -744,7 +753,7 @@ body.ai-fullpage { } /* Helpers */ -.hidden { +.ai-hidden { display: none !important; } @@ -964,7 +973,7 @@ body.ai-fullpage { position: fixed; top: var(--ai-popover-top, 48px); right: 12px; - width: 380px; + width: 460px; height: min(560px, calc(100vh - var(--ai-popover-top, 48px) - 16px)); z-index: 1010; display: flex; diff --git a/htdocs/ai/js/ai_assistant.js b/htdocs/ai/js/ai_assistant.js index a0dc7f0632f..af92d8de282 100644 --- a/htdocs/ai/js/ai_assistant.js +++ b/htdocs/ai/js/ai_assistant.js @@ -175,8 +175,8 @@ export function initAiAssistant(container) { */ function updateInterfaceMode(mode) { // Reset all wrappers - micWrapper.classList.add('hidden'); - uploadWrapper.classList.add('hidden'); + micWrapper.classList.add('ai-hidden'); + uploadWrapper.classList.add('ai-hidden'); if (mode === 'text') { input.placeholder = t('TypeYourQuestion'); @@ -184,13 +184,13 @@ export function initAiAssistant(container) { } else if (mode === 'cloud' || mode === 'whisper') { // Voice Modes - micWrapper.classList.remove('hidden'); + micWrapper.classList.remove('ai-hidden'); input.placeholder = 'Type or speak...'; initEngine(mode); } else if (mode === 'local_docs' || mode === 'cloud_docs') { // Document Modes - uploadWrapper.classList.remove('hidden'); + uploadWrapper.classList.remove('ai-hidden'); input.placeholder = mode === 'local_docs' ? t('UploadLocalDoc') : t('UploadCloudDoc'); diff --git a/htdocs/ai/lib/ai.lib.php b/htdocs/ai/lib/ai.lib.php index 05c95c03182..81c79806ff5 100644 --- a/htdocs/ai/lib/ai.lib.php +++ b/htdocs/ai/lib/ai.lib.php @@ -616,7 +616,7 @@ function getAiChatAssistantConfig() */ function getAiChatAssistantHtml($mode = 'page') { - global $langs, $user; + global $langs, $user, $conf; $out = ''; @@ -711,16 +711,20 @@ function getAiChatAssistantHtml($mode = 'page') $out .= '
'; $out .= '
'; // Upload Wrapper (Visible only in Doc modes) - $out .= '
'.$langs->trans('ProductUsage').''.$langs->trans('AccountancyCodeToSet').'...
'.$langs->trans('OptionModeProductSellDesc'); @@ -619,7 +618,7 @@ if ($resql) { print ''; print ''; $listofvals = array('withoutvalidaccount' => $langs->trans("WithoutValidAccount"), 'withvalidaccount' => $langs->trans("WithValidAccount")); - print ' '.$langs->trans("or").' '.$form->selectarray('search_current_account_valid', $listofvals, $search_current_account_valid, 1); + print ' '.$langs->trans("or").' '.$form->selectarray('search_current_account_valid', $listofvals, $search_current_account_valid, 1); print ' '; - print ''; - print ''; + print ''; + print ''; - print ''; - print ''; + print ''; + print ''; - print '
'; - print $form->selectDate($search_date_start ? $search_date_start : -1, 'search_date_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); - print '
'; - print '
'; - print $form->selectDate($search_date_end ? $search_date_end : -1, 'search_date_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); - print '
'; - print '
'; + print '
'; + print $form->selectDate($search_date_start ? $search_date_start : -1, 'search_date_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); + print '
'; + print '
'; + print $form->selectDate($search_date_end ? $search_date_end : -1, 'search_date_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); + print '
'; + print '
'; - print ''; - print ''; + print ''; + print ''; - print ''; - print ''; + print ''; + print ''; - print ''; - print ''; + print ''; + print ''; - print ''; - print ''; + print ''; + print ''; - print ''; - print ''; + print ''; + print ''; - print $form->select_country($search_country, 'search_country', '', 0, 'maxwidth125', 'code2', 1, 0, 1); - print ''; + print $form->select_country($search_country, 'search_country', '', 0, 'maxwidth125', 'code2', 1, 0, 1); + print ''; - print ''; - print ''; + print ''; + print '
'.$langs->trans('MonthShort'.str_pad((string) $j, 2, '0', STR_PAD_LEFT)).''.$langs->trans('MonthShort'.str_pad((string) ((int) $j), 2, '0', STR_PAD_LEFT)).''.$langs->trans("Total").'
'.$langs->trans('MonthShort'.str_pad((string) $j, 2, '0', STR_PAD_LEFT)).''.$langs->trans('MonthShort'.str_pad((string) ((int) $j), 2, '0', STR_PAD_LEFT)).''.$langs->trans("Total").'
'; $selected = in_array($objp->rowid."_".$i, $toselect); - print ''; print ''; + print ''; $can_modify = !($value_subtotal == 0 && $value_title == 0); print '
'; print ''; print ''; - print ''; print $can_modify ? '' : ''; diff --git a/htdocs/admin/system/constall.php b/htdocs/admin/system/constall.php index ceea3ec95ba..77dd012fd04 100644 --- a/htdocs/admin/system/constall.php +++ b/htdocs/admin/system/constall.php @@ -2,7 +2,7 @@ /* Copyright (C) 2003 Rodolphe Quiedeville * Copyright (C) 2004-2011 Laurent Destailleur * Copyright (C) 2009 Regis Houssin - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024-2025 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -231,11 +231,11 @@ $sql .= ", entity"; $sql .= " FROM ".MAIN_DB_PREFIX."const"; if (!isModEnabled('multicompany')) { // If no multicompany mode, admins can see global and their constantes - $sql .= " WHERE entity IN (0,".$conf->entity.")"; + $sql .= " WHERE entity IN (0,".((int) $conf->entity).")"; } else { // If multicompany mode, superadmin (user->entity=0) can see everything, admin are limited to their entities. if ($user->entity) { - $sql .= " WHERE entity IN (".$db->sanitize($user->entity.",".$conf->entity).")"; + $sql .= " WHERE entity IN (".$db->sanitize($user->entity.",".((int) $conf->entity)).")"; } } $sql .= " ORDER BY entity, name ASC"; diff --git a/htdocs/admin/system/dbtable.php b/htdocs/admin/system/dbtable.php index f1d01ae598e..64d174b2869 100644 --- a/htdocs/admin/system/dbtable.php +++ b/htdocs/admin/system/dbtable.php @@ -4,7 +4,7 @@ * Copyright (C) 2004 Sebastien Di Cintio * Copyright (C) 2004 Benoit Mortier * Copyright (C) 2005-2012 Regis Houssin - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -67,7 +67,7 @@ if ($action == 'convertutf8') { while ($i < $num) { $row = $db->fetch_row($resql); if ($row[0] == $field) { - $sql = "ALTER TABLE ".$db->sanitize($table)." MODIFY ".$db->sanitize($row[0])." ".$row[1]." CHARACTER SET utf8"; // We must not sanitize the $row[1] + $sql = "ALTER TABLE ".$db->sanitize($table)." MODIFY ".$db->sanitize($row[0])." ".$row[1]." CHARACTER SET utf8"; // We must not sanitize the $row[1] @phan-suppress-current-line SqlInjection $logsql .= $sql.'
'; $db->query($sql); @@ -78,7 +78,7 @@ if ($action == 'convertutf8') { $collation = 'utf8_general_ci'; } - $sql = "ALTER TABLE ".$db->sanitize($table)." MODIFY ".$db->sanitize($row[0])." ".$row[1]." COLLATE ".$db->sanitize($collation); // We must not sanitize the $row[1] + $sql = "ALTER TABLE ".$db->sanitize($table)." MODIFY ".$db->sanitize($row[0])." ".$row[1]." COLLATE ".$db->sanitize($collation); // We must not sanitize the $row[1] @phan-suppress-current-line SqlInjection $logsql .= $sql.'
'; $resql2 = $db->query($sql); @@ -103,7 +103,7 @@ if ($action == 'convertutf8mb4') { while ($i < $num) { $row = $db->fetch_row($resql); if ($row[0] == $field) { - $sql = "ALTER TABLE ".$db->sanitize($table)." MODIFY ".$db->sanitize($row[0])." ".$row[1]." CHARACTER SET utf8mb4"; // We must not sanitize the $row[1] + $sql = "ALTER TABLE ".$db->sanitize($table)." MODIFY ".$db->sanitize($row[0])." ".$row[1]." CHARACTER SET utf8mb4"; // We must not sanitize the $row[1] @phan-suppress-current-line SqlInjection $logsql .= $sql.'
'; $db->query($sql); @@ -114,7 +114,7 @@ if ($action == 'convertutf8mb4') { $collation = 'utf8mb4_general_ci'; } - $sql = "ALTER TABLE ".$db->sanitize($table)." MODIFY ".$db->sanitize($row[0])." ".$row[1]." COLLATE ".$db->sanitize($collation); // We must not sanitize the $row[1] + $sql = "ALTER TABLE ".$db->sanitize($table)." MODIFY ".$db->sanitize($row[0])." ".$row[1]." COLLATE ".$db->sanitize($collation); // We must not sanitize the $row[1] @phan-suppress-current-line SqlInjection $logsql .= $sql.'
'; $resql2 = $db->query($sql); diff --git a/htdocs/admin/system/dolibarr.php b/htdocs/admin/system/dolibarr.php index 6aee101399f..ce38d2cd1e9 100644 --- a/htdocs/admin/system/dolibarr.php +++ b/htdocs/admin/system/dolibarr.php @@ -2,7 +2,7 @@ /* Copyright (C) 2005-2020 Laurent Destailleur * Copyright (C) 2007 Rodolphe Quiedeville * Copyright (C) 2007-2012 Regis Houssin - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024-2026 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -49,7 +49,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; require_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php'; // Load translation files required by the page -$langs->loadLangs(array("install", "other", "admin")); +$langs->loadLangs(array("blockedlog", "install", "other", "admin")); $action = GETPOST('action', 'aZ09'); @@ -595,11 +595,11 @@ $sql .= ", entity"; $sql .= " FROM ".MAIN_DB_PREFIX."const"; if (!isModEnabled('multicompany')) { // If no multicompany mode, admins can see global and their constantes - $sql .= " WHERE entity IN (0,".$conf->entity.")"; + $sql .= " WHERE entity IN (0,".((int) $conf->entity).")"; } else { // If multicompany mode, superadmin (user->entity=0) can see everything, admin are limited to their entities. if ($user->entity) { - $sql .= " WHERE entity IN (".$db->sanitize($user->entity.",".$conf->entity).")"; + $sql .= " WHERE entity IN (".$db->sanitize($user->entity.",".((int) $conf->entity)).")"; } } $sql .= " ORDER BY entity, name ASC"; diff --git a/htdocs/admin/system/phpinfo.php b/htdocs/admin/system/phpinfo.php index 552c3c175d5..107792f71be 100644 --- a/htdocs/admin/system/phpinfo.php +++ b/htdocs/admin/system/phpinfo.php @@ -23,14 +23,11 @@ /** * \file htdocs/admin/system/phpinfo.php - * \brief Page des infos systeme de php + * \brief Page of PHP system information */ // Load Dolibarr environment require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -38,6 +35,8 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; $langs->loadLangs(array("admin", "install", "errors")); @@ -154,6 +153,16 @@ print '
'.$langs->trans("Extension").''.$langs->trans("Test").'
".$name."
'.$sessionentry['login'].''.dol_escape_htmltag($sessionentry['login']).''; @@ -207,7 +207,7 @@ if ($savehandler == 'files') { print '
'.$sessionentry['login'].''.dol_escape_htmltag($sessionentry['login']).''; @@ -228,10 +228,10 @@ if ($savehandler == 'files') { print ''.$sessionentry['age'].''.$sessionentry['remote_ip'].''.dol_escape_htmltag((string) $sessionentry['remote_ip']).''.$sessionentry['user_agent'].''.dol_escape_htmltag((string) $sessionentry['user_agent']).' 
'; print ''; print ""; -print '"; +print ''; print ""; print ""; diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index 363156fc27c..ff637e677e6 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -5,7 +5,7 @@ * Copyright (C) 2016 Jean-François Ferry * Copyright (C) 2023 Romain Neil * Copyright (C) 2024-2025 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * Copyright (C) 2025 William Mead * Copyright (C) 2025-2026 Charlene Benke * @@ -50,7 +50,7 @@ class Documents extends DolibarrApi /** - * Download a document + * Download a given document * * Note that, this API is similar to using the wrapper link "documents.php" to download a file (used for * internal HTML links of documents into application), but with no need to have a session cookie (the token is used instead). @@ -432,7 +432,7 @@ class Documents extends DolibarrApi } /** - * List documents of an element + * List all documents of an element * * Use element ID or Ref. * Supported modules: thirdparty, user, member, proposal, order, supplier_order, shipment, invoice, supplier_invoice, product, event, expensereport, knowledgemanagement, category, contract, project, project_task @@ -449,22 +449,19 @@ class Documents extends DolibarrApi * @param string $content_type Filter on content-type (example 'application/pdf' or 'application/pdf,image/jpeg')) * @param bool $pagination_data If this parameter is set to true the response will include pagination data. Default value is false. Page starts from 0* * @return array Array of documents with path - * @phan-return array> + * @phan-return array{data:array,pagination:array{total:int,page:int,page_count:int,limit:int}}|array * @phpstan-return array> * * @url GET / * * @throws RestException 400 Bad value for parameter modulepart, id or ref * @throws RestException 403 Access denied - * @throws RestException 404 Thirdparty, User, Member, Order, Invoice or Proposal not found + * @throws RestException 404 Object (Thirdparty, User, Member, Order, Invoice, Proposal...) not found * @throws RestException 500 Error while fetching object - * @throws RestException 503 Error when retrieve ecm list + * @throws RestException 503 Error when retrieving ecm list */ public function getDocumentsListByElement($modulepart, $id = 0, $ref = '', $sortfield = '', $sortorder = '', $limit = 100, $page = 0, $content_type = '', $pagination_data = false) { - global $conf; - /** @var Conf $conf */ - if (empty($modulepart)) { throw new RestException(400, 'bad value for parameter modulepart'); } @@ -474,372 +471,155 @@ class Documents extends DolibarrApi } $id = (empty($id) ? 0 : $id); - $recursive = 0; - $type = 'files'; + + // Define $object + $object = fetchObjectByElement($id, $modulepart, $ref); + if (!is_object($object)) { + throw new RestException(404, 'Object with (id, ref) = ('.$id.', '.$ref.') not found or not allowed for modulepart = '.$modulepart); + } + + // Define $upload_dir to scan + $upload_dir = getMultidirOutput($object, '', 1); + + // Check object-level permissions + $ok = checkUserAccessToObject(DolibarrApiAccess::$user, array($object->element), $object, $object->table_element, ''); + if (empty($ok)) { + throw new RestException(403, 'Access not allowed to this object (refused by checkUserAccessToObject)'); + } + + // Check permission on module if ($modulepart == 'societe' || $modulepart == 'thirdparty') { - require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; - if (!DolibarrApiAccess::$user->hasRight('societe', 'lire')) { throw new RestException(403); } - - $object = new Societe($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Thirdparty not found'); - } - - $upload_dir = $conf->societe->multidir_output[$object->entity ?? $conf->entity]."/".$object->id; } elseif ($modulepart == 'user') { - require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; - // Can get doc if has permission to read all user or if it is user itself if (!DolibarrApiAccess::$user->hasRight('user', 'user', 'lire') && DolibarrApiAccess::$user->id != $id) { throw new RestException(403); } - - $object = new User($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'User not found'); - } - - $upload_dir = getMultidirOutput($object) . '/'.get_exdir(0, 0, 0, 0, $object, 'user').'/'.$object->id; } elseif ($modulepart == 'adherent' || $modulepart == 'member') { - require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; - if (!DolibarrApiAccess::$user->hasRight('adherent', 'lire')) { throw new RestException(403); } - - $object = new Adherent($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Member not found'); - } - - $upload_dir = getMultidirOutput($object) . "/".get_exdir(0, 0, 0, 1, $object, 'member'); } elseif ($modulepart == 'propal' || $modulepart == 'proposal') { - require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php'; - if (!DolibarrApiAccess::$user->hasRight('propal', 'lire')) { throw new RestException(403); } - - $object = new Propal($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Proposal not found'); - } - - $upload_dir = $conf->propal->multidir_output[$object->entity ?? $conf->entity]."/".get_exdir(0, 0, 0, 1, $object, 'propal'); } elseif ($modulepart == 'supplier_proposal') { - require_once DOL_DOCUMENT_ROOT.'/supplier_proposal/class/supplier_proposal.class.php'; - if (!DolibarrApiAccess::$user->hasRight('supplier_proposal', 'read')) { throw new RestException(403); } - - $object = new Propal($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Supplier proposal not found'); - } - - $upload_dir = $conf->propal->multidir_output[$object->entity ?? $conf->entity]."/".get_exdir(0, 0, 0, 1, $object, 'propal'); } elseif ($modulepart == 'commande' || $modulepart == 'order') { - require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php'; - if (!DolibarrApiAccess::$user->hasRight('commande', 'lire')) { throw new RestException(403); } - - $object = new Commande($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Order not found'); - } - - $upload_dir = getMultidirOutput($object). "/".get_exdir(0, 0, 0, 1, $object, 'commande'); } elseif ($modulepart == 'commande_fournisseur' || $modulepart == 'supplier_order') { $modulepart = 'supplier_order'; - - require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php'; - if (!DolibarrApiAccess::$user->hasRight('fournisseur', 'commande', 'lire') && !DolibarrApiAccess::$user->hasRight('supplier_order', 'lire')) { throw new RestException(403); } - - $object = new CommandeFournisseur($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Purchase order not found'); - } - - $upload_dir = getMultidirOutput($object) . "/commande/".dol_sanitizeFileName((string) $object->ref); } elseif ($modulepart == 'shipment' || $modulepart == 'expedition') { - require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php'; - if (!DolibarrApiAccess::$user->hasRight('expedition', 'lire')) { throw new RestException(403); } - - $object = new Expedition($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Shipment not found'); - } - - $upload_dir = getMultidirOutput($object) . "/sending/".get_exdir(0, 0, 0, 1, $object, 'shipment'); } elseif ($modulepart == 'facture' || $modulepart == 'invoice') { - require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; - if (!DolibarrApiAccess::$user->hasRight('facture', 'lire')) { throw new RestException(403); } - - $object = new Facture($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Invoice not found'); - } - - $upload_dir = getMultidirOutput($object) . "/".get_exdir(0, 0, 0, 1, $object, 'invoice'); } elseif ($modulepart == 'facture_fournisseur' || $modulepart == 'supplier_invoice') { $modulepart = 'supplier_invoice'; - - require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php'; - if (!DolibarrApiAccess::$user->hasRight('fournisseur', 'facture', 'lire') && !DolibarrApiAccess::$user->hasRight('supplier_invoice', 'lire')) { throw new RestException(403); } - - $object = new FactureFournisseur($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Invoice not found'); - } - - $upload_dir = getMultidirOutput($object) . "/".get_exdir($object->id, 2, 0, 0, $object, 'invoice_supplier').dol_sanitizeFileName($object->ref); } elseif ($modulepart == 'produit' || $modulepart == 'product' || $modulepart == 'service') { - require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; - if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) { throw new RestException(403); } - - $object = new Product($this->db); - $result = $object->fetch($id, $ref); - if ($result == 0) { - throw new RestException(404, 'Product not found'); - } elseif ($result < 0) { - throw new RestException(500, 'Error while fetching object: '.$object->error); - } - - $upload_dir = getMultidirOutput($object) . '/'.dol_sanitizeFileName((string) $object->ref); } elseif ($modulepart == 'agenda' || $modulepart == 'action' || $modulepart == 'event' || $modulepart == 'actioncomm') { - require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php'; - if (!DolibarrApiAccess::$user->hasRight('agenda', 'myactions', 'read') && !DolibarrApiAccess::$user->hasRight('agenda', 'allactions', 'read')) { throw new RestException(403); } - - $object = new ActionComm($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Event not found'); - } - - $upload_dir = getMultidirOutput($object) . '/'.dol_sanitizeFileName((string) $object->ref); } elseif ($modulepart == 'expensereport') { - require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php'; - if (!DolibarrApiAccess::$user->hasRight('expensereport', 'read')) { throw new RestException(403); } - - $object = new ExpenseReport($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Expense report not found'); - } - - $upload_dir = getMultidirOutput($object) . '/'.dol_sanitizeFileName((string) $object->ref); } elseif ($modulepart == 'holiday') { - require_once DOL_DOCUMENT_ROOT.'/holiday/class/holiday.class.php'; - if (!DolibarrApiAccess::$user->hasRight('holiday', 'read')) { throw new RestException(403); } - - $object = new Holiday($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Holiday not found'); - } - - $upload_dir = getMultidirOutput($object) . '/'.dol_sanitizeFileName((string) $object->ref); } elseif ($modulepart == 'ticket') { - require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php'; - if (!DolibarrApiAccess::$user->hasRight('ticket', 'read')) { throw new RestException(403); } - - $object = new Ticket($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Ticket not found'); - } - - $upload_dir = $conf->ticket->dir_output.'/'.dol_sanitizeFileName((string) $object->ref); } elseif ($modulepart == 'knowledgemanagement') { - require_once DOL_DOCUMENT_ROOT.'/knowledgemanagement/class/knowledgerecord.class.php'; - if (!DolibarrApiAccess::$user->hasRight('knowledgemanagement', 'knowledgerecord', 'read')) { throw new RestException(403); } - - $object = new KnowledgeRecord($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'KM article not found'); - } - - $upload_dir = getMultidirOutput($object) . '/knowledgerecord/'.dol_sanitizeFileName((string) $object->ref); } elseif ($modulepart == 'categorie' || $modulepart == 'category') { - require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; - if (!DolibarrApiAccess::$user->hasRight('categorie', 'lire')) { throw new RestException(403); } - - $object = new Categorie($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Category not found'); - } - - $upload_dir = $conf->categorie->multidir_output[$object->entity ?? $conf->entity].'/'.get_exdir($object->id, 2, 0, 0, $object, 'category').$object->id."/photos/".dol_sanitizeFileName((string) $object->ref); } elseif ($modulepart == 'ecm') { throw new RestException(500, 'Modulepart Ecm not implemented yet.'); - // require_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmdirectory.class.php'; - // if (!DolibarrApiAccess::$user->hasRight('ecm', 'read')) { // throw new RestException(403); // } - - // // $object = new EcmDirectory($this->db); - // // $result = $object->fetch($ref); - // // if (!$result) { - // // throw new RestException(404, 'EcmDirectory not found'); - // // } - // $upload_dir = $conf->ecm->dir_output; - // $type = 'all'; - // $recursive = 0; } elseif ($modulepart == 'contrat' || $modulepart == 'contract') { $modulepart = 'contrat'; - require_once DOL_DOCUMENT_ROOT . '/contrat/class/contrat.class.php'; - - $object = new Contrat($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Contract not found'); + if (!DolibarrApiAccess::$user->hasRight('contrat', 'lire')) { + throw new RestException(403); } - - $upload_dir = getMultidirOutput($object) . "/" . get_exdir(0, 0, 0, 1, $object, 'contract'); } elseif ($modulepart == 'intervention' || $modulepart == 'ficheinter') { $modulepart = 'ficheinter'; - require_once DOL_DOCUMENT_ROOT . '/fichinter/class/fichinter.class.php'; - - $object = new Fichinter($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Interventional not found'); + if (!DolibarrApiAccess::$user->hasRight('ficheinter', 'lire')) { + throw new RestException(403); } - - $upload_dir = $conf->ficheinter->dir_output . "/" . get_exdir(0, 0, 0, 1, $object, 'ficheinter'); } elseif ($modulepart == 'projet' || $modulepart == 'project') { $modulepart = 'project'; - require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php'; - - $object = new Project($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Project not found'); - } - $upload_dir = $conf->project->dir_output . "/" . get_exdir(0, 0, 0, 1, $object, 'project'); - } elseif ($modulepart == 'task' || $modulepart == 'project_task') { - $modulepart = 'project_task'; - require_once DOL_DOCUMENT_ROOT . '/projet/class/task.class.php'; - if (!DolibarrApiAccess::$user->hasRight('projet', 'lire')) { throw new RestException(403); } - - $object = new Task($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Task not found'); + } elseif ($modulepart == 'task' || $modulepart == 'project_task') { + $modulepart = 'project_task'; + if (!DolibarrApiAccess::$user->hasRight('projet', 'lire')) { + throw new RestException(403); } - - // Fetch the project to build the correct path - $project_result = $object->fetchProject(); - if ($project_result < 0) { - throw new RestException(500, 'Error while fetching project for task'); - } - - $upload_dir = $conf->project->dir_output . "/" . dol_sanitizeFileName((string) $object->project->ref) . "/" . dol_sanitizeFileName((string) $object->ref); } elseif ($modulepart == 'mrp') { $modulepart = 'mrp'; - require_once DOL_DOCUMENT_ROOT . '/mrp/class/mo.class.php'; - - $object = new Mo($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'MO not found'); + if (!DolibarrApiAccess::$user->hasRight('mrp', 'read')) { + throw new RestException(403); } - - $upload_dir = getMultidirOutput($object) . "/" . get_exdir(0, 0, 0, 1, $object, 'mrp'); } elseif ($modulepart == 'contact' || $modulepart == 'socpeople') { $modulepart = 'contact'; - require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; - - $object = new Contact($this->db); - $result = $object->fetch($id?$id:$ref); - if (!$result) { - throw new RestException(404, 'Contact not found'); + if (!DolibarrApiAccess::$user->hasRight('societe', 'contact', 'lire')) { + throw new RestException(403); } - $upload_dir = $conf->societe->multidir_output[$object->entity ?? $conf->entity] . "/contact/" . get_exdir(0, 0, 0, 1, $object, 'contact'); } elseif ($modulepart == 'stock') { - require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php'; - if (!DolibarrApiAccess::$user->hasRight('stock', 'lire')) { throw new RestException(403); } - - $object = new Entrepot($this->db); - $result = $object->fetch($id, $ref); - if (!$result) { - throw new RestException(404, 'Warehouse not found'); - } - $upload_dir = $conf->stock->multidir_output[$object->entity ?? $conf->entity].'/'.get_exdir(0, 0, 0, 1, $object, 'stock'); } else { throw new RestException(500, 'Modulepart '.$modulepart.' not implemented yet.'); } + // Scan files into directory + $recursive = 0; + $type = 'files'; + $objectType = $modulepart; if (! empty($object->id) && ! empty($object->table_element)) { $objectType = $object->table_element; } $filearray = dol_dir_list($upload_dir, $type, $recursive, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1); - $countarray = count($filearray); - $filearray = array_slice($filearray, $limit * $page, $limit); + + $countarray = is_array($filearray) ? count($filearray) : 0; + if (empty($filearray)) { throw new RestException(404, 'Search for modulepart '.$modulepart.' with Id '.$object->id.(!empty($object->ref) ? ' or Ref '.$object->ref : '').' does not return any document.'); } else { + $filearray = array_slice($filearray, $limit * $page, $limit); if (($object->id) > 0 && !empty($modulepart)) { require_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php'; $ecmfile = new EcmFiles($this->db); @@ -847,36 +627,63 @@ class Documents extends DolibarrApi if ($result < 0) { throw new RestException(503, 'Error when retrieve ecm list : '.$this->db->lasterror()); } elseif (is_array($ecmfile->lines) && count($ecmfile->lines) > 0) { - $count = count($filearray); - for ($i = 0 ; $i < $count ; $i++) { + foreach ($filearray as &$fileitem) { foreach ($ecmfile->lines as $line) { - unset($line->db); - if ($filearray[$i]['name'] == $line->filename) { + if ($fileitem['name'] == $line->filename) { // Next line converts EcmFilesLine properties to array - $filearray[$i] = array_merge($filearray[$i], (array) $line); + //$fileitem = array_merge($fileitem, (array) $line); + $fileitem['ref'] = $line->ref; + $fileitem['label'] = $line->label; + $fileitem['filepath'] = $line->filepath; + $fileitem['filename'] = $line->filename; + $fileitem['fullpath_orig'] = $line->fullpath_orig; + $fileitem['position'] = $line->position; + $fileitem['gen_or_uploaded'] = $line->gen_or_uploaded; + $fileitem['description'] = $line->desc; + $fileitem['keywords'] = $line->keywords; + $fileitem['cover'] = $line->cover; + $fileitem['share'] = $line->share; + $fileitem['date_c'] = $line->date_c; + $fileitem['agenda_id'] = $line->agenda_id; + $fileitem['fk_user_c'] = $line->fk_user_c; + $fileitem['fk_user_m'] = $line->fk_user_m; + $fileitem['note_private'] = $line->note_private; + $fileitem['note_public'] = $line->note_public; } } - if (isset($filearray[$i]['relativename'])) $filearray[$i]['content-type'] = dol_mimetype($filearray[$i]['relativename']); - $arraycontenttype = explode(",", $content_type); - if (!empty($content_type) && isset($filearray[$i]['relativename']) && !in_array(dol_mimetype($filearray[$i]['relativename']), $arraycontenttype)) { - unset($filearray[$i]); - $countarray -= 1; + if (isset($fileitem['relativename'])) { + $fileitem['content-type'] = dol_mimetype((string) $fileitem['relativename']); } } + + // Select only files that match the requested $content_type, if provided + $arraycontenttype = explode(",", $content_type); + if (!empty($arraycontenttype)) { + $filearray = array_filter( + $filearray, + /** + * @param array{name:string,path:string,level1name:string,relativename:string,fullname:string,date:string,size:int,perm:int,type:string,content-type?:string} $fileitem Item to check for content type (for filtering) + * @return bool True if file matches a content type + */ + static function ($fileitem) use (&$arraycontenttype) { + return in_array(($fileitem['content-type'] ?: 'UNKNOWN'), $arraycontenttype); + } + ); + } } } } //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit) if ($pagination_data) { - $tmp = $filearray; - $filearray = []; - $filearray['data'] = $tmp; - $filearray['pagination'] = [ - 'total' => (int) $countarray, - 'page' => $page, //count starts from 0 - 'page_count' => ceil((int) $countarray / $limit), - 'limit' => $limit + $filearray = [ + 'data' => $filearray, + 'pagination' => [ + 'total' => (int) $countarray, + 'page' => $page, // count starts from 0 + 'page_count' => (int) ceil((int) $countarray / $limit), + 'limit' => $limit + ] ]; } @@ -1043,12 +850,12 @@ class Documents extends DolibarrApi require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; $object = new Societe($this->db); $fetchbyid = true; - } elseif ($modulepart == 'knowledgemanagement' ) { + } elseif ($modulepart == 'knowledgemanagement') { $modulepart = 'knowledgemanagement'; require_once DOL_DOCUMENT_ROOT.'/knowledgemanagement/class/knowledgerecord.class.php'; $object = new KnowledgeRecord($this->db); $fetchbyid = true; - } elseif ($modulepart == 'ticket' ) { + } elseif ($modulepart == 'ticket') { $modulepart = 'ticket'; require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php'; $object = new Ticket($this->db); @@ -1104,6 +911,9 @@ class Documents extends DolibarrApi $relativefile = $tmpreldir.dol_sanitizeFileName((string) $object->ref); } $tmp = dol_check_secure_access_document($modulepart, $relativefile, $entity, DolibarrApiAccess::$user, $ref, 'write'); + if (empty($tmp['accessallowed'])) { + throw new RestException(403, 'Access not allowed to upload file into this directory'); + } $upload_dir = $tmp['original_file']; // No dirname here, tmp['original_file'] is already the dir because dol_check_secure_access_document was called with param original_file that is only the dir /*} else { if (!DolibarrApiAccess::$user->hasRight('ecm', 'upload')) { @@ -1127,6 +937,9 @@ class Documents extends DolibarrApi if ($modulepart != 'ecm') { $relativefile = $subdir; $tmp = dol_check_secure_access_document($modulepart, $relativefile, $entity, DolibarrApiAccess::$user, '', 'write'); + if (empty($tmp['accessallowed'])) { + throw new RestException(403, 'Access not allowed to upload file into this directory'); + } $upload_dir = $tmp['original_file']; // No dirname here, tmp['original_file'] is already the dir because dol_check_secure_access_document was called with param original_file that is only the dir } else { if (!DolibarrApiAccess::$user->hasRight('ecm', 'upload')) { diff --git a/htdocs/api/class/api_emailtemplates.class.php b/htdocs/api/class/api_emailtemplates.class.php index 86205906f62..558a5321da5 100644 --- a/htdocs/api/class/api_emailtemplates.class.php +++ b/htdocs/api/class/api_emailtemplates.class.php @@ -87,9 +87,9 @@ class EmailTemplates extends DolibarrApi */ public function deleteById($id) { - $allowaccess = $this->_checkAccessRights('lire'); + $allowaccess = $this->_checkAccessRights('supprimer'); if (!$allowaccess) { - throw new RestException(403, 'denied read access to email templates'); + throw new RestException(403, 'denied delete access to email templates'); } $result = $this->email_template->apifetch($id, ''); @@ -123,11 +123,11 @@ class EmailTemplates extends DolibarrApi * @throws RestException 404 * @throws RestException 500 */ - public function deleteByLAbel($label) + public function deleteByLabel($label) { - $allowaccess = $this->_checkAccessRights('lire'); + $allowaccess = $this->_checkAccessRights('supprimer'); if (!$allowaccess) { - throw new RestException(403, 'denied read access to email templates'); + throw new RestException(403, 'denied delete access to email templates'); } $result = $this->email_template->apifetch(0, $label); diff --git a/htdocs/api/class/api_login.class.php b/htdocs/api/class/api_login.class.php index 3bcb380ab3a..e58e2cf0798 100644 --- a/htdocs/api/class/api_login.class.php +++ b/htdocs/api/class/api_login.class.php @@ -163,7 +163,7 @@ class Login // We store API token into database $sql = "UPDATE ".MAIN_DB_PREFIX."user"; $sql .= " SET api_key = '".$this->db->escape(dolEncrypt($token, '', '', 'dolibarr'))."'"; - $sql .= " WHERE login = '".$this->db->escape($login)."'"; + $sql .= " WHERE rowid = ".((int) $tmpuser->id); dol_syslog(get_class($this)."::login", LOG_DEBUG); // No log $result = $this->db->query($sql); diff --git a/htdocs/api/class/api_objectlinks.class.php b/htdocs/api/class/api_objectlinks.class.php index 863b85e2aa9..a1cabfa7173 100644 --- a/htdocs/api/class/api_objectlinks.class.php +++ b/htdocs/api/class/api_objectlinks.class.php @@ -167,7 +167,14 @@ class ObjectLinks extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight((string) $tgttype, 'creer') && !DolibarrApiAccess::$user->hasRight((string) $tgttype, 'write')) { throw new RestException(403, 'denied access to create the objectlinks targettype='.$this->objectlink->targettype); } + if (!checkUserAccessToObject(DolibarrApiAccess::$user, array($srctype), $this->objectlink->fk_source)) { + throw new RestException(403, 'denied access to create the objectlinks sourcetype='.$this->objectlink->sourcetype); + } + if (!checkUserAccessToObject(DolibarrApiAccess::$user, array($tgttype), $this->objectlink->fk_target)) { + throw new RestException(403, 'denied access to create the objectlinks targettype='.$this->objectlink->targettype); + } + // Create object link (in database) $result = $this->objectlink->create(DolibarrApiAccess::$user, $this->objectlink->fk_source, $this->objectlink->sourcetype, $this->objectlink->fk_target, $this->objectlink->targettype, $this->objectlink->relationtype, $this->notrigger); if ($result < 0) { @@ -225,6 +232,12 @@ class ObjectLinks extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight(((string) $tgttype), 'creer') && !DolibarrApiAccess::$user->hasRight(((string) $tgttype), 'write')) { throw new RestException(403, 'denied access to the objectlinks targettype'); } + if (!checkUserAccessToObject(DolibarrApiAccess::$user, array($srctype), $this->objectlink->fk_source)) { + throw new RestException(403, 'denied access to the objectlinks sourcetype'); + } + if (!checkUserAccessToObject(DolibarrApiAccess::$user, array($tgttype), $this->objectlink->fk_target)) { + throw new RestException(403, 'denied access to the objectlinks targettype'); + } } else { throw new RestException(404, 'Object Link not found'); } @@ -297,6 +310,12 @@ class ObjectLinks extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight((string) $tgttype, 'creer') && !DolibarrApiAccess::$user->hasRight((string) $tgttype, 'write')) { throw new RestException(403, 'denied access to get the objectlinks targettype='.$this->objectlink->targettype); } + if (!checkUserAccessToObject(DolibarrApiAccess::$user, array($srctype), $this->objectlink->fk_source)) { + throw new RestException(403, 'denied access to the objectlinks sourcetype'); + } + if (!checkUserAccessToObject(DolibarrApiAccess::$user, array($tgttype), $this->objectlink->fk_target)) { + throw new RestException(403, 'denied access to the objectlinks targettype'); + } $findresult = $this->objectlink->fetchByValues($this->objectlink->fk_source, $this->objectlink->sourcetype, $this->objectlink->fk_target, $this->objectlink->targettype, $this->objectlink->relationtype); @@ -367,6 +386,12 @@ class ObjectLinks extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight((string) $tgttype, 'creer') && !DolibarrApiAccess::$user->hasRight((string) $tgttype, 'write')) { throw new RestException(403, 'denied access to delete the objectlinks targettype='.$this->objectlink->targettype); } + if (!checkUserAccessToObject(DolibarrApiAccess::$user, array($srctype), $this->objectlink->fk_source)) { + throw new RestException(403, 'denied access to the objectlinks sourcetype'); + } + if (!checkUserAccessToObject(DolibarrApiAccess::$user, array($tgttype), $this->objectlink->fk_target)) { + throw new RestException(403, 'denied access to the objectlinks targettype'); + } $findresult = $this->objectlink->fetchByValues($this->objectlink->fk_source, $this->objectlink->sourcetype, $this->objectlink->fk_target, $this->objectlink->targettype, $this->objectlink->relationtype); @@ -427,6 +452,12 @@ class ObjectLinks extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight(((string) $tgttype), 'lire') && !DolibarrApiAccess::$user->hasRight(((string) $tgttype), 'read')) { throw new RestException(403, 'denied access to the objectlinks targettype'); } + if (!checkUserAccessToObject(DolibarrApiAccess::$user, array($srctype), $this->objectlink->fk_source)) { + throw new RestException(403, 'denied access to the objectlinks sourcetype'); + } + if (!checkUserAccessToObject(DolibarrApiAccess::$user, array($tgttype), $this->objectlink->fk_target)) { + throw new RestException(403, 'denied access to the objectlinks targettype'); + } } else { throw new RestException(404, 'Object Link not found'); } diff --git a/htdocs/api/class/api_setup.class.php b/htdocs/api/class/api_setup.class.php index 9e33ca357d3..ad0c3dd7b17 100644 --- a/htdocs/api/class/api_setup.class.php +++ b/htdocs/api/class/api_setup.class.php @@ -2744,6 +2744,9 @@ class Setup extends DolibarrApi public function getEstablishments() { $list = array(); + if (!DolibarrApiAccess::$user->admin) { + throw new RestException(403, 'Error API open to admin users only'); + } $limit = 0; @@ -2782,6 +2785,10 @@ class Setup extends DolibarrApi */ public function getEtablishmentByID($id) { + if (!DolibarrApiAccess::$user->admin) { + throw new RestException(403, 'Error API open to admin users only'); + } + $establishment = new Establishment($this->db); $result = $establishment->fetch($id); @@ -2790,6 +2797,9 @@ class Setup extends DolibarrApi } elseif ($result == 0) { throw new RestException(404, 'Establishment not found'); } + if (!in_array($establishment->entity, explode(',', getEntity('establishment')))) { + throw new RestException(404, 'Establishment not found'); + } return $this->_cleanObjectDatas($establishment); } diff --git a/htdocs/asset/class/asset.class.php b/htdocs/asset/class/asset.class.php index bc4ee8939ef..78e6755f467 100644 --- a/htdocs/asset/class/asset.class.php +++ b/htdocs/asset/class/asset.class.php @@ -1765,14 +1765,14 @@ class Asset extends CommonObject if (preg_match('/^[\(]?PROV/i', $this->ref)) { // Now we rename also files into index $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'asset/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'asset/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'asset/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'asset/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; diff --git a/htdocs/asset/class/assetaccountancycodes.class.php b/htdocs/asset/class/assetaccountancycodes.class.php index be13ff55c8d..b164614508c 100644 --- a/htdocs/asset/class/assetaccountancycodes.class.php +++ b/htdocs/asset/class/assetaccountancycodes.class.php @@ -1,6 +1,6 @@ - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2025 Alexandre Spangaro * * This program is free software; you can redistribute it and/or modify @@ -203,8 +203,8 @@ class AssetAccountancyCodes extends CommonObject $accountancy_codes = array(); foreach ($this->accountancy_codes_fields as $mode_key => $mode_info) { - $sql = "SELECT " . implode(',', array_keys($mode_info['fields'])); - $sql .= " FROM " . MAIN_DB_PREFIX . $mode_info['table']; + $sql = "SELECT " . implode(',', array_keys($mode_info['fields'])); // From safe table @phan-suppress-current-line SqlInjection + $sql .= " FROM " . MAIN_DB_PREFIX . $mode_info['table']; // From safe table @phan-suppress-current-line SqlInjection $sql .= " WHERE " . ($asset_id > 0 ? " fk_asset = " . (int) $asset_id : " fk_asset_model = " . (int) $asset_model_id); $resql = $this->db->query($sql); @@ -273,7 +273,7 @@ class AssetAccountancyCodes extends CommonObject foreach ($this->accountancy_codes_fields as $mode_key => $mode_info) { // Delete old accountancy codes - $sql = "DELETE FROM " . MAIN_DB_PREFIX . $mode_info['table']; + $sql = "DELETE FROM " . MAIN_DB_PREFIX . $mode_info['table']; // From safe table @phan-suppress-current-line SqlInjection $sql .= " WHERE " . ($asset_id > 0 ? " fk_asset = " . (int) $asset_id : " fk_asset_model = " . (int) $asset_model_id); $resql = $this->db->query($sql); if (!$resql) { @@ -283,12 +283,12 @@ class AssetAccountancyCodes extends CommonObject if (!$error && !empty($this->accountancy_codes[$mode_key])) { // Insert accountancy codes - $sql = "INSERT INTO " . MAIN_DB_PREFIX . $mode_info['table'] . "("; + $sql = "INSERT INTO " . MAIN_DB_PREFIX . $mode_info['table'] . "("; // From safe table @phan-suppress-current-line SqlInjection $sql .= $asset_id > 0 ? "fk_asset," : "fk_asset_model,"; - $sql .= implode(',', array_keys($mode_info['fields'])); + $sql .= implode(',', array_keys($mode_info['fields'])); // From safe table @phan-suppress-current-line SqlInjection $sql .= ", tms, fk_user_modif"; $sql .= ") VALUES("; - $sql .= $asset_id > 0 ? $asset_id : $asset_model_id; + $sql .= $asset_id > 0 ? ((int) $asset_id) : ((int) $asset_model_id); foreach ($mode_info['fields'] as $field_key => $field_info) { $sql .= ', ' . (empty($this->accountancy_codes[$mode_key][$field_key]) ? 'NULL' : "'" . $this->db->escape($this->accountancy_codes[$mode_key][$field_key]) . "'"); } diff --git a/htdocs/asset/class/assetdepreciationoptions.class.php b/htdocs/asset/class/assetdepreciationoptions.class.php index ebda757dba8..a93655c0408 100644 --- a/htdocs/asset/class/assetdepreciationoptions.class.php +++ b/htdocs/asset/class/assetdepreciationoptions.class.php @@ -494,7 +494,7 @@ class AssetDepreciationOptions extends CommonObject foreach ($this->deprecation_options_fields as $mode_key => $mode_info) { // Delete old accountancy codes - $sql = "DELETE FROM " . MAIN_DB_PREFIX . $mode_info['table']; + $sql = "DELETE FROM " . MAIN_DB_PREFIX . $mode_info['table']; // From safe table @phan-suppress-current-line SqlInjection $sql .= " WHERE " . ($asset_id > 0 ? " fk_asset = " . (int) $asset_id : " fk_asset_model = " . (int) $asset_model_id); $resql = $this->db->query($sql); if (!$resql) { diff --git a/htdocs/blockedlog/admin/filecheck.php b/htdocs/blockedlog/admin/filecheck.php index edace4b6c5d..f982620c64d 100644 --- a/htdocs/blockedlog/admin/filecheck.php +++ b/htdocs/blockedlog/admin/filecheck.php @@ -2,7 +2,7 @@ /* Copyright (C) 2005-2026 Laurent Destailleur * Copyright (C) 2007 Rodolphe Quiedeville * Copyright (C) 2007-2012 Regis Houssin - * Copyright (C) 2015-2025 Frédéric France + * Copyright (C) 2015-2026 Frédéric France * Copyright (C) 2017 Nicolas ZABOURI * Copyright (C) 2024-2025 MDW * @@ -487,7 +487,11 @@ if (empty($error) && !empty($xml)) { $i++; $out .= ''; $out .= ''."\n"; - $out .= ''."\n"; + $out .= ''."\n"; $out .= ''."\n"; $out .= ''."\n"; $out .= '
".$langs->trans("Parameter")."'.$langs->trans("Value")." 
'.$i.''.dol_escape_htmltag($file['filename']).''.dol_escape_htmltag($file['filename']); + $out .= ' '; + $out .= img_picto($langs->trans("ShowDiffWithOriginal"), 'split', 'class="paddingleft"'); + $out .= ''; + $out .= ''.dol_escape_htmltag(dol_trunc($file['expectedhash'], 16)).''.dol_escape_htmltag(dol_trunc($file['hash'], 16)).''; @@ -516,6 +520,28 @@ if (empty($error) && !empty($xml)) { $out .= '
'; $out .= ''; + // Ajax loader for the per-file diff against the original version + $out .= ''."\n"; + $out .= '
'; // Files added diff --git a/htdocs/blockedlog/admin/filecheck_diff.php b/htdocs/blockedlog/admin/filecheck_diff.php new file mode 100644 index 00000000000..e2ec50419a2 --- /dev/null +++ b/htdocs/blockedlog/admin/filecheck_diff.php @@ -0,0 +1,397 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/blockedlog/admin/filecheck_diff.php + * \brief Ajax fragment to show the diff of a modified file against its original version + */ + +if (!defined('NOTOKENRENEWAL')) { + define('NOTOKENRENEWAL', '1'); +} +if (!defined('NOREQUIREMENU')) { + define('NOREQUIREMENU', '1'); +} +if (!defined('NOREQUIREHTML')) { + define('NOREQUIREHTML', '1'); +} +if (!defined('NOREQUIREAJAX')) { + define('NOREQUIREAJAX', '1'); +} + +// Load Dolibarr environment +require '../../main.inc.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var Translate $langs + * @var User $user + */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php'; + +$langs->loadLangs(array("admin", "errors")); + +if (!$user->admin && !$user->hasRight('bockedlog', 'read')) { + accessforbidden(); +} + +$file = GETPOST('file', 'alphanohtml'); +$algo = GETPOST('algo', 'aZ09'); +$expectedhash = GETPOST('expectedhash', 'aZ09'); + +if (!in_array($algo, array('md5', 'sha256'), true)) { + $algo = 'sha256'; +} + + +/** + * Compute a line based diff between two arrays of lines, restricted to the changed + * region (common prefix and suffix are trimmed first to keep it cheap and memory safe). + * + * @param string[] $a Lines of the original file + * @param string[] $b Lines of the local file + * @return array List of [type, line] where type is ' ' (context), '-' (removed) or '+' (added) + */ +function filecheckLineDiff($a, $b) +{ + $na = count($a); + $nb = count($b); + + // Trim common prefix + $start = 0; + while ($start < $na && $start < $nb && $a[$start] === $b[$start]) { + $start++; + } + // Trim common suffix + $enda = $na - 1; + $endb = $nb - 1; + while ($enda >= $start && $endb >= $start && $a[$enda] === $b[$endb]) { + $enda--; + $endb--; + } + + $result = array(); + for ($i = 0; $i < $start; $i++) { + $result[] = array(' ', $a[$i]); + } + + $midA = array_slice($a, $start, $enda - $start + 1); + $midB = array_slice($b, $start, $endb - $start + 1); + + foreach (filecheckMiddleDiff($midA, $midB) as $d) { + $result[] = $d; + } + + for ($i = $enda + 1; $i < $na; $i++) { + $result[] = array(' ', $a[$i]); + } + + return $result; +} + +/** + * Diff of the changed region. For large regions, the region is recursively split on + * unique common lines (anchors, patience-diff style) so the expensive LCS only runs + * on small segments. This keeps small changes detected instead of one big block. + * + * @param string[] $a Changed lines of the original file + * @param string[] $b Changed lines of the local file + * @return array List of [type, line] + */ +function filecheckMiddleDiff($a, $b) +{ + $a = array_values($a); + $b = array_values($b); + $na = count($a); + $nb = count($b); + + if ($na == 0 || $nb == 0) { + $result = array(); + foreach ($a as $line) { + $result[] = array('-', $line); + } + foreach ($b as $line) { + $result[] = array('+', $line); + } + return $result; + } + + // Small enough region: a direct LCS gives the optimal line-level diff. + if ($na * $nb <= 1000000) { + return filecheckLcs($a, $b); + } + + // Large region: split on a unique common line (anchor) and recurse on both sides. + $anchor = filecheckFindAnchor($a, $b); + if ($anchor !== null) { + return array_merge( + filecheckMiddleDiff(array_slice($a, 0, $anchor[0]), array_slice($b, 0, $anchor[1])), + array(array(' ', $a[$anchor[0]])), + filecheckMiddleDiff(array_slice($a, $anchor[0] + 1), array_slice($b, $anchor[1] + 1)) + ); + } + + // No anchor available in a very large region: fall back to a plain block replacement. + $result = array(); + foreach ($a as $line) { + $result[] = array('-', $line); + } + foreach ($b as $line) { + $result[] = array('+', $line); + } + return $result; +} + +/** + * Find a line that appears exactly once in both arrays (a unique common "anchor"), + * choosing the candidate closest to the middle of $a to balance the recursion. + * + * @param string[] $a Lines + * @param string[] $b Lines + * @return ?array{0:int,1:int} [index in $a, index in $b] of the anchor, or null if none found + */ +function filecheckFindAnchor($a, $b) +{ + $countA = array(); + foreach ($a as $line) { + $countA[$line] = (isset($countA[$line]) ? $countA[$line] : 0) + 1; + } + $countB = array(); + $posB = array(); + foreach ($b as $j => $line) { + $countB[$line] = (isset($countB[$line]) ? $countB[$line] : 0) + 1; + $posB[$line] = $j; + } + + $middle = (int) (count($a) / 2); + $best = null; + $bestdist = -1; + foreach ($a as $i => $line) { + if ($countA[$line] == 1 && isset($countB[$line]) && $countB[$line] == 1) { + $dist = abs($i - $middle); + if ($bestdist < 0 || $dist < $bestdist) { + $bestdist = $dist; + $best = array($i, $posB[$line]); + } + } + } + + return $best; +} + +/** + * Diff of two small arrays of lines using a classic LCS dynamic programming matrix. + * + * @param string[] $a Lines of the original file + * @param string[] $b Lines of the local file + * @return array List of [type, line] + */ +function filecheckLcs($a, $b) +{ + $na = count($a); + $nb = count($b); + + // LCS length matrix + $lcs = array(); + for ($i = 0; $i <= $na; $i++) { + $lcs[$i] = array_fill(0, $nb + 1, 0); + } + for ($i = $na - 1; $i >= 0; $i--) { + for ($j = $nb - 1; $j >= 0; $j--) { + if ($a[$i] === $b[$j]) { + $lcs[$i][$j] = $lcs[$i + 1][$j + 1] + 1; + } else { + $lcs[$i][$j] = max($lcs[$i + 1][$j], $lcs[$i][$j + 1]); + } + } + } + + // Backtrack to build the diff + $result = array(); + $i = 0; + $j = 0; + while ($i < $na && $j < $nb) { + if ($a[$i] === $b[$j]) { + $result[] = array(' ', $a[$i]); + $i++; + $j++; + } elseif ($lcs[$i + 1][$j] >= $lcs[$i][$j + 1]) { + $result[] = array('-', $a[$i]); + $i++; + } else { + $result[] = array('+', $b[$j]); + $j++; + } + } + while ($i < $na) { + $result[] = array('-', $a[$i]); + $i++; + } + while ($j < $nb) { + $result[] = array('+', $b[$j]); + $j++; + } + + return $result; +} + +/** + * Collapse long runs of unchanged context lines, keeping a few lines around each change. + * + * @param array $diff Full diff as returned by filecheckLineDiff() + * @param int $context Number of context lines to keep around changes + * @return array Diff with collapsed context (type '@' marks a collapsed gap) + */ +function filecheckCollapseContext($diff, $context = 3) +{ + $n = count($diff); + $keep = array_fill(0, $n, false); + for ($i = 0; $i < $n; $i++) { + if ($diff[$i][0] !== ' ') { + $from = max(0, $i - $context); + $to = min($n - 1, $i + $context); + for ($k = $from; $k <= $to; $k++) { + $keep[$k] = true; + } + } + } + + $result = array(); + $ingap = false; + for ($i = 0; $i < $n; $i++) { + if ($keep[$i]) { + $result[] = $diff[$i]; + $ingap = false; + } elseif (!$ingap) { + $result[] = array('@', ''); + $ingap = true; + } + } + + return $result; +} + + +top_httphead('text/html'); + +print ''."\n"; + +// Validate the requested file: must be a relative path inside DOL_DOCUMENT_ROOT, with no traversal. +$errormsg = ''; +$reallocal = ''; +if (empty($file) || $file[0] !== '/' || strpos($file, '..') !== false || !preg_match('/^[A-Za-z0-9_\/.\-]+$/', $file)) { + $errormsg = $langs->trans("ErrorBadValueForParameter", dol_escape_htmltag($file), "file"); +} + +if (empty($errormsg)) { + $reallocal = realpath(DOL_DOCUMENT_ROOT.$file); + if ($reallocal === false || strpos($reallocal, realpath(DOL_DOCUMENT_ROOT).'/') !== 0 || !is_file($reallocal)) { + $errormsg = $langs->trans("ErrorFileNotFound", $file); + } +} +// Only text files can be diffed +if (empty($errormsg) && preg_match('/\.(jpg|jpeg|png|gif|ico|svg|eot|woff|woff2|ttf|mp3|mp4|wav|mkv|z|gz|zip|rar|tar)$/i', $file)) { + $errormsg = $langs->trans("DiffNotAvailableForBinaryFiles"); +} + +if (!empty($errormsg)) { + print '
'.$errormsg.'
'; + llxFooterFragment(); +} + +// Build the URL of the original file for the running version. +// For a stable version (eg 24.0.0) the matching git tag exists, so we compare against that tag. +// For an alpha/beta/rc version no tag exists yet, so we compare against the develop branch. +$baseurl = getDolGlobalString('MAIN_FILECHECK_DIFF_BASEURL', 'https://raw.githubusercontent.com/Dolibarr/dolibarr'); +if (preg_match('/alpha|beta|rc/i', DOL_VERSION)) { + $ref = 'develop'; +} else { + $ref = DOL_VERSION; +} +$ref = getDolGlobalString('MAIN_FILECHECK_DIFF_REF', $ref); +$originurl = $baseurl.'/'.$ref.'/htdocs'.$file; + +$res = getURLContent($originurl, 'GET', '', 1, array(), array('http', 'https'), 0); // Accept http or https links on external remote server only. +if (!empty($res['curl_error_no']) || (isset($res['http_code']) && !in_array((int) $res['http_code'], array(0, 200), true))) { + print '
'.$langs->trans("CouldNotFetchOriginalFile").': '.dol_escape_htmltag($originurl); + print ' ('.dol_escape_htmltag((string) (empty($res['http_code']) ? $res['curl_error_msg'] : $res['http_code'])).')
'; + llxFooterFragment(); +} + +$origincontent = (string) $res['content']; +$localcontent = (string) file_get_contents($reallocal); + +// Confirm the fetched original is the genuine reference file expected by the signature. +$verified = true; +if (!empty($expectedhash)) { + $verified = (hash($algo, $origincontent) === $expectedhash); +} +if (!$verified) { + print '
'.$langs->trans("OriginalFileChecksumMismatch").'
'; +} + +if ($origincontent === $localcontent) { + print '
'.$langs->trans("NoDifferenceFound").'
'; + llxFooterFragment(); +} + +$diff = filecheckLineDiff(explode("\n", $origincontent), explode("\n", $localcontent)); +$diff = filecheckCollapseContext($diff, 3); + +print '
'; +print img_picto('', 'split', 'class="pictofixedwidth"').dol_escape_htmltag($file).' — '.dol_escape_htmltag($originurl); +print '
'; + +print ''; +foreach ($diff as $line) { + $type = $line[0]; + if ($type === '@') { + print ''."\n"; + continue; + } + $bg = ''; + $sign = ' '; + if ($type === '+') { + $bg = 'background:#e6ffed'; + $sign = '+'; + } elseif ($type === '-') { + $bg = 'background:#ffeef0'; + $sign = '-'; + } + print ''."\n"; +} +print '
…
'; + print dol_escape_htmltag($sign.' '.$line[1]); + print '
'; + +llxFooterFragment(); + + +/** + * Close the fragment output and stop the script. + * + * @return void + */ +function llxFooterFragment() +{ + global $db; + if (is_object($db)) { + $db->close(); + } + exit; +} diff --git a/htdocs/blockedlog/class/blockedlog.class.php b/htdocs/blockedlog/class/blockedlog.class.php index 08f507749b3..8e44413bfdb 100644 --- a/htdocs/blockedlog/class/blockedlog.class.php +++ b/htdocs/blockedlog/class/blockedlog.class.php @@ -1410,7 +1410,7 @@ class BlockedLog $sql .= "0,"; $sql .= ((int) $this->fk_user).","; $sql .= "'".$this->db->escape($this->user_fullname)."',"; - $sql .= ((int) ($this->entity ? $this->entity : $conf->entity)).","; + $sql .= ((int) ($this->entity ? ((int) $this->entity) : ((int) $conf->entity))).","; $sql .= "'".$this->db->escape($this->debuginfo)."'"; $sql .= ")"; @@ -2228,16 +2228,16 @@ class BlockedLog if ($element == 'all') { $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog - WHERE entity = ".$conf->entity; + WHERE entity = ".((int) $conf->entity); } elseif ($element == 'not_certified') { $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog - WHERE entity = ".$conf->entity." AND certified = 0"; + WHERE entity = ".((int) $conf->entity)." AND certified = 0"; } elseif ($element == 'just_certified') { $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog - WHERE entity = ".$conf->entity." AND certified = 1"; + WHERE entity = ".((int) $conf->entity)." AND certified = 1"; } else { $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog - WHERE entity = ".$conf->entity." AND element = '".$this->db->escape($element)."'"; + WHERE entity = ".((int) $conf->entity)." AND element = '".$this->db->escape($element)."'"; } if ($fk_object) { @@ -2290,9 +2290,9 @@ class BlockedLog } } if (!empty($search_module_source)) { - $tmp = natural_search("module_source", implode(',', $search_module_source), 0, 1); - $tmp = str_replace('%backoffice%', '', $tmp); - $sql .= $tmp; + $sqlTmp = natural_search("module_source", implode(',', $search_module_source), 0, 1); + $sqlTmp = str_replace('%backoffice%', '', $sqlTmp); + $sql .= $sqlTmp; } $sql .= " OR module_source = 'mix'"; // When a payment was recorded and payment was on an invoice with different origins (pos and not pos) $sql .= ")"; diff --git a/htdocs/blockedlog/lib/securitycore.lib.php b/htdocs/blockedlog/lib/securitycore.lib.php index 7495f1551a6..56f21b80f73 100644 --- a/htdocs/blockedlog/lib/securitycore.lib.php +++ b/htdocs/blockedlog/lib/securitycore.lib.php @@ -71,7 +71,7 @@ function dolEncrypt($chain, $key = '', $ciphering = '', $forceseed = '', $obfusc } $reg = array(); - if (preg_match('/^(dolobfuscationv1[^:]+|dolcrypt):([^:]+):(.+)$/', $chain, $reg)) { + if (preg_match('/^(dolobfuscation|dolcrypt)[^:]*:([^:]+):(.+)$/', $chain, $reg)) { // The $chain is already an encrypted string return $chain; } @@ -158,17 +158,17 @@ function dolDecrypt($chain, $key = '', $patterntotest = '') } // New method - if (preg_match('/^dol[^:]+:([^:]+):(.+)$/', $chain, $reg)) { + if (preg_match('/^(dolobfuscation|dolcrypt)[^:]*:([^:]+):(.+)$/', $chain, $reg)) { // Do not enable this log, except during debug //dol_syslog("We try to decrypt the chain: ".$chain, LOG_DEBUG); - $ciphering = $reg[1]; + $ciphering = $reg[2]; if (function_exists('openssl_decrypt')) { if (empty($key)) { dol_syslog("Error dolDecrypt decrypt key is empty", LOG_WARNING); return $chain; } - $tmpexplode = explode(':', $reg[2]); + $tmpexplode = explode(':', $reg[3]); if (!empty($tmpexplode[1])) { $data = $tmpexplode[1]; $iv = $tmpexplode[0]; @@ -193,9 +193,10 @@ function dolDecrypt($chain, $key = '', $patterntotest = '') } // Test validity of decryption - if (!ascii_check($newchain)) { + //if (!ascii_check($newchain)) { + if (!ascii_check($newchain) && !utf8_check($newchain)) { if (empty($savkey)) { - dol_syslog("Error dolDecrypt failed: The key dolibarr_main_dolcrypt or dolibarr_main_instance_unique_id, found in conf.php file, is the the one used to encrypt this encrypted string", LOG_ERR); + dol_syslog("Error dolDecrypt failed: The key dolibarr_main_dolcrypt or dolibarr_main_instance_unique_id, found in conf.php file, seems the one used to encrypt the encrypted string", LOG_ERR); } else { dol_syslog("Error dolDecrypt failed: The string decoded with the key return a non valid value (not ascii)", LOG_ERR); } diff --git a/htdocs/blockedlog/versionmod.inc.php b/htdocs/blockedlog/versionmod.inc.php index 3eb8be1d1b5..811c007acda 100644 --- a/htdocs/blockedlog/versionmod.inc.php +++ b/htdocs/blockedlog/versionmod.inc.php @@ -24,7 +24,7 @@ // The version of the POS system (Immutable Log system) // Can be 3.0.0-beta when not yet certified // 3.0.0 once certified -define('DOLCERT_VERSION', '3.0.0-beta'); +define('DOLCERT_VERSION', '3.0.0'); // 1 was used for beta version candidate for certification, or for stable version that has been certified. @@ -32,7 +32,7 @@ define('DOLCERT_VERSION', '3.0.0-beta'); // 0 was used for old version, for version not certified but compliant with the law by using an attestation of an IT provider that guarantee // the the software is compliant. WARNING: In this case, you must find an IT company that give you the attestation of conformity. if (!defined('CERTIF_LNE')) { - define('CERTIF_LNE', '2'); + define('CERTIF_LNE', '0'); } diff --git a/htdocs/bom/bom_agenda.php b/htdocs/bom/bom_agenda.php index 90f8dd53099..2549a448157 100644 --- a/htdocs/bom/bom_agenda.php +++ b/htdocs/bom/bom_agenda.php @@ -84,7 +84,7 @@ if (!$sortorder) { // Initialize a technical objects $object = new BOM($db); -$diroutputmassaction = getMultidirOutput($object) . '/temp/massgeneration/'.$user->id; +$diroutputmassaction = getMultidirOutput($object, '', 0, 'temp') . 'massgeneration/'.$user->id; $hookmanager->initHooks(array('bomagenda', 'globalcard')); // Note that conf->hooks_modules contains array // Fetch optionals attributes and labels diff --git a/htdocs/bom/bom_card.php b/htdocs/bom/bom_card.php index e4cd35e11ad..a172030ea86 100644 --- a/htdocs/bom/bom_card.php +++ b/htdocs/bom/bom_card.php @@ -64,7 +64,7 @@ $hideref = (GETPOSTINT('hideref') ? GETPOSTINT('hideref') : (getDolGlobalString( // Initialize a technical objects $object = new BOM($db); -$diroutputmassaction = getMultidirOutput($object) . '/temp/massgeneration/'.$user->id; +$diroutputmassaction = getMultidirOutput($object, '', 0, 'temp') . 'massgeneration/'.$user->id; $hookmanager->initHooks(array('bomcard', 'globalcard')); // Note that conf->hooks_modules contains array // Fetch optionals attributes and labels diff --git a/htdocs/bom/bom_document.php b/htdocs/bom/bom_document.php index 707873d57fd..01635cbe9ea 100644 --- a/htdocs/bom/bom_document.php +++ b/htdocs/bom/bom_document.php @@ -75,7 +75,7 @@ if (!$sortfield) { // Initialize a technical objects $object = new BOM($db); -$diroutputmassaction = getMultidirOutput($object) . '/temp/massgeneration/'.$user->id; +$diroutputmassaction = getMultidirOutput($object, '', 0, 'temp') . 'massgeneration/'.$user->id; $hookmanager->initHooks(array('bomdocument', 'globalcard')); // Note that conf->hooks_modules contains array // Fetch optionals attributes and labels $extrafields->fetch_name_optionals_label($object->table_element); diff --git a/htdocs/bom/bom_list.php b/htdocs/bom/bom_list.php index 534b77bf0df..8ec1e1113fa 100644 --- a/htdocs/bom/bom_list.php +++ b/htdocs/bom/bom_list.php @@ -73,7 +73,7 @@ $pagenext = $page + 1; // Initialize a technical objects $object = new BOM($db); -$diroutputmassaction = getMultidirOutput($object) . '/temp/massgeneration/'.$user->id; +$diroutputmassaction = getMultidirOutput($object, '', 0, 'temp') . 'massgeneration/'.$user->id; $hookmanager->initHooks(array('bomlist')); // Note that conf->hooks_modules contains array // Fetch optionals attributes and labels diff --git a/htdocs/bom/bom_net_needs.php b/htdocs/bom/bom_net_needs.php index 8ed73e67725..346cd67f1aa 100644 --- a/htdocs/bom/bom_net_needs.php +++ b/htdocs/bom/bom_net_needs.php @@ -60,7 +60,7 @@ $object = new BOM($db); $hookmanager->initHooks(array('bomnetneeds')); // Note that conf->hooks_modules contains array // Massaction -$diroutputmassaction = getMultidirOutput($object) . '/temp/massgeneration/'.$user->id; +$diroutputmassaction = getMultidirOutput($object, '', 0, 'temp') . 'massgeneration/'.$user->id; // Fetch optionals attributes and labels $extrafields->fetch_name_optionals_label($object->table_element); @@ -257,57 +257,33 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea print ''; print ''; - $tablerows = array(); - $position = ''; - $levelposition = ''; - $lineposition = 0; if (count($TChildBom) > 0) { if ($action == 'treeview') { foreach ($TChildBom as $fk_bom => $TProduct) { $repeatChar = ' '; if (!empty($TProduct['bom'])) { - // we make position string in format 'lineposition.bomlevel.childposition' - if (!empty($TProduct['position']) && $TProduct['parentid'] == $object->id) { - // define lineposition - $lineposition = $TProduct['position']; - } - // define lineposition.bomlevel - $position = sprintf('%d.%d', $lineposition, $TProduct['level']); - // memorize level position for products in bom - $levelposition = $position; - if (!empty($TProduct['parentid']) && $TProduct['parentid'] != $object->id && empty($TProduct['product'])) { - // define lineposition.bomlevel.childposition - $position = sprintf('%s.%d', $position, $TProduct['position']); - } $prod = new Product($db); $prod->fetch($TProduct['bom']->fk_product); if ($TProduct['parentid'] != $object->id) { - $tablerows[$position] = ''; + print ''; } else { - $tablerows[$position] = ''; + print ''; } - $tablerows[$position] .= ''.str_repeat($repeatChar, $TProduct['level']).$prod->getNomUrl(1); - $tablerows[$position] .= ' '; - $tablerows[$position] .= img_picto('', 'folder-open'); - $tablerows[$position] .= ''; - $tablerows[$position] .= ''; - $tablerows[$position] .= ''.$TProduct['bom']->getNomUrl(1).''; - $tablerows[$position] .= ''.price(price2num($TProduct['qty'], 'MS')).''; - $tablerows[$position] .= ''; - $tablerows[$position] .= ''; - $tablerows[$position] .= ''; - $tablerows[$position] .= ''; - $tablerows[$position] .= ''; + print ''.str_repeat($repeatChar, $TProduct['level']).$prod->getNomUrl(1); + print ' '; + print img_picto('', 'folder-open'); + print ''; + print ''; + print ''.$TProduct['bom']->getNomUrl(1).''; + print ''.price(price2num($TProduct['qty'], 'MS')).''; + print ''; + print ''; + print ''; + print ''; + print ''; } if (!empty($TProduct['product'])) { foreach ($TProduct['product'] as $fk_product => $TInfos) { - if (!empty($TInfos['position']) && $fk_bom == $object->id) { - // top level position - $position = $TInfos['position']; - } else { - // sublevel position - $position = sprintf('%s.%d', $levelposition, $TInfos['position']); - } $prod = new Product($db); $prod->fetch($fk_product); $prod->load_virtual_stock(); @@ -315,27 +291,21 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea $prod->stock_reel = 0; } if ($fk_bom != $object->id) { - $tablerows[$position] = ''; // sub bom on same position + print ''; } else { - $tablerows[$position] = ''; + print ''; } - $tablerows[$position] .= ''.str_repeat($repeatChar, (int) $TInfos['level']).$prod->getNomUrl(1).''; - $tablerows[$position] .= ''; - $tablerows[$position] .= ''.price(price2num((float) $TInfos['qty'], 'MS')).''; - $tablerows[$position] .= ''; - $tablerows[$position] .= ''; - $tablerows[$position] .= ''.price2num($prod->stock_reel, 'MS').''; - $tablerows[$position] .= ''.$prod->stock_theorique.''; - $tablerows[$position] .= ''; + print ''.str_repeat($repeatChar, $TInfos['level']).$prod->getNomUrl(1).''; + print ''; + print ''.price(price2num($TInfos['qty'], 'MS')).''; + print ''; + print ''; + print ''.price2num($prod->stock_reel, 'MS').''; + print ''.$prod->stock_theorique.''; + print ''; } } } - // Sort the rows numeric by position string - ksort($tablerows, SORT_NUMERIC); - // Print the rows - foreach ($tablerows as $position => $row) { - print $row; - } } else { foreach ($TChildBom as $fk_product => $elem) { $prod = new Product($db); diff --git a/htdocs/bom/bom_note.php b/htdocs/bom/bom_note.php index c802dabda5b..2a4047b5c29 100644 --- a/htdocs/bom/bom_note.php +++ b/htdocs/bom/bom_note.php @@ -53,7 +53,7 @@ $object = new BOM($db); $hookmanager->initHooks(array('bomnote', 'globalcard')); // Note that conf->hooks_modules contains array // Massactions -$diroutputmassaction = getMultidirOutput($object) . '/temp/massgeneration/'.$user->id; +$diroutputmassaction = getMultidirOutput($object, '', 0, 'temp') . 'massgeneration/'.$user->id; // Fetch optionals attributes and labels $extrafields->fetch_name_optionals_label($object->table_element); diff --git a/htdocs/bom/class/api_boms.class.php b/htdocs/bom/class/api_boms.class.php index 3aab8b49831..66d532b4d61 100644 --- a/htdocs/bom/class/api_boms.class.php +++ b/htdocs/bom/class/api_boms.class.php @@ -277,6 +277,42 @@ class Boms extends DolibarrApi } } + /** + * Validate BOM + * + * @param int $id BOM ID + * @param int $notrigger 1=Does not execute triggers, 0= execute triggers + * @return Object Object with cleaned properties + * + * @url POST {id}/validate + * + * @throws RestException 304 + * @throws RestException 401 + * @throws RestException 404 + * @throws RestException 500 System error + */ + public function validate($id, $notrigger = 0) + { + if (!DolibarrApiAccess::$user->hasRight('bom', 'write')) { + throw new RestException(403); + } + $result = $this->bom->fetch($id); + if (!$result) { + throw new RestException(404, 'Bom not found'); + } + + $result = $this->bom->validate(DolibarrApiAccess::$user, $notrigger); + if ($result == 0) { + throw new RestException(304, 'Error nothing done. May be object is already validated'); + } + if ($result < 0) { + throw new RestException(500, 'Error when validating BOM: '.$this->bom->error); + } + $result = $this->bom->fetch($id); + + return $this->_cleanObjectDatas($this->bom); + } + /** * Delete bom * diff --git a/htdocs/bom/class/bom.class.php b/htdocs/bom/class/bom.class.php index f6c06d7b1c6..6f56a0d7192 100644 --- a/htdocs/bom/class/bom.class.php +++ b/htdocs/bom/class/bom.class.php @@ -983,14 +983,14 @@ class BOM extends CommonObject if (preg_match('/^[\(]?PROV/i', $this->ref)) { // Now we rename also files into index $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'bom/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'bom/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; diff --git a/htdocs/bookcal/class/availabilities.class.php b/htdocs/bookcal/class/availabilities.class.php index 72eb0ca3f24..b77948d7b9c 100644 --- a/htdocs/bookcal/class/availabilities.class.php +++ b/htdocs/bookcal/class/availabilities.class.php @@ -550,14 +550,14 @@ class Availabilities extends CommonObject if (preg_match('/^[\(]?PROV/i', $this->ref)) { // Now we rename also files into index $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'availabilities/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'availabilities/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'availabilities/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'availabilities/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'availabilities/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'availabilities/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; diff --git a/htdocs/bookcal/class/calendar.class.php b/htdocs/bookcal/class/calendar.class.php index e8398f57e7d..c4135a3564e 100644 --- a/htdocs/bookcal/class/calendar.class.php +++ b/htdocs/bookcal/class/calendar.class.php @@ -533,14 +533,14 @@ class Calendar extends CommonObject if (preg_match('/^[\(]?PROV/i', $this->ref)) { // Now we rename also files into index $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'calendar/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'calendar/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'calendar/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'calendar/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'calendar/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'calendar/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; diff --git a/htdocs/bookmarks/class/bookmark.class.php b/htdocs/bookmarks/class/bookmark.class.php index f109e67531a..a14dca9f0c8 100644 --- a/htdocs/bookmarks/class/bookmark.class.php +++ b/htdocs/bookmarks/class/bookmark.class.php @@ -2,6 +2,7 @@ /* Copyright (C) 2005 Laurent Destailleur * Copyright (C) 2015 Marcos García * Copyright (C) 2024 Frédéric France + * Copyright (C) 2026 MDW * * 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 @@ -118,7 +119,7 @@ class Bookmark extends CommonObject $sql .= " title, position, favicon"; $sql .= " FROM ".MAIN_DB_PREFIX."bookmark"; $sql .= " WHERE rowid = ".((int) $id); - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); dol_syslog("Bookmark::fetch", LOG_DEBUG); $resql = $this->db->query($sql); @@ -168,7 +169,7 @@ class Bookmark extends CommonObject $sql .= ",title,favicon,position"; $sql .= ",entity"; $sql .= ") VALUES ("; - $sql .= ($this->fk_user > 0 ? $this->fk_user : "0").","; + $sql .= ($this->fk_user > 0 ? ((int) $this->fk_user) : "0").","; $sql .= " '".$this->db->idate($now)."',"; $sql .= " '".$this->db->escape($this->url)."', '".$this->db->escape($this->target)."',"; $sql .= " '".$this->db->escape($this->title)."', '".$this->db->escape($this->favicon)."', ".(int) $this->position; @@ -212,7 +213,7 @@ class Bookmark extends CommonObject } $sql = "UPDATE ".MAIN_DB_PREFIX."bookmark"; - $sql .= " SET fk_user = ".($this->fk_user > 0 ? $this->fk_user : "0"); + $sql .= " SET fk_user = ".($this->fk_user > 0 ? ((int) $this->fk_user) : "0"); $sql .= " ,dateb = '".$this->db->idate($this->datec)."'"; $sql .= " ,url = '".$this->db->escape($this->url)."'"; $sql .= " ,target = '".$this->db->escape($this->target)."'"; @@ -343,7 +344,7 @@ class Bookmark extends CommonObject global $action, $hookmanager; $hookmanager->initHooks(array('mybookmarkdao')); - $parameters = array('id'=>$this->id, 'getnomurl' => &$result); + $parameters = array('id' => $this->id, 'getnomurl' => &$result); $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks if ($reshook > 0) { $result = $hookmanager->resPrint; diff --git a/htdocs/categories/class/categorie.class.php b/htdocs/categories/class/categorie.class.php index a0f7500555a..7e4f55e8e15 100644 --- a/htdocs/categories/class/categorie.class.php +++ b/htdocs/categories/class/categorie.class.php @@ -413,10 +413,10 @@ class Categorie extends CommonObject $mapCode = $mapList['code']; //self::$MAP_ID_TO_CODE[$mapId] = $mapCode; $this->MAP_ID[$mapCode] = $mapId; - $this->MAP_CAT_FK[$mapCode] = isset($mapList['cat_fk']) ? $mapList['cat_fk'] : null; - $this->MAP_CAT_TABLE[$mapCode] = isset($mapList['cat_table']) ? $mapList['cat_table'] : null; - $this->MAP_OBJ_CLASS[$mapCode] = $mapList['obj_class']; - $this->MAP_OBJ_TABLE[$mapCode] = $mapList['obj_table']; + $this->MAP_CAT_FK[$mapCode] = isset($mapList['cat_fk']) ? $mapList['cat_fk'] : null; // @phan-suppress-current-line SqlInjection + $this->MAP_CAT_TABLE[$mapCode] = isset($mapList['cat_table']) ? $mapList['cat_table'] : null; // @phan-suppress-current-line SqlInjection + $this->MAP_OBJ_CLASS[$mapCode] = $mapList['obj_class']; // @phan-suppress-current-line SqlInjection + $this->MAP_OBJ_TABLE[$mapCode] = $mapList['obj_table']; // @phan-suppress-current-line SqlInjection self::$MAP_TYPE_TITLE_AREA[$mapCode] = isset($mapList['label']) ? $mapList['label'] : null; } } @@ -607,7 +607,7 @@ class Categorie extends CommonObject $sql .= "'".$this->db->escape($this->color)."', "; $sql .= (int) $this->position.","; if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) { - $sql .= ($this->socid > 0 ? $this->socid : 'null').", "; + $sql .= ($this->socid > 0 ? ((int) $this->socid) : 'null').", "; } $sql .= "'".$this->db->escape((string) $this->visible)."', "; $sql .= ((int) $type).", "; @@ -704,7 +704,7 @@ class Categorie extends CommonObject $sql .= " color = '".$this->db->escape($this->color)."'"; $sql .= ", position = ".(int) $this->position; if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) { - $sql .= ", fk_soc = ".($this->socid > 0 ? $this->socid : 'null'); + $sql .= ", fk_soc = ".($this->socid > 0 ? ((int) $this->socid) : 'null'); } $sql .= ", visible = ".(int) $this->visible; $sql .= ", fk_parent = ".(int) $this->fk_parent; @@ -817,7 +817,7 @@ class Categorie extends CommonObject if (empty($value['enabled'])) { continue; } - $sanitizedvalue = $value['field']; + $sanitizedvalue = $value['field']; // From explicit array above @phan-suppress-current-line SqlInjection } $sql = "DELETE FROM ".$this->db->sanitize(MAIN_DB_PREFIX.$key); diff --git a/htdocs/categories/photos.php b/htdocs/categories/photos.php index f4fa2c44dd8..3a82bd2e795 100644 --- a/htdocs/categories/photos.php +++ b/htdocs/categories/photos.php @@ -149,7 +149,7 @@ if ($object->id) { dol_banner_tab($object, 'label', $linkback, ($user->socid ? 0 : 1), 'label', 'label', $morehtmlref, '&type='.$type, 0, '', '', 1); /* - * Confirmation deletion of picture + * Confirm photo deletion */ if ($action == 'delete') { print $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&type='.urlencode($type).'&file='.urlencode(GETPOST("file")), $langs->trans('DeletePicture'), $langs->trans('ConfirmDeletePicture'), 'confirm_delete', '', 0, 1); @@ -204,15 +204,15 @@ if ($object->id) { print ''."\n"; /* - * Ajouter une photo + * Add a photo */ if ($action == 'ajout_photo' && $user->hasRight('categorie', 'creer') && getDolGlobalString('MAIN_UPLOAD_DOC')) { - // Affiche formulaire upload + // Display upload form $formfile = new FormFile($db); $formfile->form_attach_new_file($_SERVER['PHP_SELF'].'?id='.$object->id.'&type='.$type, $langs->trans("AddPhoto"), 1, 0, $user->hasRight('categorie', 'creer'), 50, $object, '', 0, '', 0); } - // Affiche photos + // Display photos if ($action != 'ajout_photo') { $nbphoto = 0; $nbbyrow = 5; @@ -262,7 +262,7 @@ if ($object->id) { print '
'.$viewfilename; print '
'; - // On propose la generation de la vignette si elle n'existe pas et si la taille est superieure aux limites + // Suggest generation of the thumbnail if it doesn't exist and if the size exceeds the limits if (!$obj['photo_vignette'] && preg_match('/(\.bmp|\.gif|\.jpg|\.jpeg|\.png)$/i', $obj['photo']) && ($object->imgWidth > $maxWidth || $object->imgHeight > $maxHeight)) { print ''.img_picto($langs->trans('GenerateThumb'), 'refresh').'  '; } diff --git a/htdocs/comm/action/class/actioncomm.class.php b/htdocs/comm/action/class/actioncomm.class.php index cfe601075d4..a8ce446b104 100644 --- a/htdocs/comm/action/class/actioncomm.class.php +++ b/htdocs/comm/action/class/actioncomm.class.php @@ -6,7 +6,7 @@ * Copyright (C) 2015 Marcos García * Copyright (C) 2018 Nicolas ZABOURI * Copyright (C) 2018-2025 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 William Mead * * This program is free software; you can redistribute it and/or modify @@ -651,15 +651,15 @@ class ActionComm extends CommonObject $sql .= "'".$this->db->idate($this->datep)."', "; // date start event $sql .= (strval($this->datef) != '' ? "'".$this->db->idate($this->datef)."'" : "null").", "; $sql .= ((isset($this->durationp) && $this->durationp >= 0 && $this->durationp != '') ? "'".$this->db->escape((string) $this->durationp)."'" : "null").", "; // deprecated - $sql .= (isset($this->type_id) ? $this->type_id : "null").","; + $sql .= (isset($this->type_id) ? ((int) $this->type_id) : "null").","; $sql .= ($code ? ("'".$this->db->escape($code)."'") : "null").", "; $sql .= (!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").", "; $sql .= ((isset($this->socid) && $this->socid > 0) ? ((int) $this->socid) : "null").", "; $sql .= ((isset($this->fk_project) && $this->fk_project > 0) ? ((int) $this->fk_project) : "null").", "; $sql .= " '".$this->db->escape($this->note_private)."', "; $sql .= ((isset($this->contact_id) && $this->contact_id > 0) ? ((int) $this->contact_id) : "null").", "; // deprecated, use ->socpeopleassigned - $sql .= (isset($user->id) && $user->id > 0 ? $user->id : "null").", "; - $sql .= ($userownerid > 0 ? $userownerid : "null").", "; + $sql .= (isset($user->id) && $user->id > 0 ? ((int) $user->id) : "null").", "; + $sql .= ($userownerid > 0 ? ((int) $userownerid) : "null").", "; $sql .= (!empty($this->fk_task) ? ((int) $this->fk_task) : "null").", "; $sql .= "'".$this->db->escape($this->label)."', "; $sql .= "'".$this->db->escape((string) $this->percentage)."', "; @@ -695,7 +695,7 @@ class ActionComm extends CommonObject if ($resql) { $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."actioncomm", "id"); $this->ref = (string) $this->id; - $sql = "UPDATE ".MAIN_DB_PREFIX."actioncomm SET ref='".$this->db->escape($this->ref)."' WHERE id=".$this->id; + $sql = "UPDATE ".MAIN_DB_PREFIX."actioncomm SET ref='".$this->db->escape($this->ref)."' WHERE id=".((int) $this->id); $resql = $this->db->query($sql); if (!$resql) { $error++; @@ -1406,13 +1406,13 @@ class ActionComm extends CommonObject * @param int $socid Filter by thirdparty * @param int $fk_element Id of element action is linked to * @param string $elementtype Type of element action is linked to - * @param string $filter Other filter + * @param string $sqlfilter Other filter * @param string $sortfield Sort on this field * @param string $sortorder ASC or DESC * @param int $limit Limit number of answers * @return ActionComm[]|string Error string if KO, array with actions if OK */ - public function getActions($socid = 0, $fk_element = 0, $elementtype = '', $filter = '', $sortfield = 'a.datep', $sortorder = 'DESC', $limit = 0) + public function getActions($socid = 0, $fk_element = 0, $elementtype = '', $sqlfilter = '', $sortfield = 'a.datep', $sortorder = 'DESC', $limit = 0) { global $hookmanager; @@ -1461,8 +1461,8 @@ class ActionComm extends CommonObject $sql .= " AND a.fk_element = ".((int) $fk_element)." AND a.elementtype = '".$this->db->escape($elementtype)."'"; } } - if (!empty($filter)) { - $sql .= $filter; + if (!empty($sqlfilter)) { + $sql .= $sqlfilter; } // Fields where hook $parameters = array('sql' => &$sql, 'socid' => $socid, 'fk_element' => $fk_element, 'elementtype' => $elementtype); diff --git a/htdocs/comm/action/index.php b/htdocs/comm/action/index.php index 12f5e97cb8f..611858c94ad 100644 --- a/htdocs/comm/action/index.php +++ b/htdocs/comm/action/index.php @@ -1826,7 +1826,7 @@ if (empty($mode) || $mode == 'show_month') { // View by month $arraytimestamp = dol_getdate($timestamp); print '
'; - print_actions_filter($form, $canedit, $status, $year, $month, $day, $check_birthday, '', $filtert, '', $pid, $socid, $action, -1, $actioncode, $usergroup, '', $resourceid); + print_actions_filter($form, $canedit, $status, $year, $month, $day, $check_birthday, '', $filtert, '', $pid, $socid, $action, -1, $actioncode, $usergroupids, '', $resourceid); print '
'; print '
'; diff --git a/htdocs/comm/index.php b/htdocs/comm/index.php index 00abcdb2743..3a8bab1a967 100644 --- a/htdocs/comm/index.php +++ b/htdocs/comm/index.php @@ -74,7 +74,8 @@ if (!empty($user->socid) && $user->socid > 0) { $total = 0; -$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); +$max = getDolUserInt('MAIN_SIZE_SHORTLIST_LIMIT', getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5)); + $maxofloop = getDolGlobalInt('MAIN_MAXLIST_OVERLOAD', 500); $now = dol_now(); @@ -624,7 +625,7 @@ if (isModEnabled('intervention') && is_object($fichinterstatic)) { print $fichinterstatic->getNomUrl(1); print ""; print ''; - print $companystatic->getNomUrl(1, 'customer'); + print $companystatic->getNomUrl(1); print ''; print ''; print convertSecondToTime($obj->duration); diff --git a/htdocs/comm/mailing/advtargetemailing.php b/htdocs/comm/mailing/advtargetemailing.php index ba06d99dab2..7187c97a6a1 100644 --- a/htdocs/comm/mailing/advtargetemailing.php +++ b/htdocs/comm/mailing/advtargetemailing.php @@ -203,7 +203,7 @@ if ($action == 'add' && $permissiontoadd) { // Add recipients } if (preg_match("/^contact_/", $key)) { - $array_query[$key] = GETPOST($key); + $array_query[$key] = GETPOSTISARRAY($key) ? GETPOST($key, 'array:int') : GETPOST($key); $specials_date_key = array( 'contact_update_st_dt', @@ -232,7 +232,7 @@ if ($action == 'add' && $permissiontoadd) { // Add recipients } if (preg_match("/^type_of_target/", $key)) { - $array_query[$key] = GETPOST($key); + $array_query[$key] = GETPOSTINT($key); } } @@ -270,12 +270,12 @@ if ($action == 'add' && $permissiontoadd) { // Add recipients } if ($result > 0) { - $query_temlate_id = ''; + $get_param_template_id = ''; if (!empty($template_id)) { - $query_temlate_id = '&template_id='.$template_id; + $get_param_template_id = '&template_id='.$template_id; } setEventMessages($langs->trans("XTargetsAdded", $result), null, 'mesgs'); - header("Location: ".$_SERVER['PHP_SELF']."?id=".$id.$query_temlate_id); + header("Location: ".$_SERVER['PHP_SELF']."?id=".$id.$get_param_template_id); exit(); } if ($result == 0) { @@ -617,11 +617,9 @@ if ($object->fetch($id) >= 0) { print dol_get_fiche_end(); - print '
'; - // Show email selectors - if ($object->status == 0 && $user->hasRight('mailing', 'creer')) { + if ($object->status < 2 && $user->hasRight('mailing', 'creer')) { // @phan-assert FormAdvTargetEmailing $formadvtargetemaling // @phan-assert AdvanceTargetingMailing $advTarget @@ -666,7 +664,9 @@ if ($object->fetch($id) >= 0) { '; - print load_fiche_titre($langs->trans("AdvTgtTitle").'...', '', ''); + print '
'; + print $langs->trans("AdvTgtTitle").'...'; + print '
'; print ''."\n"; print ''."\n"; @@ -709,7 +709,7 @@ if ($object->fetch($id) >= 0) { // Customer name print ''.$langs->trans('ThirdPartyName'); if (!empty($array_query['cust_name'])) { - print img_picto($langs->trans('AdvTgtUse'), 'ok.png@advtargetemailing'); + print img_picto($langs->trans('AdvTgtUse'), 'tick', 'class="marginleftonly"'); } print ''."\n"; print $form->textwithpicto('', $langs->trans("AdvTgtSearchTextHelp"), 1, 'help'); @@ -1085,6 +1085,7 @@ if ($object->fetch($id) >= 0) { $contactstatic = new Contact($db); $elementtype = $contactstatic->table_element; // fetch optionals attributes and labels + $extrafields->fetch_name_optionals_label($elementtype); $extrafieldinfo = $extrafields->attributes[$elementtype] ?? []; if (!empty($extrafieldinfo['type'])) { diff --git a/htdocs/comm/mailing/card.php b/htdocs/comm/mailing/card.php index d27170fd24b..ba6be4e5d97 100644 --- a/htdocs/comm/mailing/card.php +++ b/htdocs/comm/mailing/card.php @@ -3,7 +3,7 @@ * Copyright (C) 2005-2019 Laurent Destailleur * Copyright (C) 2005-2016 Regis Houssin * Copyright (C) 2021 Waël Almoman - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2025 Jon Bendtsen * Copyright (C) 2024-2026 Frédéric France * @@ -63,8 +63,8 @@ $id = (GETPOSTINT('mailid') ? GETPOSTINT('mailid') : GETPOSTINT('id')); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); $cancel = GETPOST('cancel', 'alpha'); -$urlfrom = GETPOST('urlfrom'); $projectid = GETPOSTINT('projectid'); +$urlfrom = GETPOST('urlfrom'); $backtopage = GETPOST('backtopage'); $backtopageforcancel = GETPOST('backtopageforcancel'); @@ -202,7 +202,7 @@ if (empty($reshook)) { $id = $object->id; $subject = $object->sujet; $message = $object->body; - $from = $object->email_from; + $email_from = $object->email_from; $replyto = $object->email_replyto; $errorsto = $object->email_errorsto; // Is the message in html @@ -382,7 +382,7 @@ if (empty($reshook)) { // Mail making $trackid = 'emailing-'.$obj->fk_mailing.'-'.$obj->rowid; $upload_dir_tmp = $upload_dir; - $mail = new CMailFile($newsubject, $sendto, $from, $newmessage, $arr_file, $arr_mime, $arr_name, '', '', 0, $msgishtml, $errorsto, $arr_css, $trackid, $moreinheader, 'emailing', $replyto, $upload_dir_tmp); + $mail = new CMailFile($newsubject, $sendto, $email_from, $newmessage, $arr_file, $arr_mime, $arr_name, '', '', 0, $msgishtml, $errorsto, $arr_css, $trackid, $moreinheader, 'emailing', $replyto, $upload_dir_tmp); if ($mail->error) { $res = 0; diff --git a/htdocs/comm/mailing/class/advtargetemailing.class.php b/htdocs/comm/mailing/class/advtargetemailing.class.php index 2e7f7ed560c..4e8e5e3affa 100644 --- a/htdocs/comm/mailing/class/advtargetemailing.class.php +++ b/htdocs/comm/mailing/class/advtargetemailing.class.php @@ -2,7 +2,7 @@ /* Advance Targeting Emailing for mass emailing module * Copyright (C) 2013 Florian Henry * Copyright (C) 2024 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -430,7 +430,7 @@ class AdvanceTargetingMailing extends CommonObject $sql .= " name=".(isset($this->name) ? "'".$this->db->escape($this->name)."'" : "''").","; $sql .= " entity=".((int) $conf->entity).","; - $sql .= " fk_element=".(isset($this->fk_element) ? $this->fk_element : "null").","; + $sql .= " fk_element=".(isset($this->fk_element) ? ((int) $this->fk_element) : "null").","; $sql .= " type_element=".(isset($this->type_element) ? "'".$this->db->escape($this->type_element)."'" : "null").","; $sql .= " filtervalue=".(isset($this->filtervalue) ? "'".$this->db->escape($this->filtervalue)."'" : "null").","; $sql .= " fk_user_mod=".((int) $user->id); @@ -573,8 +573,8 @@ class AdvanceTargetingMailing extends CommonObject $sqlwhere[] = $this->transformToSQL('t.town', $arrayquery['cust_city']); } if (!empty($arrayquery['cust_mothercompany'])) { - $str = $this->transformToSQL('nom', $arrayquery['cust_mothercompany']); - $sqlwhere[] = " (t.parent IN (SELECT rowid FROM ".MAIN_DB_PREFIX."societe WHERE (".$str.")))"; + $sqlStr = $this->transformToSQL('nom', $arrayquery['cust_mothercompany']); + $sqlwhere[] = " (t.parent IN (SELECT rowid FROM ".MAIN_DB_PREFIX."societe WHERE (".$sqlStr.")))"; } if (!empty($arrayquery['cust_status']) && count($arrayquery['cust_status']) > 0) { $sqlwhere[] = " (t.status IN (".$this->db->sanitize(implode(',', $arrayquery['cust_status']))."))"; @@ -646,7 +646,7 @@ class AdvanceTargetingMailing extends CommonObject if (count($arrayquery['options_'.$key])) { $i2 = 0; $field = "te.".$key; - $sqlwhereselllist=""; + $sqlwhereselllist = ""; foreach ($arrayquery['options_'.$key] as $data) { $data = trim($data); if ($data) { @@ -714,8 +714,6 @@ class AdvanceTargetingMailing extends CommonObject public function query_contact($arrayquery, $withThirdpartyFilter = 0) { // phpcs:enable - global $langs, $conf; - $sql = "SELECT"; $sql .= " t.rowid"; $sql .= " FROM ".MAIN_DB_PREFIX."socpeople as t"; @@ -751,13 +749,13 @@ class AdvanceTargetingMailing extends CommonObject $sqlwhere[] = " (t.civility IN (".$this->db->sanitize("'".implode("','", $arrayquery['contact_civility'])."'", 1)."))"; } if ($arrayquery['contact_no_email'] != '') { - $tmpwhere = ''; + $sqlTmpWhere = ''; if (!empty($arrayquery['contact_no_email'])) { - $tmpwhere .= "(t.email IN (SELECT email FROM ".MAIN_DB_PREFIX."mailing_unsubscribe WHERE t.entity IN (".getEntity('mailing').") AND email = '".$this->db->escape($arrayquery['contact_no_email'])."'))"; + $sqlTmpWhere .= "(t.email IN (SELECT email FROM ".MAIN_DB_PREFIX."mailing_unsubscribe WHERE t.entity IN (".getEntity('mailing').") AND email = '".$this->db->escape($arrayquery['contact_no_email'])."'))"; } else { - $tmpwhere .= "(t.email NOT IN (SELECT email FROM ".MAIN_DB_PREFIX."mailing_unsubscribe WHERE t.entity IN (".getEntity('mailing').") AND email = '".$this->db->escape((string) $arrayquery['contact_no_email'])."'))"; + $sqlTmpWhere .= "(t.email NOT IN (SELECT email FROM ".MAIN_DB_PREFIX."mailing_unsubscribe WHERE t.entity IN (".getEntity('mailing').") AND email = '".$this->db->escape((string) $arrayquery['contact_no_email'])."'))"; } - $sqlwhere[] = $tmpwhere; + $sqlwhere[] = $sqlTmpWhere; } if ($arrayquery['contact_update_st_dt'] != '') { $sqlwhere[] = " (t.tms >= '".$this->db->idate($arrayquery['contact_update_st_dt'])."' AND t.tms <= '".$this->db->idate($arrayquery['contact_update_end_dt'])."')"; @@ -766,7 +764,7 @@ class AdvanceTargetingMailing extends CommonObject $sqlwhere[] = " (t.datec >= '".$this->db->idate($arrayquery['contact_create_st_dt'])."' AND t.datec <= '".$this->db->idate($arrayquery['contact_create_end_dt'])."')"; } if (!empty($arrayquery['contact_categ']) && count($arrayquery['contact_categ']) > 0) { - $sqlwhere[] = " (contactcateg.fk_categorie IN (".$this->db->escape(implode(",", $arrayquery['contact_categ']))."))"; + $sqlwhere[] = " (contactcateg.fk_categorie IN (".$this->db->sanitize(implode(",", $arrayquery['contact_categ']), 0, 0, 0, 0)."))"; } //Standard Extrafield feature @@ -807,14 +805,14 @@ class AdvanceTargetingMailing extends CommonObject } } elseif ($extrafields->attributes[$elementtype]['type'][$key] == 'link') { if ($arrayquery['options_'.$key."_cnct"] > 0) { - $sqlwhere[]= " (te.".$key." = ".((int) $arrayquery["options_".$key."_cnct"]).")"; + $sqlwhere[] = " (te.".$key." = ".((int) $arrayquery["options_".$key."_cnct"]).")"; } } elseif ($extrafields->attributes[$elementtype]['type'][$key] == 'chkbxlst' && is_array($arrayquery['options_'.$key.'_cnct'])) { if (count($arrayquery['options_'.$key.'_cnct'])) { $i2 = 0; $field = "te.".$key; - $sqlwhereselllist=""; + $sqlwhereselllist = ""; foreach ($arrayquery['options_'.$key.'_cnct'] as $data) { $data = trim($data); if ($data) { @@ -863,8 +861,8 @@ class AdvanceTargetingMailing extends CommonObject $sqlwhere[] = $this->transformToSQL('ts.town', $arrayquery['cust_city']); } if (!empty($arrayquery['cust_mothercompany'])) { - $str = $this->transformToSQL('nom', $arrayquery['cust_mothercompany']); - $sqlwhere[] = " (ts.parent IN (SELECT rowid FROM ".MAIN_DB_PREFIX."societe WHERE (".$str.")))"; + $sql_str = $this->transformToSQL('nom', $arrayquery['cust_mothercompany']); + $sqlwhere[] = " (ts.parent IN (SELECT rowid FROM ".MAIN_DB_PREFIX."societe WHERE (".$sql_str.")))"; } if (!empty($arrayquery['cust_status']) && count($arrayquery['cust_status']) > 0) { $sqlwhere[] = " (ts.status IN (".$this->db->sanitize(implode(',', $arrayquery['cust_status']))."))"; @@ -936,14 +934,14 @@ class AdvanceTargetingMailing extends CommonObject } } elseif ($extrafields->attributes[$elementtype]['type'][$key] == 'link') { if ($arrayquery['options_'.$key] > 0) { - $sqlwhere[]= " (te.".$key." = ".((int) $arrayquery["options_".$key]).")"; + $sqlwhere[] = " (te.".$key." = ".((int) $arrayquery["options_".$key]).")"; } } elseif ($extrafields->attributes[$elementtype]['type'][$key] == 'chkbxlst' && is_array($arrayquery['options_'.$key])) { if (count($arrayquery['options_'.$key])) { $i2 = 0; $field = "tse.".$key; - $sqlwhereselllist=""; + $sqlwhereselllist = ""; foreach ($arrayquery['options_'.$key] as $data) { $data = trim($data); if ($data) { diff --git a/htdocs/comm/mailing/class/html.formadvtargetemailing.class.php b/htdocs/comm/mailing/class/html.formadvtargetemailing.class.php index 89fe486647b..37df5897f7e 100644 --- a/htdocs/comm/mailing/class/html.formadvtargetemailing.class.php +++ b/htdocs/comm/mailing/class/html.formadvtargetemailing.class.php @@ -226,7 +226,7 @@ class FormAdvTargetEmailing extends Form $sql_usr = ''; $sql_usr .= "SELECT DISTINCT u2.rowid, u2.lastname as name, u2.firstname, u2.login"; $sql_usr .= " FROM ".MAIN_DB_PREFIX."user as u2, ".MAIN_DB_PREFIX."societe_commerciaux as sc"; - $sql_usr .= " WHERE u2.entity IN (0,".$conf->entity.")"; + $sql_usr .= " WHERE u2.entity IN (0,".((int) $conf->entity).")"; $sql_usr .= " AND u2.rowid = sc.fk_user"; if (getDolGlobalString('USER_HIDE_INACTIVE_IN_COMBOBOX')) { $sql_usr .= " AND u2.statut <> 0"; @@ -325,7 +325,7 @@ class FormAdvTargetEmailing extends Form if (!empty($InfoFieldList[1])) { $sql .= $this->db->order($InfoFieldList[1]); } - // $sql.= ' WHERE entity = '.$conf->entity; + // $sql.= ' WHERE entity = '.((int) $conf->entity); $resql = $this->db->query($sql); if ($resql) { diff --git a/htdocs/comm/mailing/class/mailing.class.php b/htdocs/comm/mailing/class/mailing.class.php index dcc05eb1e5f..70c0cbc5d0c 100644 --- a/htdocs/comm/mailing/class/mailing.class.php +++ b/htdocs/comm/mailing/class/mailing.class.php @@ -3,7 +3,7 @@ * Copyright (C) 2005-2016 Laurent Destailleur * Copyright (C) 2005-2009 Regis Houssin * Copyright (C) 2024-2025 Frédéric France - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2025 Jon Bendtsen * * This program is free software; you can redistribute it and/or modify @@ -703,7 +703,7 @@ class Mailing extends CommonObject $now = dol_now(); $sql = "UPDATE ".MAIN_DB_PREFIX."mailing "; - $sql .= " SET statut = 1, date_valid = '".$this->db->idate($now)."', fk_user_valid=".$user->id; + $sql .= " SET statut = 1, date_valid = '".$this->db->idate($now)."', fk_user_valid=".((int) $user->id); $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog("Mailing::valid", LOG_DEBUG); @@ -726,7 +726,7 @@ class Mailing extends CommonObject $now = dol_now(); $sql = "UPDATE ".MAIN_DB_PREFIX."mailing "; - $sql .= " SET statut = 0, tms = '".$this->db->idate($now)."', fk_user_modif=".$user->id; + $sql .= " SET statut = 0, tms = '".$this->db->idate($now)."', fk_user_modif=".((int) $user->id); $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog("Mailing::valid", LOG_DEBUG); diff --git a/htdocs/comm/mailing/index.php b/htdocs/comm/mailing/index.php index e761c2c1a95..3d9d67527f0 100644 --- a/htdocs/comm/mailing/index.php +++ b/htdocs/comm/mailing/index.php @@ -81,11 +81,12 @@ print '
'; print ''; print ''; print ''; -print ''; -print ''; - +print '
'.$titlesearch.'
'; -print $langs->trans("Ref").':
'; -print $langs->trans("Other").':
'; +print ''; +print ''; +print '
'.$langs->trans("Ref").':
'.$langs->trans("Other").':
'; +print ''; +print ''; print "

\n"; diff --git a/htdocs/comm/mailing/list.php b/htdocs/comm/mailing/list.php index 6cfe5abf85a..e77f12035e2 100644 --- a/htdocs/comm/mailing/list.php +++ b/htdocs/comm/mailing/list.php @@ -1,7 +1,7 @@ * Copyright (C) 2005-2010 Regis Houssin - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Alexandre Spangaro * Copyright (C) 2024-2026 Frédéric France * Copyright (C) 2025 Jon Bendtsen @@ -197,7 +197,7 @@ if ($filteremail) { $sql .= " FROM ".MAIN_DB_PREFIX."mailing as m, ".MAIN_DB_PREFIX."mailing_cibles as mc"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet as pr ON pr.rowid = m.fk_project"; - $sql .= " WHERE m.rowid = mc.fk_mailing AND m.entity = ".$conf->entity; + $sql .= " WHERE m.rowid = mc.fk_mailing AND m.entity = ".((int) $conf->entity); $sql .= " AND mc.email = '".$db->escape($filteremail)."'"; if ($search_ref) { $sql .= natural_search("m.rowid", $search_ref, 1); diff --git a/htdocs/comm/mailing/targetemailing.php b/htdocs/comm/mailing/targetemailing.php index b3f67677724..ea986d16006 100644 --- a/htdocs/comm/mailing/targetemailing.php +++ b/htdocs/comm/mailing/targetemailing.php @@ -501,8 +501,6 @@ if ($object->fetch($id) >= 0) { print dol_get_fiche_end(); - print '
'; - $newcardbutton = ''; $allowaddtarget = ($object->status == $object::STATUS_DRAFT); @@ -515,14 +513,19 @@ if ($object->fetch($id) >= 0) { // Show email selectors if ($allowaddtarget && $user->hasRight('mailing', 'creer')) { - print load_fiche_titre($langs->trans("ToAddRecipientsChooseHere").'...', ($user->admin ? info_admin($langs->trans("YouCanAddYourOwnPredefindedListHere"), 1) : ''), ''); + print '
'; + print $langs->trans("ToAddRecipientsChooseHere").'...'; + //print ($user->admin ? info_admin($langs->trans("YouCanAddYourOwnPredefindedListHere"), 1) : ''); + print '
'; print '
'; print '
'; print '
'; print '
'; - print '
'.$langs->trans("RecipientSelectionModules").'
'; + print '
'.$langs->trans("RecipientSelectionModules"); + print ($user->admin ? info_admin($langs->trans("YouCanAddYourOwnPredefindedListHere"), 1) : ''); + print '
'; print '
'; if ($object->messtype != 'sms') { print $langs->trans("NbOfUniqueEMails"); @@ -533,7 +536,7 @@ if ($object->fetch($id) >= 0) { print '
'.$langs->trans("Filters").'
'; if ($object->messtype != 'sms') { print '              
'.$langs->trans("EvenUnsubscribe").' '; - print ajax_object_onoff($object, 'evenunsubscribe', 'evenunsubscribe', 'EvenUnsubscribe:switch_on:warning', 'EvenUnsubscribe', array(), 'small valignmiddle reposition', '', 1); + print ajax_object_onoff($object, 'evenunsubscribe', 'evenunsubscribe', 'EvenUnsubscribe:switch_on:warning', 'EvenUnsubscribe', array(), 'small valignmiddle reposition', '', 1, 'allowaddtarget=1'); print '
'; } print '
'; @@ -596,6 +599,7 @@ if ($object->fetch($id) >= 0) { print ''; print ''; print ''; + //print ''; } else { print '
'; } diff --git a/htdocs/comm/propal/card.php b/htdocs/comm/propal/card.php index 7e96849bb97..100dac6c8c9 100644 --- a/htdocs/comm/propal/card.php +++ b/htdocs/comm/propal/card.php @@ -3077,7 +3077,7 @@ if ($action == 'create') { // Thirdparty $morehtmlref .= '
' . $soc->getNomUrl(1, 'customer'); if (!getDolGlobalString('MAIN_DISABLE_OTHER_LINK') && $soc->id > 0) { - $morehtmlref .= ' (' . $langs->trans("OtherProposals") . ')'; + $morehtmlref .= ' (' . $langs->trans("OtherProposals") . ')'; } // Project if (isModEnabled('project')) { diff --git a/htdocs/comm/propal/class/propal.class.php b/htdocs/comm/propal/class/propal.class.php index 2aa29f0dcfe..882ca701000 100644 --- a/htdocs/comm/propal/class/propal.class.php +++ b/htdocs/comm/propal/class/propal.class.php @@ -1264,7 +1264,7 @@ class Propal extends CommonObject $sql .= ", multicurrency_tx"; $sql .= ") "; $sql .= " VALUES ("; - $sql .= $this->socid; + $sql .= ((int) $this->socid); $sql .= ", 0"; $sql .= ", 0"; $sql .= ", 0"; @@ -1283,11 +1283,11 @@ class Propal extends CommonObject $sql .= ", '".$this->db->escape((string) $this->ref_client)."'"; $sql .= ", '".$this->db->escape((string) $this->ref_ext)."'"; $sql .= ", ".(!isDolTms($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'"); - $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL'); - $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL'); + $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : 'NULL'); + $sql .= ", ".($this->warehouse_id > 0 ? ((int) $this->warehouse_id) : 'NULL'); $sql .= ", ".((int) $this->availability_id); $sql .= ", ".((int) $this->demand_reason_id); - $sql .= ", ".($this->fk_project ? $this->fk_project : "null"); + $sql .= ", ".($this->fk_project ? ((int) $this->fk_project) : "null"); $sql .= ", ".(int) $this->fk_incoterms; $sql .= ", '".$this->db->escape($this->location_incoterms)."'"; $sql .= ", ".(int) $this->entity; @@ -2162,7 +2162,7 @@ class Propal extends CommonObject $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; @@ -2422,7 +2422,7 @@ class Propal extends CommonObject $this->db->begin(); $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element; - $sql .= " SET fk_availability = ".((int) ($id > 0 ? $id : 0)); + $sql .= " SET fk_availability = ".((int) ($id > 0 ? ((int) $id) : 0)); $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG); @@ -2690,7 +2690,7 @@ class Propal extends CommonObject $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element; $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."', note_public = '".$this->db->escape($newpublicnote)."'"; if ($status == self::STATUS_SIGNED) { - $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".($fk_user_signature); + $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".((int) $fk_user_signature); } $sql .= " WHERE rowid = ".((int) $this->id); @@ -3110,8 +3110,8 @@ class Propal extends CommonObject $tab_sqlobj = array(); $nump = $this->db->num_rows($resql); for ($i = 0; $i < $nump; $i++) { - $sqlobj = $this->db->fetch_object($resql); - $tab_sqlobj[] = $sqlobj; + $obj = $this->db->fetch_object($resql); + $tab_sqlobj[] = $obj; } $this->db->free($resql); diff --git a/htdocs/comm/propal/class/propaleligne.class.php b/htdocs/comm/propal/class/propaleligne.class.php index 1861ed3ce60..6a5a51cdc18 100644 --- a/htdocs/comm/propal/class/propaleligne.class.php +++ b/htdocs/comm/propal/class/propaleligne.class.php @@ -583,7 +583,7 @@ class PropaleLigne extends CommonObjectLine $sql .= ' fk_unit,'; $sql .= ' date_start, date_end'; $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)'; - $sql .= " VALUES (".$this->fk_propal.","; + $sql .= " VALUES (".((int) $this->fk_propal).","; $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape((string) $this->fk_parent_line)."'" : "null").","; $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").","; $sql .= " '".$this->db->escape($this->desc)."',"; @@ -795,7 +795,7 @@ class PropaleLigne extends CommonObjectLine $sql .= " description = '".$this->db->escape($this->desc)."'"; $sql .= ", label = ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null"); $sql .= ", product_type = ".((int) $this->product_type); - $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'"; + $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->db->escape($this->vat_src_code))."'"; $sql .= ", tva_tx='".price2num($this->tva_tx)."'"; $sql .= ", localtax1_tx=".price2num($this->localtax1_tx); $sql .= ", localtax2_tx=".price2num($this->localtax2_tx); diff --git a/htdocs/comm/propal/index.php b/htdocs/comm/propal/index.php index 4438bed9e71..d50c9ec9737 100644 --- a/htdocs/comm/propal/index.php +++ b/htdocs/comm/propal/index.php @@ -47,7 +47,8 @@ $hookmanager->initHooks(array('proposalindex')); $langs->loadLangs(array('propal', 'companies')); $now = dol_now(); -$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); + +$max = getDolUserInt('MAIN_SIZE_SHORTLIST_LIMIT', getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5)); // Security check $socid = GETPOSTINT('socid'); diff --git a/htdocs/comm/propal/stats/index.php b/htdocs/comm/propal/stats/index.php index 6156188f04c..83c19058a23 100644 --- a/htdocs/comm/propal/stats/index.php +++ b/htdocs/comm/propal/stats/index.php @@ -30,13 +30,6 @@ // Load Dolibarr environment require '../../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propalestats.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; -require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/html.formpropal.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -44,6 +37,12 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propalestats.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; +require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formpropal.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; $WIDTH = DolGraph::getDefaultGraphSizeForStats('width'); $HEIGHT = DolGraph::getDefaultGraphSizeForStats('height'); @@ -99,7 +98,8 @@ $formother = new FormOther($db); $langs->loadLangs(array('propal', 'other', 'companies')); $picto = 'propal'; -$title = $langs->trans("ProposalsStatistics"); +$title = $langs->trans('Proposals'); + $dir = $conf->propal->dir_temp; $cat_type = Categorie::TYPE_CUSTOMER; $cat_label = $langs->trans("Category").' '.lcfirst($langs->trans("Customer")); diff --git a/htdocs/comm/remx.php b/htdocs/comm/remx.php index 30ad014ece8..48515a90b5c 100644 --- a/htdocs/comm/remx.php +++ b/htdocs/comm/remx.php @@ -192,7 +192,7 @@ if ($action == 'confirm_split' && GETPOST("confirm", "alpha") == 'yes' && $permi } if (!$error) { // Split a discount in two - $newDiscounts = $discount->splitAmount((float) $amount_ttc_1, (float) $amount_ttc_2); + $newDiscounts = $discount->splitAmount((float) $amount_ttc_1, (float) $amount_ttc_2); // Note: splitting this way will result of a total ttc similar to original but total ht and total taxes may differ. $newdiscount1 = $newDiscounts[0]; $newdiscount2 = $newDiscounts[1]; @@ -1159,18 +1159,18 @@ if ($socid > 0) { $num = $db->num_rows($resql); if ($num > 0) { for ($i = 0; $i < $num; $i++) { - $sqlobj = $db->fetch_object($resql); - $tab_sqlobj[] = $sqlobj; - $tab_sqlobjOrder[] = $db->jdate($sqlobj->dc); + $fetched_obj = $db->fetch_object($resql); + $tab_sqlobj[] = $fetched_obj; + $tab_sqlobjOrder[] = $db->jdate($fetched_obj->dc); } } $db->free($resql); $num = $db->num_rows($resql2); for ($i = 0; $i < $num; $i++) { - $sqlobj = $db->fetch_object($resql2); - $tab_sqlobj[] = $sqlobj; - $tab_sqlobjOrder[] = $db->jdate($sqlobj->dc); + $fetched_obj = $db->fetch_object($resql2); + $tab_sqlobj[] = $fetched_obj; + $tab_sqlobjOrder[] = $db->jdate($fetched_obj->dc); } $db->free($resql2); $array1_sort_order = SORT_DESC; @@ -1266,7 +1266,7 @@ if ($socid > 0) { $sql .= " rc.multicurrency_amount_ht, rc.multicurrency_amount_tva, rc.multicurrency_amount_ttc,"; $sql .= " rc.datec as dc, rc.description, rc.fk_invoice_supplier_line,"; $sql .= " rc.fk_invoice_supplier_source,"; - $sql .= " u.login, u.rowid as user_id, u.statut as user_status, u.firstname, u.lastname, u.photo,"; + $sql .= " u.login, u.rowid as user_id, u.statut as status, u.firstname, u.lastname, u.photo,"; $sql .= " f.rowid as invoiceid, f.ref as ref,"; $sql .= " fa.ref as invoice_source_ref, fa.type as type"; $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f"; @@ -1286,7 +1286,7 @@ if ($socid > 0) { $sql2 .= " rc.multicurrency_amount_ht, rc.multicurrency_amount_tva, rc.multicurrency_amount_ttc,"; $sql2 .= " rc.datec as dc, rc.description, rc.fk_invoice_supplier,"; $sql2 .= " rc.fk_invoice_supplier_source,"; - $sql2 .= " u.login, u.rowid as user_id, u.statut as user_status, u.firstname, u.lastname, u.photo,"; + $sql2 .= " u.login, u.rowid as user_id, u.statut as status, u.firstname, u.lastname, u.photo,"; $sql2 .= " f.rowid as invoiceid, f.ref as ref,"; $sql2 .= " fa.ref as invoice_source_ref, fa.type as type"; $sql2 .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f"; @@ -1329,18 +1329,18 @@ if ($socid > 0) { $num = $db->num_rows($resql); if ($num > 0) { for ($i = 0; $i < $num; $i++) { - $sqlobj = $db->fetch_object($resql); - $tab_sqlobj[] = $sqlobj; - $tab_sqlobjOrder[] = $db->jdate($sqlobj->dc); + $fetched_obj = $db->fetch_object($resql); + $tab_sqlobj[] = $fetched_obj; + $tab_sqlobjOrder[] = $db->jdate($fetched_obj->dc); } } $db->free($resql); $num = $db->num_rows($resql2); for ($i = 0; $i < $num; $i++) { - $sqlobj = $db->fetch_object($resql2); - $tab_sqlobj[] = $sqlobj; - $tab_sqlobjOrder[] = $db->jdate($sqlobj->dc); + $fetched_obj = $db->fetch_object($resql2); + $tab_sqlobj[] = $fetched_obj; + $tab_sqlobjOrder[] = $db->jdate($fetched_obj->dc); } $db->free($resql2); $array1_sort_order = SORT_DESC; diff --git a/htdocs/commande/card.php b/htdocs/commande/card.php index 2b49c728406..9876695778e 100644 --- a/htdocs/commande/card.php +++ b/htdocs/commande/card.php @@ -2958,7 +2958,7 @@ if ($action == 'create' && $usercancreate) { // Thirdparty $morehtmlref .= '
' . $soc->getNomUrl(1, 'customer'); if (!getDolGlobalString('MAIN_DISABLE_OTHER_LINK') && $object->thirdparty->id > 0) { - $morehtmlref .= ' (' . $langs->trans("OtherOrders") . ')'; + $morehtmlref .= ' (' . $langs->trans("OtherOrders") . ')'; } // Project if (isModEnabled('project')) { @@ -3513,15 +3513,15 @@ if ($action == 'create' && $usercancreate) { // Valid if ($object->status == Commande::STATUS_DRAFT && ($object->total_ttc >= 0 || getDolGlobalString('ORDER_ENABLE_NEGATIVE')) && $usercanvalidate) { if ($numlines > 0) { - print dolGetButtonAction('', $langs->trans('Validate'), 'default', $_SERVER["PHP_SELF"] . '?action=validate&token=' . newToken() . '&id=' . $object->id, (string) $object->id, 1); + print dolGetButtonAction('', $langs->trans('Validate'), 'default', $_SERVER["PHP_SELF"] . '?action=validate&token=' . newToken() . '&id=' . $object->id, (string) $object->id, 1); } else { $langs->load("errors"); - print dolGetButtonAction($langs->trans("ErrorObjectMustHaveLinesToBeValidated", $object->ref), $langs->trans('Validate'), 'default', $_SERVER["PHP_SELF"] . '?action=validate&token=' . newToken() . '&id=' . $object->id, (string) $object->id, -1); + print dolGetButtonAction($langs->trans("ErrorObjectMustHaveLinesToBeValidated", $object->ref), $langs->trans('Validate'), 'default', $_SERVER["PHP_SELF"] . '?action=validate&token=' . newToken() . '&id=' . $object->id, (string) $object->id, -1); } } // Edit if (($object->status == Commande::STATUS_VALIDATED || ($object->status == Commande::STATUS_SHIPMENTONPROCESS && getDolGlobalString('EDIT_ORDER_SHIPMENT_ON_PROCESS'))) && $usercancreate) { - print dolGetButtonAction('', $langs->trans('Modify'), 'default', $_SERVER["PHP_SELF"] . '?action=modif&token=' . newToken() . '&id=' . $object->id, ''); + print dolGetButtonAction('', $langs->trans('Modify'), 'default', $_SERVER["PHP_SELF"] . '?action=modif&token=' . newToken() . '&id=' . $object->id, ''); } // Create a purchase order diff --git a/htdocs/commande/class/api_orders.class.php b/htdocs/commande/class/api_orders.class.php index f1b7bb5355a..9eaac11ecbb 100644 --- a/htdocs/commande/class/api_orders.class.php +++ b/htdocs/commande/class/api_orders.class.php @@ -1019,6 +1019,10 @@ class Orders extends DolibarrApi throw new RestException(404, 'Order not found'); } + if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) { + throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login); + } + $result = $this->commande->set_reopen(DolibarrApiAccess::$user); if ($result < 0) { throw new RestException(405, $this->commande->error); @@ -1056,20 +1060,15 @@ class Orders extends DolibarrApi throw new RestException(404, 'Order not found'); } + if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) { + throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login); + } + $result = $this->commande->classifyBilled(DolibarrApiAccess::$user); if ($result < 0) { throw new RestException(400, $this->commande->error); } - $result = $this->commande->fetch($id); - if (!$result) { - throw new RestException(404, 'Order not found'); - } - - if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) { - throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login); - } - $this->commande->fetchObjectLinked(); return $this->_cleanObjectDatas($this->commande); diff --git a/htdocs/commande/class/commande.class.php b/htdocs/commande/class/commande.class.php index 65f7516e323..f43f1156c05 100644 --- a/htdocs/commande/class/commande.class.php +++ b/htdocs/commande/class/commande.class.php @@ -618,14 +618,14 @@ class Commande extends CommonOrder if (preg_match('/^[\(]?PROV/i', $this->ref)) { // Now we rename also files into index $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'commande/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'commande/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'commande/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'commande/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; @@ -1910,7 +1910,7 @@ class Commande extends CommonOrder * @param int|'' $date_end End date of the line * @return void * - * TODO Remplacer les appels a cette fonction par generation object Ligne + * TODO Replace calls to this function by generation of the `Ligne` (line) object */ public function add_product($idproduct, $qty, $remise_percent = 0.0, $date_start = '', $date_end = '') { @@ -3443,29 +3443,29 @@ class Commande extends CommonOrder $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").","; $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").","; $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").","; - $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").","; + $sql .= " fk_soc=".(isset($this->socid) ? ((int) $this->socid) : "null").","; $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').","; $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').","; - $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").","; - $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").","; - $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").","; - $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").","; - $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").","; - $sql .= " fk_statut=".(isset($this->status) ? $this->status : "null").","; - $sql .= " fk_user_modif=".(isset($user->id) ? $user->id : "null").","; - $sql .= " fk_user_valid=".((isset($this->user_validation_id) && $this->user_validation_id > 0) ? $this->user_validation_id : "null").","; - $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").","; - $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").","; - $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? strval($this->deposit_percent) : "null").","; - $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").","; + $sql .= " total_tva=".(isset($this->total_tva) ? ((float) $this->total_tva) : "null").","; + $sql .= " localtax1=".(isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null").","; + $sql .= " localtax2=".(isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null").","; + $sql .= " total_ht=".(isset($this->total_ht) ? ((float) $this->total_ht) : "null").","; + $sql .= " total_ttc=".(isset($this->total_ttc) ? ((float) $this->total_ttc) : "null").","; + $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : "null").","; + $sql .= " fk_user_modif=".(isset($user->id) ? ((int) $user->id) : "null").","; + $sql .= " fk_user_valid=".((isset($this->user_validation_id) && $this->user_validation_id > 0) ? ((int) $this->user_validation_id) : "null").","; + $sql .= " fk_projet=".(isset($this->fk_project) ? ((int) $this->fk_project) : "null").","; + $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null").","; + $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? ((float) $this->deposit_percent) : "null").","; + $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? ((int) $this->mode_reglement_id) : "null").","; $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').","; - $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").","; - $sql .= " fk_account=".($this->fk_account > 0 ? $this->fk_account : "null").","; - $sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").","; + $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? ((int) $this->shipping_method_id) : "null").","; + $sql .= " fk_account=".($this->fk_account > 0 ? ((int) $this->fk_account) : "null").","; + $sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? ((int) $this->demand_reason_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").","; - $sql .= " fk_warehouse=".($this->warehouse_id > 0 ? $this->warehouse_id : "null").","; + $sql .= " fk_warehouse=".($this->warehouse_id > 0 ? ((int) $this->warehouse_id) : "null").","; $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").","; $sql .= " module_source = ".(isset($this->module_source) ? "'".$this->db->escape($this->module_source)."'" : "null").","; $sql .= " pos_source = ".(isset($this->pos_source) ? "'".$this->db->escape($this->pos_source)."'" : "null"); diff --git a/htdocs/commande/class/orderline.class.php b/htdocs/commande/class/orderline.class.php index 8d9beccded9..99a113262d6 100644 --- a/htdocs/commande/class/orderline.class.php +++ b/htdocs/commande/class/orderline.class.php @@ -469,7 +469,7 @@ class OrderLine extends CommonOrderLine $sql .= ' fk_unit,'; $sql .= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc'; $sql .= ')'; - $sql .= " VALUES (".$this->fk_commande.","; + $sql .= " VALUES (".((int) $this->fk_commande).","; $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape((string) $this->fk_parent_line)."'" : "null").","; $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").","; $sql .= " '".$this->db->escape($this->desc)."',"; @@ -481,15 +481,15 @@ class OrderLine extends CommonOrderLine $sql .= " '".price2num($this->localtax2_tx)."',"; $sql .= " '".$this->db->escape($this->localtax1_type)."',"; $sql .= " '".$this->db->escape($this->localtax2_type)."',"; - $sql .= ' '.((!empty($this->fk_product) && $this->fk_product > 0) ? $this->fk_product : "null").','; + $sql .= ' '.((!empty($this->fk_product) && $this->fk_product > 0) ? ((int) $this->fk_product) : "null").','; $sql .= " ".((int) $this->product_type).","; $sql .= " '".price2num($this->remise_percent)."',"; $sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice) : "null").","; $sql .= " ".($this->price != '' ? "'".price2num($this->price)."'" : "null").","; - $sql .= ' '.(!empty($this->fk_remise_except) ? $this->fk_remise_except : "null").','; + $sql .= ' '.(!empty($this->fk_remise_except) ? ((int) $this->fk_remise_except) : "null").','; $sql .= ' '.((int) $this->special_code).','; $sql .= ' '.((int) $this->rang).','; - $sql .= ' '.(!empty($this->fk_fournprice) ? $this->fk_fournprice : "null").','; + $sql .= ' '.(!empty($this->fk_fournprice) ? ((int) $this->fk_fournprice) : "null").','; $sql .= ' '.price2num($this->pa_ht).','; $sql .= " ".((int) $this->info_bits).","; $sql .= " ".price2num($this->total_ht, 'MT').","; @@ -653,14 +653,14 @@ class OrderLine extends CommonOrderLine $sql .= " , total_localtax1=".price2num($this->total_localtax1); $sql .= " , total_localtax2=".price2num($this->total_localtax2); } - $sql .= " , fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? $this->fk_fournprice : "null"); + $sql .= " , fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? ((int) $this->fk_fournprice) : "null"); $sql .= " , buy_price_ht='".price2num($this->pa_ht)."'"; $sql .= " , info_bits=".((int) $this->info_bits); $sql .= " , special_code=".((int) $this->special_code); $sql .= " , date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null"); $sql .= " , date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null"); $sql .= " , product_type = ".((int) $this->product_type); - $sql .= " , fk_parent_line=".(!empty($this->fk_parent_line) ? $this->fk_parent_line : "null"); + $sql .= " , fk_parent_line=".(!empty($this->fk_parent_line) ? ((int) $this->fk_parent_line) : "null"); if (!empty($this->rang)) { $sql .= ", rang=".((int) $this->rang); } diff --git a/htdocs/commande/index.php b/htdocs/commande/index.php index cf33b2d22a5..3b75ed7b108 100644 --- a/htdocs/commande/index.php +++ b/htdocs/commande/index.php @@ -62,7 +62,8 @@ if ($user->socid > 0) { } // Maximum elements of the tables -$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); +$max = getDolUserInt('MAIN_SIZE_SHORTLIST_LIMIT', getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5)); + $maxDraftCount = !getDolGlobalString('MAIN_MAXLIST_OVERLOAD') ? 500 : $conf->global->MAIN_MAXLIST_OVERLOAD; $maxLatestEditCount = 5; $maxOpenCount = !getDolGlobalString('MAIN_MAXLIST_OVERLOAD') ? 500 : $conf->global->MAIN_MAXLIST_OVERLOAD; diff --git a/htdocs/commande/list.php b/htdocs/commande/list.php index aff05be426c..98ede89955e 100644 --- a/htdocs/commande/list.php +++ b/htdocs/commande/list.php @@ -411,7 +411,7 @@ if (empty($reshook)) { $month = ""; include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php'; - if ($massaction == 'confirm_createbills') { // Create bills from orders. + if ($massaction == 'confirm_createbills' && $user->hasRight('facture', 'creer')) { // Create bills from orders. $orders = GETPOST('toselect', 'array:int'); $createbills_onebythird = GETPOSTINT('createbills_onebythird'); $validate_invoices = GETPOSTINT('validate_invoices'); @@ -436,6 +436,15 @@ if (empty($reshook)) { if ($cmd->fetch($id_order) <= 0) { continue; } + + // Skip orders that cannot be billed, to mirror the "CreateBill" button availability on the order card + // (card.php only shows it when status > STATUS_DRAFT and the order is not already billed): this excludes + // draft and canceled orders, as well as orders already classified as billed. + if ($cmd->status <= Commande::STATUS_DRAFT || !empty($cmd->billed)) { + setEventMessages($langs->trans('WarningOrderNotEligibleForInvoicing', $cmd->ref), null, 'warnings'); + continue; + } + $cmd->fetch_thirdparty(); $objecttmp = new Facture($db); @@ -695,6 +704,7 @@ if (empty($reshook)) { } $id = $objecttmp->id; // For builddoc action + $lastref = $objecttmp->ref; // Refresh ref after validation (was the draft PROVxxxx ref set at creation) // Builddoc $donotredirect = 1; diff --git a/htdocs/commande/stats/index.php b/htdocs/commande/stats/index.php index e32c6155c48..c5760d1f02a 100644 --- a/htdocs/commande/stats/index.php +++ b/htdocs/commande/stats/index.php @@ -109,12 +109,12 @@ $formcompany = new FormCompany($db); $formother = new FormOther($db); $picto = 'order'; -$title = $langs->trans("OrdersStatistics"); -$dir = $conf->commande->dir_temp; +$title = $langs->trans("Orders"); +$dir = $conf->order->dir_temp; if ($mode == 'supplier') { $picto = 'supplier_order'; - $title = $langs->trans("OrdersStatisticsSuppliers"); + $title = $langs->trans("SuppliersOrders"); $dir = $conf->fournisseur->commande->dir_temp; } diff --git a/htdocs/compta/accounting-files.php b/htdocs/compta/accounting-files.php index 214f22a2b0f..058e45d3977 100644 --- a/htdocs/compta/accounting-files.php +++ b/htdocs/compta/accounting-files.php @@ -196,7 +196,7 @@ if ($action == 'searchfiles' || $action == 'dl') { // Test on permission not req $sql .= " t.multicurrency_code as currency, t.fk_soc, t.datef as date, t.date_lim_reglement as date_due, 'Invoice' as item, s.nom as thirdparty_name, s.code_client as thirdparty_code, c.code as country_code, s.tva_intra as vatnum, ".PAY_CREDIT." as sens"; $sql .= " FROM ".MAIN_DB_PREFIX."facture as t LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = t.fk_soc LEFT JOIN ".MAIN_DB_PREFIX."c_country as c ON c.rowid = s.fk_pays"; $sql .= " WHERE datef between ".$sanitizedwheretail; - $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : $entity).')'; + $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : ((int) $entity)).')'; $sql .= " AND t.fk_statut <> ".Facture::STATUS_DRAFT; if (!empty($projectid)) { $sql .= " AND fk_projet = ".((int) $projectid); @@ -212,7 +212,7 @@ if ($action == 'searchfiles' || $action == 'dl') { // Test on permission not req $sql .= " t.multicurrency_code as currency, t.fk_soc, t.datef as date, t.date_lim_reglement as date_due, 'SupplierInvoice' as item, s.nom as thirdparty_name, s.code_fournisseur as thirdparty_code, c.code as country_code, s.tva_intra as vatnum, ".PAY_DEBIT." as sens"; $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as t LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = t.fk_soc LEFT JOIN ".MAIN_DB_PREFIX."c_country as c ON c.rowid = s.fk_pays"; $sql .= " WHERE datef between ".$sanitizedwheretail; - $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : $entity).')'; + $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : ((int) $entity)).')'; $sql .= " AND t.fk_statut <> ".FactureFournisseur::STATUS_DRAFT; if (!empty($projectid)) { $sql .= " AND fk_projet = ".((int) $projectid); @@ -234,7 +234,7 @@ if ($action == 'searchfiles' || $action == 'dl') { // Test on permission not req $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user as u ON u.rowid = t.fk_user_author"; $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = u.fk_country"; $sql .= " WHERE date_fin between " . $sanitizedwheretail; - $sql .= " AND t.entity IN (" . $db->sanitize($entity == 1 ? '0,1' : $entity) . ')'; + $sql .= " AND t.entity IN (" . $db->sanitize($entity == 1 ? '0,1' : ((int) $entity)) . ')'; $sql .= " AND t.fk_statut <> " . ExpenseReport::STATUS_DRAFT; $sql .= " AND fk_projet = ".((int) $projectid); } else { @@ -243,7 +243,7 @@ if ($action == 'searchfiles' || $action == 'dl') { // Test on permission not req $sql .= " t.multicurrency_code as currency, t.fk_user_author as fk_soc, t.date_fin as date, t.date_fin as date_due, 'ExpenseReport' as item, CONCAT(CONCAT(u.lastname, ' '), u.firstname) as thirdparty_name, '' as thirdparty_code, c.code as country_code, '' as vatnum, " . PAY_DEBIT . " as sens"; $sql .= " FROM " . MAIN_DB_PREFIX . "expensereport as t LEFT JOIN " . MAIN_DB_PREFIX . "user as u ON u.rowid = t.fk_user_author LEFT JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = u.fk_country"; $sql .= " WHERE date_fin between " . $sanitizedwheretail; - $sql .= " AND t.entity IN (" . $db->sanitize($entity == 1 ? '0,1' : $entity) . ')'; + $sql .= " AND t.entity IN (" . $db->sanitize($entity == 1 ? '0,1' : ((int) $entity)) . ')'; $sql .= " AND t.fk_statut <> " . ExpenseReport::STATUS_DRAFT; } } @@ -257,7 +257,7 @@ if ($action == 'searchfiles' || $action == 'dl') { // Test on permission not req $sql .= " '".$db->escape($conf->currency)."' as currency, 0 as fk_soc, t.datedon as date, t.datedon as date_due, 'Donation' as item, t.societe as thirdparty_name, '' as thirdparty_code, c.code as country_code, '' as vatnum, ".PAY_CREDIT." as sens"; $sql .= " FROM ".MAIN_DB_PREFIX."don as t LEFT JOIN ".MAIN_DB_PREFIX."c_country as c ON c.rowid = t.fk_country"; $sql .= " WHERE datedon between ".$sanitizedwheretail; - $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : $entity).')'; + $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : ((int) $entity)).')'; $sql .= " AND t.fk_statut <> ".Don::STATUS_DRAFT; if (!empty($projectid)) { $sql .= " AND fk_projet = ".((int) $projectid); @@ -273,7 +273,7 @@ if ($action == 'searchfiles' || $action == 'dl') { // Test on permission not req $sql .= " '".$db->escape($conf->currency)."' as currency, t.fk_user as fk_soc, t.datep as date, t.dateep as date_due, 'SalaryPayment' as item, CONCAT(CONCAT(u.lastname, ' '), u.firstname) as thirdparty_name, '' as thirdparty_code, c.code as country_code, '' as vatnum, ".PAY_DEBIT." as sens"; $sql .= " FROM ".MAIN_DB_PREFIX."payment_salary as t LEFT JOIN ".MAIN_DB_PREFIX."user as u ON u.rowid = t.fk_user LEFT JOIN ".MAIN_DB_PREFIX."c_country as c ON c.rowid = u.fk_country"; $sql .= " WHERE datep between ".$sanitizedwheretail; - $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : $entity).')'; + $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : ((int) $entity)).')'; //$sql.=" AND fk_statut <> ".PaymentSalary::STATUS_DRAFT; if (!empty($projectid)) { $sql .= " AND fk_projet = ".((int) $projectid); @@ -289,7 +289,7 @@ if ($action == 'searchfiles' || $action == 'dl') { // Test on permission not req $sql .= " '".$db->escape($conf->currency)."' as currency, 0 as fk_soc, t.date_ech as date, t.periode as date_due, 'SocialContributions' as item, '' as thirdparty_name, '' as thirdparty_code, '' as country_code, '' as vatnum, ".PAY_DEBIT." as sens"; $sql .= " FROM ".MAIN_DB_PREFIX."chargesociales as t"; $sql .= " WHERE t.date_ech between ".$sanitizedwheretail; - $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : $entity).')'; + $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : ((int) $entity)).')'; //$sql.=" AND fk_statut <> ".ChargeSociales::STATUS_UNPAID; if (!empty($projectid)) { $sql .= " AND fk_projet = ".((int) $projectid); @@ -305,7 +305,7 @@ if ($action == 'searchfiles' || $action == 'dl') { // Test on permission not req $sql .= " '".$db->escape($conf->currency)."' as currency, 0 as fk_soc, t.datep as date, t.datep as date_due, 'VariousPayment' as item, '' as thirdparty_name, '' as thirdparty_code, '' as country_code, '' as vatnum, sens"; $sql .= " FROM ".MAIN_DB_PREFIX."payment_various as t"; $sql .= " WHERE datep between ".$sanitizedwheretail; - $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : $entity).')'; + $sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : ((int) $entity)).')'; if (!empty($projectid)) { $sql .= " AND fk_projet = ".((int) $projectid); } @@ -320,7 +320,7 @@ if ($action == 'searchfiles' || $action == 'dl') { // Test on permission not req $sql .= " '".$db->escape($conf->currency)."' as currency, 0 as fk_soc, t.datep as date, t.datep as date_due, 'LoanPayment' as item, '' as thirdparty_name, '' as thirdparty_code, '' as country_code, '' as vatnum, ".PAY_DEBIT." as sens"; $sql .= " FROM ".MAIN_DB_PREFIX."payment_loan as t LEFT JOIN ".MAIN_DB_PREFIX."loan as l ON l.rowid = t.fk_loan"; $sql .= " WHERE datep between ".$sanitizedwheretail; - $sql .= " AND l.entity IN (".$db->sanitize($entity == 1 ? '0,1' : $entity).')'; + $sql .= " AND l.entity IN (".$db->sanitize($entity == 1 ? '0,1' : ((int) $entity)).')'; } if ($sql) { diff --git a/htdocs/compta/bank/annuel.php b/htdocs/compta/bank/annuel.php index e53472cf70c..19285c92f68 100644 --- a/htdocs/compta/bank/annuel.php +++ b/htdocs/compta/bank/annuel.php @@ -3,7 +3,7 @@ * Copyright (C) 2004-2017 Laurent Destailleur * Copyright (C) 2005-2012 Regis Houssin * Copyright (C) 2013-2023 Charlene BENKE - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024-2026 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -325,8 +325,8 @@ if ($result < 0) { $sql .= ", ".MAIN_DB_PREFIX."bank_account as ba"; $sql .= " WHERE b.fk_account = ba.rowid"; $sql .= " AND ba.entity IN (".getEntity('bank_account').")"; - $sql .= " AND b.datev >= '".($year - $annee)."-01-01 00:00:00'"; - $sql .= " AND b.datev <= '".($year - $annee)."-12-31 23:59:59'"; + $sql .= " AND b.datev >= '".((int) ($year - $annee))."-01-01 00:00:00'"; + $sql .= " AND b.datev <= '".((int) ($year - $annee))."-12-31 23:59:59'"; $sql .= " AND b.amount > 0"; if ($id && GETPOST("option") != 'all') { $sql .= " AND b.fk_account IN (".$db->sanitize($id).")"; @@ -408,8 +408,8 @@ if ($result < 0) { $sql .= ", ".MAIN_DB_PREFIX."bank_account as ba"; $sql .= " WHERE b.fk_account = ba.rowid"; $sql .= " AND ba.entity IN (".getEntity('bank_account').")"; - $sql .= " AND b.datev >= '".($year - $annee)."-01-01 00:00:00'"; - $sql .= " AND b.datev <= '".($year - $annee)."-12-31 23:59:59'"; + $sql .= " AND b.datev >= '".((int) ($year - $annee))."-01-01 00:00:00'"; + $sql .= " AND b.datev <= '".((int) ($year - $annee))."-12-31 23:59:59'"; $sql .= " AND b.amount < 0"; if ($id && GETPOST("option") != 'all') { $sql .= " AND b.fk_account IN (".$db->sanitize($id).")"; diff --git a/htdocs/compta/bank/bankentries_list.php b/htdocs/compta/bank/bankentries_list.php index 74c3be9913f..832eb7d8904 100644 --- a/htdocs/compta/bank/bankentries_list.php +++ b/htdocs/compta/bank/bankentries_list.php @@ -10,7 +10,7 @@ * Copyright (C) 2018 Ferran Marcet * Copyright (C) 2018-2026 Frédéric France * Copyright (C) 2021 Gauthier VERDOL - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -1340,7 +1340,7 @@ if ($resql) { $sqlforbalance .= " AND ba.entity IN (".getEntity('bank_account').")"; $sqlforbalance .= " AND b.fk_account = ".((int) $search_account); // To limit record on the page - $sqlforbalance .= " AND (b.datev < '".$db->idate($db->jdate($objp->dv))."' OR (b.datev = '".$db->idate($db->jdate($objp->dv))."' AND (b.dateo < '".$db->idate($db->jdate($objp->do))."' OR (b.dateo = '".$db->idate($db->jdate($objp->do))."' AND b.rowid < ".$objp->rowid."))))"; + $sqlforbalance .= " AND (b.datev < '".$db->idate($db->jdate($objp->dv))."' OR (b.datev = '".$db->idate($db->jdate($objp->dv))."' AND (b.dateo < '".$db->idate($db->jdate($objp->do))."' OR (b.dateo = '".$db->idate($db->jdate($objp->do))."' AND b.rowid < ".((int) $objp->rowid)."))))"; $resqlforbalance = $db->query($sqlforbalance); //print $sqlforbalance; diff --git a/htdocs/compta/bank/budget.php b/htdocs/compta/bank/budget.php index d0067c23cff..a77b7dce21e 100644 --- a/htdocs/compta/bank/budget.php +++ b/htdocs/compta/bank/budget.php @@ -3,7 +3,7 @@ * Copyright (C) 2004-2010 Laurent Destailleur * Copyright (C) 2005-2009 Regis Houssin * Copyright (C) 2015 Jean-François Ferry - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -74,7 +74,7 @@ $sql = "SELECT sum(d.amount) as somme, count(*) as nombre, c.label, c.rowid "; $sql .= " FROM ".MAIN_DB_PREFIX."categorie as c"; $sql .= ", ".MAIN_DB_PREFIX."category_bankline as l"; $sql .= ", ".MAIN_DB_PREFIX."bank as d"; -$sql .= " WHERE c.entity = ".$conf->entity; +$sql .= " WHERE c.entity = ".((int) $conf->entity); $sql .= " AND c.rowid = l.fk_categ"; $sql .= " AND d.rowid = l.lineid"; $sql .= " GROUP BY c.label, c.rowid"; diff --git a/htdocs/compta/bank/categ.php b/htdocs/compta/bank/categ.php index 9d8a7ba1686..355dbe427a5 100644 --- a/htdocs/compta/bank/categ.php +++ b/htdocs/compta/bank/categ.php @@ -6,7 +6,7 @@ * Copyright (C) 2015 Jean-François Ferry * Copyright (C) 2016 Marcos García * Copyright (C) 2024-2025 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * * 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 @@ -144,7 +144,7 @@ $catTypeID = $cats->getMapId()[Categorie::TYPE_BANK_LINE]; $sql = "SELECT rowid, label"; $sql .= " FROM ".MAIN_DB_PREFIX."categorie"; -$sql .= " WHERE entity = ".$conf->entity." AND type = " . ((int) $catTypeID); +$sql .= " WHERE entity = ".((int) $conf->entity)." AND type = " . ((int) $catTypeID); $sql .= " ORDER BY rowid"; $result = $db->query($sql); diff --git a/htdocs/compta/bank/class/account.class.php b/htdocs/compta/bank/class/account.class.php index b4fec8d6aab..4fa25c79788 100644 --- a/htdocs/compta/bank/class/account.class.php +++ b/htdocs/compta/bank/class/account.class.php @@ -508,6 +508,22 @@ class Account extends CommonObject public function add_url_line($line_id, $url_id, $url, $label, $type) { // phpcs:enable + // Avoid uk_bank_url collision when the same target (line_id, url_id, type) is linked twice + // e.g. two distinct credit transfers for the same employee dispatched on the same bank line. + $sqlcheck = "SELECT rowid FROM ".MAIN_DB_PREFIX."bank_url"; + $sqlcheck .= " WHERE fk_bank = ".((int) $line_id); + $sqlcheck .= " AND url_id = ".((int) $url_id); + $sqlcheck .= " AND type = '".$this->db->escape($type)."'"; + $resqlcheck = $this->db->query($sqlcheck); + if ($resqlcheck) { + $obj = $this->db->fetch_object($resqlcheck); + if ($obj) { + $this->db->free($resqlcheck); + return (int) $obj->rowid; + } + $this->db->free($resqlcheck); + } + $sql = "INSERT INTO ".MAIN_DB_PREFIX."bank_url ("; $sql .= "fk_bank"; $sql .= ", url_id"; @@ -1070,8 +1086,8 @@ class Account extends CommonObject $sql .= ",owner_zip = '".$this->db->escape($this->owner_zip)."'"; $sql .= ",owner_town = '".$this->db->escape($this->owner_town)."'"; $sql .= ",owner_country_id = ".($this->owner_country_id > 0 ? ((int) $this->owner_country_id) : "null"); - $sql .= ",state_id = ".($this->state_id > 0 ? $this->state_id : "null"); - $sql .= ",fk_pays = ".($this->country_id > 0 ? $this->country_id : "null"); + $sql .= ",state_id = ".($this->state_id > 0 ? ((int) $this->state_id) : "null"); + $sql .= ",fk_pays = ".($this->country_id > 0 ? ((int) $this->country_id) : "null"); $sql .= " WHERE rowid = ".((int) $this->id); $sql .= " AND entity = ".((int) $conf->entity); @@ -2589,7 +2605,7 @@ class AccountLine extends CommonObjectLine $sql .= "lineid"; $sql .= ", fk_categ"; $sql .= ") VALUES ("; - $sql .= $this->id; + $sql .= ((int) $this->id); $sql .= ", ".((int) $cat); $sql .= ")"; diff --git a/htdocs/compta/bank/class/bankcateg.class.php b/htdocs/compta/bank/class/bankcateg.class.php index c97fb2e4416..ce6a4b95d4f 100644 --- a/htdocs/compta/bank/class/bankcateg.class.php +++ b/htdocs/compta/bank/class/bankcateg.class.php @@ -3,7 +3,7 @@ * Copyright (C) 2009 Regis Houssin * Copyright (C) 2016 Marcos García * Copyright (C) 2024 Frédéric France - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -159,7 +159,7 @@ class BankCateg // extends CommonObject $sql .= " t.label"; $sql .= " FROM ".MAIN_DB_PREFIX."categorie as t"; $sql .= " WHERE t.rowid = ".((int) $id); - $sql .= " AND t.entity = ".$conf->entity." AND t.type = " . ((int) $catTypeID); + $sql .= " AND t.entity = ".((int) $conf->entity)." AND t.type = " . ((int) $catTypeID); dol_syslog(get_class($this)."::fetch", LOG_DEBUG); $resql = $this->db->query($sql); @@ -203,7 +203,7 @@ class BankCateg // extends CommonObject $sql = "UPDATE ".MAIN_DB_PREFIX."categorie SET"; $sql .= " label=".(isset($this->label) ? "'".$this->db->escape($this->label)."'" : "null"); $sql .= " WHERE rowid=".((int) $this->id); - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); $this->db->begin(); @@ -349,7 +349,7 @@ class BankCateg // extends CommonObject $return = array(); - $sql = "SELECT rowid, label FROM ".MAIN_DB_PREFIX."categorie WHERE entity = ".$conf->entity." AND type = ".((int) $catTypeID)." ORDER BY label"; + $sql = "SELECT rowid, label FROM ".MAIN_DB_PREFIX."categorie WHERE entity = ".((int) $conf->entity)." AND type = ".((int) $catTypeID)." ORDER BY label"; $resql = $this->db->query($sql); if ($resql) { diff --git a/htdocs/compta/bank/class/paymentvarious.class.php b/htdocs/compta/bank/class/paymentvarious.class.php index bf569b033e1..bd92970f419 100644 --- a/htdocs/compta/bank/class/paymentvarious.class.php +++ b/htdocs/compta/bank/class/paymentvarious.class.php @@ -261,7 +261,7 @@ class PaymentVarious extends CommonObject $sql .= " accountancy_code='".$this->db->escape($this->accountancy_code)."',"; $sql .= " subledger_account='".$this->db->escape($this->subledger_account)."',"; $sql .= " fk_projet='".$this->db->escape((string) $this->fk_project)."',"; - $sql .= " fk_bank=".($this->fk_bank > 0 ? $this->fk_bank : "null").","; + $sql .= " fk_bank=".($this->fk_bank > 0 ? ((int) $this->fk_bank) : "null").","; $sql .= " fk_user_author=".(int) $this->fk_user_author.","; $sql .= " fk_user_modif=".(int) $this->fk_user_modif; $sql .= " WHERE rowid=".((int) $this->id); diff --git a/htdocs/compta/bank/graph.php b/htdocs/compta/bank/graph.php index f0f8d881c26..45b21c55df4 100644 --- a/htdocs/compta/bank/graph.php +++ b/htdocs/compta/bank/graph.php @@ -191,7 +191,7 @@ if ($result < 0) { $sql .= ", ".MAIN_DB_PREFIX."bank_account as ba"; $sql .= " WHERE b.fk_account = ba.rowid"; $sql .= " AND ba.entity IN (".getEntity('bank_account').")"; - $sql .= " AND b.datev < '".$db->escape($year)."-".sprintf("%02s", $month)."-01'"; + $sql .= " AND b.datev < '".$db->escape($year)."-".sprintf("%02u", ((int) $month))."-01'"; if ($account && GETPOST("option") != 'all') { $sql .= " AND b.fk_account IN (".$db->sanitize($account).")"; } diff --git a/htdocs/compta/bank/treso.php b/htdocs/compta/bank/treso.php index 939bde75a43..d22f78a931c 100644 --- a/htdocs/compta/bank/treso.php +++ b/htdocs/compta/bank/treso.php @@ -125,7 +125,7 @@ if (GETPOST("account") || GETPOST("ref")) { $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid"; $sql .= " WHERE f.entity IN (".getEntity('invoice').")"; $sql .= " AND f.paye = 0 AND f.fk_statut = 1"; // Not paid - $sql .= " AND (f.fk_account IN (0, ".$object->id.") OR f.fk_account IS NULL)"; // Id bank account of invoice + $sql .= " AND (f.fk_account IN (0, ".((int) $object->id).") OR f.fk_account IS NULL)"; // Id bank account of invoice $sql .= " ORDER BY dlr ASC"; $sqls[] = $sql; @@ -134,9 +134,9 @@ if (GETPOST("account") || GETPOST("ref")) { $sql .= " s.rowid as socid, s.nom as name, s.fournisseur"; $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as ff"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON ff.fk_soc = s.rowid"; - $sql .= " WHERE ff.entity = ".$conf->entity; + $sql .= " WHERE ff.entity = ".((int) $conf->entity); $sql .= " AND ff.paye = 0 AND fk_statut = 1"; // Not paid - $sql .= " AND (ff.fk_account IN (0, ".$object->id.") OR ff.fk_account IS NULL)"; // Id bank account of supplier invoice + $sql .= " AND (ff.fk_account IN (0, ".((int) $object->id).") OR ff.fk_account IS NULL)"; // Id bank account of supplier invoice $sql .= " ORDER BY dlr ASC"; $sqls[] = $sql; @@ -145,9 +145,9 @@ if (GETPOST("account") || GETPOST("ref")) { $sql .= " 0 as socid, 'noname' as name, 0 as fournisseur"; $sql .= " FROM ".MAIN_DB_PREFIX."chargesociales as cs"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_chargesociales as ccs ON cs.fk_type = ccs.id"; - $sql .= " WHERE cs.entity = ".$conf->entity; + $sql .= " WHERE cs.entity = ".((int) $conf->entity); $sql .= " AND cs.paye = 0"; // Not paid - $sql .= " AND (cs.fk_account IN (0, ".$object->id.") OR cs.fk_account IS NULL)"; // Id bank account of social contribution + $sql .= " AND (cs.fk_account IN (0, ".((int) $object->id).") OR cs.fk_account IS NULL)"; // Id bank account of social contribution $sql .= " ORDER BY dlr ASC"; $sqls[] = $sql; @@ -156,9 +156,9 @@ if (GETPOST("account") || GETPOST("ref")) { $sql .= " s.rowid as socid, CONCAT(s.firstname, ' ', s.lastname) as name, 0 as fournisseur"; $sql .= " FROM ".MAIN_DB_PREFIX."salary as sa"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as s ON sa.fk_user = s.rowid"; - $sql .= " WHERE sa.entity = ".$conf->entity; + $sql .= " WHERE sa.entity = ".((int) $conf->entity); $sql .= " AND sa.paye = 0"; // Not paid - $sql .= " AND (sa.fk_account IN (0, ".$object->id.") OR sa.fk_account IS NULL)"; // Id bank account of salary + $sql .= " AND (sa.fk_account IN (0, ".((int) $object->id).") OR sa.fk_account IS NULL)"; // Id bank account of salary $sql .= " ORDER BY dlr ASC"; $sqls[] = $sql; @@ -166,9 +166,9 @@ if (GETPOST("account") || GETPOST("ref")) { $sql = " SELECT 'vat' as family, t.rowid as objid, t.label as ref, (-1*t.amount) as total_ttc, t.datev as dlr,"; $sql .= " 0 as socid, 'noname' as name, 0 as fournisseur"; $sql .= " FROM ".MAIN_DB_PREFIX."tva as t"; - $sql .= " WHERE t.entity = ".$conf->entity; + $sql .= " WHERE t.entity = ".((int) $conf->entity); $sql .= " AND t.paye = 0"; // Not paid - $sql .= " AND (t.fk_account IN (-1, 0, ".$object->id.") OR t.fk_account IS NULL)"; // Id bank account of vat + $sql .= " AND (t.fk_account IN (-1, 0, ".((int) $object->id).") OR t.fk_account IS NULL)"; // Id bank account of vat $sql .= " ORDER BY dlr ASC"; $sqls[] = $sql; @@ -188,20 +188,20 @@ if (GETPOST("account") || GETPOST("ref")) { $resql = $db->query($sql); if ($resql) { $nbtotalofrecords += $db->num_rows($resql); - while ($sqlobj = $db->fetch_object($resql)) { + while ($res_obj = $db->fetch_object($resql)) { $tmpobj = new stdClass(); - $tmpobj->family = $sqlobj->family; - $tmpobj->objid = $sqlobj->objid; - $tmpobj->ref = $sqlobj->ref; - $tmpobj->total_ttc = $sqlobj->total_ttc; - $tmpobj->type = $sqlobj->type; - $tmpobj->dlr = $db->jdate($sqlobj->dlr); - $tmpobj->socid = $sqlobj->socid; - $tmpobj->name = $sqlobj->name; - $tmpobj->fournisseur = $sqlobj->fournisseur; + $tmpobj->family = $res_obj->family; + $tmpobj->objid = $res_obj->objid; + $tmpobj->ref = $res_obj->ref; + $tmpobj->total_ttc = $res_obj->total_ttc; + $tmpobj->type = $res_obj->type; + $tmpobj->dlr = $db->jdate($res_obj->dlr); + $tmpobj->socid = $res_obj->socid; + $tmpobj->name = $res_obj->name; + $tmpobj->fournisseur = $res_obj->fournisseur; $tab_sqlobj[] = $tmpobj; - $tab_sqlobjOrder[] = $db->jdate($sqlobj->dlr); + $tab_sqlobjOrder[] = $db->jdate($res_obj->dlr); } $db->free($resql); } else { diff --git a/htdocs/compta/cashcontrol/cashcontrol_list.php b/htdocs/compta/cashcontrol/cashcontrol_list.php index 3429ccf7c4e..e7e1c6220ab 100644 --- a/htdocs/compta/cashcontrol/cashcontrol_list.php +++ b/htdocs/compta/cashcontrol/cashcontrol_list.php @@ -255,7 +255,7 @@ if (!empty($object->ismultientitymanaged) && (int) $object->ismultientitymanaged $sql .= " WHERE t.entity IN (".getEntity($object->element, (GETPOSTINT('search_current_entity') ? 0 : 1)).")"; } elseif (preg_match('/^\w+@\w+$/', (string) $object->ismultientitymanaged)) { $tmparray = explode('@', (string) $object->ismultientitymanaged); - $sql .= " LEFT JOIN ".$object->db->prefix().$tmparray[1]." as pt ON t.".$db->sanitize($tmparray[0])." = pt.rowid"; + $sql .= " LEFT JOIN ".$object->db->prefix().$db->sanitize($tmparray[1])." as pt ON t.".$db->sanitize($tmparray[0])." = pt.rowid"; $sql .= " WHERE pt.entity IN (".getEntity($object->element, (GETPOSTINT('search_current_entity') ? 0 : 1)).")"; } else { $sql .= " WHERE 1 = 1"; diff --git a/htdocs/compta/cashcontrol/class/cashcontrol.class.php b/htdocs/compta/cashcontrol/class/cashcontrol.class.php index b3aa0965896..136608105ad 100644 --- a/htdocs/compta/cashcontrol/class/cashcontrol.class.php +++ b/htdocs/compta/cashcontrol/class/cashcontrol.class.php @@ -3,7 +3,7 @@ * Copyright (C) 2009 Regis Houssin * Copyright (C) 2016 Marcos García * Copyright (C) 2018 Andreu Bisquerra - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024-2025 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -300,8 +300,8 @@ class CashControl extends CommonObject $sql .= ", '".$this->db->idate(dol_now())."'"; $sql .= ", '".$this->db->escape($this->posmodule)."'"; $sql .= ", '".$this->db->escape($this->posnumber)."'"; - $sql .= ", ".($this->day_close > 0 ? $this->day_close : "null"); - $sql .= ", ".($this->month_close > 0 ? $this->month_close : "null"); + $sql .= ", ".($this->day_close > 0 ? ((int) $this->day_close) : "null"); + $sql .= ", ".($this->month_close > 0 ? ((int) $this->month_close) : "null"); $sql .= ", ".((int) $this->year_close); $sql .= ", ".(isset($this->hour_close) ? (int) $this->hour_close : 23); $sql .= ", ".(isset($this->min_close) ? (int) $this->min_close : 59); diff --git a/htdocs/compta/charges/index.php b/htdocs/compta/charges/index.php index 1ea1dd0b71e..50a815f86cc 100644 --- a/htdocs/compta/charges/index.php +++ b/htdocs/compta/charges/index.php @@ -7,7 +7,7 @@ * Copyright (C) 2015 Jean-François Ferry * Copyright (C) 2019 Nicolas ZABOURI * Copyright (C) 2021 Gauthier VERDOL - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024-2026 Frédéric France * Copyright (C) 2026 Nick Fragoulis * @@ -456,7 +456,7 @@ while ($j < $numlt) { $sql = "SELECT pv.rowid, pv.amount, pv.label, pv.datev as dm, pv.datep as dp"; $sql .= " FROM ".MAIN_DB_PREFIX."localtax as pv"; - $sql .= " WHERE pv.entity = ".$conf->entity." AND localtaxtype = ".((int) $j); + $sql .= " WHERE pv.entity = ".((int) $conf->entity)." AND localtaxtype = ".((int) $j); if ($year > 0) { // If period defined, we use it as date criteria, if not we use date echeance, // so we are compatible when period is not mandatory diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index eb15a2c0a2e..0836db09123 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -1454,7 +1454,10 @@ if (empty($reshook)) { $source_fk_prev_id = $line->fk_prev_id; // temporary storing situation invoice fk_prev_id $line->fk_prev_id = $line->id; // The new line of the new credit note we are creating must be linked to the situation invoice line it is created from - if (!empty($facture_source->tab_previous_situation_invoice)) { + // The subtraction below assumes total_ht/situation_percent are stored cumulative on situation invoice lines + // (INVOICE_USE_SITUATION = 1, legacy). In progressive mode (INVOICE_USE_SITUATION = 2), each line already + // holds its own delta, so subtracting the previous invoice's line here would credit the wrong amount. + if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2 && !empty($facture_source->tab_previous_situation_invoice)) { // search the last standard invoice in cycle and the possible credit note between this last and facture_source // TODO Move this out of loop of $facture_source->lines $tab_jumped_credit_notes = array(); @@ -2317,6 +2320,12 @@ if (empty($reshook)) { $object->situation_counter += 1; + // Set extrafields from the create form BEFORE createFromCurrent(), so they are already + // present when create() inserts them and fires the BILL_CREATE trigger. This avoids firing + // BILL_CREATE a second time just to expose the extrafields (#32217). + $extrafields->fetch_name_optionals_label($object->table_element); + $extrafields->setOptionalsFromPost(null, $object); + $id = $object->createFromCurrent($user); if ($id <= 0) { $mesg = $object->error; @@ -2324,13 +2333,6 @@ if (empty($reshook)) { $nextSituationInvoice = new Facture($db); $nextSituationInvoice->fetch($id); - // create extrafields with data from create form - $extrafields->fetch_name_optionals_label($nextSituationInvoice->table_element); - $ret = $extrafields->setOptionalsFromPost(null, $nextSituationInvoice); - if ($ret > 0) { - $nextSituationInvoice->insertExtraFields(); - } - // Hooks $parameters = array('origin_type' => $object->origin_type, 'origin_id' => $object->origin_id); $reshook = $hookmanager->executeHooks('createFrom', $parameters, $nextSituationInvoice, $action); // Note that $action and $object may have been @@ -4480,17 +4482,17 @@ if ($action == 'create') { print ''; } @@ -7574,7 +7596,7 @@ class Form $newfilter .= ' AND fk_facture IS NULL AND fk_facture_line IS NULL'; // Customer discounts available } if ($filter) { - $sanitizedfilter = $filter; + $sanitizedfilter = $filter; // @phan-suppress-current-line SqlInjection $newfilter .= ' AND (' . $sanitizedfilter . ')'; } // output the combo of discounts @@ -8009,12 +8031,22 @@ class Form //exit; // Define list of countries to use to search VAT rates to show - // First we defined code_country to use to find list - if (is_object($societe_vendeuse)) { - $code_country = "'" . $societe_vendeuse->country_code . "'"; - } else { - $code_country = "'" . $mysoc->country_code . "'"; // Pour compatibilite ascendente + // First we defined code_country to use to find list. + // country_code must be a c_country ISO code (e.g. 'FR', 'CH'). In some setups it may hold a + // country label (e.g. 'Suisse') instead, which would make the "c.code IN (...)" lookup done by + // load_cache_vatrates() match nothing and wrongly force the VAT rate to 0%. A valid ISO code is + // always 2 chars, so when the value is not a well formed ISO code (empty or a label) and we have + // a valid country id, we recover the ISO code from the authoritative country id. This way we do + // not run any SQL on each page access when we already have a valid ISO code. + $sellercountrycode = is_object($societe_vendeuse) ? $societe_vendeuse->country_code : $mysoc->country_code; + $sellercountryid = is_object($societe_vendeuse) ? $societe_vendeuse->country_id : $mysoc->country_id; + if ((int) $sellercountryid > 0 && strlen((string) $sellercountrycode) != 2) { + $tmpcountrycode = dol_getIdFromCode($this->db, (string) $sellercountryid, 'c_country', 'rowid', 'code'); + if (!empty($tmpcountrycode) && !is_numeric($tmpcountrycode)) { + $sellercountrycode = $tmpcountrycode; + } } + $code_country = "'" . $sellercountrycode . "'"; // Pour compatibilite ascendente if ($societe_vendeuse == $mysoc && getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) { // If option to have vat for end customer for services is on require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php'; @@ -9852,6 +9884,7 @@ class Form if (!empty($objecttmp->parent_element)) { // If parent_element is defined '@phan-var-force CommonObjectLine $objecttmp'; $parent_properties = getElementProperties($objecttmp->parent_element); + // @phan-suppress-next-line SqlInjection $sql .= " INNER JOIN " . $this->db->prefix() . $this->db->sanitize($parent_properties['table_element']) . " as o ON o.rowid = t.".$objecttmp->fk_parent_attribute; } if (!empty($objecttmp->parent_element) && in_array($objecttmp->parent_element, ['commande', 'propal', 'facture', 'expedition'])) { @@ -10008,7 +10041,7 @@ class Form * Generates a set of HTML radio inputs from an array of key-value items. * * @param string $htmlName Name of the HTML input group - * @param array, unescapedAttr?: string[],attrLabel?: array,unescapedAttrLabel?: string[],disabled?: bool,css?: string,labelIsHtml?: bool}> $radioItems Array of items in the form key => label or key => array of item properties + * @param array, attrLabel?: array, disabled?: bool,css?: string,labelIsHtml?: bool}> $radioItems Array of items in the form key => label or key => array of item properties * @param string|int $selected Preselected key for selection. * @param array|string|bool> $moreGlobalParams Additional global parameters applied to all items (e.g., attributes) * @@ -10023,9 +10056,7 @@ class Form 'type' => 'radio', 'name' => $htmlName, ], - 'unescapedAttr' => [], 'attrLabel' => [], - 'unescapedAttrLabel' => [], 'labelIsHtml' => false ]; @@ -10063,8 +10094,8 @@ class Form } // Build HTML attributes for input and label - $inputAttributes = implode(' ', commonHtmlAttributeBuilder($item['attr'], $item['unescapedAttr'])); - $labelAttributes = implode(' ', commonHtmlAttributeBuilder($item['attrLabel'], $item['unescapedAttrLabel'])); + $inputAttributes = implode(' ', commonHtmlAttributeBuilder($item['attr'])); + $labelAttributes = implode(' ', commonHtmlAttributeBuilder($item['attrLabel'])); // prevent accidental Xss todo : escape $item['label'] but html friendly compatible $text = $item['labelIsHtml'] ? $item['label'] : htmlspecialchars($item['label'], ENT_QUOTES | ENT_SUBSTITUTE); @@ -11988,7 +12019,7 @@ class Form if (isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && !$user->entity) { $sql .= " LEFT JOIN " . $this->db->prefix() . "entity as e ON e.rowid=ug.entity"; if ($force_entity) { - $sql .= " WHERE ug.entity IN (0, " . $force_entity . ")"; + $sql .= " WHERE ug.entity IN (0, " . ((int) $force_entity) . ")"; } else { $sql .= " WHERE ug.entity IS NOT NULL"; } diff --git a/htdocs/core/class/html.formaccounting.class.php b/htdocs/core/class/html.formaccounting.class.php index cb18ad826dc..5f110f6b68a 100644 --- a/htdocs/core/class/html.formaccounting.class.php +++ b/htdocs/core/class/html.formaccounting.class.php @@ -179,7 +179,7 @@ class FormAccounting extends Form $sql = "SELECT rowid, code, label, nature, entity, active"; $sql .= " FROM ".$this->db->prefix()."accounting_journal"; $sql .= " WHERE active = 1"; - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); if ($nature && is_numeric($nature)) { $sql .= " AND nature = ".((int) $nature); } diff --git a/htdocs/core/class/html.formbarcode.class.php b/htdocs/core/class/html.formbarcode.class.php index 9a6023e6a96..d063c1c7a82 100644 --- a/htdocs/core/class/html.formbarcode.class.php +++ b/htdocs/core/class/html.formbarcode.class.php @@ -143,7 +143,7 @@ class FormBarCode $sql = "SELECT rowid, code, libelle as label"; $sql .= " FROM ".$this->db->prefix()."c_barcode_type"; $sql .= " WHERE coder <> '0'"; - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); $sql .= " ORDER BY code"; $result = $this->db->query($sql); @@ -185,7 +185,7 @@ class FormBarCode * * @param string $page Page * @param int $selected Id condition preselected - * @param string $htmlname Nom du formulaire select + * @param string $htmlname Name of the select form * @return void * @deprecated */ @@ -200,7 +200,7 @@ class FormBarCode * * @param string $page Page * @param int $selected Id condition preselected - * @param string $htmlname Nom du formulaire select + * @param string $htmlname Name of the select form * @return string */ public function formBarcodeType($page, $selected = 0, $htmlname = 'barcodetype_id') diff --git a/htdocs/core/class/html.formcompany.class.php b/htdocs/core/class/html.formcompany.class.php index 9e2c7c2de0f..70fcaa3df4c 100644 --- a/htdocs/core/class/html.formcompany.class.php +++ b/htdocs/core/class/html.formcompany.class.php @@ -58,12 +58,12 @@ class FormCompany extends Form $sql = "SELECT id, code, libelle as label"; $sql .= " FROM " . $this->db->prefix() . "c_typent"; - $sql .= " WHERE active = 1 AND (fk_country IS NULL OR fk_country = " . (empty($mysoc->country_id) ? '0' : $mysoc->country_id) . ")"; + $sql .= " WHERE active = 1 AND (fk_country IS NULL OR fk_country = " . (empty($mysoc->country_id) ? '0' : ((int) $mysoc->country_id)) . ")"; $errormsg = ''; $filter = forgeSQLFromUniversalSearchCriteria($filter, $errormsg, 0); if ($filter) { - $sqlwhere = $filter; + $sqlwhere = $filter; // @phan-suppress-current-line SqlInjection $sql .= " " . $sqlwhere; } @@ -119,7 +119,7 @@ class FormCompany extends Form $errormsg = ''; $filter = forgeSQLFromUniversalSearchCriteria($filter, $errormsg, 0); if ($filter) { - $sqlwhere = $filter; + $sqlwhere = $filter; // @phan-suppress-current-line SqlInjection $sql .= " " . $sqlwhere; } @@ -153,11 +153,11 @@ class FormCompany extends Form // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps /** - * Affiche formulaire de selection des modes de reglement + * Displays form for selecting settlement methods * * @param string $page Page * @param int $selected Id or code preselected - * @param string $htmlname Nom du formulaire select + * @param string $htmlname Name of the select form * @param int $empty Add empty value in list * @return void */ @@ -414,10 +414,10 @@ class FormCompany extends Form // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps /** - * Provides the dropdown of the active regions including the actif country. + * Provides the dropdown of the active regions including the active country. * The key of the list is the code (there may be more than one entry for a * code but in that case the fields country and language are different). - * un code donnee mais dans ce cas, le champ pays et lang differe). + * a given code but in this case, the country and language field differs). * This way the links with the regions are made independent of its name. * * @param string $selected Preselected value @@ -581,7 +581,7 @@ class FormCompany extends Form $errormsg = ''; $filter = forgeSQLFromUniversalSearchCriteria($filter, $errormsg, 0); if ($filter) { - $sqlwhere = $filter; + $sqlwhere = $filter; // @phan-suppress-current-line SqlInjection $sql .= " " . $sqlwhere; } @@ -1186,7 +1186,7 @@ class FormCompany extends Form * @param Contact|Client|null $prospectstatic Prospect object * @param int $statusprospect status of prospect * @param int $idprospect id of prospect - * @param 'html'|'js' $mode select if we want activate de html part or js + * @param 'html'|'js' $mode select whether to activate the HTML part or JS * @return void */ public function selectProspectStatus($htmlname, $prospectstatic, $statusprospect, $idprospect, $mode = "html") diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 32eba4c116b..2b424aa8fc4 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -972,59 +972,59 @@ class FormFile $relativepath = (string) $file["name"]; // Other case } - $out .= ''; + $tmpout = ''; $documenturl = getDolGlobalString('DOL_URL_ROOT_DOCUMENT_PHP', DOL_URL_ROOT.'/document.php'); // DOL_URL_ROOT_DOCUMENT_PHP can be used to set another wrapper // Show file name with link to download $imgpreview = $this->showPreview($file, $modulepart, $relativepath, 0, $param.'&preview=1'); - $out .= ''; + $tmpout .= ''; if ($imgpreview) { - $out .= ''; + $tmpout .= ''; } else { - $out .= ''; + $tmpout .= ''; } if (getDolGlobalInt('PREVIEW_PICTO_ON_LEFT_OF_NAME')) { - $out .= $imgpreview; + $tmpout .= $imgpreview; } if (is_object($ecmfile)) { - $out .= $ecmfile->getNomUrl(1, $modulepart, 0, 0, ' documentdownload'); // We show property in ECM + $tmpout .= $ecmfile->getNomUrl(1, $modulepart, 0, 0, ' documentdownload'); // We show property in ECM //$out .= $ecmfile->getNomUrl(1, $modulepart, 0, 0, ' documentdownload', $object); // We show property on object } else { - $out .= 'trans("File").': '.$file["name"]); - $out .= dol_trunc($file["name"], 150); - $out .= ''; + $tmpout .= ' title="'.dol_escape_htmltag($file["name"]).'"'; + $tmpout .= '>'; + $tmpout .= img_mime($file["name"], $langs->trans("File").': '.$file["name"]); + $tmpout .= dol_trunc($file["name"], 150); + $tmpout .= ''; } - $out .= ''."\n"; + $tmpout .= ''."\n"; if (!getDolGlobalInt('PREVIEW_PICTO_ON_LEFT_OF_NAME')) { - $out .= $imgpreview; + $tmpout .= $imgpreview; } - $out .= ''; + $tmpout .= ''; // Show file size $size = (!empty($file['size']) ? $file['size'] : dol_filesize($filedir."/".$file["name"])); - $out .= ''.dol_print_size($size, 1, 1).''; + $tmpout .= ''.dol_print_size($size, 1, 1).''; // Show file date $date = (!empty($file['date']) ? $file['date'] : dol_filemtime($filedir."/".$file["name"])); - $out .= ''.dol_print_date($date, 'dayhour', 'tzuser').''; + $tmpout .= ''.dol_print_date($date, 'dayhour', 'tzuser').''; // Show share link - $out .= ''; + $tmpout .= ''; if (!empty($file['share'])) { // Define $urlwithroot $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root)); @@ -1043,50 +1043,54 @@ class FormFile $fulllink = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : ''); - $out .= ''.img_picto($langs->trans("FileSharedViaALink"), 'globe').' '; - $out .= ''; - $out .= ajax_autoselect('downloadlink'.$file['rowid']); + $tmpout .= ''.img_picto($langs->trans("FileSharedViaALink"), 'globe').' '; + $tmpout .= ''; + $tmpout .= ajax_autoselect('downloadlink'.$file['rowid']); } else { //print ''.$langs->trans("FileNotShared").''; } - $out .= ''; + $tmpout .= ''; // Show picto delete, print... if ($delallowed || $printer || $morepicto) { - $out .= ''; + $tmpout .= ''; if ($delallowed) { $tmpurlsource = preg_replace('/#[a-zA-Z0-9_]*$/', '', $urlsource); - $out .= ''.img_picto($langs->trans("Delete"), 'delete').''; + $tmpout .= '">'.img_picto($langs->trans("Delete"), 'delete').''; } if ($printer) { - $out .= ''.img_picto($langs->trans("PrintFile", $relativepath), 'printer').''; + $tmpout .= ''.img_picto($langs->trans("PrintFile", $relativepath), 'printer').''; } if ($morepicto) { $morepicto = preg_replace('/__FILENAMEURLENCODED__/', urlencode($relativepath), $morepicto); - $out .= $morepicto; + $tmpout .= $morepicto; } - $out .= ''; + $tmpout .= ''; } if (is_object($hookmanager)) { $addcolumforpicto = ($delallowed || $printer || $morepicto); $colspan = (4 + ($addcolumforpicto ? 1 : 0)); $colspanmore = 0; - $parameters = array('colspan' => ($colspan + $colspanmore), 'socid' => (isset($GLOBALS['socid']) ? $GLOBALS['socid'] : ''), 'id' => (isset($GLOBALS['id']) ? $GLOBALS['id'] : ''), 'modulepart' => $modulepart, 'relativepath' => $relativepath); + $parameters = array('tmpout' => &$tmpout, 'colspan' => ($colspan + $colspanmore), 'socid' => (isset($GLOBALS['socid']) ? $GLOBALS['socid'] : ''), 'id' => (isset($GLOBALS['id']) ? $GLOBALS['id'] : ''), 'modulepart' => $modulepart, 'relativepath' => $relativepath); $res = $hookmanager->executeHooks('formBuilddocLineOptions', $parameters, $file); if (empty($res)) { - $out .= $hookmanager->resPrint; // Complete line - $out .= ''; + $tmpout .= $hookmanager->resPrint; // Complete line + $tmpout .= ''; } else { - $out = $hookmanager->resPrint; // Replace all $out + $tmpout = $hookmanager->resPrint; // Replace all $out } + } else { + $tmpout .= ''; } + + $out .= $tmpout; } $this->numoffiles++; @@ -1121,7 +1125,7 @@ class FormFile } if ($headershown) { - // Affiche pied du tableau + // end of table $out .= "\n"; $out .= "
\n"; if ($genallowed) { @@ -1403,7 +1407,7 @@ class FormFile // Show title of list of existing files $morehtmlright = ''; if (!empty($moreoptions['showhideaddbutton']) && $conf->use_javascript_ajax) { - $tmpurlforbutton = 'javascript:console.log("open add file form");jQuery(".divattachnewfile").removeClass("hidden").toggle(); if (!jQuery(".divattachnewfile").is(":hidden")) { jQuery(".divattachnewfile input[type=\'file\']").first().click();} void(0);'; // scope file picker click to the just-shown form to avoid triggering native file dialog twice when other input[type=file] exist on the page; removeClass keeps the toggle effective when a theme stylesheet ships a `.hidden { display: none !important }` rule (see #34780, #36782) + $tmpurlforbutton = 'javascript:console.log("open add file form"); if (jQuery(".divattachnewfile").is(":hidden")) { jQuery(".divattachnewfile").removeClass("hidden"); jQuery(".divattachnewfile input[type=\'file\']").first().click(); } else { jQuery(".divattachnewfile").addClass("hidden"); } void(0);'; $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton, '', $permtoeditline); } @@ -2282,7 +2286,7 @@ class FormFile $morehtmlright = ''; if (!empty($moreoptions['showhideaddbutton']) && $conf->use_javascript_ajax) { - $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', 'javascript:console.log("open addlink form"); jQuery(".divlinkfile").toggle(); void(0);', '', $permissiontoedit); + $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', 'javascript:console.log("open addlink form"); if (jQuery(".divlinkfile").is(":hidden")) { jQuery(".divlinkfile").removeClass("hidden"); } else { jQuery(".divlinkfile").addClass("hidden"); } void(0);', '', $permissiontoedit); } // Show list of associated links diff --git a/htdocs/core/class/html.formfiscalyear.class.php b/htdocs/core/class/html.formfiscalyear.class.php index 9f49aa16f41..9092450d521 100644 --- a/htdocs/core/class/html.formfiscalyear.class.php +++ b/htdocs/core/class/html.formfiscalyear.class.php @@ -1,5 +1,6 @@ + * Copyright (C) 2026 MDW * * 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 @@ -65,7 +66,7 @@ class FormFiscalYear extends Form $sql = "SELECT f.rowid, f.label, f.date_start, f.date_end, f.statut as status"; $sql .= " FROM ".$this->db->prefix()."accounting_fiscalyear as f"; - $sql .= " WHERE f.entity = ".$conf->entity; + $sql .= " WHERE f.entity = ".((int) $conf->entity); $sql .= " ORDER BY f.date_start ASC"; dol_syslog(get_class($this).'::'.__METHOD__, LOG_DEBUG); diff --git a/htdocs/core/class/html.formintervention.class.php b/htdocs/core/class/html.formintervention.class.php index cd01dc3fbce..bebd44df22f 100644 --- a/htdocs/core/class/html.formintervention.class.php +++ b/htdocs/core/class/html.formintervention.class.php @@ -1,6 +1,6 @@ - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * Copyright (C) 2025 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -75,7 +75,7 @@ class FormIntervention // Search all contacts $sql = "SELECT f.rowid, f.ref, f.fk_soc, f.fk_statut as status"; $sql .= " FROM ".$this->db->prefix()."fichinter as f"; - $sql .= " WHERE f.entity = ".$conf->entity; + $sql .= " WHERE f.entity = ".((int) $conf->entity); if ($socid >= 0) { if ($socid == '0') { $sql .= " AND (f.fk_soc = 0 OR f.fk_soc IS NULL)"; diff --git a/htdocs/core/class/html.formmargin.class.php b/htdocs/core/class/html.formmargin.class.php index b4530c893de..ab05efe02a1 100644 --- a/htdocs/core/class/html.formmargin.class.php +++ b/htdocs/core/class/html.formmargin.class.php @@ -112,8 +112,14 @@ class FormMargin /** @var Facture $object */ if (($object->element == 'facture' && $object->type == $object::TYPE_SITUATION) || ($object->element == 'facture' && $object->type == $object::TYPE_CREDIT_NOTE && getDolGlobalInt('INVOICE_USE_SITUATION_CREDIT_NOTE') && $object->situation_counter > 0)) { - // We need a compensation relative to $line->situation_percent - $pa = $line->qty * $pa_ht * ($line->situation_percent / 100); + // We need only the delta between this situation and the previous one to avoid cumulating margins across situation invoices + // total_ht is stored cumulatively (e.g. 60% of full price), so we extract only this invoice's share using the delta percent + $prevPercent = $line->get_prev_progress($object->id); + $deltaPercent = $line->situation_percent - $prevPercent; + if ($line->situation_percent > 0) { + $pv *= ($deltaPercent / $line->situation_percent); + } + $pa = $line->qty * $pa_ht * ($deltaPercent / 100); } else { $pa = $line->qty * $pa_ht; } diff --git a/htdocs/core/class/html.formsetup.class.php b/htdocs/core/class/html.formsetup.class.php index e06f6490821..2568363d449 100644 --- a/htdocs/core/class/html.formsetup.class.php +++ b/htdocs/core/class/html.formsetup.class.php @@ -1,6 +1,6 @@ - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2026 Frédéric France * Copyright (C) 2026 Alexandre Spangaro * @@ -1745,7 +1745,7 @@ class FormSetupItem /** * Set type of input as a select list. * - * @param ?array $fieldOptions A table of field options + * @param ?array $fieldOptions A table of field options * @return self */ public function setAsSelect($fieldOptions) diff --git a/htdocs/core/class/html.formsms.class.php b/htdocs/core/class/html.formsms.class.php index 15336ce4f70..7d6953902d3 100644 --- a/htdocs/core/class/html.formsms.class.php +++ b/htdocs/core/class/html.formsms.class.php @@ -27,10 +27,10 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php'; /** - * Class permettant la generation du formulaire d'envoi de Sms + * Class for generating the SMS sending form * Usage: $formsms = new FormSms($db) * $formsms->proprietes=1 or string or table of values - * $formsms->show_form() affiche le formulaire + * $formsms->show_form() displays the form */ class FormSms { diff --git a/htdocs/core/class/html.formticket.class.php b/htdocs/core/class/html.formticket.class.php index f9cd0a81d21..a80e0d94e6e 100644 --- a/htdocs/core/class/html.formticket.class.php +++ b/htdocs/core/class/html.formticket.class.php @@ -1213,7 +1213,7 @@ class FormTicket if (!empty($arrayidused)) { $sql .= " AND ctc.fk_parent IN ( "; foreach ($arrayidused as $idused) { - $sql .= $idused.", "; + $sql .= ((int) $idused).", "; } $sql = substr($sql, 0, -2); $sql .= ")"; diff --git a/htdocs/core/class/infobox.class.php b/htdocs/core/class/infobox.class.php index 9f1ff93c0e0..6b08c756c38 100644 --- a/htdocs/core/class/infobox.class.php +++ b/htdocs/core/class/infobox.class.php @@ -3,7 +3,7 @@ * Copyright (C) 2004-2012 Laurent Destailleur * Copyright (C) 2005-2012 Regis Houssin * Copyright (C) 2019 Nicolas ZABOURI - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -105,12 +105,12 @@ class InfoBox $sql .= " d.rowid as box_id, d.file, d.note, d.tms"; $sql .= " FROM ".$dbs->prefix()."boxes as b, ".$dbs->prefix()."boxes_def as d"; $sql .= " WHERE b.box_id = d.rowid"; - $sql .= " AND b.entity IN (0,".$conf->entity.")"; + $sql .= " AND b.entity IN (0,".((int) $conf->entity).")"; if ($zone >= 0) { $sql .= " AND b.position = ".((int) $zone); } if (is_object($user)) { - $sql .= " AND b.fk_user IN (0,".$user->id.")"; + $sql .= " AND b.fk_user IN (0,".((int) $user->id).")"; } else { $sql .= " AND b.fk_user = 0"; } @@ -118,7 +118,7 @@ class InfoBox } else { // available $sql = "SELECT d.rowid as box_id, d.file, d.note, d.tms"; $sql .= " FROM ".$dbs->prefix()."boxes_def as d"; - $sql .= " WHERE d.entity IN (0, ".$conf->entity.")"; + $sql .= " WHERE d.entity IN (0, ".((int) $conf->entity).")"; } dol_syslog(self::class."::listBoxes get default box list for mode=".$mode." userid=".(is_object($user) ? $user->id : ''), LOG_DEBUG); @@ -260,7 +260,7 @@ class InfoBox // Delete all lines $sql = "DELETE FROM ".$dbs->prefix()."boxes"; - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); $sql .= " AND fk_user = ".((int) $userid); $sql .= " AND position = ".((int) $zone); diff --git a/htdocs/core/class/ldap.class.php b/htdocs/core/class/ldap.class.php index f587a905600..11774f158b7 100644 --- a/htdocs/core/class/ldap.class.php +++ b/htdocs/core/class/ldap.class.php @@ -359,7 +359,10 @@ class Ldap dol_syslog(get_class($this)."::connectBind serverPing true, we try ldap_connect to ".$host, LOG_DEBUG); } if (version_compare(PHP_VERSION, '8.3.0', '>=')) { - $uri = $host.':'.$this->serverPort; + // Since PHP 8.3, ldap_connect() expects a single URI argument. A scheme-less + // host (ex: localhost, 192.168.0.2) must be turned into a valid ldap:// URI, + // otherwise the host is parsed as the URI scheme and the later bind fails. + $uri = preg_match('/^ldaps?:\/\//i', $host) ? $host : 'ldap://'.$host.':'.$this->serverPort; $this->connection = ldap_connect($uri); } else { $this->connection = ldap_connect($host, $this->serverPort); @@ -372,7 +375,7 @@ class Ldap dol_syslog(get_class($this)."::connectBind serverPing false, we try ldap_connect to ".$host, LOG_DEBUG); } if (version_compare(PHP_VERSION, '8.3.0', '>=')) { - $uri = $host.':'.$this->serverPort; + $uri = preg_match('/^ldaps?:\/\//i', $host) ? $host : 'ldap://'.$host.':'.$this->serverPort; $this->connection = ldap_connect($uri); } else { $this->connection = ldap_connect($host, $this->serverPort); diff --git a/htdocs/core/class/link.class.php b/htdocs/core/class/link.class.php index 73a2adda0f2..37b10cf41a2 100644 --- a/htdocs/core/class/link.class.php +++ b/htdocs/core/class/link.class.php @@ -1,6 +1,6 @@ - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -123,7 +123,7 @@ class Link extends CommonObject $this->db->begin(); $sql = "INSERT INTO ".$this->db->prefix()."links (entity, datea, url, label, objecttype, objectid, share,share_pass)"; - $sql .= " VALUES (".$conf->entity.", '".$this->db->idate($this->datea)."'"; + $sql .= " VALUES (".((int) $conf->entity).", '".$this->db->idate($this->datea)."'"; $sql .= ", '".$this->db->escape($this->url)."'"; $sql .= ", '".$this->db->escape($this->label)."'"; $sql .= ", '".$this->db->escape($this->objecttype)."'"; @@ -345,7 +345,7 @@ class Link extends CommonObject $rowid = $this->id; } - $sqlwhere=[]; + $sqlwhere = []; $sql = "SELECT rowid, entity, datea, url, label, objecttype, objectid, share, share_pass FROM ".$this->db->prefix()."links"; if (!empty((int) $rowid)) { @@ -356,10 +356,10 @@ class Link extends CommonObject } if ($conf->entity != 0) { - $sqlwhere[] = " entity = ".$conf->entity; + $sqlwhere[] = " entity = ".((int) $conf->entity); } - if (count($sqlwhere)>0) { - $sql .=' WHERE '.implode(' AND ', $sqlwhere); + if (count($sqlwhere) > 0) { + $sql .= ' WHERE '.implode(' AND ', $sqlwhere); } dol_syslog(get_class($this)."::fetch", LOG_DEBUG); diff --git a/htdocs/core/class/menubase.class.php b/htdocs/core/class/menubase.class.php index 2b9d18fa4fe..065080d289e 100644 --- a/htdocs/core/class/menubase.class.php +++ b/htdocs/core/class/menubase.class.php @@ -230,7 +230,7 @@ class Menubase $maxrowid = 1; } - $sql = "SELECT setval('".$this->db->prefix()."menu_rowid_seq', ".($maxrowid).")"; + $sql = "SELECT setval('".$this->db->prefix()."menu_rowid_seq', ".((int) $maxrowid).")"; //print $sql; exit; $resqlrowidset = $this->db->query($sql); if (!$resqlrowidset) { @@ -248,7 +248,7 @@ class Menubase $sql .= " AND fk_menu = ".((int) $this->fk_menu); $sql .= " AND position = ".((int) $this->position); $sql .= " AND url = '".$this->db->escape($this->url)."'"; - $sql .= " AND entity IN (0, ".$conf->entity.")"; + $sql .= " AND entity IN (0, ".((int) $conf->entity).")"; $result = $this->db->query($sql); if ($result) { @@ -649,7 +649,7 @@ class Menubase $sql = "SELECT m.rowid, m.type, m.module, m.fk_menu, m.fk_mainmenu, m.fk_leftmenu, m.url, m.titre,"; $sql .= " m.prefix, m.langs, m.perms, m.enabled, m.target, m.mainmenu, m.leftmenu, m.position, m.showtopmenuinframe"; $sql .= " FROM ".$this->db->prefix()."menu as m"; - $sql .= " WHERE m.entity IN (0,".$conf->entity.")"; + $sql .= " WHERE m.entity IN (0,".((int) $conf->entity).")"; $sql .= " AND m.menu_handler IN ('".$this->db->escape($menu_handler)."','all')"; if ($type_user == 0) { $sql .= " AND m.usertype IN (0,2)"; @@ -679,7 +679,7 @@ class Menubase $tmpcond = preg_replace('/\$leftmenu\s*==\s*["\'a-zA-Z_]+/', '1==1', $tmpcond); // Force the part of condition on leftmenu to true } $perms = verifCond($tmpcond); - //print "verifCond rowid=".$menu['rowid']." ".$tmpcond.":".$perms."
\n"; + //var_dump($menu['rowid'].' - '.$menu['titre'].' - '.$menu['perms'].' => '.$tmpcond.":".$perms); } // Define $enabled diff --git a/htdocs/core/class/openid.class.php b/htdocs/core/class/openid.class.php index 9a0e91c48a7..5f04f6a9a19 100644 --- a/htdocs/core/class/openid.class.php +++ b/htdocs/core/class/openid.class.php @@ -388,7 +388,7 @@ class SimpleOpenID $response = getURLContent($url, 'GET', '', 1, array(), array('http', 'https')); - list($servers, $delegates) = $this->HTML2OpenIDServer($response); + list($servers, $delegates) = $this->HTML2OpenIDServer($response['content']); if (count($servers) == 0) { $this->ErrorStore('OPENID_NOSERVERSFOUND'); return false; diff --git a/htdocs/core/class/timespent.class.php b/htdocs/core/class/timespent.class.php index 3e9464d6c14..0709c296533 100644 --- a/htdocs/core/class/timespent.class.php +++ b/htdocs/core/class/timespent.class.php @@ -561,14 +561,14 @@ class TimeSpent extends CommonObject if (preg_match('/^[\(]?PROV/i', $this->ref)) { // Now we rename also files into index $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'timespent/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'timespent/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'timespent/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'timespent/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'timespent/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'timespent/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; diff --git a/htdocs/core/class/utils.class.php b/htdocs/core/class/utils.class.php index d9fafe911ed..ca041a98ad9 100644 --- a/htdocs/core/class/utils.class.php +++ b/htdocs/core/class/utils.class.php @@ -246,9 +246,10 @@ class Utils */ public function dumpDatabase($compression = 'none', $type = 'auto', $usedefault = 1, $file = 'auto', $keeplastnfiles = 0, $execmethod = 0, $lowmemorydump = 0) { - global $db, $conf, $langs, $dolibarr_main_data_root; + global $db, $conf, $langs; global $dolibarr_main_db_name, $dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_port, $dolibarr_main_db_pass; global $dolibarr_main_db_character_set; + global $dolibarr_main_restrict_os_commands; $langs->load("admin"); @@ -318,6 +319,23 @@ class Utils $outputerror = $outputfile.'.err'; dol_mkdir($conf->admin->dir_output.'/backup'); + $cmddump = dol_sanitizePathName($cmddump); // Sanitize path + $cmddump = dol_string_nospecial($cmddump, '', array("|", ";", "<", ">", "&", "+")); // Sanitize command + $basenamecmddump = basename(str_replace('\\', '/', $cmddump)); + + // Sanitize and validate $cmddump + if (!empty($dolibarr_main_restrict_os_commands)) { + $arrayofallowedcommand = explode(',', $dolibarr_main_restrict_os_commands); + $arrayofallowedcommand = array_map('trim', $arrayofallowedcommand); + dol_syslog("Command are restricted to ".$dolibarr_main_restrict_os_commands.". We check that one of this command is inside ".$cmddump); + if (!in_array($basenamecmddump, $arrayofallowedcommand)) { // the provided command $cmddump must be an allowed command + $langs->load("errors"); + $this->error = $langs->trans('CommandIsNotInsideAllowedCommands'); + $this->error .= '
'.$langs->trans('ErrorCheckTheCommandInsideTheAdvancedOptions'); + return -1; + } + } + // Parameters execution $command = $cmddump; $command = preg_replace('/(\$|%)/', '', $command); // We removed chars that can be used to inject vars that contains space inside path of command without seeing there is a space to bypass the escapeshellarg. @@ -631,6 +649,23 @@ class Utils $outputerror = $outputfile.'.err'; dol_mkdir($conf->admin->dir_output.'/backup'); + // Sanitize and validate $cmddump + $cmddump = dol_sanitizePathName($cmddump); // Sanitize path + $cmddump = dol_string_nospecial($cmddump, '', array("|", ";", "<", ">", "&", "+")); // Sanitize command + $basenamecmddump = basename(str_replace('\\', '/', $cmddump)); + + if (!empty($dolibarr_main_restrict_os_commands)) { + $arrayofallowedcommand = explode(',', $dolibarr_main_restrict_os_commands); + $arrayofallowedcommand = array_map('trim', $arrayofallowedcommand); + dol_syslog("Command are restricted to ".$dolibarr_main_restrict_os_commands.". We check that one of this command is inside ".$cmddump); + if (!in_array($basenamecmddump, $arrayofallowedcommand)) { // the provided command $cmddump must be an allowed command + $langs->load("errors"); + $this->error = $langs->trans('CommandIsNotInsideAllowedCommands'); + $this->error .= '
'.$langs->trans('ErrorCheckTheCommandInsideTheAdvancedOptions'); + return -1; + } + } + // Parameters execution $command = $cmddump; $command = preg_replace('/(\$|%)/', '', $command); // We removed chars that can be used to inject vars that contains space inside path of command without seeing there is a space to bypass the escapeshellarg. diff --git a/htdocs/core/customreports.php b/htdocs/core/customreports.php index b2d3bdf701e..56fef148376 100644 --- a/htdocs/core/customreports.php +++ b/htdocs/core/customreports.php @@ -92,10 +92,10 @@ if (!defined('USE_CUSTOM_REPORT_AS_INCLUDE')) { return preg_replace('/[^a-z0-9\._\-]+/', '', $value); } - $search_measures = array_map('sanititzekey', $search_measures); - $search_xaxis = array_map('sanititzekey', $search_xaxis); - $search_yaxis = array_map('sanititzekey', $search_yaxis); - $search_groupby = array_map('sanititzekey', $search_groupby); + $sanitized_search_measures = array_map('sanititzekey', $search_measures); + $sanitized_search_xaxis = array_map('sanititzekey', $search_xaxis); + $sanitized_search_yaxis = array_map('sanititzekey', $search_yaxis); + $sanitized_search_groupby = array_map('sanititzekey', $search_groupby); // Load variable for pagination $limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; @@ -116,7 +116,7 @@ if (!defined('USE_CUSTOM_REPORT_AS_INCLUDE')) { * @var ?int $SHOWLEGEND Show legend or not * @var string $customreportkey Custom report key * @var ?string $customsql Custom SQL - * @var ?string[] $search_groupby Array with the third dimension + * @var ?string[] $sanitized_search_groupby Array with the third dimension * @var int $page * @var string $sortfield * @var string $sortorder @@ -125,13 +125,13 @@ if (!defined('USE_CUSTOM_REPORT_AS_INCLUDE')) { @phan-var-force int<0,1> $SHOWLEGEND @phan-var-force string $customreportkey @phan-var-force ?string $customsql - @phan-var-force ?string[] $search_groupby Array with the third dimension + @phan-var-force ?string[] $sanitized_search_groupby Array with the third dimension @phan-var-force int $page @phan-var-force string $sortfield @phan-var-force string $sortorder '; - // $search_measures, $search_xaxis or $search_yaxis may have been defined by the parent. + // $sanitized_search_measures, $sanitized_search_xaxis or $sanitized_search_yaxis may have been defined by the parent. if (empty($user) || empty($user->id)) { print 'Page is called as an include but $user and its permission loaded with loadRights() are not defined. We stop here.'; @@ -150,20 +150,20 @@ $dolibarr_allow_unsecured_select_in_extrafields_filter = 0; if (empty($mode)) { $mode = 'graph'; } -if (!isset($search_measures)) { - $search_measures = array(0 => 't.count'); +if (!isset($sanitized_search_measures)) { + $sanitized_search_measures = array(0 => 't.count'); } -if (!isset($search_xaxis)) { +if (!isset($sanitized_search_xaxis)) { // Ensure value is set and not null. - $search_xaxis = array(); + $sanitized_search_xaxis = array(); } -if (!isset($search_yaxis)) { +if (!isset($sanitized_search_yaxis)) { // Ensure value is set and not null. - $search_yaxis = array(); + $sanitized_search_yaxis = array(); } -if (!isset($search_groupby)) { +if (!isset($sanitized_search_groupby)) { // Ensure value is set and not null. - $search_groupby = array(); + $sanitized_search_groupby = array(); } if (!isset($search_graph)) { // Ensure value is set and not null @@ -476,19 +476,19 @@ $arrayofgroupby = dol_sort_array($arrayofgroupby, 'position', 'asc', 0, 0, 1); // Check parameters if ($action == 'viewgraph') { - if (!count($search_measures)) { + if (!count($sanitized_search_measures)) { setEventMessages($langs->trans("AtLeastOneMeasureIsRequired"), null, 'warnings'); - } elseif ($mode == 'graph' && is_array($search_xaxis) && count($search_xaxis) > 1) { + } elseif ($mode == 'graph' && is_array($sanitized_search_xaxis) && count($sanitized_search_xaxis) > 1) { setEventMessages($langs->trans("OnlyOneFieldForXAxisIsPossible"), null, 'warnings'); - $search_xaxis = array(0 => $search_xaxis[0]); + $sanitized_search_xaxis = array(0 => $sanitized_search_xaxis[0]); } - if (count($search_groupby) >= 2) { + if (count($sanitized_search_groupby) >= 2) { setEventMessages($langs->trans("ErrorOnlyOneFieldForGroupByIsPossible"), null, 'warnings'); - $search_groupby = array(0 => $search_groupby[0]); + $sanitized_search_groupby = array(0 => $sanitized_search_groupby[0]); } - if (!count($search_xaxis)) { + if (!count($sanitized_search_xaxis)) { setEventMessages($langs->trans("AtLeastOneXAxisIsRequired"), null, 'warnings'); - } elseif ($mode == 'graph' && $search_graph == 'bars' && count($search_measures) > $MAXMEASURESINBARGRAPH) { + } elseif ($mode == 'graph' && $search_graph == 'bars' && count($sanitized_search_measures) > $MAXMEASURESINBARGRAPH) { $langs->load("errors"); setEventMessages($langs->trans("GraphInBarsAreLimitedToNMeasures", $MAXMEASURESINBARGRAPH), null, 'warnings'); $search_graph = 'lines'; @@ -497,34 +497,36 @@ if ($action == 'viewgraph') { // Get all possible values of fields when a 'group by' is set, and save this into $arrayofvaluesforgroupby // $arrayofvaluesforgroupby will be used to forge lael of each grouped series -if (count($search_groupby)) { +if (count($sanitized_search_groupby)) { $fieldtocount = ''; - foreach ($search_groupby as $gkey => $gval) { - $gvalwithoutprefix = preg_replace('/^[a-z]+\./i', '', $gval); - $gvalsanitized = preg_replace('/[^a-z0-9\._\-]+/i', '', $gval); + foreach ($sanitized_search_groupby as $gkey => $sanitized_gval) { + $gvalwithoutprefix = preg_replace('/^[a-z]+\./i', '', $sanitized_gval); + $gvalsanitized = preg_replace('/[^a-z0-9\._\-]+/i', '', $sanitized_gval); + + $sanitizedfieldtocount = ''; if (preg_match('/\-year$/', $gvalsanitized)) { $tmpval = preg_replace('/\-year$/', '', $gvalsanitized); - $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y')"; + $sanitizedfieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y')"; } elseif (preg_match('/\-month$/', $gvalsanitized)) { $tmpval = preg_replace('/\-month$/', '', $gvalsanitized); - $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y-%m')"; + $sanitizedfieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y-%m')"; } elseif (preg_match('/\-day$/', $gvalsanitized)) { $tmpval = preg_replace('/\-day$/', '', $gvalsanitized); - $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y-%m-%d')"; + $sanitizedfieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y-%m-%d')"; } else { - $fieldtocount = $gvalsanitized; + $sanitizedfieldtocount = $gvalsanitized; } - $sanitizedfieldtocount = $fieldtocount; - $sql = "SELECT DISTINCT ".$sanitizedfieldtocount." as val"; // $fieldtocount has been sanitized by previous lines as we can't use db->sanitie() + $fieldtocount = $sanitizedfieldtocount; + $sql = "SELECT DISTINCT ".$sanitizedfieldtocount." as val"; // $fieldtocount has been sanitized by previous lines as we can't use db->sanitize() if (strpos($fieldtocount, 'te') === 0) { $tabletouse = $object->table_element; $tablealiastouse = 'te'; - if (!empty($arrayofgroupby[$gval])) { - $tmpval = explode('.', $gval); - $tabletouse = $arrayofgroupby[$gval]['table']; + if (!empty($arrayofgroupby[$sanitized_gval])) { + $tmpval = explode('.', $sanitized_gval); + $tabletouse = $arrayofgroupby[$sanitized_gval]['table']; $tablealiastouse = $tmpval[0]; } //var_dump($tablealiastouse);exit; @@ -534,9 +536,9 @@ if (count($search_groupby)) { } else { $tabletouse = $object->table_element; $tablealiastouse = 't'; - if (!empty($arrayofgroupby[$gval])) { - $tmpval = explode('.', $gval); - $tabletouse = $arrayofgroupby[$gval]['table']; + if (!empty($arrayofgroupby[$sanitized_gval])) { + $tmpval = explode('.', $sanitized_gval); + $tabletouse = $arrayofgroupby[$sanitized_gval]['table']; $tablealiastouse = $tmpval[0]; } $sql .= " FROM ".MAIN_DB_PREFIX.$tabletouse." as ".$tablealiastouse; @@ -601,10 +603,10 @@ if (count($search_groupby)) { $arrayofvaluesforgroupby['g_'.$gkey][$keytouse] = $valuetranslated; } // Add also the possible NULL value if field is a parent field that is not a strict join - $tmpfield = explode('.', $gval); + $tmpfield = explode('.', $sanitized_gval); if ($tmpfield[0] != 't' || (isset($object->fields[$tmpfield[1]]) && is_array($object->fields[$tmpfield[1]]) && empty($object->fields[$tmpfield[1]]['notnull']))) { - dol_syslog("The group by field ".$gval." may be null (because field is null or it is a left join), so we add __NULL__ entry in list of possible values"); - //var_dump($gval); var_dump($object->fields); + dol_syslog("The group by field ".$sanitized_gval." may be null (because field is null or it is a left join), so we add __NULL__ entry in list of possible values"); + //var_dump($sanitized_gval); var_dump($object->fields); $arrayofvaluesforgroupby['g_'.$gkey]['__NULL__'] = $langs->transnoentitiesnoconv("NotDefined"); } @@ -648,7 +650,7 @@ if (count($search_groupby)) { } //var_dump($labeloffield); setEventMessages($langs->transnoentitiesnoconv("ErrorTooManyDifferentValueForSelectedGroupBy", (string) $MAXUNIQUEVALFORGROUP, (string) $labeloffield), null, 'warnings'); - $search_groupby = array(); + $sanitized_search_groupby = array(); } $db->free($resql); @@ -741,7 +743,7 @@ if (!defined('MAIN_CUSTOM_REPORT_KEEP_GRAPH_ONLY')) { foreach ($arrayofmesures as $key => $val) { $simplearrayofmesures[$key] = $arrayofmesures[$key]['label']; } - print $form->multiselectarray('search_measures', $simplearrayofmesures, $search_measures, 0, 0, 'minwidth300 widthcentpercentminusx', 1, 0, '', '', $langs->transnoentitiesnoconv("Measures")); // Fill the array $arrayofmeasures with possible fields + print $form->multiselectarray('search_measures', $simplearrayofmesures, $sanitized_search_measures, 0, 0, 'minwidth300 widthcentpercentminusx', 1, 0, '', '', $langs->transnoentitiesnoconv("Measures")); // Fill the array $arrayofmeasures with possible fields print '
'; // XAxis @@ -749,14 +751,14 @@ if (!defined('MAIN_CUSTOM_REPORT_KEEP_GRAPH_ONLY')) { print '
'; print '
'; //var_dump($arrayofxaxis); - print $formother->selectXAxisField($object, $search_xaxis, $arrayofxaxis, $langs->trans("XAxis"), 'minwidth300 maxwidth400 widthcentpercentminusx'); // Fill the array $arrayofxaxis with possible fields + print $formother->selectXAxisField($object, $sanitized_search_xaxis, $arrayofxaxis, $langs->trans("XAxis"), 'minwidth300 maxwidth400 widthcentpercentminusx'); // Fill the array $arrayofxaxis with possible fields print '
'; // Group by $count = 0; print '
'; print '
'; - print $formother->selectGroupByField($object, $search_groupby, $arrayofgroupby, 'minwidth250 maxwidth300 widthcentpercentminusx', $langs->trans("GroupBy")); // Fill the array $arrayofgroupby with possible fields + print $formother->selectGroupByField($object, $sanitized_search_groupby, $arrayofgroupby, 'minwidth250 maxwidth300 widthcentpercentminusx', $langs->trans("GroupBy")); // Fill the array $arrayofgroupby with possible fields print '
'; } @@ -817,7 +819,7 @@ if (!defined('MAIN_CUSTOM_REPORT_KEEP_GRAPH_ONLY')) { $arrayofyaxislabel[$key] = $val['label']; } print '
'.$langs->trans("YAxis").'
'; - print $form->multiselectarray('search_yaxis', $arrayofyaxislabel, $search_yaxis != null ? $search_yaxis : array(), 0, 0, 'minwidth100', 1); + print $form->multiselectarray('search_yaxis', $arrayofyaxislabel, $sanitized_search_yaxis != null ? $sanitized_search_yaxis : array(), 0, 0, 'minwidth100', 1); print '
'; } @@ -833,60 +835,60 @@ if (!defined('MAIN_CUSTOM_REPORT_KEEP_GRAPH_ONLY')) { // Generate the SQL request $sql = ''; -if (!empty($search_measures) && !empty($search_xaxis)) { +if (!empty($sanitized_search_measures) && !empty($sanitized_search_xaxis)) { $errormessage = ''; $fieldid = 'rowid'; $sql = "SELECT "; - foreach ($search_xaxis as $key => $val) { - if (preg_match('/\-year$/', $val)) { - $tmpval = preg_replace('/\-year$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y') as x_".$key.', '; - } elseif (preg_match('/\-month$/', $val)) { - $tmpval = preg_replace('/\-month$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m') as x_".$key.', '; - } elseif (preg_match('/\-day$/', $val)) { - $tmpval = preg_replace('/\-day$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d') as x_".$key.', '; + foreach ($sanitized_search_xaxis as $sql_key => $sql_val) { + if (preg_match('/\-year$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-year$/', '', $sql_val); + $sql .= "DATE_FORMAT(".$sql_tmpval.", '%Y') as x_".$sql_key.', '; + } elseif (preg_match('/\-month$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-month$/', '', $sql_val); + $sql .= "DATE_FORMAT(".$sql_tmpval.", '%Y-%m') as x_".$sql_key.', '; + } elseif (preg_match('/\-day$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-day$/', '', $sql_val); + $sql .= "DATE_FORMAT(".$sql_tmpval.", '%Y-%m-%d') as x_".$sql_key.', '; } else { - $sql .= $val." as x_".$key.", "; + $sql .= $sql_val." as x_".$sql_key.", "; } } - if (!empty($search_groupby)) { - foreach ($search_groupby as $key => $val) { - if (preg_match('/\-year$/', $val)) { - $tmpval = preg_replace('/\-year$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y') as g_".$key.', '; - } elseif (preg_match('/\-month$/', $val)) { - $tmpval = preg_replace('/\-month$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m') as g_".$key.', '; - } elseif (preg_match('/\-day$/', $val)) { - $tmpval = preg_replace('/\-day$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d') as g_".$key.', '; + if (!empty($sanitized_search_groupby)) { + foreach ($sanitized_search_groupby as $sql_key => $sql_val) { + if (preg_match('/\-year$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-year$/', '', $sql_val); + $sql .= "DATE_FORMAT(".$sql_tmpval.", '%Y') as g_".$sql_key.', '; + } elseif (preg_match('/\-month$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-month$/', '', $sql_val); + $sql .= "DATE_FORMAT(".$sql_tmpval.", '%Y-%m') as g_".$sql_key.', '; + } elseif (preg_match('/\-day$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-day$/', '', $sql_val); + $sql .= "DATE_FORMAT(".$sql_tmpval.", '%Y-%m-%d') as g_".$sql_key.', '; } else { - $sql .= $val." as g_".$key.", "; + $sql .= $sql_val." as g_".$sql_key.", "; } } } - foreach ($search_measures as $key => $val) { - if ($val == 't.count') { - $sql .= "COUNT(t.".$fieldid.") as y_".$key.', '; - } elseif (preg_match('/\-sum$/', $val)) { - $tmpval = preg_replace('/\-sum$/', '', $val); - $sql .= "SUM(".$db->ifsql($tmpval.' IS NULL', '0', $tmpval).") as y_".$key.", "; - } elseif (preg_match('/\-average$/', $val)) { - $tmpval = preg_replace('/\-average$/', '', $val); - $sql .= "AVG(".$db->ifsql($tmpval.' IS NULL', '0', $tmpval).") as y_".$key.", "; - } elseif (preg_match('/\-min$/', $val)) { - $tmpval = preg_replace('/\-min$/', '', $val); - $sql .= "MIN(".$db->ifsql($tmpval.' IS NULL', '0', $tmpval).") as y_".$key.", "; - } elseif (preg_match('/\-max$/', $val)) { - $tmpval = preg_replace('/\-max$/', '', $val); - $sql .= "MAX(".$db->ifsql($tmpval.' IS NULL', '0', $tmpval).") as y_".$key.", "; - } elseif (preg_match('/\-stddevpop$/', $val)) { - $tmpval = preg_replace('/\-stddevpop$/', '', $val); - $sql .= "STDDEV_POP(".$db->ifsql($tmpval.' IS NULL', '0', $tmpval).") as y_".$key.", "; + foreach ($sanitized_search_measures as $sql_key => $sql_val) { + if ($sql_val == 't.count') { + $sql .= "COUNT(t.".$fieldid.") as y_".$sql_key.', '; + } elseif (preg_match('/\-sum$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-sum$/', '', $sql_val); + $sql .= "SUM(".$db->ifsql($sql_tmpval.' IS NULL', '0', $sql_tmpval).") as y_".$sql_key.", "; + } elseif (preg_match('/\-average$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-average$/', '', $sql_val); + $sql .= "AVG(".$db->ifsql($sql_tmpval.' IS NULL', '0', $sql_tmpval).") as y_".$sql_key.", "; + } elseif (preg_match('/\-min$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-min$/', '', $sql_val); + $sql .= "MIN(".$db->ifsql($sql_tmpval.' IS NULL', '0', $sql_tmpval).") as y_".$sql_key.", "; + } elseif (preg_match('/\-max$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-max$/', '', $sql_val); + $sql .= "MAX(".$db->ifsql($sql_tmpval.' IS NULL', '0', $sql_tmpval).") as y_".$sql_key.", "; + } elseif (preg_match('/\-stddevpop$/', $sql_val)) { + $sql_tmpval = preg_replace('/\-stddevpop$/', '', $sql_val); + $sql .= "STDDEV_POP(".$db->ifsql($sql_tmpval.' IS NULL', '0', $sql_tmpval).") as y_".$sql_key.", "; } } $sql = preg_replace('/,\s*$/', '', $sql); @@ -901,7 +903,7 @@ if (!empty($search_measures) && !empty($search_xaxis)) { // No table to add here } else { $tmparray = explode('@', $object->ismultientitymanaged); - $sql .= " INNER JOIN ".MAIN_DB_PREFIX.$tmparray[1]." as parenttableforentity ON t.".$tmparray[0]." = parenttableforentity.rowid"; + $sql .= " INNER JOIN ".MAIN_DB_PREFIX.$db->sanitize($tmparray[1])." as parenttableforentity ON t.".$db->sanitize($tmparray[0])." = parenttableforentity.rowid"; $sql .= " AND parenttableforentity.entity IN (".getEntity($tmparray[1]).")"; } } @@ -910,8 +912,8 @@ if (!empty($search_measures) && !empty($search_xaxis)) { $listoftablesalreadyadded = array($object->table_element => $object->table_element); // Add LEFT JOIN for all parent tables mentioned into the Xaxis - //var_dump($arrayofxaxis); var_dump($search_xaxis); - foreach ($search_xaxis as $key => $val) { + //var_dump($arrayofxaxis); var_dump($sanitized_search_xaxis); + foreach ($sanitized_search_xaxis as $key => $val) { if (!empty($arrayofxaxis[$val])) { $tmpval = explode('.', $val); //var_dump($arrayofgroupby); @@ -937,8 +939,8 @@ if (!empty($search_measures) && !empty($search_xaxis)) { } // Add LEFT JOIN for all parent tables mentioned into the Group by - //var_dump($arrayofgroupby); var_dump($search_groupby); - foreach ($search_groupby as $key => $val) { + //var_dump($arrayofgroupby); var_dump($sanitized_search_groupby); + foreach ($sanitized_search_groupby as $key => $val) { if (!empty($arrayofgroupby[$val])) { $tmpval = explode('.', $val); //var_dump($arrayofgroupby[$val]); var_dump($tmpval); @@ -964,8 +966,8 @@ if (!empty($search_measures) && !empty($search_xaxis)) { } // Add LEFT JOIN for all parent tables mentioned into the Yaxis - //var_dump($arrayofgroupby); var_dump($search_groupby); - foreach ($search_measures as $key => $val) { + //var_dump($arrayofgroupby); var_dump($sanitized_search_groupby); + foreach ($sanitized_search_measures as $key => $val) { if (!empty($arrayofmesures[$val])) { $tmpval = explode('.', $val); //var_dump($arrayofgroupby); @@ -998,7 +1000,7 @@ if (!empty($search_measures) && !empty($search_xaxis)) { $fieldsUsedInFilter = array_unique($matches[0]); // Remove fields used before to avoid double join - $fieldsToRemove = array_merge($search_measures, $search_groupby, $search_xaxis); + $fieldsToRemove = array_merge($sanitized_search_measures, $sanitized_search_groupby, $sanitized_search_xaxis); $fieldsUsedInFilter = array_diff($fieldsUsedInFilter, $fieldsToRemove); foreach ($fieldsUsedInFilter as $key => $val) { @@ -1031,19 +1033,19 @@ if (!empty($search_measures) && !empty($search_xaxis)) { $sql .= " AND t.entity IN (".getEntity($object->element).")"; } // Add the where here - $sqlfilters = $search_component_params_hidden; - if ($sqlfilters) { - $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage, 0, 0, 1); + if ($search_component_params_hidden) { + $sql .= forgeSQLFromUniversalSearchCriteria($search_component_params_hidden, $errormessage, 0, 0, 1); - // Replace date values by $db->idate(dol_mktime(...)) + // Replace date values by $db->idate(dol_mktime(...)) @phan-suppress-next-line SqlInjection $sql = preg_replace_callback( "/(\w+)\.(\w+)\s*(=|!=|<>|<|>|<=|>=)\s*'(\d{4})-(\d{2})-(\d{2})'/", /** * @param array $matches * @return string SQL filter condition */ - function (array $matches): string { + static function (array $matches): string { global $db; + '@phan-var-force DoliDB $db'; $column = $matches[1] . '.' . $matches[2]; $operator = $matches[3]; $year = (int) $matches[4]; @@ -1075,65 +1077,65 @@ if (!empty($search_measures) && !empty($search_xaxis)) { ); } $sql .= " GROUP BY "; - foreach ($search_xaxis as $key => $val) { - if (preg_match('/\-year$/', $val)) { - $tmpval = preg_replace('/\-year$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y'), "; - } elseif (preg_match('/\-month$/', $val)) { - $tmpval = preg_replace('/\-month$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m'), "; - } elseif (preg_match('/\-day$/', $val)) { - $tmpval = preg_replace('/\-day$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d'), "; + foreach ($sanitized_search_xaxis as $key => $sanitized_val) { + if (preg_match('/\-year$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-year$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y'), "; + } elseif (preg_match('/\-month$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-month$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y-%m'), "; + } elseif (preg_match('/\-day$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-day$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y-%m-%d'), "; } else { - $sql .= $val.", "; + $sql .= $sanitized_val.", "; } } - if (!empty($search_groupby)) { - foreach ($search_groupby as $key => $val) { - if (preg_match('/\-year$/', $val)) { - $tmpval = preg_replace('/\-year$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y'), "; - } elseif (preg_match('/\-month$/', $val)) { - $tmpval = preg_replace('/\-month$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m'), "; - } elseif (preg_match('/\-day$/', $val)) { - $tmpval = preg_replace('/\-day$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d'), "; + if (!empty($sanitized_search_groupby)) { + foreach ($sanitized_search_groupby as $key => $sanitized_val) { + if (preg_match('/\-year$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-year$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y'), "; + } elseif (preg_match('/\-month$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-month$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y-%m'), "; + } elseif (preg_match('/\-day$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-day$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y-%m-%d'), "; } else { - $sql .= $val.', '; + $sql .= $sanitized_val.', '; } } } $sql = preg_replace('/,\s*$/', '', $sql); $sql .= ' ORDER BY '; - foreach ($search_xaxis as $key => $val) { - if (preg_match('/\-year$/', $val)) { - $tmpval = preg_replace('/\-year$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y'), "; - } elseif (preg_match('/\-month$/', $val)) { - $tmpval = preg_replace('/\-month$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m'), "; - } elseif (preg_match('/\-day$/', $val)) { - $tmpval = preg_replace('/\-day$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d'), "; + foreach ($sanitized_search_xaxis as $key => $sanitized_val) { + if (preg_match('/\-year$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-year$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y'), "; + } elseif (preg_match('/\-month$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-month$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y-%m'), "; + } elseif (preg_match('/\-day$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-day$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y-%m-%d'), "; } else { - $sql .= $val.', '; + $sql .= $sanitized_val.', '; } } - if (!empty($search_groupby)) { - foreach ($search_groupby as $key => $val) { - if (preg_match('/\-year$/', $val)) { - $tmpval = preg_replace('/\-year$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y'), "; - } elseif (preg_match('/\-month$/', $val)) { - $tmpval = preg_replace('/\-month$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m'), "; - } elseif (preg_match('/\-day$/', $val)) { - $tmpval = preg_replace('/\-day$/', '', $val); - $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d'), "; + if (!empty($sanitized_search_groupby)) { + foreach ($sanitized_search_groupby as $key => $sanitized_val) { + if (preg_match('/\-year$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-year$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y'), "; + } elseif (preg_match('/\-month$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-month$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y-%m'), "; + } elseif (preg_match('/\-day$/', $sanitized_val)) { + $sanitized_tmpval = preg_replace('/\-day$/', '', $sanitized_val); + $sql .= "DATE_FORMAT(".$sanitized_tmpval.", '%Y-%m-%d'), "; } else { - $sql .= $val.', '; + $sql .= $sanitized_val.', '; } } } @@ -1155,11 +1157,11 @@ if ($errormessage) { } $legend = array(); -foreach ($search_measures as $key => $val) { +foreach ($sanitized_search_measures as $key => $val) { $legend[] = $langs->trans($arrayofmesures[$val]['label']); } -$useagroupby = count($search_groupby); +$useagroupby = count($sanitized_search_groupby); //var_dump($useagroupby); //var_dump($arrayofvaluesforgroupby); @@ -1180,7 +1182,7 @@ if ($sql) { while ($obj = $db->fetch_object($resql)) { $ifetch++; if ($useagroupby) { - $xval = $search_xaxis[0]; + $xval = $sanitized_search_xaxis[0]; $fieldforxkey = 'x_0'; $xlabel = $obj->$fieldforxkey; $xvalwithoutprefix = preg_replace('/^[a-z]+\./', '', $xval); @@ -1208,9 +1210,9 @@ if ($sql) { * 'processing' => string 'processing' (length=10) * 'undeployed' => string 'undeployed' (length=10) */ - foreach ($search_measures as $key => $val) { + foreach ($sanitized_search_measures as $key => $val) { $gi = 0; - foreach ($search_groupby as $gkey => $gval) { + foreach ($sanitized_search_groupby as $gkey => $gval) { //var_dump('*** Fetch #'.$ifetch.' for labeltouse='.$labeltouse.' measure number '.$key.' and group g_'.$gi); //var_dump($arrayofvaluesforgroupby); foreach ($arrayofvaluesforgroupby['g_'.$gi] as $gvaluepossiblekey => $gvaluepossiblelabel) { @@ -1262,7 +1264,7 @@ if ($sql) { } } } else { // No group by - $xval = $search_xaxis[0]; + $xval = $sanitized_search_xaxis[0]; $fieldforxkey = 'x_0'; $xlabel = $obj->$fieldforxkey; $xvalwithoutprefix = preg_replace('/^[a-z]+\./', '', $xval); @@ -1274,7 +1276,7 @@ if ($sql) { $labeltouse = (($xlabel || $xlabel == '0') ? dol_trunc($xlabel, 20, 'middle') : ($xlabel === '' ? $langs->transnoentitiesnoconv("Empty") : $langs->transnoentitiesnoconv("NotDefined"))); $xarrayforallseries = array('label' => $labeltouse); - foreach ($search_measures as $key => $val) { + foreach ($sanitized_search_measures as $key => $val) { $fieldfory = 'y_'.$key; $xarrayforallseries[$fieldfory] = $obj->$fieldfory; } @@ -1314,7 +1316,7 @@ if ($mode == 'graph') { unset($data); $arrayoftypes = array(); - foreach ($search_measures as $key => $val) { + foreach ($sanitized_search_measures as $key => $val) { $arrayoftypes[] = $search_graph; } diff --git a/htdocs/core/datepicker.php b/htdocs/core/datepicker.php index 13af821dbc6..db7b77d46dc 100644 --- a/htdocs/core/datepicker.php +++ b/htdocs/core/datepicker.php @@ -3,7 +3,7 @@ * Copyright (C) 2005-2010 Laurent Destailleur * Copyright (C) 2005-2007 Regis Houssin * Copyright (C) 2014 Juanjo Menent - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Frédéric France * * This file is a modified version of datepicker.php from phpBSM to fix some @@ -144,7 +144,7 @@ if ($qualified) { print ''."\n"; /** - * Convert date to timestamp + * Convert date string to a Unix timestamp * * @param string $mysqldate Date YYYMMDD * @return integer Timestamp @@ -160,7 +160,7 @@ function xyzToUnixTimestamp($mysqldate) } /** - * Show box + * Displays the date picker box * * @param string $selectedDate Date YYYYMMDD * @param int $month Month @@ -274,7 +274,7 @@ function displayBox($selectedDate, $month, $year) exit; } - // Sur click dans calendrier, appelle fonction dpClickDay + // Upon a click in the calendar, call dpClickDay function echo "trans("FormatDateShortJavaInput")."')\""; diff --git a/htdocs/core/db/mysqli.class.php b/htdocs/core/db/mysqli.class.php index d89336a14e8..855f691a156 100644 --- a/htdocs/core/db/mysqli.class.php +++ b/htdocs/core/db/mysqli.class.php @@ -227,7 +227,7 @@ class DoliDBMysqli extends DoliDB * Select a database * * @param string $database Name of database - * @return boolean true if OK, false if KO + * @return boolean true if OK, false if failed */ public function select_db($database) { @@ -365,7 +365,7 @@ class DoliDBMysqli extends DoliDB } if (!preg_match("/^COMMIT/i", $query) && !preg_match("/^ROLLBACK/i", $query)) { - // Si requete utilisateur, on la sauvegarde ainsi que son resultset + // If user query, we save it along with its resultset if (!$ret) { $this->lastqueryerror = $query; $this->lasterror = $this->error(); @@ -412,7 +412,7 @@ class DoliDBMysqli extends DoliDB /** * Returns the current line (as an object) for the resultset cursor * - * @param mysqli_result $resultset Curseur de la requete voulue + * @param mysqli_result $resultset Cursor of the desired query * @return object|null Object result line or null if KO or end of cursor */ public function fetch_object($resultset) @@ -469,8 +469,8 @@ class DoliDBMysqli extends DoliDB /** * Return number of lines for result of a SELECT * - * @param mysqli_result $resultset Resulset of requests - * @return int Nb of lines + * @param mysqli_result $resultset Resultset of requests + * @return int Number of lines * @see affected_rows() */ public function num_rows($resultset) @@ -487,7 +487,7 @@ class DoliDBMysqli extends DoliDB /** * Return the number of lines in the result of a request INSERT, DELETE or UPDATE * - * @param mysqli_result $resultset Curseur de la requete voulue + * @param mysqli_result $resultset Cursor of the desired query * @return int Number of lines * @see num_rows() */ @@ -498,8 +498,7 @@ class DoliDBMysqli extends DoliDB if (!is_object($resultset)) { $resultset = $this->_results; } - // mysql necessite un link de base pour cette fonction contrairement - // a pqsql qui prend un resultset + // mysql require a db link, not like pqsql that takes a resultset return $this->db->affected_rows; } @@ -515,7 +514,7 @@ class DoliDBMysqli extends DoliDB if (!is_object($resultset)) { $resultset = $this->_results; } - // Si resultset en est un, on libere la memoire + // Si resultset is provided, free memory if (is_object($resultset)) { $resultset->free_result(); } @@ -585,6 +584,7 @@ class DoliDBMysqli extends DoliDB 1217 => 'DB_ERROR_CHILD_EXISTS', 1396 => 'DB_ERROR_USER_ALREADY_EXISTS', // When creating a user that already existing 1451 => 'DB_ERROR_CHILD_EXISTS', + 1824 => 'DB_ERROR_CANNOT_CREATE', // When creating a constraint on a parent table that does not exists 1826 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS' ); @@ -604,7 +604,7 @@ class DoliDBMysqli extends DoliDB public function error() { if (!$this->connected) { - // Si il y a eu echec de connection, $this->db n'est pas valide pour mysqli_error. + // When there is a connection failure, $this->db is invalid for to get mysqli_error. return 'Not connected. Check setup parameters in conf/conf.php file and your mysql client and server versions'; } else { return $this->db->error; @@ -837,12 +837,12 @@ class DoliDBMysqli extends DoliDB * Create a table into database * * @param string $table Name of table - * @param array|string,position?:int,notnull?:int,visible?:int<-2,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,2>,disabled?:int<0,1>,arrayofkeyval?:array,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>}> $fields Tableau associatif [nom champ][tableau des descriptions] - * @param string $primary_key Nom du champ qui sera la clef primaire - * @param string $type Type de la table - * @param ?array $unique_keys Tableau associatifs Nom de champs qui seront clef unique => valeur - * @param string[] $fulltext_keys Tableau des Nom de champs qui seront indexes en fulltext - * @param array $keys Tableau des champs cles noms => valeur + * @param array|string,position?:int,notnull?:int,visible?:int<-2,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,2>,disabled?:int<0,1>,arrayofkeyval?:array,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>}> $fields Associative table [field name][table of descriptions] + * @param string $primary_key Name of the field that will be the primary key + * @param string $type Table type + * @param ?array $unique_keys Associative table: key=field, value=field value + * @param string[] $fulltext_keys Table of fields that will be indexed as fulltext + * @param array $keys Table of fields names => value * @return int Return integer <0 if KO, >=0 if OK */ public function DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys = null, $fulltext_keys = null, $keys = null) @@ -955,7 +955,7 @@ class DoliDBMysqli extends DoliDB * Return a pointer of line with description of a table or field * * @param string $table Name of table - * @param string $field Optionnel : Name of field if we want description of field + * @param string $field Optional: Name of field if we want description of field * @return bool|mysqli_result Resultset x (x->Field, x->Type, ...) */ public function DDLDescTable($table, $field = "") @@ -1106,7 +1106,7 @@ class DoliDBMysqli extends DoliDB * Create a user and privileges to connect to database (even if database does not exists yet) * * @param string $dolibarr_main_db_host Ip server or '%' - * @param string $dolibarr_main_db_user Nom new user + * @param string $dolibarr_main_db_user Name of new user * @param string $dolibarr_main_db_pass Password for the new user * @param string $dolibarr_main_db_name Database name where user must be granted * @return int Return integer <0 if KO, >=0 if OK diff --git a/htdocs/core/db/pgsql.class.php b/htdocs/core/db/pgsql.class.php index 4a24bc0b8af..a0b8109dc94 100644 --- a/htdocs/core/db/pgsql.class.php +++ b/htdocs/core/db/pgsql.class.php @@ -27,7 +27,7 @@ /** * \file htdocs/core/db/pgsql.class.php - * \brief Fichier de la class permettant de gerer une base pgsql + * \brief Class file for managing a pgsql database */ require_once DOL_DOCUMENT_ROOT.'/core/db/DoliDB.class.php'; @@ -46,7 +46,7 @@ class DoliDBPgsql extends DoliDB //! Charset public $forcecharset = 'UTF8'; // Can't be static as it may be forced with a dynamic value - //! Collate used to force collate when creating database + //! Collation used to force collate when creating database public $forcecollate = ''; // Can't be static as it may be forced with a dynamic value //! Version min database @@ -71,13 +71,13 @@ class DoliDBPgsql extends DoliDB /** * Constructor. - * This create an opened connection to a database server and eventually to a database + * This creates an opened connection to a database server and eventually to a database * * @param string $type Type of database (mysql, pgsql...). Not used. * @param string $host Address of database server - * @param string $user Nom de l'utilisateur autorise + * @param string $user Authorized user name * @param string $pass Password - * @param string $name Nom de la database + * @param string $name Database name * @param int $port Port of database server */ public function __construct($type, $host, $user, $pass, $name = '', $port = 0) // @phpstan-ignore constructor.unusedParameter @@ -112,11 +112,11 @@ class DoliDBPgsql extends DoliDB $this->connected = false; $this->ok = false; $this->error = $langs->trans("ErrorWrongHostParameter"); - dol_syslog(get_class($this)."::DoliDBPgsql : Erreur Connect, wrong host parameters", LOG_ERR); + dol_syslog(get_class($this)."::DoliDBPgsql : Connection Error, wrong host parameters", LOG_ERR); return; } - // Essai connection serveur + // Try server connection //print "$host, $user, $pass, $name, $port"; $this->db = $this->connect($host, $user, $pass, $name, $port); @@ -128,10 +128,10 @@ class DoliDBPgsql extends DoliDB $this->connected = false; $this->ok = false; $this->error = 'Host, login or password incorrect'; - dol_syslog(get_class($this)."::DoliDBPgsql : Erreur Connect ".$this->error.'. Failed to connect to host='.$host.' port='.$port.' user='.$user, LOG_ERR); + dol_syslog(get_class($this)."::DoliDBPgsql : Connection Error ".$this->error.'. Failed to connect to host='.$host.' port='.$port.' user='.$user, LOG_ERR); } - // If server connection serveur ok and DB connection is requested, try to connect to DB + // If server connection ok and DB connection is requested, try to connect to DB if ($this->connected && $name) { if ($this->select_db($name)) { $this->database_selected = true; @@ -142,10 +142,10 @@ class DoliDBPgsql extends DoliDB $this->database_name = ''; $this->ok = false; $this->error = $this->error(); - dol_syslog(get_class($this)."::DoliDBPgsql : Erreur Select_db ".$this->error, LOG_ERR); + dol_syslog(get_class($this)."::DoliDBPgsql : Select_db Error ".$this->error, LOG_ERR); } } else { - // Pas de selection de base demandee, ok ou ko + // No database selection requested, ok or ko $this->database_selected = false; } } @@ -592,7 +592,7 @@ class DoliDBPgsql extends DoliDB /** * Returns the current line (as an object) for the resultset cursor * - * @param resource $resultset Curseur de la requete voulue + * @param resource $resultset Cursor of the db request * @return false|object Object result line or false if KO or end of cursor */ public function fetch_object($resultset) @@ -607,7 +607,7 @@ class DoliDBPgsql extends DoliDB // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps /** - * Return datas as an array + * Return data as an array * * @param resource $resultset Resultset of request * @return array|null|false Array or null if KO or end of cursor @@ -633,7 +633,7 @@ class DoliDBPgsql extends DoliDB public function fetch_row($resultset) { // phpcs:enable - // Si le resultset n'est pas fourni, on prend le dernier utilise sur cette connection + // If resultset not provided, we take the last used by connection if (!is_resource($resultset) && !is_object($resultset)) { $resultset = $this->_results; } @@ -668,10 +668,10 @@ class DoliDBPgsql extends DoliDB // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps /** - * Return the number of lines in the result of a request INSERT, DELETE or UPDATE + * Return the number of rows in the result of a request INSERT, DELETE or UPDATE * * @param resource $resultset Result set of request - * @return int Nb of lines + * @return int Number of lines * @see num_rows() */ public function affected_rows($resultset) @@ -681,8 +681,8 @@ class DoliDBPgsql extends DoliDB if (!is_resource($resultset) && !is_object($resultset)) { $resultset = $this->_results; } - // pgsql necessite un resultset pour cette fonction contrairement - // a mysql qui prend un link de base + // pgsql requires a resultset for this function contrary to + // mysql that requires a database link return pg_affected_rows($resultset); } @@ -699,7 +699,7 @@ class DoliDBPgsql extends DoliDB if (!is_resource($resultset) && !is_object($resultset)) { $resultset = $this->_results; } - // Si resultset en est un, on libere la memoire + // If it is a resource, we free the memory if (is_resource($resultset) || is_object($resultset)) { pg_free_result($resultset); } @@ -1071,13 +1071,13 @@ class DoliDBPgsql extends DoliDB /** * Create a table into database * - * @param string $table Nom de la table - * @param array|string,position?:int,notnull?:int,visible?:int<-2,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,2>,disabled?:int<0,1>,arrayofkeyval?:array,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>}> $fields Tableau associatif [nom champ][tableau des descriptions] - * @param string $primary_key Nom du champ qui sera la clef primaire + * @param string $table Table name + * @param array|string,position?:int,notnull?:int,visible?:int<-2,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,2>,disabled?:int<0,1>,arrayofkeyval?:array,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>}> $fields Associative table [field name][table of descrioptions] + * @param string $primary_key Name of the field that will be the primary key * @param string $type Type de la table - * @param ?array $unique_keys Tableau associatifs Nom de champs qui seront clef unique => valeur - * @param string[] $fulltext_keys Tableau des Nom de champs qui seront indexes en fulltext - * @param array $keys Tableau des champs cles noms => valeur + * @param ?array $unique_keys Associative table - name of fields that will be unique key => value + * @param string[] $fulltext_keys Tableau des Name of fields that will be indexed in fulltext + * @param array $keys Table of key fields - names => value * @return int Return integer <0 if KO, >=0 if OK */ public function DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys = null, $fulltext_keys = null, $keys = null) @@ -1445,7 +1445,7 @@ class DoliDBPgsql extends DoliDB if (file_exists('/usr/bin/pg_dump')) { $fullpathofdump = '/usr/bin/pg_dump'; } else { - // TODO L'utilisateur de la base doit etre un superadmin pour lancer cette commande + // TODO The database user must be a superadmin to run this command $resql = $this->query('SHOW data_directory'); if ($resql) { $liste = $this->fetch_array($resql); @@ -1458,9 +1458,9 @@ class DoliDBPgsql extends DoliDB } /** - * Return full path of restore program + * Return full path of restore program * - * @return string Full path of restore program + * @return string Full path of restore program */ public function getPathOfRestore() { diff --git a/htdocs/core/db/sqlite3.class.php b/htdocs/core/db/sqlite3.class.php index 1dd35471061..e119e67018e 100644 --- a/htdocs/core/db/sqlite3.class.php +++ b/htdocs/core/db/sqlite3.class.php @@ -887,7 +887,7 @@ class DoliDBSqlite3 extends DoliDB $sanitizedlike = "LIKE '".$this->escape($tmptable)."'"; } - $sanitizedtmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database); + $sanitizedtmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database); // @phan-suppress-current-line SqlInjection $sql = "SHOW TABLES FROM ".$sanitizedtmpdatabase." ".$sanitizedlike.";"; //print $sql; @@ -919,7 +919,7 @@ class DoliDBSqlite3 extends DoliDB $sanitizedlike = "LIKE '".$this->escape($tmptable)."'"; } - $sanitizedtmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database); + $sanitizedtmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database); // @phan-suppress-current-line SqlInjection $sql = "SHOW FULL TABLES FROM ".$sanitizedtmpdatabase." ".$sanitizedlike.";"; //print $sql; @@ -1223,7 +1223,7 @@ class DoliDBSqlite3 extends DoliDB // phpcs:enable $sql = "INSERT INTO user "; $sql .= "(Host,User,password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Index_Priv,Alter_priv,Lock_tables_priv)"; - $sql .= " VALUES ('".$this->escape($dolibarr_main_db_host)."','".$this->escape($dolibarr_main_db_user)."',password('".addslashes($dolibarr_main_db_pass)."')"; + $sql .= " VALUES ('".$this->escape($dolibarr_main_db_host)."','".$this->escape($dolibarr_main_db_user)."',password('".$this->escape($dolibarr_main_db_pass)."')"; $sql .= ",'Y','Y','Y','Y','Y','Y','Y','Y','Y')"; dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log @@ -1234,7 +1234,7 @@ class DoliDBSqlite3 extends DoliDB $sql = "INSERT INTO db "; $sql .= "(Host,Db,User,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Index_Priv,Alter_priv,Lock_tables_priv)"; - $sql .= " VALUES ('".$this->escape($dolibarr_main_db_host)."','".$this->escape($dolibarr_main_db_name)."','".addslashes($dolibarr_main_db_user)."'"; + $sql .= " VALUES ('".$this->escape($dolibarr_main_db_host)."','".$this->escape($dolibarr_main_db_name)."','".$this->escape($dolibarr_main_db_user)."'"; $sql .= ",'Y','Y','Y','Y','Y','Y','Y','Y','Y')"; dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); @@ -1371,7 +1371,7 @@ class DoliDBSqlite3 extends DoliDB // TODO prendre en compte le filtre foreach ($pragmas as $var) { - $sql = "PRAGMA $var"; + $sql = "PRAGMA $var"; // @phan-suppress-current-line SqlInjection $resql = $this->query($sql); if ($resql) { $obj = $this->fetch_row($resql); diff --git a/htdocs/core/filemanagerdol/connectors/php/config.inc.php b/htdocs/core/filemanagerdol/connectors/php/config.inc.php index 3ac8080d24c..18aac66a9ed 100644 --- a/htdocs/core/filemanagerdol/connectors/php/config.inc.php +++ b/htdocs/core/filemanagerdol/connectors/php/config.inc.php @@ -56,11 +56,13 @@ if (!getDolGlobalString('WYSIWYG_ALLOW_UPLOAD_MEDIA_FILES')) { } // If upload has been allowed with WYSIWYG_ALLOW_UPLOAD_MEDIA_FILES set, we check permissions. +// This connector browses and writes into the medias directory, so it must be +// restricted the same way as on the newer branches. Without this check any +// authenticated user (even with no module right) could reach the file manager. if (empty($user->admin) && !$user->hasRight('website', 'write')) { accessforbidden('Need to have website write permission to upload files in medias directory.'); } - // SECURITY: You must explicitly enable this "connector". (Set it to "true"). // WARNING: don't just set "$Config['Enabled'] = true ;", you must be sure that only // authenticated users can access this file or use some kind of session checking. diff --git a/htdocs/core/js/lib_head.js.php b/htdocs/core/js/lib_head.js.php index 90f093db0ad..93558ac2363 100644 --- a/htdocs/core/js/lib_head.js.php +++ b/htdocs/core/js/lib_head.js.php @@ -148,7 +148,10 @@ if ($thousand == 'Space') { // Javascript libraries for Dolibarr ERP CRM (https://www.dolibarr.org) -// To start/stop Block UI +/* + * To start/stop Block UI + */ + function dolBlockUI(message = 'Loading...', indicatorUrl = 'theme."/img/working.gif" ; ?>') { const block = document.getElementById('dol-block-ui'); if (block != null) { @@ -165,7 +168,10 @@ function dolUnblockUI() { } -// For jQuery date picker +/* + * For jQuery date picker + */ + var tradMonths = ; var tradMonthsShort = ; var tradDays = ; @@ -295,6 +301,7 @@ function dpChangeDay(dateFieldID, format) return 0; } + /* * ================================================================= * Function: formatDate(javascript object Date(), format) @@ -356,7 +363,6 @@ function formatDate(date,format) return result; } - /* * ================================================================= * Function: getDateFromFormat(date_string, format_string) @@ -499,11 +505,15 @@ function getIntegerInString(str,i,minlength,maxlength) */ function urlencode(s) { var news = s; + if (typeof news === "number") { + news = news.toString(); + } news = news.replace(/\+/gi,'%2B'); news = news.replace(/&/gi,'%26'); return news; } + /* * ================================================================= * Purpose: Clean string to get a HTML coded string. @@ -1379,9 +1389,11 @@ function generateFilterString(column, operator, context, fieldType) { } })(); + // Another solution, easier, to build a javascript rounding function function dolroundjs(number, decimals) { return +(Math.round(number + "e+" + decimals) + "e-" + decimals); } + /** * Function similar to PHP price() * @@ -1604,10 +1616,10 @@ jQuery(document).ready(function() { jQuery(document).on("click", function(event) { // search if click was outside drop down if (!$(event.target).closest('.butAction.dropdown-toggle').length) { - /* console.log("click close butAction - we click outside"); */ + /* console.log("click close butAction - we click outside"); // disabled because too verbose */ let parentholder = jQuery(".butAction.dropdown-toggle").closest(".dropdown.open"); if (parentholder){ - // Hide the menus. + // Hide the dropdown. parentholder.removeClass("open --up --left"); } } diff --git a/htdocs/core/js/listview.js b/htdocs/core/js/listview.js deleted file mode 100644 index 1e63fef36f3..00000000000 --- a/htdocs/core/js/listview.js +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (C) 2017 Laurent Destailleur -// -// 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/js/listview.js -// \brief File that include javascript functions for lists -// -var Listview_include = true; - -function Listview_OrderDown(idListe, column) { - var base_url = document.location.href; - - base_url = Listview_recup_form_param(idListe,base_url); - base_url = Listview_removeParam(base_url,'Listview['+encodeURIComponent(idListe)+'][orderBy]'); - - base_url = Listview_removeParam(base_url,'get-all-for-export'); - - document.location.href=Listview_modifyUrl(base_url,"Listview["+encodeURIComponent(idListe)+"][orderBy]["+encodeURIComponent(column)+"]","DESC"); -} -function Listview_OrderUp(idListe, column) { - - var base_url = document.location.href; - - base_url = Listview_recup_form_param(idListe,base_url); - base_url = Listview_removeParam(base_url,'Listview['+encodeURIComponent(idListe)+'][orderBy]'); - - base_url = Listview_removeParam(base_url,'get-all-for-export'); - - document.location.href=Listview_modifyUrl(base_url,"Listview["+encodeURIComponent(idListe)+"][orderBy]["+encodeURIComponent(column)+"]","ASC"); -} -function Listview_modifyUrl(strURL,paramName,paramNewValue){ - if (strURL.indexOf(paramName+'=')!=-1){ - - var strFirstPart=strURL.substring(0,strURL.indexOf(paramName+'=',0))+paramName+'='; - var strLastPart=""; - if (strURL.indexOf('&',strFirstPart.length-1)>0) - strLastPart=strURL.substring(strURL.indexOf('&',strFirstPart.length-1),strURL.length); - strURL=strFirstPart+paramNewValue+strLastPart; - } - else{ - if (strURL.search('=')!=-1) // permet de verifier s'il y a dejà des paramètres dans l'URL - strURL+='&'+paramName+'='+paramNewValue; - else - strURL+='?'+paramName+'='+paramNewValue; - } - - return strURL; -} -function Listview_removeParam(strURL, paramMask) { - var cpt=0; - var url = ''; - - while(strURL.indexOf(paramMask)!=-1 && cpt++ <50){ - var strFirstPart= strURL.substring(0,strURL.indexOf(paramMask)-1); - - var strLastPart=''; - if (strURL.indexOf('&',strFirstPart.length+1)>0) { - strLastPart = strURL.substring(strURL.indexOf('&',strFirstPart.length+1),strURL.length); - } - - url = strFirstPart+strLastPart; - - } - - if(url=='')url = strURL; - - return url; -} - -function Listview_recup_form_param(idListe,base_url) { - - $('#'+idListe+' tr.barre-recherche [listviewtbs],#'+idListe+' tr.barre-recherche-head input,#'+idListe+' tr.barre-recherche-head select,#'+idListe+' div.tabsAction input[listviewtbs]').each(function(i,item) { - if($(item).attr("name")) { - base_url = Listview_modifyUrl(base_url, $(item).attr("name") , $(item).val()); - } - - }); - - return base_url; -} - -function Listview_GoToPage(idListe,pageNumber){ - - var base_url = document.location.href; - - base_url = Listview_recup_form_param(idListe,base_url); - base_url =Listview_modifyUrl(base_url,"Listview["+encodeURIComponent(idListe)+"][page]",pageNumber); - - base_url = Listview_removeParam(base_url,'get-all-for-export'); - - document.location.href=base_url; -} -function Listview_submitSearch(obj) { - - $form = $(obj).closest('form'); - console.log($form); - if($form.length>0){ - $form.submit(); - } -} -function Listview_launch_downloadAs(mode,url,token,session_name) { - $('#listviewdAS_export_form').remove(); - - $form = $('
'); - $form.append(''); - $form.append(''); - $form.append(''); - - $('body').append($form); - - $('#listviewdAS_export_form').submit(); - -} - -function Listview_downloadAs(obj, mode,url,token,session_name) { - - $form = $(obj).closest('form'); - $div = $form.find('div.tabsAction'); - $div.append(''); - $div.append(''); - $div.append(''); - $div.append(''); - $div.append(''); - - Listview_submitSearch(obj); -} - -$(document).ready(function() { - $('tr.barre-recherche input').keypress(function(e) { - if(e.which == 13) { - - var id_list = $(this).closest('table').attr('id'); - - $('#'+id_list+' .list-search-link').click(); - - } - }); - - var $_GET = {}; - - document.location.search.replace(/\??(?:([^=]+)=([^&]*)&?)/g, function () { - function decode(s) { - return decodeURIComponent(s.split("+").join(" ")); - } - - $_GET[decode(arguments[1])] = decode(arguments[2]); - }); - - if(typeof $_GET["get-all-for-export"] != "undefined") { - Listview_launch_downloadAs($_GET['mode'],$_GET['url'],$_GET['token'],$_GET['session_name']); - } - -}); diff --git a/htdocs/core/lib/admin.lib.php b/htdocs/core/lib/admin.lib.php index 43af9790134..1dc3846227b 100644 --- a/htdocs/core/lib/admin.lib.php +++ b/htdocs/core/lib/admin.lib.php @@ -256,7 +256,7 @@ function run_sql($sqlfile, $silent = 1, $entity = 0, $usesavepoint = 1, $handler // If string contains the end of request string (';'), we save it into $arraysql. // Found new request if ($buffer) { - $arraysql[$i] = $buffer; + $arraysql[$i] = $buffer; // @phan-suppress-current-line SqlInjection } $i++; $buffer = ''; @@ -264,7 +264,7 @@ function run_sql($sqlfile, $silent = 1, $entity = 0, $usesavepoint = 1, $handler } if ($buffer) { - $arraysql[$i] = $buffer; + $arraysql[$i] = $buffer; // @phan-suppress-current-line SqlInjection } fclose($fp); } else { @@ -302,7 +302,7 @@ function run_sql($sqlfile, $silent = 1, $entity = 0, $usesavepoint = 1, $handler // Replace __+MAX_llx_table__ with +999 $from = '__+MAX_'.$table.'__'; $to = '+'.$listofmaxrowid[$table]; - $newsql = str_replace($from, $to, $newsql); + $newsql = str_replace($from, $to, $newsql); // @phan-suppress-current-line SqlInjection dol_syslog('Admin.lib::run_sql New Request '.($i + 1).' (replacing '.$from.' to '.$to.')', LOG_DEBUG); $arraysql[$i] = $newsql; @@ -327,7 +327,7 @@ function run_sql($sqlfile, $silent = 1, $entity = 0, $usesavepoint = 1, $handler // Loop on each request to execute request $cursorinsert = 0; $listofinsertedrowid = array(); - $keyforsql = md5($sqlfile); + $keyforsqlfile = md5($sqlfile); foreach ($arraysql as $i => $sql) { if ($sql) { // Test if the SQL is allowed SQL @@ -414,11 +414,11 @@ function run_sql($sqlfile, $silent = 1, $entity = 0, $usesavepoint = 1, $handler $sql = preg_replace('/__DATABASE__/i', $db->escape($database), $sql); } - $newsql = preg_replace('/__ENTITY__/i', (!empty($entity) ? $entity : (string) $conf->entity), $sql); + $newsql = preg_replace('/__ENTITY__/i', (!empty($entity) ? ((int) $entity) : (string) ((int) $conf->entity)), $sql); // Add log of request if (!$silent) { - print ''.$langs->trans("Request").' '.($i + 1)." sql='".dol_htmlentities($newsql, ENT_NOQUOTES)."'\n"; + print ''.$langs->trans("Request").' '.($i + 1)." sql='".dol_htmlentities($newsql, ENT_NOQUOTES)."'\n"; } dol_syslog('Admin.lib::run_sql Request '.($i + 1), LOG_DEBUG); $sqlmodified = 0; @@ -428,9 +428,9 @@ function run_sql($sqlfile, $silent = 1, $entity = 0, $usesavepoint = 1, $handler $num = count($reg[0]); for ($j = 0; $j < $num; $j++) { - $from = $reg[0][$j]; + $from = $reg[0][$j]; // @phan-suppress-current-line SqlInjection $to = $db->encrypt($reg[1][$j]); - $newsql = str_replace($from, $to, $newsql); + $newsql = str_replace($from, $to, $newsql); // @phan-suppress-current-line SqlInjection } $sqlmodified++; } @@ -440,9 +440,9 @@ function run_sql($sqlfile, $silent = 1, $entity = 0, $usesavepoint = 1, $handler $num = count($reg[0]); for ($j = 0; $j < $num; $j++) { - $from = $reg[0][$j]; + $from = $reg[0][$j]; // @phan-suppress-current-line SqlInjection $to = $db->decrypt($reg[1][$j]); - $newsql = str_replace($from, $to, $newsql); + $newsql = str_replace($from, $to, $newsql); // @phan-suppress-current-line SqlInjection } $sqlmodified++; } @@ -460,9 +460,9 @@ function run_sql($sqlfile, $silent = 1, $entity = 0, $usesavepoint = 1, $handler break; } - $from = '__'.$cursor.'__'; + $from = '__'.$cursor.'__'; // @phan-suppress-current-line SqlInjection $to = $listofinsertedrowid[$cursor]; - $newsql = str_replace($from, $to, $newsql); + $newsql = str_replace($from, $to, $newsql); // @phan-suppress-current-line SqlInjection $sqlmodified++; } @@ -537,19 +537,19 @@ function run_sql($sqlfile, $silent = 1, $entity = 0, $usesavepoint = 1, $handler //if (!empty($conf->use_javascript_ajax)) { // use_javascript_ajax is not defined print ''; if (count($arraysql)) { - print ' - '.$langs->trans("ShowHideDetails").''; + print ' - '.$langs->trans("ShowHideDetails").''; } else { print ' - '.$langs->trans("ScriptIsEmpty").''; } @@ -1494,7 +1494,7 @@ function complete_dictionary_with_modules(&$taborder, &$tabname, &$tablib, &$tab if (!empty($objMod->dictionaries)) { //var_dump($objMod->dictionaries['tabname']); - $nbtabname = $nbtablib = $nbtabsql = $nbtabsqlsort = $nbtabfield = $nbtabfieldvalue = $nbtabfieldinsert = $nbtabrowid = $nbtabcond = $nbtabfieldcheck = $nbtabhelp = 0; + $nbtabname = $nbtablib = $nbtabsqlsort = $nbtabfield = $nbtabfieldvalue = $nbtabfieldinsert = $nbtabrowid = $nbtabcond = $nbtabfieldcheck = $nbtabhelp = $nbtabsql = 0; $tabnamerelwithkey = array(); foreach ($objMod->dictionaries['tabname'] as $key => $val) { $tmptablename = preg_replace('/'.MAIN_DB_PREFIX.'/', '', $val); @@ -1513,14 +1513,14 @@ function complete_dictionary_with_modules(&$taborder, &$tabname, &$tablib, &$tab foreach ($objMod->dictionaries['tabsql'] as $key => $val) { $tmptablename = preg_replace('/'.MAIN_DB_PREFIX.'/', '', $tabnamerelwithkey[$key]); $nbtabsql++; - $tabsql[] = $val; - $tabcomplete[$tmptablename]['sql'] = $val; + $tabsql[] = $val; // @phan-suppress-current-line SqlInjection + $tabcomplete[$tmptablename]['sql'] = $val; // @phan-suppress-current-line SqlInjection } foreach ($objMod->dictionaries['tabsqlsort'] as $key => $val) { $tmptablename = preg_replace('/'.MAIN_DB_PREFIX.'/', '', $tabnamerelwithkey[$key]); $nbtabsqlsort++; - $tabsqlsort[] = $val; - $tabcomplete[$tmptablename]['sqlsort'] = $val; + $tabsqlsort[] = $val; // @phan-suppress-current-line SqlInjection + $tabcomplete[$tmptablename]['sqlsort'] = $val; // @phan-suppress-current-line SqlInjection } foreach ($objMod->dictionaries['tabfield'] as $key => $val) { $tmptablename = preg_replace('/'.MAIN_DB_PREFIX.'/', '', $tabnamerelwithkey[$key]); @@ -1537,14 +1537,14 @@ function complete_dictionary_with_modules(&$taborder, &$tabname, &$tablib, &$tab foreach ($objMod->dictionaries['tabfieldinsert'] as $key => $val) { $tmptablename = preg_replace('/'.MAIN_DB_PREFIX.'/', '', $tabnamerelwithkey[$key]); $nbtabfieldinsert++; - $tabfieldinsert[] = $val; - $tabcomplete[$tmptablename]['fieldinsert'] = $val; + $tabfieldinsert[] = $val; // @phan-suppress-current-line SqlInjection + $tabcomplete[$tmptablename]['fieldinsert'] = $val; // @phan-suppress-current-line SqlInjection } foreach ($objMod->dictionaries['tabrowid'] as $key => $val) { $tmptablename = preg_replace('/'.MAIN_DB_PREFIX.'/', '', $tabnamerelwithkey[$key]); $nbtabrowid++; - $tabrowid[] = $val; - $tabcomplete[$tmptablename]['rowid'] = $val; + $tabrowid[] = (int) $val; + $tabcomplete[$tmptablename]['rowid'] = (int) $val; } foreach ($objMod->dictionaries['tabcond'] as $key => $val) { $tmptablename = preg_replace('/'.MAIN_DB_PREFIX.'/', '', $tabnamerelwithkey[$key]); @@ -1821,7 +1821,7 @@ function form_constantes($tableau, $strictw3c = 2, $helptext = '', $text = '') $sql .= ", note"; $sql .= " FROM ".MAIN_DB_PREFIX."const"; $sql .= " WHERE ".$db->decrypt('name')." = '".$db->escape($const)."'"; - $sql .= " AND entity IN (0, ".$conf->entity.")"; + $sql .= " AND entity IN (0, ".((int) $conf->entity).")"; $sql .= " ORDER BY name ASC, entity DESC"; $result = $db->query($sql); diff --git a/htdocs/core/lib/ajax.lib.php b/htdocs/core/lib/ajax.lib.php index 851bab3f36c..3faefd3805f 100644 --- a/htdocs/core/lib/ajax.lib.php +++ b/htdocs/core/lib/ajax.lib.php @@ -521,11 +521,13 @@ function ajax_combobox($htmlname, $events = array(), $minLengthToAutocomplete = if (getDolGlobalString('MAIN_ENABLE_ACCENT_INSENSITIVE_SEARCH')) { // lowercase + remove accents $msg .= ' var term = params.term.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase() - var text = (data.text || "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase() || "";'; + var searchText = data.element ? $(data.element).attr("data-search") : undefined; + var text = (searchText !== undefined ? searchText : (data.text || "")).normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase() || "";'; } else { // lowercase only (accent kept) $msg .= ' var term = params.term.toLowerCase(); - var text = (data.text || "").toLowerCase();'; + var searchText = data.element ? $(data.element).attr("data-search") : undefined; + var text = (searchText !== undefined ? searchText : (data.text || "")).toLowerCase();'; } $msg .= ' var keywords = term.split(" "); diff --git a/htdocs/core/lib/bank.lib.php b/htdocs/core/lib/bank.lib.php index 8fcff263004..fc54af1e11d 100644 --- a/htdocs/core/lib/bank.lib.php +++ b/htdocs/core/lib/bank.lib.php @@ -552,7 +552,7 @@ function checkES($IentOfi, $InumCta) $sum = 0; - for ($i = 0; $i < 11; $i++) { + for ($i = 0; $i < 10; $i++) { $sum += $values[$i] * (int) substr($InumCta, $i, 1); //int to cast result of substr to a number } diff --git a/htdocs/core/lib/company.lib.php b/htdocs/core/lib/company.lib.php index 79659d031ef..e6996b6c135 100644 --- a/htdocs/core/lib/company.lib.php +++ b/htdocs/core/lib/company.lib.php @@ -2133,7 +2133,7 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr && $filterobj->table_element && $filterobj->element ) { // Generic case (if there is a $filterobj and a field rowid and (ref or label) exists. - $sql .= " AND a.fk_element = o.rowid AND a.elementtype = '" . $db->escape($filterobj->element) . ($module ? "@" . $module : "") . "'"; + $sql .= " AND a.fk_element = o.rowid AND a.elementtype = '" . $db->escape($filterobj->element) . ($module ? "@" . $db->escape($module) : "") . "'"; if ($filterobj->id) { $sql .= " AND a.fk_element = " . ((int) $filterobj->id); } @@ -2225,7 +2225,7 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr $limit = $MAXWITHOUTPAGINATION; // Complete request and execute it with limit - $sql .= $db->order($sortfield_new, $sortorder); + $sql .= $db->order($sortfield_new, $sortorder); // @phan-suppress-current-line SqlInjection if ($limit) { // @phpstan-ignore-line $sql .= $db->plimit($limit + 1, $offset); } diff --git a/htdocs/core/lib/date.lib.php b/htdocs/core/lib/date.lib.php index 9bc6a1ad301..e8319cb3fad 100644 --- a/htdocs/core/lib/date.lib.php +++ b/htdocs/core/lib/date.lib.php @@ -391,23 +391,24 @@ function dolSqlDateFilter($datefield, $day_date, $month_date, $year_date, $exclu $day_date = intval($day_date); $month_date = intval($month_date); $year_date = intval($year_date); + $sql_datefield = $db->sanitize($datefield); if ($month_date > 0) { if ($month_date > 12) { // protection for bad value of month return " AND 1 = 2"; } if ($year_date > 0 && empty($day_date)) { - $sqldate .= ($excludefirstand ? "" : " AND ").$datefield." BETWEEN '".$db->idate(dol_get_first_day($year_date, $month_date, $gm)); + $sqldate .= ($excludefirstand ? "" : " AND ").$sql_datefield." BETWEEN '".$db->idate(dol_get_first_day($year_date, $month_date, $gm)); $sqldate .= "' AND '".$db->idate(dol_get_last_day($year_date, $month_date, $gm))."'"; } elseif ($year_date > 0 && !empty($day_date)) { - $sqldate .= ($excludefirstand ? "" : " AND ").$datefield." BETWEEN '".$db->idate(dol_mktime(0, 0, 0, $month_date, $day_date, $year_date, $gm)); + $sqldate .= ($excludefirstand ? "" : " AND ").$sql_datefield." BETWEEN '".$db->idate(dol_mktime(0, 0, 0, $month_date, $day_date, $year_date, $gm)); $sqldate .= "' AND '".$db->idate(dol_mktime(23, 59, 59, $month_date, $day_date, $year_date, $gm))."'"; } else { // This case is not reliable on TZ, but we should not need it. - $sqldate .= ($excludefirstand ? "" : " AND ")." date_format( ".$datefield.", '%c') = '".$db->escape((string) $month_date)."'"; + $sqldate .= ($excludefirstand ? "" : " AND ")." date_format( ".$sql_datefield.", '%c') = '".$db->escape((string) $month_date)."'"; } } elseif ($year_date > 0) { - $sqldate .= ($excludefirstand ? "" : " AND ").$datefield." BETWEEN '".$db->idate(dol_get_first_day($year_date, 1, $gm)); + $sqldate .= ($excludefirstand ? "" : " AND ").$sql_datefield." BETWEEN '".$db->idate(dol_get_first_day($year_date, 1, $gm)); $sqldate .= "' AND '".$db->idate(dol_get_last_day($year_date, 12, $gm))."'"; } return $sqldate; @@ -806,7 +807,7 @@ function num_public_holiday($timestampStart, $timestampEnd, $countryCodeOrId = ' $tmpArrayOfPublicHolidays = array(); $sql = "SELECT id, code, entity, fk_country, dayrule, year, month, day, active"; $sql .= " FROM ".MAIN_DB_PREFIX."c_hrm_public_holiday"; - $sql .= " WHERE active = 1 and fk_country IN (0".($country_id > 0 ? ", ".$country_id : 0).")"; + $sql .= " WHERE active = 1 and fk_country IN (0".($country_id > 0 ? ", ".((int) $country_id) : 0).")"; $sql .= " AND entity IN (0," .getEntity('holiday') .")"; $resql = $db->query($sql); @@ -1006,8 +1007,11 @@ function num_public_holiday($timestampStart, $timestampEnd, $countryCodeOrId = ' } // Increase number of days (on go up into loop) + // Advance by exactly one GMT day. We can use += instead of dol_time_plus_duree() here because + // inputs of this function are GMT dates (checked above), so a day + // is always exactly 86400 seconds. //var_dump("before ".$jour.' '.$mois.' '.$annee.' '.$timestampStart); - $timestampStart = dol_time_plus_duree($timestampStart, 1, 'd'); + $timestampStart += 86400; //var_dump("after ".$jour.' '.$mois.' '.$annee.' '.$timestampStart); $i++; @@ -1069,7 +1073,7 @@ function listPublicHoliday($timestampStart, $timestampEnd, $countryCodeOrId = '' $tmpArrayOfPublicHolidays = array(); $sql = "SELECT id, code, entity, fk_country, dayrule, year, month, day, active"; $sql .= " FROM " . MAIN_DB_PREFIX . "c_hrm_public_holiday"; - $sql .= " WHERE active = 1 and fk_country IN (0" . ($country_id > 0 ? ", " . $country_id : 0) . ")"; + $sql .= " WHERE active = 1 and fk_country IN (0" . ($country_id > 0 ? ", " . ((int) $country_id) : 0) . ")"; $sql .= " AND entity IN (0," . getEntity('holiday') . ")"; $resql = $db->query($sql); @@ -1477,7 +1481,7 @@ function getWeekNumbersOfMonth($month, $year) { $nb_days = cal_days_in_month(CAL_GREGORIAN, $month, $year); $TWeek = array(); - for ($day = 1; $day < $nb_days; $day++) { + for ($day = 1; $day <= $nb_days; $day++) { $week_number = getWeekNumber($day, $month, $year); $TWeek[$week_number] = $week_number; } @@ -1494,11 +1498,20 @@ function getWeekNumbersOfMonth($month, $year) function getFirstDayOfEachWeek($TWeek, $year) { $TFirstDayOfWeek = array(); + // When a month overlaps 2 years, the weeks 52/53 of previous year and the week 01 of next year are present together in $TWeek. + // If $TWeek starts with week 52 or 53 (a January), these first weeks belong to previous year. + // If $TWeek ends with week 01 (a December), this last week belongs to next year. + $startwithweeksofpreviousyear = ((int) reset($TWeek) >= 52 && in_array('01', $TWeek)); foreach ($TWeek as $weekNb) { - if (in_array('01', $TWeek) && in_array('52', $TWeek) && $weekNb == '01') { - $year++; // If we have both week 1 and week 52, it means we are changing year + $yeartouse = $year; + if ($startwithweeksofpreviousyear) { + if ((int) $weekNb >= 52) { + $yeartouse = $year - 1; + } + } elseif ($weekNb == '01' && in_array('52', $TWeek)) { + $yeartouse = $year + 1; } - $TFirstDayOfWeek[$weekNb] = date('d', strtotime($year.'W'.$weekNb)); + $TFirstDayOfWeek[$weekNb] = date('d', strtotime($yeartouse.'W'.$weekNb)); } return $TFirstDayOfWeek; } @@ -1513,8 +1526,18 @@ function getFirstDayOfEachWeek($TWeek, $year) function getLastDayOfEachWeek($TWeek, $year) { $TLastDayOfWeek = array(); + // Same rule as in getFirstDayOfEachWeek() to find the year each week belongs to + $startwithweeksofpreviousyear = ((int) reset($TWeek) >= 52 && in_array('01', $TWeek)); foreach ($TWeek as $weekNb) { - $TLastDayOfWeek[$weekNb] = date('d', strtotime($year.'W'.$weekNb.'+6 days')); + $yeartouse = $year; + if ($startwithweeksofpreviousyear) { + if ((int) $weekNb >= 52) { + $yeartouse = $year - 1; + } + } elseif ($weekNb == '01' && in_array('52', $TWeek)) { + $yeartouse = $year + 1; + } + $TLastDayOfWeek[$weekNb] = date('d', strtotime($yeartouse.'W'.$weekNb.'+6 days')); } return $TLastDayOfWeek; } diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index ca8984aabc5..c210fff440f 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -3012,7 +3012,7 @@ function dol_most_recent_file($dir, $regexfilter = '', $excludefilter = array('( * * @param string $modulepart Module of document ('module', 'module_user_temp', 'module_user' or 'module_temp'). Example: 'medias', 'invoice', 'logs', 'tax-vat', ... * @param string $original_file Relative path with filename, relative to modulepart. - * @param int $entity Restrict onto entity (0=no restriction) + * @param int $entity Restrict objects stored into entity (0=no restriction). Note that permissions tested are the one the user is logged in, so permissions for $conf->entity that may be different than $entity. * @param User|null $fuser User object (forced) * @param string $refname Ref of object to check permission for external users (autodetect if not provided by taking the dirname of $original_file) or for hierarchy * @param string $mode Check permission for 'read' or 'write' @@ -3038,6 +3038,9 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, } else { $entity = 0; } + } else { + // TODO Test that the user in session of conf->entity can see objects of the target $entity + // ... } // Fix modulepart for backward compatibility if ($modulepart == 'facture') { @@ -3195,12 +3198,12 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, $accessallowed = 1; } $original_file = $conf->order->multidir_output[$entity].'/'.$original_file; - } elseif (($modulepart == 'apercufichinter' || $modulepart == 'apercuficheinter') && !empty($conf->ficheinter->dir_output)) { + } elseif (($modulepart == 'apercufichinter' || $modulepart == 'apercuficheinter') && !empty($conf->ficheinter->multidir_output[$entity])) { // Wrapping for preview of intervention if ($fuser->hasRight('ficheinter', $lire)) { $accessallowed = 1; } - $original_file = $conf->ficheinter->dir_output.'/'.$original_file; + $original_file = $conf->ficheinter->multidir_output[$entity].'/'.$original_file; } elseif (($modulepart == 'apercucontract') && !empty($conf->contract->multidir_output[$entity])) { // Wrapping for preview of contracts if ($fuser->hasRight('contrat', $lire)) { @@ -3394,16 +3397,17 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, $accessallowed = 1; } $original_file = $conf->societe->multidir_output[$entity].'/'.$original_file; - $sqlprotectagainstexternals = "SELECT rowid as fk_soc FROM ".MAIN_DB_PREFIX."societe WHERE rowid='".$db->escape($refname)."' AND entity IN (".getEntity('societe').")"; + $sqlprotectagainstexternals = "SELECT rowid as fk_soc FROM ".MAIN_DB_PREFIX."societe WHERE rowid = ".((int) $refname)." AND entity IN (".getEntity('societe').")"; } elseif (($modulepart == 'contact' || $modulepart == 'socpeople') && !empty($conf->societe->multidir_output[$entity])) { // Wrapping for contact if (empty($entity) || empty($conf->societe->multidir_output[$entity])) { return array('accessallowed' => 0, 'error' => 'Value entity must be provided'); } - if ($fuser->hasRight('societe', $lire)) { + if ($fuser->hasRight('societe', 'contact', $lire)) { $accessallowed = 1; } $original_file = $conf->societe->multidir_output[$entity].'/contact/'.$original_file; + $sqlprotectagainstexternals = "SELECT fk_soc FROM ".MAIN_DB_PREFIX."socpepople WHERE rowid = ".((int) $refname)." AND entity IN (".getEntity('contact').")"; } elseif (($modulepart == 'facture' || $modulepart == 'invoice') && !empty($conf->invoice->multidir_output[$entity])) { // Wrapping for invoices if ($fuser->hasRight('facture', $lire) || preg_match('/^specimen/i', $original_file)) { @@ -3472,20 +3476,20 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, $accessallowed = 1; } $original_file = $conf->stock->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file; - } elseif (($modulepart == 'fichinter' || $modulepart == 'ficheinter') && !empty($conf->ficheinter->dir_output)) { + } elseif (($modulepart == 'fichinter' || $modulepart == 'ficheinter') && !empty($conf->ficheinter->multidir_output[$entity])) { // Wrapping for interventions if ($fuser->hasRight('ficheinter', $lire) || preg_match('/^specimen/i', $original_file)) { $accessallowed = 1; } - $original_file = $conf->ficheinter->dir_output.'/'.$original_file; - $sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."fichinter WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity; + $original_file = $conf->ficheinter->multidir_output[$entity].'/'.$original_file; + $sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."fichinter WHERE ref='".$db->escape($refname)."' AND entity=".((int) $conf->entity); } elseif ($modulepart == 'deplacement' && !empty($conf->deplacement->dir_output)) { // Wrapping pour les deplacements et notes de frais if ($fuser->hasRight('deplacement', $lire) || preg_match('/^specimen/i', $original_file)) { $accessallowed = 1; } $original_file = $conf->deplacement->dir_output.'/'.$original_file; - //$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."fichinter WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity; + //$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."fichinter WHERE ref='".$db->escape($refname)."' AND entity=".((int) $conf->entity); } elseif (($modulepart == 'propal' || $modulepart == 'propale') && isset($conf->propal->multidir_output[$entity])) { // Wrapping pour les propales if ($fuser->hasRight('propal', $lire) || preg_match('/^specimen/i', $original_file)) { @@ -3533,14 +3537,14 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, $accessallowed = 1; } $original_file = $conf->fournisseur->commande->dir_output.'/'.$original_file; - $sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."commande_fournisseur WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity; + $sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."commande_fournisseur WHERE ref='".$db->escape($refname)."' AND entity=".((int) $conf->entity); } elseif (($modulepart == 'facture_fournisseur' || $modulepart == 'invoice_supplier') && !empty($conf->fournisseur->facture->dir_output)) { // Wrapping for supplier invoices if ($fuser->hasRight('fournisseur', 'facture', $lire) || preg_match('/^specimen/i', $original_file)) { $accessallowed = 1; } $original_file = $conf->fournisseur->facture->dir_output.'/'.$original_file; - $sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."facture_fourn WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity; + $sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."facture_fourn WHERE ref='".$db->escape($refname)."' AND entity=".((int) $conf->entity); } elseif ($modulepart == 'supplier_payment') { // Wrapping for supplier payments if ($fuser->hasRight('fournisseur', 'facture', $lire) || preg_match('/^specimen/i', $original_file)) { @@ -3548,7 +3552,10 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, } $original_file = preg_replace("/payment\//", "", $original_file); // Because the $conf->fournisseur->payment->dir_output already contains the "payment/" $original_file = $conf->fournisseur->payment->dir_output.'/'.$original_file; - $sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."paiementfournisseur WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity; + $sqlprotectagainstexternals = "SELECT f.fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."paiementfourn as p"; + $sqlprotectagainstexternals .= " INNER JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pf ON pf.fk_paiementfourn = p.rowid"; + $sqlprotectagainstexternals .= " INNER JOIN ".MAIN_DB_PREFIX."facture_fourn as f ON pf.fk_facturefourn = p.rowid"; + $sqlprotectagainstexternals .= " WHERE p.ref = '".$db->escape($refname)."' AND p.entity=".((int) $conf->entity); } elseif ($modulepart == 'payment') { // Wrapping for report of payments if ($fuser->hasRight('facture', $lire) || preg_match('/^specimen/i', $original_file)) { @@ -3565,9 +3572,14 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, } else { $original_file = $conf->invoice->dir_output.'/payments/'.$original_file; } + /* $sqlprotectagainstexternals = "SELECT f.fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."paiement as p"; + $sqlprotectagainstexternals .= " INNER JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON pf.fk_paiement = p.rowid"; + $sqlprotectagainstexternals .= " INNER JOIN ".MAIN_DB_PREFIX."facture as f ON pf.fk_facture = p.rowid"; + $sqlprotectagainstexternals .= " WHERE p.ref = '".$db->escape($refname)."' AND p.entity=".((int) $conf->entity); + var_dump($sqlprotectagainstexternals);exit;*/ } elseif ($modulepart == 'export_compta' && !empty($conf->accounting->dir_output)) { // Wrapping for accounting exports - if ($fuser->hasRight('accounting', 'bind', 'write') || preg_match('/^specimen/i', $original_file)) { + if ($fuser->hasRight('accounting', 'bind', 'write') || $fuser->hasRight('accounting', 'mouvements', 'export') || preg_match('/^specimen/i', $original_file)) { $accessallowed = 1; } $original_file = $conf->accounting->dir_output.'/'.$original_file; diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 7c7bd8a48da..944f5077f8b 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -192,6 +192,9 @@ function getMultidirOutput($object, $module = '', $forobject = 0, $mode = 'outpu $subdirectory = '/commande'; break; case 'expedition': + case 'shipment': + case 'shipping': + $module = 'expedition'; $subdirectory = '/sending'; break; case 'company': @@ -201,6 +204,14 @@ function getMultidirOutput($object, $module = '', $forobject = 0, $mode = 'outpu case 'produit': $module = 'product'; break; + case 'project_task': + $module = 'projet'; + + // Fetch the project to build the correct path + $object->fetchProject(); + + $subdirectory = '/'.$object->project->ref; + break; case 'action': case 'actioncomm': case 'event': @@ -220,7 +231,7 @@ function getMultidirOutput($object, $module = '', $forobject = 0, $mode = 'outpu if ($forobject && $object->id > 0) { $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object); } - return $s; + return dol_sanitizePathName($s); } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_output')) { $s = ''; if ($mode != 'outputrel') { @@ -229,15 +240,15 @@ function getMultidirOutput($object, $module = '', $forobject = 0, $mode = 'outpu if ($forobject && $object->id > 0) { $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object); } - return $s; + return dol_sanitizePathName($s); } else { return 'error-diroutput-not-defined-for-this-object=' . $module; } } elseif ($mode == 'temp') { if (isset($conf->$module) && property_exists($conf->$module, 'multidir_temp')) { - return $conf->$module->multidir_temp[(empty($object->entity) ? $conf->entity : $object->entity)]; + return dol_sanitizePathName($conf->$module->multidir_temp[(empty($object->entity) ? $conf->entity : $object->entity)]); } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_temp')) { - return $conf->$module->dir_temp; + return dol_sanitizePathName($conf->$module->dir_temp); } else { return 'error-dirtemp-not-defined-for-this-object=' . $module; } @@ -449,10 +460,9 @@ function getDolUserInt($key, $default = 0, $tmpuser = null) /** * This mapping defines the conversion to the current internal * names from the alternative allowed names (including effectively deprecated - * and future new names (not yet used as internal names). + * and future new names not yet used as internal names). * * This allows to map any temporary or future name to the effective internal name. - * * The value is typically the name of module's root directory. */ define( @@ -948,11 +958,12 @@ function GETPOSTISARRAY($paramname, $method = 0) * * @param string $paramname Name of the $_GET or $_POST parameter * @param int<0,3> $method Type of method (0 = $_GET then $_POST, 1 = only $_GET, 2 = only $_POST, 3 = $_POST then $_GET) + * @param int $nodefault Force disable of default values set from the admin setup page * @return int Value converted into integer */ -function GETPOSTINT($paramname, $method = 0) +function GETPOSTINT($paramname, $method = 0, $nodefault = 0) { - return (int) GETPOST($paramname, 'int', $method, null, null, 0); + return (int) GETPOST($paramname, 'int', $method, null, null, 0, $nodefault); } /** @@ -1073,6 +1084,7 @@ function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto', $saverestore = '') * @param ?int $filter Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for details) * @param mixed $options Options to pass to filter_var when $check is set to 'custom' * @param int $noreplace Force disable of replacement of __xxx__ strings. + * @param int $nodefault Force disable of default values set from the admin setup page * @return string|array Value found (string or array), or '' if check fails * @phpstan-return ( * $check is 'int' ? numeric-string|'' : @@ -1081,7 +1093,7 @@ function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto', $saverestore = '') * $check is 'alpha' | 'aZ' | 'aZ09' | 'aZ09arobase' | 'aZ09comma' | 'password' | 'email' | 'url' | 'alphanohtml' |'nohtml' | 'restricthtml' | 'alphawithlgt' | 'intcomma' | 'restricthtmlallowclass' | 'restricthtmlallowunvalid' | 'restricthtmlallowiframe' | 'restricthtmlallowlinkscript' ? string : string|array * ) */ -function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0) +function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0, $nodefault = 0) { global $langs, $mysoc, $user, $conf; @@ -1188,7 +1200,7 @@ function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null } } } - } elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) { + } elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname]) && empty($nodefault)) { // Management of default search_filters and sort order if (!empty($user->default_values)) { // $user->default_values defined from menu 'Setup - Default values' @@ -1290,7 +1302,7 @@ function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null } // Check type of variable and make sanitization according to this - if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:intcomma' + if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:int' $tmpcheck = 'alphanohtml'; if ($out === null || $out === '') { $out = array(); @@ -1320,7 +1332,8 @@ function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null // Note: There is no reason to allow the backtopage/backtopageforcancel/backtopagejs, backtolist or backtourl parameter to contains an external URL. Only relative URLs are allowed. // @TODO Merge backtopage with backtourl // @TODO Rename backtolist into backtopagelist - if (preg_match('/^backto/i', $paramname)) { + // @TODO Merge urlfrom into backtourl + if (preg_match('/^backto/i', $paramname) || preg_match('/^urlfrom/i', $paramname)) { $out = str_replace('\\', '/', $out); // Can be before the loop because only 1 char is replaced. No risk to get it after other replacements. $out = str_replace(array(':', ';', '@', "\t", ' '), '', $out); // Can be before the loop because only 1 char is replaced. No risk to retrieve it after other replacements. do { @@ -1771,9 +1784,9 @@ function dolBuildUrl($url, $params = [], $addtoken = false, $anchor = '') } /** - * Get properties for an object - including magic properties when requested + * Get value of properties for an object - including magic properties when requested * - * Only returns properties that exist + * By default, returns all public properties. If a list of properties is provided, only returns the ones that exist. * * @param object $obj Object to get properties from * @param string[] $properties Optional list of properties to get. @@ -2587,21 +2600,26 @@ function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $noescapeta $tmp = str_ireplace('', '__ENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp); $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . ' \/>/', '__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp); - // For case of tag with attributes - do { - $tmpold = $tmp; - - if (preg_match('/<' . preg_quote($tagtoreplace, '/') . '(\s+)([^>]+)>/', $tmp, $reg)) { + // For case of tag with attributes. + // All the occurrences are protected in a single pass: the replacement string contains no '<', so it + // can never build a new tag to protect (a loop replacing one distinct attribute string per round was + // rescanning the whole content for each of them, so the cost was quadratic on large contents). + $tmp = preg_replace_callback( + '/<'.preg_quote($tagtoreplace, '/').'(\s+)([^>]+)>/', + /** + * @param string[] $reg + * @return string + */ + static function ($reg) use ($tagtoreplace) { // We want to protect the attribute part ... in '' to avoid transformation by htmlentities() later $tmpattributes = str_ireplace(array('[', ']'), '_', $reg[2]); // We must never have [ ] inside the attribute string $tmpattributes = str_ireplace('"', '__DOUBLEQUOTE__', $tmpattributes); $tmpattributes = preg_replace('/[^a-z0-9_%,\/\?\;\s=&\.\-@:\.#\+]/i', '', $tmpattributes); //$tmpattributes = preg_replace("/float:\s*(left|right)/", "", $tmpattributes); // Disabled: we must not remove content - $tmp = str_replace('<' . $tagtoreplace . $reg[1] . $reg[2] . '>', '__BEGINTAGTOREPLACE' . $tagtoreplace . '[' . $tmpattributes . ']__', $tmp); - } - - $diff = strcmp($tmpold, $tmp); - } while ($diff); + return '__BEGINTAGTOREPLACE'.$tagtoreplace.'['.$tmpattributes.']__'; + }, + $tmp + ) ?? $tmp; } $tmp = str_ireplace('&', '__ANDNOSEMICOLON__', $tmp); @@ -3073,7 +3091,7 @@ function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $di }, close: function (event, ui) { console.log("Popup is closed, run jsonclose = ' . $jsonclose . '"); - ' . (empty($jsonclose) ? '' : $jsonclose . ';') . ' + ' . (empty($jsonclose) || preg_match('/^TODO/', $jsonclose) ? '' : $jsonclose . ';') . ' } }); @@ -3484,6 +3502,7 @@ function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldi } } } else { + // $modulepart may have been set previously if Imagick class exists (see before). if ($modulepart != 'unknown' || method_exists($object, 'getDataToShowPhoto')) { $phototoshow = ''; // Check if a preview file is available @@ -4470,7 +4489,7 @@ function dol_print_email($email, $contactid = 0, $socid = 0, $addlink = 0, $max $type = 'AC_EMAIL'; $linktoaddaction = ''; if (getDolGlobalString('AGENDA_ADDACTIONFOREMAIL')) { - $linktoaddaction = '' . img_object($langs->trans("AddAction"), "calendar") . ''; + $linktoaddaction = '' . img_object($langs->trans("AddAction"), "calendar") . ''; } if ($linktoaddaction) { $newemail = '
' . $newemail . ' ' . $linktoaddaction . '
'; @@ -5082,7 +5101,7 @@ function dol_print_phone($phone, $countrycode = '', $contactid = 0, $socid = 0, $type = 'AC_FAX'; } if (getDolGlobalString('AGENDA_ADDACTIONFORPHONE')) { - $addlinktoagenda = '' . img_object($langs->trans("AddAction"), "calendar") . ''; + $addlinktoagenda = '' . img_object($langs->trans("AddAction"), "calendar") . ''; } if ($addlinktoagenda) { $newphone = '' . $newphone . ' ' . $addlinktoagenda . ''; @@ -6862,9 +6881,10 @@ function img_searchclear($titlealt = 'default', $other = '') * @param string $textfordropdown Show a text to click to dropdown the info box. * @param string $picto '' or 'warning' * @param string $textonpictotooltip Text to show in tooltip on picto + * @param string $cssfordropdown An additional css to avoid conflict to manage the dropdown if $textfordropdown is set * @return string String with info text */ -function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = 'hideonsmartphone', $textfordropdown = '', $picto = '', $textonpictotooltip = '') +function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = 'hideonsmartphone', $textfordropdown = '', $picto = '', $textonpictotooltip = '', $cssfordropdown = 'info_admin') { global $conf, $langs; @@ -6880,19 +6900,19 @@ function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss if ($picto == 'warning') { $fa = 'exclamation-triangle'; } - $result = ($nodiv ? '' : '
'); + $result = ($nodiv ? '' : '
'); $result .= img_picto(((string) $admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')).($textonpictotooltip ? ' : '.$textonpictotooltip : ''), $fa); $result .= ' '; $result .= dol_escape_htmltag($text, 1, 0, 'div,span,b,br,a'); $result .= ($nodiv ? '' : '
'); if ($textfordropdown) { - $tmpresult = '' . $langs->trans($textfordropdown) . ' ' . img_picto($langs->trans($textfordropdown), '1downarrow') . ''; + $tmpresult = '' . $langs->trans($textfordropdown) . ' ' . img_picto($langs->trans($textfordropdown), '1downarrow') . ''; $tmpresult .= ''; @@ -8866,7 +8886,7 @@ function yn($yesno, $format = 1, $color = 0) * @param int $withoutslash 0=With slash at end (except if '/', we return ''), 1=without slash at end * @param ?CommonObject $object Object to use to get ref to forge the path. * @param string $modulepart Type of object ('invoice_supplier, 'donation', 'invoice', ...'). Use '' for autodetect from $object. - * @return string Dir to use ending. Example '' or '1/' or '1/2/' + * @return string Sanitized Dir to use ending. Example '' or '1/' or '1/2/' * @see getMultidirOutput() */ function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '') @@ -8893,7 +8913,7 @@ function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = ' if (!empty($level) && array_key_exists($modulepart, $arrayforoldpath)) { // This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided. if (empty($num) && is_object($object)) { - $num = $object->id; + $num = ((int) $object->id); } if (empty($alpha)) { $num = preg_replace('/([^0-9])/i', '', $num); @@ -8918,7 +8938,7 @@ function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = ' // Special case for thirdparty, where the ref is a company name that is not unique so path on disk is using the ID instead of the ref $path = dol_sanitizeFileName((string) $object->id); } else { - $path = dol_sanitizeFileName(empty($object->ref) ? (string) ((is_object($object) && property_exists($object, 'id')) ? $object->id : '') : $object->ref); + $path = dol_sanitizeFileName(empty($object->ref) ? (string) ((is_object($object) && property_exists($object, 'id')) ? ((int) $object->id) : '') : $object->ref); } } @@ -9092,9 +9112,9 @@ function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp); } - // And double quotes + // And double spaces if ($removedoublespaces) { - while (strpos($temp, " ")) { + while (strpos($temp, " ") !== false) { $temp = str_replace(" ", " ", $temp); } } @@ -11852,7 +11872,7 @@ function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = $sql .= " AND entity IN (" . getEntity($tablename) . ")"; } if ($filters) { - $sql .= $filters; + $sql .= $filters; // @phan-suppress-current-line SqlInjection } $resql = $db->query($sql); @@ -11926,7 +11946,7 @@ function verifCond($strToEvaluate, $onlysimplestring = '1') // On string syntax error, dol_eval may return a string that start with 'Bad call of ...' or 'Bad string syntax to evaluate...' !!! //var_dump($strToEvaluate, $rep); - $rights = (bool) $rep && (!is_string($rep) || strpos($rep, 'Bad call of') === false || strpos($rep, 'Bad string syntax to evaluate') === false); + $rights = (bool) $rep && (!is_string($rep) || (strpos($rep, 'Exception during') === false && strpos($rep, 'Bad call of') === false && strpos($rep, 'Bad string syntax to evaluate') === false)); //var_dump($rights); } return $rights; @@ -12330,6 +12350,11 @@ function dol_eval_standard($s, $hideerrors = 1, $onlysimplestring = '1') return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space after): ' . $s; } + // Check if there is an include or a require + if (preg_match('/(include|include_once|require|require_once)/', $s)) { + return 'Bad string syntax to evaluate (found not allowed key include|include_once|require|require_once): ' . $s; + } + // Check if there is dynamic call (first we use black list patterns) if (preg_match('/\$[\w]*\s*\(/', $s)) { return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a call using "$abc(" or "$abc (" instead of using the direct name of the function): ' . $s; @@ -14795,11 +14820,12 @@ function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = unset($attr['href']); } - // TODO replace $TCompiledAttr generation by commonHtmlAttributeBuilder given below + // TODO replace this $TCompiledAttr generation block by commonHtmlAttributeBuilder like line below + // $TCompiledAttr = commonHtmlAttributeBuilder($attr, $params['use_unsecured_unescapedattr'] ?? []); $TCompiledAttr = array(); foreach ($attr as $key => $value) { if (!empty($params['use_unsecured_unescapedattr']) && is_array($params['use_unsecured_unescapedattr']) && in_array($key, $params['use_unsecured_unescapedattr'])) { - // Not recommended + // Deprecated, forbidden. $value = dol_htmlentities($value, ENT_QUOTES | ENT_SUBSTITUTE); } elseif ($key == 'href') { $value = dolPrintHTMLForAttributeUrl($value); @@ -14809,8 +14835,6 @@ function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = $TCompiledAttr[] = $key . '="' . $value . '"'; // $value has been escaped by the dolPrintHTMLForAttribute... just before } - // TODO replace $TCompiledAttr generation by uncomment line below and remove old code - // $TCompiledAttr = commonHtmlAttributeBuilder($attr,$params['use_unsecured_unescapedattr'] ?? []); $compiledAttributes = empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr); $tag = !empty($attr['href']) ? 'a' : 'span'; @@ -14848,14 +14872,9 @@ function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = /** * Builds an array of safe and properly escaped HTML attributes from a key-value pair list. * - * This function ensures that HTML attributes are correctly encoded for safe output, - * while allowing certain attributes to remain unescaped if explicitly specified. - * Special handling is applied for attributes such as `href`, which are processed - * using `dolPrintHTMLForAttributeUrl()`. All other attributes are escaped using - * `dolPrintHTMLForAttribute()`. - * - * Note: Disabling escaping (via `$unescapedAttr`) is **not recommended** unless you - * fully trust the input data, as it may lead to XSS vulnerabilities. + * This function ensures that HTML attributes are correctly encoded for safe output. + * Special handling is applied for attributes such as `href`, which are processed using `dolPrintHTMLForAttributeUrl()`. + * All other attributes are escaped using `dolPrintHTMLForAttribute()`. * * Example: * ```php @@ -14874,8 +14893,8 @@ function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = * // ] * ``` * - * @param array $attr Associative array of attribute names and their values. - * @param string[] $unescapedAttr Optional list of attribute names that should **not** be escaped. + * @param array $attr Associative array of attribute names and their values. + * @param string[] $unescapedAttr Optional list of attribute names that should **not** be escaped. * * @return array An array where each key corresponds to the attribute name * and each value is a full `key="escaped_value"` string ready for HTML output. @@ -14987,6 +15006,7 @@ function dolCompletUrlForDropdownButton(string $url, array $params, bool $addDol if (!empty($parsedUrl['query'])) { // Use parse_str() function to parse the string passed via URL + $urlQuery = ''; parse_str($parsedUrl['query'], $urlQuery); if (!isset($urlQuery['backtopage']) && isset($params['backtopage'])) { $url .= '&backtopage=' . urlencode($params['backtopage']); @@ -15140,6 +15160,7 @@ function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $u /** * Get an array with properties of an element. + * We must not have database access in this method. * * @param string $elementType Element type (Value of $object->element or value of $object->element@$object->module). Example: * 'action', 'facture', 'project', 'project_task' or @@ -15293,7 +15314,7 @@ function getElementProperties($elementType) $module = 'propal'; $table_element = 'propaldet'; $parent_element = 'propal'; - } elseif ($elementType == 'shipping') { + } elseif ($elementType == 'shipping' || $elementType == 'shipment') { $classpath = 'expedition/class'; $classfile = 'expedition'; $classname = 'Expedition'; @@ -15629,7 +15650,8 @@ function getElementProperties($elementType) /** * Fetch an object from its id and element_type - * Inclusion of classes is automatic + * Inclusion of classes is automatic. + * This does not include permission check that must be done separately with checkUserAccessToObject() * * @param int $element_id Element id (Use this or element_ref but not both. If id and ref are empty, object with no fetch is returned) * @param string $element_type Element type ('module' or 'myobject@mymodule' or 'mymodule_myobject') @@ -15646,6 +15668,7 @@ function fetchObjectByElement($element_id, $element_type, $element_ref = '', $us $ret = 0; $element_prop = getElementProperties($element_type); + //var_dump($element_prop); exit; if ($element_prop['module'] == 'product' || $element_prop['module'] == 'service') { // For example, for an extrafield 'product' (shared for both product and service) that is a link to an object, @@ -16487,11 +16510,11 @@ function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = null, $sortfield_list = explode(',', $sortfield); $sortfield_label_list = array('a.id' => 'id', 'a.datep' => 'dp', 'a.percent' => 'percent'); - $sortfield_new_list = array(); + $sanitized_sortfield_new_list = array(); foreach ($sortfield_list as $sortfield_value) { - $sortfield_new_list[] = $sortfield_label_list[trim($sortfield_value)]; + $sanitized_sortfield_new_list[] = $sortfield_label_list[trim($sortfield_value)]; //@phan-suppress-current-line SqlInjection } - $sortfield_new = implode(',', $sortfield_new_list); + $sanitized_sortfield_new = implode(',', $sanitized_sortfield_new_list); $sql = null; $sql2 = null; @@ -16726,7 +16749,7 @@ function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = null, $limit = $MAXWITHOUTPAGINATION; // Complete request and execute it with limit - $sql .= $db->order($sortfield_new, $sortorder); + $sql .= $db->order($sanitized_sortfield_new, $sortorder); if ($limit) { $sql .= $db->plimit($limit + 1, $offset); } @@ -17444,6 +17467,8 @@ function recordNotFound($message = '', $printheader = 1, $printfooter = 1, $show * @param array $array1 The base array (default parameters). * @param array $array2 The array with values to override or extend the base array. * @return array The merged array with recursive replacement. + * + * TODO Move it out of functions.lib.php */ function array_merge_recursive_distinct(array $array1, array $array2): array { diff --git a/htdocs/core/lib/functions2.lib.php b/htdocs/core/lib/functions2.lib.php index 556890f89ba..e6c99899a8a 100644 --- a/htdocs/core/lib/functions2.lib.php +++ b/htdocs/core/lib/functions2.lib.php @@ -1088,17 +1088,17 @@ function get_next_value($db, $mask, $table, $field, $where = '', $objsoc = '', $ } $sqlwhere .= "("; - $sqlwhere .= " (SUBSTRING(".$field.", ".$yearpos.", ".$yearlen.") = '".$db->escape($yearcomp)."'"; - $sqlwhere .= " AND SUBSTRING(".$field.", ".$monthpos.", ".$monthlen.") >= '".str_pad($monthcomp, $monthlen, '0', STR_PAD_LEFT)."')"; + $sqlwhere .= " (SUBSTRING(".$field.", ".((int) $yearpos).", ".((int) $yearlen).") = '".$db->escape($yearcomp)."'"; + $sqlwhere .= " AND SUBSTRING(".$field.", ".((int) $monthpos).", ".((int) $monthlen).") >= '".str_pad((string) ((int) $monthcomp), ((int) $monthlen), '0', STR_PAD_LEFT)."')"; $sqlwhere .= " OR"; - $sqlwhere .= " (SUBSTRING(".$field.", ".$yearpos.", ".$yearlen.") = '".$db->escape($yearcomp1)."'"; - $sqlwhere .= " AND SUBSTRING(".$field.", ".$monthpos.", ".$monthlen.") < '".str_pad($monthcomp, $monthlen, '0', STR_PAD_LEFT)."') "; + $sqlwhere .= " (SUBSTRING(".$field.", ".((int) $yearpos).", ".((int) $yearlen).") = '".$db->escape($yearcomp1)."'"; + $sqlwhere .= " AND SUBSTRING(".$field.", ".((int) $monthpos).", ".((int) $monthlen).") < '".str_pad((string) ((int) $monthcomp), ((int) $monthlen), '0', STR_PAD_LEFT)."') "; $sqlwhere .= ')'; } elseif ($resetEveryMonth) { - $sqlwhere .= "(SUBSTRING(".$field.", ".$yearpos.", ".$yearlen.") = '".$db->escape($yearcomp)."'"; - $sqlwhere .= " AND SUBSTRING(".$field.", ".$monthpos.", ".$monthlen.") = '".str_pad($monthcomp, $monthlen, '0', STR_PAD_LEFT)."')"; + $sqlwhere .= "(SUBSTRING(".$field.", ".((int) $yearpos).", ".((int) $yearlen).") = '".$db->escape($yearcomp)."'"; + $sqlwhere .= " AND SUBSTRING(".$field.", ".((int) $monthpos).", ".((int) $monthlen).") = '".str_pad((string) ((int) $monthcomp), ((int) $monthlen), '0', STR_PAD_LEFT)."')"; } else { // reset is done on january - $sqlwhere .= "(SUBSTRING(".$field.", ".$yearpos.", ".$yearlen.") = '".$db->escape($yearcomp)."')"; + $sqlwhere .= "(SUBSTRING(".$field.", ".((int) $yearpos).", ".((int) $yearlen).") = '".$db->escape($yearcomp)."')"; } } //print "sqlwhere=".$sqlwhere." yearcomp=".$yearcomp."
\n"; // sqlwhere and yearcomp defined only if we ask a reset @@ -1113,7 +1113,7 @@ function get_next_value($db, $mask, $table, $field, $where = '', $objsoc = '', $ if ($posnumstart < 0) { return 'ErrorBadMaskFailedToLocatePosOfSequence'; } - $sqlstring = "SUBSTRING(".$field.", ".($posnumstart + 1).", ".dol_strlen($maskcounter).")"; + $sqlstring = "SUBSTRING(".$field.", ".(((int) $posnumstart) + 1).", ".dol_strlen($maskcounter).")"; // Define $maskLike $maskLike = dol_string_nospecial($mask); @@ -1273,7 +1273,7 @@ function get_next_value($db, $mask, $table, $field, $where = '', $objsoc = '', $ if ($maskrefclient_posnumstart <= 0) { return 'ErrorBadMask'; } - $maskrefclient_sqlstring = 'SUBSTRING('.$field.', '.($maskrefclient_posnumstart + 1).', '.dol_strlen($maskrefclient_maskcounter).')'; + $sanitized_maskrefclient_string = 'SUBSTRING('.$field.', '.((int) $maskrefclient_posnumstart + 1).', '.dol_strlen($maskrefclient_maskcounter).')'; //print "x".$sqlstring; // Define $maskrefclient_maskLike @@ -1291,7 +1291,7 @@ function get_next_value($db, $mask, $table, $field, $where = '', $objsoc = '', $ $maskrefclient_maskLike = str_replace(dol_string_nospecial('{'.$maskrefclient.'}'), $maskrefclient_clientcode.str_pad("", dol_strlen($maskrefclient_maskcounter), "_"), $maskrefclient_maskLike); // Get counter in database - $maskrefclient_sql = "SELECT MAX(".$maskrefclient_sqlstring.") as val"; + $maskrefclient_sql = "SELECT MAX(".$sanitized_maskrefclient_string.") as val"; $maskrefclient_sql .= " FROM ".MAIN_DB_PREFIX.$db->sanitize($table); $maskrefclient_sql .= " WHERE ".$db->sanitize($field)." LIKE '".$db->escape($maskrefclient_maskLike) . (getDolGlobalString('SEARCH_FOR_NEXT_VAL_ON_START_ONLY') ? "%" : "") . "'"; if ($bentityon) { // only if entity enable @@ -3114,6 +3114,11 @@ function csvClean($newvalue, $charset = '', $separator = '') $addquote = 1; } + // Rule 4 CSV: If value is a string started with char that trigger formula in calc software, we must add " + if (preg_match('/^\s*[=+-@]/', $newvalue)) { + $addquote = 1; + } + return ($addquote ? '"' : '').$newvalue.($addquote ? '"' : ''); } diff --git a/htdocs/core/lib/invoice.lib.php b/htdocs/core/lib/invoice.lib.php index 06fe1196383..db908458aba 100644 --- a/htdocs/core/lib/invoice.lib.php +++ b/htdocs/core/lib/invoice.lib.php @@ -1303,7 +1303,7 @@ function getPurchaseInvoiceUnpaidOpenTable($maxCount = 500, $socid = 0) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; } $sql .= " WHERE s.rowid = ff.fk_soc"; - $sql .= " AND ff.entity = ".$conf->entity; + $sql .= " AND ff.entity = ".((int) $conf->entity); $sql .= " AND ff.paye = 0"; $sql .= " AND ff.fk_statut = ".FactureFournisseur::STATUS_VALIDATED; if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) { diff --git a/htdocs/core/lib/modulebuilder.lib.php b/htdocs/core/lib/modulebuilder.lib.php index c4b3645a03b..9219dd79cbd 100644 --- a/htdocs/core/lib/modulebuilder.lib.php +++ b/htdocs/core/lib/modulebuilder.lib.php @@ -1,6 +1,6 @@ - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024-2026 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -333,7 +333,7 @@ function rebuildObjectSql($destdir, $module, $objectname, $newmask, $readdir = ' // Backup old file dol_copy($pathoffiletoedittarget, $pathoffiletoedittarget.'.back', $newmask, 1); - $contentsql = file_get_contents(dol_osencode($pathoffiletoeditsrc)); + $contentsqlfile = file_get_contents(dol_osencode($pathoffiletoeditsrc)); $i = 0; $texttoinsert = '-- BEGIN MODULEBUILDER FIELDS'."\n"; @@ -392,9 +392,9 @@ function rebuildObjectSql($destdir, $module, $objectname, $newmask, $readdir = ' } $texttoinsert .= "\t".'-- END MODULEBUILDER FIELDS'; - $contentsql = preg_replace('/-- BEGIN MODULEBUILDER FIELDS.*END MODULEBUILDER FIELDS/ims', $texttoinsert, $contentsql); + $contentsqlfile = preg_replace('/-- BEGIN MODULEBUILDER FIELDS.*END MODULEBUILDER FIELDS/ims', $texttoinsert, $contentsqlfile); - $result = file_put_contents($pathoffiletoedittarget, $contentsql); + $result = file_put_contents($pathoffiletoedittarget, $contentsqlfile); if ($result) { dolChmod($pathoffiletoedittarget, $newmask); } else { @@ -407,7 +407,7 @@ function rebuildObjectSql($destdir, $module, $objectname, $newmask, $readdir = ' $pathoffiletoedittarget = preg_replace('/\.sql$/', '.key.sql', $pathoffiletoedittarget); $pathoffiletoedittarget = preg_replace('/\.sql.new$/', '.key.sql.new', $pathoffiletoedittarget); - $contentsql = file_get_contents(dol_osencode($pathoffiletoeditsrc)); + $contentsqlfile = file_get_contents(dol_osencode($pathoffiletoeditsrc)); $i = 0; $texttoinsert = '-- BEGIN MODULEBUILDER INDEXES'."\n"; @@ -429,11 +429,11 @@ function rebuildObjectSql($destdir, $module, $objectname, $newmask, $readdir = ' } $texttoinsert .= '-- END MODULEBUILDER INDEXES'; - $contentsql = preg_replace('/-- BEGIN MODULEBUILDER INDEXES.*END MODULEBUILDER INDEXES/ims', $texttoinsert, $contentsql); + $contentsqlfile = preg_replace('/-- BEGIN MODULEBUILDER INDEXES.*END MODULEBUILDER INDEXES/ims', $texttoinsert, $contentsqlfile); dol_mkdir(dirname($pathoffiletoedittarget)); - $result2 = file_put_contents($pathoffiletoedittarget, $contentsql); + $result2 = file_put_contents($pathoffiletoedittarget, $contentsqlfile); if ($result2) { dolChmod($pathoffiletoedittarget, $newmask); } else { diff --git a/htdocs/core/lib/openid_connect.lib.php b/htdocs/core/lib/openid_connect.lib.php index 544a5b87438..8c73fe1273a 100644 --- a/htdocs/core/lib/openid_connect.lib.php +++ b/htdocs/core/lib/openid_connect.lib.php @@ -124,14 +124,14 @@ function openid_connect_create_user($db, $userinfo, $login, $entity) // Sanitize login using Dolibarr forbidden characters for logins. $badChars = getDolGlobalLoginBadCharUnauthorized(); - $sanitized_login = $login; + $sanitized_login = $login; // @phan-suppress-current-line SqlInjection if ($badChars !== '' && preg_match('/['.preg_quote($badChars, '/').']/', $login)) { // First try preferred_username from OIDC (common standard claim) if (property_exists($userinfo, 'preferred_username') && !empty($userinfo->preferred_username)) { $preferred = $userinfo->preferred_username; if (!preg_match('/['.preg_quote($badChars, '/').']/', $preferred)) { - $sanitized_login = $preferred; + $sanitized_login = $preferred; // @phan-suppress-current-line SqlInjection } } // If still invalid (no preferred_username or it also has bad chars), extract local part of email diff --git a/htdocs/core/lib/pdf.lib.php b/htdocs/core/lib/pdf.lib.php index b0d43113f1f..83e9bd927ff 100644 --- a/htdocs/core/lib/pdf.lib.php +++ b/htdocs/core/lib/pdf.lib.php @@ -3228,9 +3228,11 @@ function pdfGetLineTotalDiscountAmount($object, $i, $outputlangs, $hidedetails = if (empty($hidedetails) || $hidedetails > 1) { if (empty($multicurrency)) { - return (float) price2num($sign * (($object->lines[$i]->subprice * (float) $object->lines[$i]->qty) - $object->lines[$i]->total_ht), 'MT', 1); + $diff = (float) price2num($sign * $object->lines[$i]->subprice * (float) $object->lines[$i]->qty, 'MT', 1) - $object->lines[$i]->total_ht; + return (float) price2num($diff, 'MT', 1); } else { - return (float) price2num($sign * (($object->lines[$i]->multicurrency_subprice * (float) $object->lines[$i]->qty) - $object->lines[$i]->multicurrency_total_ht), 'MT', 1); + $diff = (float) price2num($sign * $object->lines[$i]->multicurrency_subprice * (float) $object->lines[$i]->qty, 'MT', 1) - $object->lines[$i]->multicurrency_total_ht; + return (float) price2num($diff, 'MT', 1); } } } diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index 55b2bb4b54a..0a7a6bcee92 100644 --- a/htdocs/core/lib/price.lib.php +++ b/htdocs/core/lib/price.lib.php @@ -44,7 +44,7 @@ * @param float $txtva 0=do not apply VAT tax, VAT rate=apply (this is VAT rate only without text code, we don't need text code because we already have all tax info into $localtaxes_array) * @param float $uselocaltax1_rate 0=do not use localtax1, >0=apply and get value from localtaxes_array (or database if empty), -1=autodetect according to seller if we must apply, get value from localtaxes_array (or database if empty). Try to always use -1. * @param float $uselocaltax2_rate 0=do not use localtax2, >0=apply and get value from localtaxes_array (or database if empty), -1=autodetect according to seller if we must apply, get value from localtaxes_array (or database if empty). Try to always use -1. - * @param float $remise_percent_global 0 + * @param int $notused 0 * @param string $price_base_type 'HT'=Unit price parameter $pu is HT, 'TTC'=Unit price parameter $pu is TTC (HT+VAT but not Localtax. TODO Add also mode 'INCT' when pu is price HT+VAT+LT1+LT2) * @param int $info_bits Miscellaneous information on line * @param int<0,1> $type 0/1=Product/service @@ -87,7 +87,7 @@ * * @phan-suppress PhanTypeMismatchDefault */ -function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller = null, $localtaxes_array = [], $progress = 100, $multicurrency_tx = 1, $pu_devise = 0, $multicurrency_code = '') // @phpstan-ignore-line +function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $price_base_type, $info_bits, $type, $seller = null, $localtaxes_array = [], $progress = 100, $multicurrency_tx = 1, $pu_devise = 0, $multicurrency_code = '') // @phpstan-ignore-line { global $conf, $mysoc, $db, $hookmanager; @@ -118,7 +118,7 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt dol_syslog("Price.lib::calcul_price_total Warning: function was called with a bad value for vat rate (should be often < 100, always < 1000). There is surely a bug.", LOG_ERR); } // Too verbose. Enable for debug only - // dol_syslog("Price.lib::calcul_price_total qty=".$qty." pu=".$pu." remiserpercent_ligne=".$remise_percent_ligne." txtva=".$txtva." uselocaltax1_rate=".$uselocaltax1_rate." uselocaltax2_rate=".$uselocaltax2_rate.' remise_percent_global='.$remise_percent_global.' price_base_type='.$ice_base_type.' type='.$type.' progress='.$progress); + // dol_syslog("Price.lib::calcul_price_total qty=".$qty." pu=".$pu." remiserpercent_ligne=".$remise_percent_ligne." txtva=".$txtva." uselocaltax1_rate=".$uselocaltax1_rate." uselocaltax2_rate=".$uselocaltax2_rate.' price_base_type='.$ice_base_type.' type='.$type.' progress='.$progress); $countryid = $seller->country_id; @@ -137,7 +137,7 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt } //var_dump($uselocaltax1_rate.' - '.$uselocaltax2_rate); - dol_syslog('Price.lib::calcul_price_total qty='.$qty.' pu='.$pu.' remise_percent_ligne='.$remise_percent_ligne.' txtva='.$txtva.' uselocaltax1_rate='.$uselocaltax1_rate.' uselocaltax2_rate='.$uselocaltax2_rate.' remise_percent_global='.$remise_percent_global.' price_base_type='.$price_base_type.' type='.$type.' progress='.$progress); + dol_syslog('Price.lib::calcul_price_total qty='.$qty.' pu='.$pu.' remise_percent_ligne='.$remise_percent_ligne.' txtva='.$txtva.' uselocaltax1_rate='.$uselocaltax1_rate.' uselocaltax2_rate='.$uselocaltax2_rate.' price_base_type='.$price_base_type.' type='.$type.' progress='.$progress); // Now we search localtaxes information ourself (rates and types). $localtax1_type = 0; @@ -208,7 +208,7 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt $tot_sans_remise = $pu * $qty * ($progress / 100); $tot_avec_remise_ligne = $tot_sans_remise * (1 - ((float) $remise_percent_ligne / 100)); } - $tot_avec_remise = $tot_avec_remise_ligne * (1 - ((float) $remise_percent_global / 100)); + $tot_avec_remise = $tot_avec_remise_ligne; // initialize result array for ($i = 0; $i <= 15; $i++) { @@ -423,7 +423,7 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt } // Recall function using the multicurrency price as reference price. We must set param $multicurrency_tx to 1 to avoid infinite loop. - $newresult = calcul_price_total($qty, $pu_devise, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller, $localtaxes_array, $progress, 1, 0, ''); // pu_devise is normally arg#15, here as arg#2 @phan-suppress-current-line PhanPluginSuspiciousParamPosition + $newresult = calcul_price_total($qty, $pu_devise, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, 0, $price_base_type, $info_bits, $type, $seller, $localtaxes_array, $progress, 1, 0, ''); // pu_devise is normally arg#15, here as arg#2 @phan-suppress-current-line PhanPluginSuspiciousParamPosition if ($multicurrency_code) { // Restore setup of currency accurency diff --git a/htdocs/core/lib/security.lib.php b/htdocs/core/lib/security.lib.php index ab0f324001a..0aad43d0721 100644 --- a/htdocs/core/lib/security.lib.php +++ b/htdocs/core/lib/security.lib.php @@ -166,7 +166,7 @@ function dolGetLdapPasswordHash($password, $type = 'md5') * @param string $features Features to check (it must be module name or $object->element. Can be a 'or' check with 'levela|levelb'. * Examples: 'societe', 'contact', 'produit&service', 'produit|service', ...) * This is used to check permission $user->rights->features->... - * @param int|string|Object $object Object or Object ID or list of Object ID if we want to check a particular record (optional) is linked to a owned thirdparty (optional). + * @param int|string|Object $object Object (recommended) or Object ID or list of Object ID if we want to check a particular record (optional) is linked to a owned thirdparty (optional). * @param string $tableandshare 'TableName&SharedElement' with Tablename is table where object is stored. SharedElement is an optional key to define where to check entity for multicompany module. Param not used if objectid is null (optional). * @param string $feature2 Feature to check, second level of permission (optional). Can be a 'or' check with 'sublevela|sublevelb'. * This is used to check permission $user->rights->features->feature2... @@ -174,7 +174,7 @@ function dolGetLdapPasswordHash($password, $type = 'md5') * @param string $dbt_select Field rowid name, for select into tableandshare if not "rowid". Not used if objectid is null (optional) * @param int<0,1> $isdraft 1=The object with id=$objectid is a draft * @param int<0,1> $mode Mode (0=default, 1=return without dying) - * @return int If mode = 0 (default): Always 1, die process if not allowed. If mode = 1: Return 0 if access not allowed. + * @return int If mode = 0 (default): die process if not allowed (else return 1). If mode = 1: Return 0 if access not allowed (else return 1). * @see dol_check_secure_access_document(), checkUserAccessToObject() */ function restrictedArea(User $user, $features, $object = 0, $tableandshare = '', $feature2 = '', $dbt_keyfield = 'fk_soc', $dbt_select = 'rowid', $isdraft = 0, $mode = 0) @@ -194,8 +194,8 @@ function restrictedArea(User $user, $features, $object = 0, $tableandshare = '', $objectid = preg_replace('/[^0-9\.\,]/', '', (string) $objectid); // For the case value is coming from a non sanitized user input } - //dol_syslog("functions.lib:restrictedArea $feature, $objectid, $dbtablename, $feature2, $dbt_socfield, $dbt_select, $isdraft"); - /*print "user_id=".$user->id.", features=".$features.", feature2=".$feature2.", objectid=".$objectid; + //dol_syslog("functions.lib:restrictedArea $feature, $object, $dbtablename, $feature2, $dbt_socfield, $dbt_select, $isdraft"); + /*print "user_id=".$user->id.", features=".$features.", feature2=".$feature2.", object=".$object; print ", dbtablename=".$tableandshare.", dbt_socfield=".$dbt_keyfield.", dbt_select=".$dbt_select; print ", perm: user->hasRight(".$features.($feature2 ? ",".$feature2 : "").", lire) = ".($feature2 ? $user->hasRight($features, $feature2, 'lire') : $user->hasRight($features, 'lire'))."
"; */ @@ -508,6 +508,11 @@ function restrictedArea(User $user, $features, $object = 0, $tableandshare = '', $createok = 0; $nbko++; } + } elseif ($feature == 'payment') { + if (!$user->hasRight('facture', 'paiement')) { + $createok = 0; + $nbko++; + } } elseif ($feature == 'webhook') { if (empty($user->admin)) { $createok = 0; @@ -706,7 +711,7 @@ function restrictedArea(User $user, $features, $object = 0, $tableandshare = '', * * @param User $user User to check * @param string[] $featuresarray Features/modules to check. Example: ('user','service','member','project','task',...) - * @param int|string|Object $object Full object or object ID or list of object id. For example if we want to check a particular record (optional) is linked to a owned thirdparty (optional). + * @param int|string|Object $object Object (recommended) or Object ID or list of Object id. For example if we want to check a particular record (optional) is linked to a owned thirdparty (optional). * @param string $tableandshare 'TableName&SharedElement' with Tablename is table where object is stored. SharedElement is an optional key to define where to check entity for multicompany modume. Param not used if objectid is null (optional). * @param string[]|string $feature2 Feature to check, second level of permission (optional). Can be or check with 'level1|level2'. * @param string $dbt_keyfield Field name for socid foreign key if not fk_soc. Not used if objectid is null (optional). Can use '' if NA. @@ -724,10 +729,10 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl } else { $objectid = $object; // $objectid can be X or 'X,Y,Z' } - $objectid = preg_replace('/[^0-9\.\,]/', '', $objectid); // For the case value is coming from a non sanitized user input + $objectid = preg_replace('/[^0-9\.\,]/', '', (string) $objectid); // For the case value is coming from a non sanitized user input - //dol_syslog("functions.lib:restrictedArea $feature, $objectid, $dbtablename, $feature2, $dbt_socfield, $dbt_select, $isdraft"); - //print "user_id=".$user->id.", features=".join(',', $featuresarray).", objectid=".$objectid; + //dol_syslog("functions.lib:restrictedArea $feature, $object, $dbtablename, $feature2, $dbt_socfield, $dbt_select, $isdraft"); + //print "user_id=".$user->id.", features=".join(',', $featuresarray).", object=".$object; //print ", tableandshare=".$tableandshare.", dbt_socfield=".$dbt_keyfield.", dbt_select=".$dbt_select."
"; // More parameters @@ -770,7 +775,7 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl $parenttableforentity = ''; $dbtablename = "chargesociales"; $feature = "chargesociales"; - $objectid = $object->fk_charge; + $objectid = (string) $object->fk_charge; } $checkonentitydone = 0; @@ -793,12 +798,14 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl $sharedelement = (!empty($params[1]) ? $params[1] : $dbtablename); // We change dbtablename, so we set sharedelement too. } + // $objectid was already sanitized at begin of this method (can be an int or a list of int separated by comma). // To avoid an access forbidden with a numeric ref if ($dbt_select != 'rowid' && $dbt_select != 'id') { - $objectid = "'".$objectid."'"; // Note: $objectid was already cast into int at begin of this method. + $objectid = "'".$objectid."'"; } + // Check permission for objectid on entity only - if (in_array($feature, $check) && $objectid > 0) { // For $objectid = 0, no check + if (in_array($feature, $check) && !empty($objectid)) { // For $objectid = 0, no check $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb"; $sql .= " FROM ".MAIN_DB_PREFIX.$db->sanitize($dbtablename)." as dbt"; if (($feature == 'user' || $feature == 'usergroup') && isModEnabled('multicompany')) { // Special for multicompany @@ -830,10 +837,10 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl } $checkonentitydone = 1; } - if (in_array($feature, $checksoc) && $objectid > 0) { // We check feature = checksoc. For $objectid = 0, no check + if (in_array($feature, $checksoc) && !empty($objectid)) { // We check feature = checksoc. For $objectid = 0, no check // If external user: Check permission for external users if ($user->socid > 0) { - if ($user->socid != $objectid) { + if ((string) $user->socid != $objectid) { return false; } } elseif (isModEnabled('societe') && !$user->hasRight('societe', 'lire') && !$user->hasRight('societe', 'client', 'voir')) { @@ -863,7 +870,7 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl $checkonentitydone = 1; } - if (in_array($feature, $checkparentsoc) && $objectid > 0) { // Test on entity + link to thirdparty. Allowed if link is empty (Ex: contacts...). + if (in_array($feature, $checkparentsoc) && !empty($objectid)) { // Test on entity + link to thirdparty. Allowed if link is empty (Ex: contacts...). // If external user: Check permission for external users if ($user->socid > 0) { $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb"; @@ -888,9 +895,9 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl $checkonentitydone = 1; } - if (in_array($feature, $checkproject) && $objectid > 0) { + if (in_array($feature, $checkproject) && !empty($objectid)) { if (isModEnabled('project') && !$user->hasRight('projet', 'all', 'lire')) { - $projectid = $objectid; + $projectid = $objectid; // Note that if $objectid is a string list of id; the test later will return false include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; $projectstatic = new Project($db); @@ -908,8 +915,11 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl } $checkonentitydone = 1; } - if (in_array($feature, $checktask) && (int) $objectid > 0) { + if (in_array($feature, $checktask) && !empty($objectid)) { if (isModEnabled('project') && !$user->hasRight('projet', 'all', 'lire')) { + if (preg_match('/,/', $objectid)) { // if this is a list of id + return false; + } $task = new Task($db); $task->fetch((int) $objectid); $projectid = $task->fk_project; @@ -934,7 +944,7 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl } //var_dump($sql); - if (!$checkonentitydone && !in_array($feature, $nocheck) && $objectid > 0) { // By default (case of $checkdefault), we check on object entity + link to third party on field $dbt_keyfield + if (!$checkonentitydone && !in_array($feature, $nocheck) && !empty($objectid)) { // By default (case of $checkdefault), we check on object entity + link to third party on field $dbt_keyfield // If external user: Check permission for external users if ($user->socid > 0) { if (empty($dbt_keyfield)) { @@ -971,7 +981,7 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; $sql .= " AND (sc.fk_user = ".((int) $user->id)." OR dbt.".$dbt_keyfield." IS NULL OR dbt.".$dbt_keyfield." = 0)"; } - } elseif (isModEnabled('multicompany')) { + } elseif (isModEnabled('multicompany') && (!empty($object->ismultientitymanaged) || !isset($object->ismultientitymanaged))) { // If multicompany, and user is an internal user with all permissions, check that object is in correct entity $sql = "SELECT COUNT(dbt.".$db->sanitize($dbt_select).") as nb"; $sql .= " FROM ".MAIN_DB_PREFIX.$db->sanitize($dbtablename)." as dbt"; @@ -981,9 +991,13 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl } // For events, check on users assigned to event - if ($feature === 'agenda' && ((int) $objectid) > 0) { + if ($feature === 'agenda' && !empty($objectid)) { // Also check owner or attendee for users without allactions->read - if (/* $objectid > 0 && */ !$user->hasRight('agenda', 'allactions', 'read')) { + if (!$user->hasRight('agenda', 'allactions', 'read')) { + if (preg_match('/,/', $objectid)) { // if this is a list of id + return false; + } + require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php'; $action = new ActionComm($db); $action->fetch((int) $objectid); @@ -995,7 +1009,7 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl // For some object, we also have to check it is in the user hierarchy // Param $object must be the full object and not a simple id to have this test possible. - if (in_array($feature, $checkhierarchy) && is_object($object) && $objectid > 0) { + if (in_array($feature, $checkhierarchy) && is_object($object) && !empty($objectid)) { $childids = $user->getAllChildIds(1); $useridtocheck = 0; if ($feature == 'holiday') { @@ -1030,7 +1044,7 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl // For some object, we also have to check it is public or owned by user // Param $object must be the full object and not a simple id to have this test possible. - if (in_array($feature, $checkuser) && is_object($object) && $objectid > 0) { + if (in_array($feature, $checkuser) && is_object($object) && !empty($objectid)) { $useridtocheck = $object->fk_user; if (!empty($useridtocheck) && $useridtocheck > 0 && $useridtocheck != $user->id && empty($user->admin)) { return false; @@ -1041,7 +1055,7 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl $resql = $db->query($sql); if ($resql) { $obj = $db->fetch_object($resql); - if (!$obj || $obj->nb < count(explode(',', $objectid))) { // error if we found 0 or less record than nb of id provided + if (!$obj || $obj->nb < count(explode(',', $objectid))) { // error if we found 0 or less record than the nb of ids provided return false; } } else { diff --git a/htdocs/core/lib/sendings.lib.php b/htdocs/core/lib/sendings.lib.php index e4e8f55baee..17932a1dfca 100644 --- a/htdocs/core/lib/sendings.lib.php +++ b/htdocs/core/lib/sendings.lib.php @@ -1,7 +1,7 @@ * Copyright (C) 2012 Regis Houssin - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2025 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -335,7 +335,7 @@ function show_list_sending_receive($origin, $origin_id, $filter = '') } $sql .= " AND ed.fk_expedition = e.rowid"; if ($filter) { - $sql .= $filter; + $sql .= $filter; // @phan-suppress-current-line SqlInjection } $sql .= " ORDER BY obj.rowid, obj.fk_product"; diff --git a/htdocs/core/lib/website.lib.php b/htdocs/core/lib/website.lib.php index 31eda36ebed..602b74c43d5 100644 --- a/htdocs/core/lib/website.lib.php +++ b/htdocs/core/lib/website.lib.php @@ -1289,17 +1289,17 @@ function getPagesFromSearchCriterias($type, $algo, $searchstring, $max = 25, $so $sql .= " AND wp.type_container IN (".$db->sanitize($typestring, 1).")"; } $sql .= " AND ("; - $searchalgo = ''; + $sqlsearchalgo = ''; if (preg_match('/meta/', $algo)) { // TODO Use a better way to scan keywords - $searchalgo .= "wp.title LIKE '%".$db->escape($db->escapeforlike($searchstring))."%' OR wp.description LIKE '%".$db->escape($db->escapeforlike($searchstring))."%'"; - $searchalgo .= " OR wp.pageurl LIKE '%".$db->escape($db->escapeforlike($searchstring))."%' OR wp.aliasalt LIKE '%".$db->escape($db->escapeforlike($searchstring))."%'"; - $searchalgo .= " OR wp.keywords LIKE '".$db->escape($db->escapeforlike($searchstring)).",%' OR wp.keywords LIKE '% ".$db->escape($db->escapeforlike($searchstring))."%'"; + $sqlsearchalgo .= "wp.title LIKE '%".$db->escape($db->escapeforlike($searchstring))."%' OR wp.description LIKE '%".$db->escape($db->escapeforlike($searchstring))."%'"; + $sqlsearchalgo .= " OR wp.pageurl LIKE '%".$db->escape($db->escapeforlike($searchstring))."%' OR wp.aliasalt LIKE '%".$db->escape($db->escapeforlike($searchstring))."%'"; + $sqlsearchalgo .= " OR wp.keywords LIKE '".$db->escape($db->escapeforlike($searchstring)).",%' OR wp.keywords LIKE '% ".$db->escape($db->escapeforlike($searchstring))."%'"; } if (preg_match('/content/', $algo)) { - $searchalgo .= ($searchalgo ? ' OR ' : '')."wp.content LIKE '%".$db->escape($db->escapeforlike($searchstring))."%'"; + $sqlsearchalgo .= ($sqlsearchalgo ? ' OR ' : '')."wp.content LIKE '%".$db->escape($db->escapeforlike($searchstring))."%'"; } - $sql .= $searchalgo; + $sql .= $sqlsearchalgo; if (is_array($otherfilters) && !empty($otherfilters['category'])) { $sql .= ' AND cwp.fk_website_page = wp.rowid AND cwp.fk_categorie = '.((int) $otherfilters['category']); } diff --git a/htdocs/core/lib/website2.lib.php b/htdocs/core/lib/website2.lib.php index ce582efbc82..eae2a335841 100644 --- a/htdocs/core/lib/website2.lib.php +++ b/htdocs/core/lib/website2.lib.php @@ -430,8 +430,8 @@ function dolSavePageContent($filetpl, Website $object, WebsitePage $objectpage, $tplcontent .= '$tmp = preg_replace("/^/ms", "", $tmp);'."\n"; } if (strpos($objectpage->content, 'define("__SEO_PAGE_TITLE__"') !== false) { - $tplcontent .= '$tmp = preg_replace("/^.*?<\/title>/ms", "<title>" . dolPrintHTMLForAttribute(constant("__SEO_PAGE_TITLE__"), 1) . "", $tmp);'."\n"; - $tplcontent .= '$tmp = preg_replace("/^/ms", "", $tmp);'."\n"; + $tplcontent .= '$tmp = preg_replace("/^.*?<\/title>/ms", "<title>" . dolPrintHTMLForAttribute(defined("__SEO_PAGE_TITLE__") ? constant("__SEO_PAGE_TITLE__") : "", 1) . "", $tmp);'."\n"; + $tplcontent .= '$tmp = preg_replace("/^/ms", "", $tmp);'."\n"; } if (strpos($objectpage->content, 'define("__SEO_PAGE_DESC__"') !== false) { $tplcontent .= '$tmp = preg_replace("/^/ms", "", $tmp);'."\n"; diff --git a/htdocs/core/lib/xcal.lib.php b/htdocs/core/lib/xcal.lib.php index 7508e650082..329cb14ae78 100644 --- a/htdocs/core/lib/xcal.lib.php +++ b/htdocs/core/lib/xcal.lib.php @@ -1,6 +1,6 @@ - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -123,7 +123,7 @@ function build_calfile($format, $title, $desc, $events_array, $outputfile) LOCATION: SEQUENCE:0 STATUS:CONFIRMED - SUMMARY:Tâche 1 heure + SUMMARY:Task 1 hour TRANSP:OPAQUE END:VEVENT @@ -139,7 +139,7 @@ function build_calfile($format, $title, $desc, $events_array, $outputfile) LOCATION: SEQUENCE:0 STATUS:CONFIRMED - SUMMARY:Tâche 1 jour + SUMMARY:Task 1 day TRANSP:TRANSPARENT END:VEVENT */ diff --git a/htdocs/core/login/functions_googleoauth.php b/htdocs/core/login/functions_googleoauth.php index 72d35c9032d..ccb84eb36e4 100644 --- a/htdocs/core/login/functions_googleoauth.php +++ b/htdocs/core/login/functions_googleoauth.php @@ -93,7 +93,7 @@ function check_user_password_googleoauth($usertotest, $passwordtotest, $entityto $backtourl = preg_replace('/#.*$/i', '', $backtourl); // We remove part after the #... - $url = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode('forlogin-'.$shortscope.'-'.$oauthstateanticsrf).'&username='.urlencode($usertotest).'&backtourl='.urldecode($backtourl); + $url = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode('forlogin-'.$shortscope.'-'.$oauthstateanticsrf).'&username='.urlencode($usertotest).'&backtourl='.urlencode($backtourl); // we go on oauth provider authorization page header('Location: '.$url); diff --git a/htdocs/core/login/functions_ldap.php b/htdocs/core/login/functions_ldap.php index 58ceff905b1..472c4948425 100644 --- a/htdocs/core/login/functions_ldap.php +++ b/htdocs/core/login/functions_ldap.php @@ -1,7 +1,7 @@ * Copyright (C) 2008-2021 Regis Houssin - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 William Mead * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ /** * Check validity of user/password/entity - * If test is ko, reason must be filled into $_SESSION["dol_loginmesg"] + * If the test fails, the reason must be filled into $_SESSION["dol_loginmesg"] * * @param string $usertotest Login * @param string $passwordtotest Password @@ -68,7 +68,7 @@ function check_user_password_ldap($usertotest, $passwordtotest, $entitytotest) if ($usertotest) { dol_syslog("functions_ldap::check_user_password_ldap usertotest=".$usertotest." passwordtotest=".preg_replace('/./', '*', $passwordtotest)." entitytotest=".$entitytotest); - // If test username/password asked, we define $test=false and $login var if ok, set $_SESSION["dol_loginmesg"] if ko + // If testing user/password is requested, we define $test=false and $login var if successful, and set $_SESSION["dol_loginmesg"] if failed. $ldaphost = $dolibarr_main_auth_ldap_host; $ldapport = $dolibarr_main_auth_ldap_port; $ldapversion = $dolibarr_main_auth_ldap_version; @@ -141,7 +141,7 @@ function check_user_password_ldap($usertotest, $passwordtotest, $entitytotest) // If LDAP need a dn with login like "uid=jbloggs,ou=People,dc=foo,dc=com", default dn may work even if previous code with // admin login no executed. $ldap->searchUser = $ldapuserattr."=".$usertotest.",".$ldapdn; // Default dn (will work if LDAP accept a dn with login value inside) - // But if LDAP need a dn with name like "cn=Jhon Bloggs,ou=People,dc=foo,dc=com", previous part must have been executed to have + // However, if LDAP requires a DN with a name like "cn=Jhon Bloggs,ou=People,dc=foo,dc=com", the previous part must have been executed to have // dn detected into ldapUserDN. if ($resultFetchLdapUser && !empty($ldap->ldapUserDN)) { $ldap->searchUser = $ldap->ldapUserDN; @@ -166,11 +166,11 @@ function check_user_password_ldap($usertotest, $passwordtotest, $entitytotest) // Note: Test on date validity is done later natively with isNotIntoValidityDateRange() by core after calling checkLoginPassEntity() that call this method - // ldap2dolibarr synchronisation + // ldap2dolibarr synchronization if ($login && isModEnabled('ldap') && getDolGlobalInt('LDAP_SYNCHRO_ACTIVE') == Ldap::SYNCHRO_LDAP_TO_DOLIBARR) { // ldap2dolibarr synchronization dol_syslog("functions_ldap::check_user_password_ldap Sync ldap2dolibarr"); - // On charge les attributes du user ldap + // We fetch the LDAP user attributes if ($ldapdebug) { print "DEBUG: login ldap = ".$login."
\n"; } @@ -186,7 +186,7 @@ function check_user_password_ldap($usertotest, $passwordtotest, $entitytotest) print "DEBUG: badPasswordTime = ".dol_print_date($ldap->badpwdtime, 'day')."
\n"; } - // On recherche le user dolibarr en fonction de son SID ldap (only for Active Directory) + // We search for the Dolibarr user based on its LDAP SID (only for Active Directory) $sid = null; if (getDolGlobalString('LDAP_SERVER_TYPE') == "activedirectory") { $sid = $ldap->getObjectSid($login); diff --git a/htdocs/core/menus/standard/auguria.lib.php b/htdocs/core/menus/standard/auguria.lib.php index 2390b91712a..75aec03ab68 100644 --- a/htdocs/core/menus/standard/auguria.lib.php +++ b/htdocs/core/menus/standard/auguria.lib.php @@ -1,7 +1,7 @@ * Copyright (C) 2010-2012 Regis Houssin - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Frédéric France * Copyright (C) 2025 Alexandre Spangaro * @@ -382,7 +382,7 @@ function print_left_auguria_menu($db, $menu_array_before, $menu_array_after, &$t $sql = "SELECT rowid, label, courant, rappro, courant"; $sql .= " FROM ".MAIN_DB_PREFIX."bank_account"; - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); $sql .= " AND clos = 0"; $sql .= " ORDER BY label"; @@ -415,7 +415,7 @@ function print_left_auguria_menu($db, $menu_array_before, $menu_array_after, &$t // Multi journal $sql = "SELECT rowid, code, label, nature"; $sql .= " FROM ".MAIN_DB_PREFIX."accounting_journal"; - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); if (getDolGlobalString('ACCOUNTING_MODE') == 'RECETTES-DEPENSES') { $sql .= " AND nature = 4"; // only bank journal when using treasury accounting mode } diff --git a/htdocs/core/menus/standard/eldy.lib.php b/htdocs/core/menus/standard/eldy.lib.php index 28417c2f98f..5926d7bafd0 100644 --- a/htdocs/core/menus/standard/eldy.lib.php +++ b/htdocs/core/menus/standard/eldy.lib.php @@ -883,7 +883,7 @@ function print_left_eldy_menu($db, $menu_array_before, $menu_array_after, &$tabM get_left_menu_members($mainmenu, $newmenu, $usemenuhider, $leftmenu, $type_user); } - // Add personalized menus and modules menus + // Add personalized menus (from database setup) and modules menus //var_dump($newmenu->liste); // $menuArbo = new Menubase($db, 'eldy'); $newmenu = $menuArbo->menuLeftCharger($newmenu, $mainmenu, $leftmenu, (empty($user->socid) ? 0 : 1), 'eldy', $tabMenu); @@ -908,7 +908,7 @@ function print_left_eldy_menu($db, $menu_array_before, $menu_array_after, &$tabM //var_dump($newmenu->liste); // Build final $menu_array = $menu_array_before +$newmenu->liste + $menu_array_after - //var_dump($menu_array_before);exit; + //var_dump($menu_array_after);exit; $menu_array = $newmenu->liste; if (is_array($menu_array_before)) { @@ -1063,7 +1063,12 @@ function print_left_eldy_menu($db, $menu_array_before, $menu_array_after, &$tabM print '
'; -$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); +$max = getDolUserInt('MAIN_SIZE_SHORTLIST_LIMIT', getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5)); /* diff --git a/htdocs/don/payment/card.php b/htdocs/don/payment/card.php index 537f979e5e3..a4de324e766 100644 --- a/htdocs/don/payment/card.php +++ b/htdocs/don/payment/card.php @@ -1,7 +1,7 @@ * Copyright (C) 2019-2026 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -110,7 +110,7 @@ if (empty($object->id)) { $h = 0; $head = []; -$head[$h][0] = dolBuildUrl(DOL_URL_ROOT.'/don/payment/card.php', ['id'=> $id]); +$head[$h][0] = dolBuildUrl(DOL_URL_ROOT.'/don/payment/card.php', ['id' => $id]); $head[$h][1] = $langs->trans("DonationPayment"); $hselected = (string) $h; $h++; @@ -172,7 +172,7 @@ $disable_delete = 0; $sql = 'SELECT d.rowid as did, d.paid, d.amount as d_amount, pd.amount'; $sql .= ' FROM '.MAIN_DB_PREFIX.'payment_donation as pd,'.MAIN_DB_PREFIX.'don as d'; $sql .= ' WHERE pd.fk_donation = d.rowid'; -$sql .= ' AND d.entity = '.$conf->entity; +$sql .= ' AND d.entity = '.((int) $conf->entity); $sql .= ' AND pd.rowid = '.((int) $id); dol_syslog("don/payment/card.php", LOG_DEBUG); diff --git a/htdocs/ecm/class/ecmdirectory.class.php b/htdocs/ecm/class/ecmdirectory.class.php index 7b9184e09c4..63b7644e8c5 100644 --- a/htdocs/ecm/class/ecmdirectory.class.php +++ b/htdocs/ecm/class/ecmdirectory.class.php @@ -599,7 +599,7 @@ class EcmDirectory extends CommonObject $sql = "SELECT fk_parent as id_parent, rowid as id_son"; $sql .= " FROM ".MAIN_DB_PREFIX."ecm_directories"; $sql .= " WHERE fk_parent != 0"; - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); dol_syslog(get_class($this)."::load_motherof", LOG_DEBUG); $resql = $this->db->query($sql); @@ -688,7 +688,7 @@ class EcmDirectory extends CommonObject $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."ecm_directories as ca"; $sql .= " ON c.rowid = ca.fk_parent"; $sql .= " WHERE c.fk_user_c = u.rowid"; - $sql .= " AND c.entity = ".$conf->entity; + $sql .= " AND c.entity = ".((int) $conf->entity); $sql .= " ORDER BY c.label, c.rowid"; dol_syslog(get_class($this)."::get_full_arbo", LOG_DEBUG); @@ -800,7 +800,7 @@ class EcmDirectory extends CommonObject if (empty($all)) { // By default $sql .= " WHERE rowid = ".((int) $this->id); } else { - $sql .= " WHERE entity = ".$conf->entity; + $sql .= " WHERE entity = ".((int) $conf->entity); } dol_syslog(get_class($this)."::refreshcachenboffile", LOG_DEBUG); diff --git a/htdocs/ecm/class/ecmfiles.class.php b/htdocs/ecm/class/ecmfiles.class.php index 3dbfc6f401e..f890a1c6e3e 100644 --- a/htdocs/ecm/class/ecmfiles.class.php +++ b/htdocs/ecm/class/ecmfiles.class.php @@ -62,6 +62,11 @@ class EcmFiles extends CommonObject */ public $share; + /** + * @var EcmFilesLine[] + */ + public $lines = array(); + /** * @var ?string filename, Note: Into ecm database record, the entry never ends with .noexe */ @@ -464,7 +469,7 @@ class EcmFiles extends CommonObject if (isset($entity)) { $sql .= " AND t.entity = " . (int) $entity; } else { - $sql .= " AND t.entity = " . $conf->entity; // unique key include the entity so each company has its own index + $sql .= " AND t.entity = " . ((int) $conf->entity); // unique key include the entity so each company has its own index } $filterfound++; } @@ -473,7 +478,7 @@ class EcmFiles extends CommonObject if (isset($entity)) { $sql .= " AND t.entity = " . (int) $entity; } else { - $sql .= " AND t.entity = " . $conf->entity; // unique key include the entity so each company has its own index + $sql .= " AND t.entity = " . ((int) $conf->entity); // unique key include the entity so each company has its own index } $filterfound++; } @@ -482,7 +487,7 @@ class EcmFiles extends CommonObject if (isset($entity)) { $sql .= " AND t.entity = " . (int) $entity; } else { - $sql .= " AND t.entity = " . $conf->entity; // unique key include the entity so each company has its own index + $sql .= " AND t.entity = " . ((int) $conf->entity); // unique key include the entity so each company has its own index } $filterfound++; } @@ -492,7 +497,7 @@ class EcmFiles extends CommonObject } else { $sql .= " AND t.share IS NOT NULL AND t.share <> ''"; } - //$sql .= " AND t.entity = ".$conf->entity; // hashforshare already unique + //$sql .= " AND t.entity = ".((int) $conf->entity); // hashforshare already unique $filterfound++; } if ($src_object_type && $src_object_id) { @@ -500,7 +505,7 @@ class EcmFiles extends CommonObject if (isset($entity)) { $sql .= " AND t.entity = " . (int) $entity; } else { - $sql .= " AND t.entity = " . $conf->entity; // unique key include the entity so each company has its own index + $sql .= " AND t.entity = " . ((int) $conf->entity); // unique key include the entity so each company has its own index } $filterfound++; } @@ -684,7 +689,9 @@ class EcmFiles extends CommonObject $line->src_object_type = $obj->src_object_type; $line->src_object_id = $obj->src_object_id; $line->agenda_id = $obj->agenda_id; + $line->fetch_optionals(); + $this->lines[] = $line; } $this->db->free($resql); @@ -769,7 +776,7 @@ class EcmFiles extends CommonObject $sql .= " ref = '".$this->db->escape(dol_hash($this->filepath."/".$this->filename, '3'))."',"; $sql .= ' label = '.(isset($this->label) ? "'".$this->db->escape($this->label)."'" : "null").','; $sql .= ' share = '.(!empty($this->share) ? "'".$this->db->escape($this->share)."'" : "null").','; - $sql .= ' entity = '.(isset($this->entity) ? $this->entity : $conf->entity).','; + $sql .= ' entity = '.(isset($this->entity) ? ((int) $this->entity) : ((int) $conf->entity)).','; $sql .= ' filename = '.(isset($this->filename) ? "'".$this->db->escape($this->filename)."'" : "null").','; $sql .= ' filepath = '.(isset($this->filepath) ? "'".$this->db->escape($this->filepath)."'" : "null").','; $sql .= ' fullpath_orig = '.(isset($this->fullpath_orig) ? "'".$this->db->escape($this->fullpath_orig)."'" : "null").','; @@ -1253,4 +1260,12 @@ class EcmFilesLine extends CommonObjectLine * @var int */ public $src_object_id; + /** + * @var int + */ + public $agenda_id; + /** + * @var ?string Hash for file sharing + */ + public $share; } diff --git a/htdocs/ecm/dir_add_card.php b/htdocs/ecm/dir_add_card.php index 071ecb1a581..9e63db431f1 100644 --- a/htdocs/ecm/dir_add_card.php +++ b/htdocs/ecm/dir_add_card.php @@ -53,7 +53,7 @@ $cancel = GETPOST('cancel'); $backtopage = GETPOST('backtopage', 'alpha'); $confirm = GETPOST('confirm', 'alpha'); -$module = GETPOST('module', 'alpha'); +$module = GETPOST('module', 'aZ09arobase'); $website = GETPOST('website', 'alpha'); $pageid = GETPOSTINT('pageid'); if (empty($module)) { diff --git a/htdocs/ecm/dir_card.php b/htdocs/ecm/dir_card.php index b20a5025fd0..7245dd5e895 100644 --- a/htdocs/ecm/dir_card.php +++ b/htdocs/ecm/dir_card.php @@ -49,7 +49,7 @@ $cancel = GETPOST('cancel'); $backtopage = GETPOST('backtopage', 'alpha'); $confirm = GETPOST('confirm', 'alpha'); -$module = GETPOST('module', 'alpha'); +$module = GETPOST('module', 'aZ09arobase'); $website = GETPOST('website', 'alpha'); $pageid = GETPOSTINT('pageid'); if (empty($module)) { diff --git a/htdocs/ecm/file_card.php b/htdocs/ecm/file_card.php index ae08004edcf..760d4fb452e 100644 --- a/htdocs/ecm/file_card.php +++ b/htdocs/ecm/file_card.php @@ -48,7 +48,7 @@ $langs->loadLangs(array('ecm', 'companies', 'other', 'users', 'orders', 'propal' $action = GETPOST('action', 'aZ09'); $cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); -$module = GETPOST('module', 'alpha'); +$module = GETPOST('module', 'aZ09arobase'); // Get parameters $socid = GETPOSTINT("socid"); diff --git a/htdocs/ecm/index.php b/htdocs/ecm/index.php index ff30d95c5ff..e7eaf0fdbce 100644 --- a/htdocs/ecm/index.php +++ b/htdocs/ecm/index.php @@ -2,7 +2,7 @@ /* Copyright (C) 2008-2017 Laurent Destailleur * Copyright (C) 2008-2010 Regis Houssin * Copyright (C) 2024 Frédéric France - * Copyright (C) 2025 MDW + * Copyright (C) 2025-2026 MDW * * 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 @@ -215,16 +215,16 @@ if ($action == 'refreshmanual' && $permissiontoread) { $disktree = dol_dir_list($conf->ecm->dir_output, 'directories', 1, '', '^temp$', '', 0, 0); // Scan directory tree in database - $sqltree = $ecmdirstatic->get_full_arbo(0); + $treesqldir = $ecmdirstatic->get_full_arbo(0); $adirwascreated = 0; // Now we compare both trees to complete missing trees into database //var_dump($disktree); - //var_dump($sqltree); + //var_dump($treesqldir); foreach ($disktree as $dirdesc) { // Loop on tree onto disk $dirisindatabase = 0; - foreach ($sqltree as $dirsqldesc) { + foreach ($treesqldir as $dirsqldesc) { if ($conf->ecm->dir_output.'/'.$dirsqldesc['fullrelativename'] == $dirdesc['fullname']) { $dirisindatabase = 1; break; @@ -248,7 +248,7 @@ if ($action == 'refreshmanual' && $permissiontoread) { dol_syslog($txt); //print $txt." -> "; $parentdirisindatabase = 0; - foreach ($sqltree as $dirsqldesc) { + foreach ($treesqldir as $dirsqldesc) { if ($dirsqldesc['fullrelativename'] == $relativepathtosearchparent) { $parentdirisindatabase = $dirsqldesc['id']; break; @@ -279,13 +279,13 @@ if ($action == 'refreshmanual' && $permissiontoread) { //print $ecmdirtmp->cachenbofdoc."
\n";exit; $id = $ecmdirtmp->create($user); if ($id > 0) { - $newdirsql = array('id' => $id, + $newsqldir = array('id' => $id, 'id_mere' => $ecmdirtmp->fk_parent, 'label' => $ecmdirtmp->label, 'description' => $ecmdirtmp->description, 'fullrelativename' => $relativepathmissing); - $sqltree[] = $newdirsql; // We complete fulltree for following loops - //var_dump($sqltree); + $treesqldir[] = $newsqldir; // We complete fulltree for following loops + //var_dump($treesqldir); $adirwascreated = 1; } else { dol_syslog("Failed to create directory ".$ecmdirtmp->label, LOG_ERR); @@ -299,7 +299,7 @@ if ($action == 'refreshmanual' && $permissiontoread) { } // Loop now on each sql tree to check if dir exists - foreach ($sqltree as $dirdesc) { // Loop on each sqltree to check dir is on disk + foreach ($treesqldir as $dirdesc) { // Loop on each treesqldir to check dir is on disk $dirtotest = $conf->ecm->dir_output.'/'.$dirdesc['fullrelativename']; if (!dol_is_dir($dirtotest)) { $ecmdirtmp->id = $dirdesc['id']; @@ -315,7 +315,7 @@ if ($action == 'refreshmanual' && $permissiontoread) { // If a directory was added, the fulltree array is not correctly completed and sorted, so we clean // it to be sure that fulltree array is not used without reloading it. if ($adirwascreated) { - $sqltree = null; + $treesqldir = null; } } @@ -329,7 +329,7 @@ if ($action == 'refreshmanual' && $permissiontoread) { //print $_SESSION["dol_screenheight"]; $maxheightwin = (isset($_SESSION["dol_screenheight"]) && $_SESSION["dol_screenheight"] > 466) ? ($_SESSION["dol_screenheight"] - 136) : 660; // Also into index_auto.php file -$morejs=array(); +$morejs = array(); if (!getDolGlobalString('MAIN_ECM_DISABLE_JS')) { $morejs[] = "public/includes/jquery/plugins/jqueryFileTree/jqueryFileTree.js"; } diff --git a/htdocs/ecm/index_auto.php b/htdocs/ecm/index_auto.php index dd2d0b12ade..792efe753dd 100644 --- a/htdocs/ecm/index_auto.php +++ b/htdocs/ecm/index_auto.php @@ -28,12 +28,6 @@ // Load Dolibarr environment require '../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/ecm.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/treeview.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmdirectory.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -41,6 +35,11 @@ require_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmdirectory.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/ecm.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/treeview.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmdirectory.class.php'; // Load translation files required by the page $langs->loadLangs(array("ecm", "companies", "other", "users", "orders", "propal", "bills", "contracts")); @@ -49,7 +48,7 @@ $langs->loadLangs(array("ecm", "companies", "other", "users", "orders", "propal" $socid = GETPOSTINT('socid'); $action = GETPOST('action', 'aZ09'); $section = GETPOSTINT('section') ? GETPOSTINT('section') : GETPOSTINT('section_id'); -$module = GETPOST('module', 'alpha'); +$module = GETPOST('module', 'aZ09arobase'); if (!$section) { $section = 0; } @@ -185,16 +184,16 @@ if ($action == 'refreshmanual' && $user->hasRight('ecm', 'read')) { $disktree = dol_dir_list($conf->ecm->dir_output, 'directories', 1, '', '^temp$', '', 0, 0); // Scan directory tree in database - $sqltree = $ecmdirstatic->get_full_arbo(0); + $treesqldir = $ecmdirstatic->get_full_arbo(0); $adirwascreated = 0; // Now we compare both trees to complete missing trees into database //var_dump($disktree); - //var_dump($sqltree); + //var_dump($treesqldir); foreach ($disktree as $dirdesc) { // Loop on tree onto disk $dirisindatabase = 0; - foreach ($sqltree as $dirsqldesc) { + foreach ($treesqldir as $dirsqldesc) { if ($conf->ecm->dir_output.'/'.$dirsqldesc['fullrelativename'] == $dirdesc['fullname']) { $dirisindatabase = 1; break; @@ -218,7 +217,7 @@ if ($action == 'refreshmanual' && $user->hasRight('ecm', 'read')) { dol_syslog($txt); //print $txt." -> "; $parentdirisindatabase = 0; - foreach ($sqltree as $dirsqldesc) { + foreach ($treesqldir as $dirsqldesc) { if ($dirsqldesc['fullrelativename'] == $relativepathtosearchparent) { $parentdirisindatabase = $dirsqldesc['id']; break; @@ -249,13 +248,13 @@ if ($action == 'refreshmanual' && $user->hasRight('ecm', 'read')) { //print $ecmdirtmp->cachenbofdoc."
\n";exit; $id = $ecmdirtmp->create($user); if ($id > 0) { - $newdirsql = array('id' => $id, + $newsqldir = array('id' => $id, 'id_mere' => $ecmdirtmp->fk_parent, 'label' => $ecmdirtmp->label, 'description' => $ecmdirtmp->description, 'fullrelativename' => $relativepathmissing); - $sqltree[] = $newdirsql; // We complete fulltree for following loops - //var_dump($sqltree); + $treesqldir[] = $newsqldir; // We complete fulltree for following loops + //var_dump($treesqldir); $adirwascreated = 1; } else { dol_syslog("Failed to create directory ".$ecmdirtmp->label, LOG_ERR); @@ -269,7 +268,7 @@ if ($action == 'refreshmanual' && $user->hasRight('ecm', 'read')) { } // Loop now on each sql tree to check if dir exists - foreach ($sqltree as $dirdesc) { // Loop on each sqltree to check dir is on disk + foreach ($treesqldir as $dirdesc) { // Loop on each treesqldir to check dir is on disk $dirtotest = $conf->ecm->dir_output.'/'.$dirdesc['fullrelativename']; if (!dol_is_dir($dirtotest)) { $ecmdirtmp->id = $dirdesc['id']; @@ -285,7 +284,7 @@ if ($action == 'refreshmanual' && $user->hasRight('ecm', 'read')) { // If a directory was added, the fulltree array is not correctly completed and sorted, so we clean // it to be sure that fulltree array is not used without reloading it. if ($adirwascreated) { - $sqltree = null; + $treesqldir = null; } } diff --git a/htdocs/ecm/index_medias.php b/htdocs/ecm/index_medias.php index b4abcf31565..90c337d5660 100644 --- a/htdocs/ecm/index_medias.php +++ b/htdocs/ecm/index_medias.php @@ -2,6 +2,7 @@ /* Copyright (C) 2008-2017 Laurent Destailleur * Copyright (C) 2008-2010 Regis Houssin * Copyright (C) 2024-2025 Frédéric France + * Copyright (C) 2026 MDW * * 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 @@ -183,14 +184,14 @@ if ($action == 'refreshmanual' && $permissiontoread) { $disktree = dol_dir_list($conf->ecm->dir_output, 'directories', 1, '', '^temp$', '', 0, 0); // Scan directory tree in database - $sqltree = $ecmdirstatic->get_full_arbo(0); + $treesqldir = $ecmdirstatic->get_full_arbo(0); $adirwascreated = 0; // Now we compare both trees to complete missing trees into database foreach ($disktree as $dirdesc) { // Loop on tree onto disk $dirisindatabase = 0; - foreach ($sqltree as $dirsqldesc) { + foreach ($treesqldir as $dirsqldesc) { if ($conf->ecm->dir_output.'/'.$dirsqldesc['fullrelativename'] == $dirdesc['fullname']) { $dirisindatabase = 1; break; @@ -213,7 +214,7 @@ if ($action == 'refreshmanual' && $permissiontoread) { dol_syslog($txt); //print $txt." -> "; $parentdirisindatabase = 0; - foreach ($sqltree as $dirsqldesc) { + foreach ($treesqldir as $dirsqldesc) { if ($dirsqldesc['fullrelativename'] == $relativepathtosearchparent) { $parentdirisindatabase = $dirsqldesc['id']; break; @@ -244,14 +245,14 @@ if ($action == 'refreshmanual' && $permissiontoread) { //print $ecmdirtmp->cachenbofdoc."
\n";exit; $id = $ecmdirtmp->create($user); if ($id > 0) { - $newdirsql = [ + $newsqldir = [ 'id' => $id, 'id_mere' => $ecmdirtmp->fk_parent, 'label' => $ecmdirtmp->label, 'description' => $ecmdirtmp->description, 'fullrelativename' => $relativepathmissing, ]; - $sqltree[] = $newdirsql; // We complete fulltree for following loops + $treesqldir[] = $newsqldir; // We complete fulltree for following loops $adirwascreated = 1; } else { dol_syslog("Failed to create directory ".$ecmdirtmp->label, LOG_ERR); @@ -264,7 +265,7 @@ if ($action == 'refreshmanual' && $permissiontoread) { } // Loop now on each sql tree to check if dir exists - foreach ($sqltree as $dirdesc) { // Loop on each sqltree to check dir is on disk + foreach ($treesqldir as $dirdesc) { // Loop on each treesqldir to check dir is on disk $dirtotest = $conf->ecm->dir_output.'/'.$dirdesc['fullrelativename']; if (!dol_is_dir($dirtotest)) { $ecmdirtmp->id = $dirdesc['id']; @@ -280,7 +281,7 @@ if ($action == 'refreshmanual' && $permissiontoread) { // If a directory was added, the fulltree array is not correctly completed and sorted, so we clean // it to be sure that fulltree array is not used without reloading it. if ($adirwascreated) { - $sqltree = null; + $treesqldir = null; } } diff --git a/htdocs/ecm/search.php b/htdocs/ecm/search.php index 4794c95c8da..76788029518 100644 --- a/htdocs/ecm/search.php +++ b/htdocs/ecm/search.php @@ -60,7 +60,7 @@ if (!$section) { $section = 0; } -$module = GETPOST('module', 'alpha'); +$module = GETPOST('module', 'aZ09arobase'); $website = GETPOST('website', 'alpha'); $pageid = GETPOSTINT('pageid'); if (empty($module)) { diff --git a/htdocs/ecm/tpl/enablefiletreeajax.tpl.php b/htdocs/ecm/tpl/enablefiletreeajax.tpl.php index 30181d55171..67288acf25c 100644 --- a/htdocs/ecm/tpl/enablefiletreeajax.tpl.php +++ b/htdocs/ecm/tpl/enablefiletreeajax.tpl.php @@ -115,7 +115,7 @@ function loadandshowpreview(filedirname,section) $('#ecmfileview').empty(); - var url = '?action=preview&module=§ion='+section+'&file='+urlencode(filedirname); + var url = '§ion='+urlencode(section)+'&file='+urlencode(filedirname); $.get(url, function(data) { //alert('Load of url '+url+' was performed : '+data); pos=data.indexOf("TYPE=directory",0); diff --git a/htdocs/emailcollector/class/emailcollector.class.php b/htdocs/emailcollector/class/emailcollector.class.php index e5176e9084f..e074a3e66e4 100644 --- a/htdocs/emailcollector/class/emailcollector.class.php +++ b/htdocs/emailcollector/class/emailcollector.class.php @@ -2288,10 +2288,10 @@ class EmailCollector extends CommonObject $reg = array(); if (preg_match('/^(.*)<(.*)>$/', $fromstring, $reg)) { - $from = $reg[2]; + $email_from = $reg[2]; $fromtext = $reg[1]; } else { - $from = $fromstring; + $email_from = $fromstring; $fromtext = ''; } if (preg_match('/^(.*)<(.*)>$/', $replytostring, $reg)) { @@ -2315,7 +2315,7 @@ class EmailCollector extends CommonObject } if (empty($emailsToCheck)) { - foreach (array($from, $replyto) as $tmpEmail) { + foreach (array($email_from, $replyto) as $tmpEmail) { $tmpEmail = strtolower(trim((string) $tmpEmail)); if ($tmpEmail !== '' && strpos($tmpEmail, '@') !== false) { $emailsToCheck[$tmpEmail] = true; @@ -2635,28 +2635,28 @@ class EmailCollector extends CommonObject } if (empty($contactid)) { // Try to find contact using email - $result = $contactstatic->fetch(0, null, '', $from); + $result = $contactstatic->fetch(0, null, '', $email_from); if ($result > 0) { - dol_syslog("We found a contact with the email ".$from); + dol_syslog("We found a contact with the email ".$email_from); $contactid = $contactstatic->id; - $contactfoundby = 'email of contact ('.$from.')'; + $contactfoundby = 'email of contact ('.$email_from.')'; if (empty($thirdpartyid) && $contactstatic->socid > 0) { $result = $thirdpartystatic->fetch($contactstatic->socid); if ($result > 0) { $thirdpartyid = $thirdpartystatic->id; - $thirdpartyfoundby = 'email of contact ('.$from.')'; + $thirdpartyfoundby = 'email of contact ('.$email_from.')'; } } } } if (empty($thirdpartyid)) { // Try to find thirdparty using email - $result = $thirdpartystatic->fetch(0, '', '', '', '', '', '', '', '', '', $from); + $result = $thirdpartystatic->fetch(0, '', '', '', '', '', '', '', '', '', $email_from); if ($result > 0) { - dol_syslog("We found a thirdparty with the email ".$from); + dol_syslog("We found a thirdparty with the email ".$email_from); $thirdpartyid = $thirdpartystatic->id; - $thirdpartyfoundby = 'email ('.$from.')'; + $thirdpartyfoundby = 'email ('.$email_from.')'; } } @@ -3005,7 +3005,7 @@ class EmailCollector extends CommonObject } else { $thirdpartystatic->name_alias = (empty($replytostring) ? (empty($fromtext) ? '' : $fromtext) : $replytostring); } - $thirdpartystatic->email = (empty($emailtouseforthirdparty) ? (empty($replyto) ? (empty($from) ? '' : $from) : $replyto) : $emailtouseforthirdparty); + $thirdpartystatic->email = (empty($emailtouseforthirdparty) ? (empty($replyto) ? (empty($email_from) ? '' : $email_from) : $replyto) : $emailtouseforthirdparty); // Overwrite values with values extracted from source email $errorforthisaction = $this->overwritePropertiesOfObject($thirdpartystatic, $operation['actionparam'], $messagetext, $subject, $header, $operationslog); @@ -3051,26 +3051,26 @@ class EmailCollector extends CommonObject if ($errorforthisaction) { $errorforactions++; } else { - if (!empty($contact_static->email) && $contact_static->email != $from) { - $from = $contact_static->email; + if (!empty($contact_static->email) && $contact_static->email != $email_from) { + $email_from = $contact_static->email; } - $from = (string) $from; + $email_from = (string) $email_from; - $from = (string) $from; + $email_from = (string) $email_from; - $result = $contactstatic->fetch(0, null, '', $from); + $result = $contactstatic->fetch(0, null, '', $email_from); if ($result < 0) { $errorforactions++; - $this->error = 'Error when getting contact with email ' . $from; + $this->error = 'Error when getting contact with email ' . $email_from; $this->errors[] = $this->error; break; } elseif ($result == 0) { - dol_syslog("Contact with email " . $from . " was not found. We try to create it."); + dol_syslog("Contact with email " . $email_from . " was not found. We try to create it."); $contactstatic = new Contact($this->db); // Create contact - $contactstatic->email = $from; - $operationslog .= '
We set property email='.dol_escape_htmltag($from); + $contactstatic->email = $email_from; + $operationslog .= '
We set property email='.dol_escape_htmltag($email_from); // Overwrite values with values extracted from source email $errorforthisaction = $this->overwritePropertiesOfObject($contactstatic, $operation['actionparam'], $messagetext, $subject, $header, $operationslog); @@ -3195,7 +3195,7 @@ class EmailCollector extends CommonObject // Insert record of emails sent $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...) $actioncomm->code = 'AC_'.$actioncode; - $actioncomm->label = $langs->trans("ActionAC_".$actioncode).' - '.$langs->trans("MailFrom").' '.$from; + $actioncomm->label = $langs->trans("ActionAC_".$actioncode).' - '.$langs->trans("MailFrom").' '.$email_from; $actioncomm->note_private = $descriptionfull; $actioncomm->fk_project = $projectstatic->id; $actioncomm->datep = $dateemail; // date of email @@ -3294,10 +3294,10 @@ class EmailCollector extends CommonObject } } if (count($data) > 0) { - $sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."user WHERE email LIKE '%".$this->db->escape($from)."%'"; + $sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."user WHERE email LIKE '%".$this->db->escape($email_from)."%'"; $resql = $this->db->query($sql); if ($this->db->num_rows($resql) == 0) { - $this->errors[] = "User Not allowed to add documents ({$from})"; + $this->errors[] = "User Not allowed to add documents ({$email_from})"; } $arrayobject = array( 'propale' => array('table' => 'propal', @@ -3443,7 +3443,7 @@ class EmailCollector extends CommonObject foreach ($dirs as $target) { $prefix = $this->actions[$this->id]['actionparam']; foreach ($data as $filename => $content) { - $resr = saveAttachment($target, $prefix . '_' . $filename, $content); + $resr = $this->saveAttachment($target, $prefix . '_' . $filename, $content); if ($resr == -1) { $this->errors[] = 'Doc not saved'; } @@ -3641,7 +3641,7 @@ class EmailCollector extends CommonObject $tickettocreate->type_code = (getDolGlobalString('MAIN_EMAILCOLLECTOR_TICKET_TYPE_CODE', dol_getIdFromCode($this->db, 1, 'c_ticket_type', 'use_default', 'code', 1))); $tickettocreate->category_code = (getDolGlobalString('MAIN_EMAILCOLLECTOR_TICKET_CATEGORY_CODE', dol_getIdFromCode($this->db, 1, 'c_ticket_category', 'use_default', 'code', 1))); $tickettocreate->severity_code = (getDolGlobalString('MAIN_EMAILCOLLECTOR_TICKET_SEVERITY_CODE', dol_getIdFromCode($this->db, 1, 'c_ticket_severity', 'use_default', 'code', 1))); - $tickettocreate->origin_email = $from; + $tickettocreate->origin_email = $email_from; $tickettocreate->origin_replyto = (!empty($replyto) ? $replyto : null); $tickettocreate->origin_references = (!empty($headers['References']) ? $headers['References'] : null); $tickettocreate->fk_user_create = $user->id; @@ -3723,7 +3723,7 @@ class EmailCollector extends CommonObject // Add sender to context array to make sure that confirmation e-mail can be sent by trigger script $sender_contact = new Contact($this->db); - $sender_contact->fetch(0, null, '', $from); + $sender_contact->fetch(0, null, '', $email_from); if (!empty($sender_contact->id)) { $tickettocreate->context['contact_id'] = $sender_contact->id; } @@ -3791,8 +3791,8 @@ class EmailCollector extends CommonObject $candidaturetocreate->type_code = 0; $candidaturetocreate->category_code = null; $candidaturetocreate->severity_code = null; - $candidaturetocreate->email = $from; - //$candidaturetocreate->lastname = $langs->trans("Anonymous").' - '.$from; + $candidaturetocreate->email = $email_from; + //$candidaturetocreate->lastname = $langs->trans("Anonymous").' - '.$email_from; $candidaturetocreate->fk_user_creat = $user->id; $candidaturetocreate->date_creation = dol_now(); $candidaturetocreate->fk_project = $projectstatic->id; @@ -3877,7 +3877,7 @@ class EmailCollector extends CommonObject 'imapemail' => $imapemail, 'overview' => $overview, - 'from' => $from, + 'from' => $email_from, 'fromtext' => $fromtext, 'actionparam' => $operation['actionparam'], @@ -4461,33 +4461,56 @@ class EmailCollector extends CommonObject /** * saveAttachment * - * @param string $destdir destination - * @param string $filename filename - * @param string $content content - * @return void + * @param string $destdir destination + * @param string $filename filename + * @param string $content content + * @return string|-1 Return the path to the saved file, or -1 if error */ private function saveAttachment($destdir, $filename, $content) { require_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php'; - $tmparraysize = getDefaultImageSizes(); - $maxwidthsmall = $tmparraysize['maxwidthsmall']; - $maxheightsmall = $tmparraysize['maxheightsmall']; - $maxwidthmini = $tmparraysize['maxwidthmini']; - $maxheightmini = $tmparraysize['maxheightmini']; - $quality = $tmparraysize['quality']; + $destdir = dol_sanitizePathName($destdir); + $filename = dol_sanitizeFileName($filename); - file_put_contents($destdir.'/'.$filename, $content); - dolChmod($destdir.'/'.$filename); + if (!file_exists($destdir)) { + if (dol_mkdir($destdir) < 0) { + return -1; + } + } + + $tmp = explode('.', $filename); + $ext = array_pop($tmp); + $filename = implode('.', $tmp); + + $i = 1; + $filepath = $destdir . (preg_match('/\/$/', $destdir) ? '' : '/') . $filename . '.' . $ext; + + while (file_exists($filepath)) { + $filepath = $destdir . (preg_match('/\/$/', $destdir) ? '' : '/') . $filename . '(' . $i . ').' . $ext; + $i++; + } + + file_put_contents($filepath, $content); + dolChmod($filepath); if (image_format_supported($filename) == 1) { + $tmparraysize = getDefaultImageSizes(); + $maxwidthsmall = $tmparraysize['maxwidthsmall']; + $maxheightsmall = $tmparraysize['maxheightsmall']; + $maxwidthmini = $tmparraysize['maxwidthmini']; + $maxheightmini = $tmparraysize['maxheightmini']; + $quality = $tmparraysize['quality']; + // Create thumbs - vignette($destdir.'/'.$filename, $maxwidthsmall, $maxheightsmall, '_small', $quality, "thumbs"); + vignette($filepath, $maxwidthsmall, $maxheightsmall, '_small', $quality, "thumbs"); // Create mini thumbs for image (Ratio is near 16/9) - vignette($destdir.'/'.$filename, $maxwidthmini, $maxheightmini, '_mini', $quality, "thumbs"); + vignette($filepath, $maxwidthmini, $maxheightmini, '_mini', $quality, "thumbs"); } addFileIntoDatabaseIndex($destdir, $filename); + + return $filepath; } /** diff --git a/htdocs/emailcollector/lib/emailcollector.lib.php b/htdocs/emailcollector/lib/emailcollector.lib.php index 43f44c92bf4..5fd4281957b 100644 --- a/htdocs/emailcollector/lib/emailcollector.lib.php +++ b/htdocs/emailcollector/lib/emailcollector.lib.php @@ -163,38 +163,6 @@ function getFileData($jk, $fpos, $type, $mbox) return $data; } -/** - * Save the attached file into a directory with a given name - * - * @param string $path Path to file - * @param string $filename Name of file - * @param mixed $data Content to save - * @return string|-1 Return the path to the saved file, or -1 if error - **/ -function saveAttachment($path, $filename, $data) -{ - $tmp = explode('.', $filename); - $ext = array_pop($tmp); - $filename = implode('.', $tmp); - if (!file_exists($path)) { - if (dol_mkdir($path) < 0) { - return -1; - } - } - - $i = 1; - $filepath = $path . $filename . '.' . $ext; - - while (file_exists($filepath)) { - $filepath = $path . $filename . '(' . $i . ').' . $ext; - $i++; - } - file_put_contents($filepath, $data); - dolChmod($filepath); - - return $filepath; -} - /** * Decode content of a message * diff --git a/htdocs/eventorganization/class/conferenceorboothattendee.class.php b/htdocs/eventorganization/class/conferenceorboothattendee.class.php index 49c80af2030..486ab4ed32c 100644 --- a/htdocs/eventorganization/class/conferenceorboothattendee.class.php +++ b/htdocs/eventorganization/class/conferenceorboothattendee.class.php @@ -639,14 +639,14 @@ class ConferenceOrBoothAttendee extends CommonObject if (preg_match('/^[\(]?PROV/i', $this->ref)) { // Now we rename also files into index $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'conferenceorboothattendee/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'conferenceorboothattendee/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'conferenceorboothattendee/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'conferenceorboothattendee/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'conferenceorboothattendee/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'conferenceorboothattendee/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; diff --git a/htdocs/eventorganization/core/actions_massactions_mail.inc.php b/htdocs/eventorganization/core/actions_massactions_mail.inc.php index 19c03151d67..8b63f9ed59b 100644 --- a/htdocs/eventorganization/core/actions_massactions_mail.inc.php +++ b/htdocs/eventorganization/core/actions_massactions_mail.inc.php @@ -4,7 +4,7 @@ * Copyright (C) 2018 Juanjo Menent * Copyright (C) 2019 Ferran Marcet * Copyright (C) 2019-2025 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -179,26 +179,26 @@ if (!$error && $massaction == 'confirm_presend_attendees') { $reg = array(); $fromtype = GETPOST('fromtype'); if ($fromtype === 'user') { - $from = $user->getFullName($langs) . ' <' . $user->email . '>'; + $email_from = $user->getFullName($langs) . ' <' . $user->email . '>'; } elseif ($fromtype === 'company') { - $from = getDolGlobalString('MAIN_INFO_SOCIETE_NOM') . ' <' . getDolGlobalString('MAIN_INFO_SOCIETE_MAIL') . '>'; + $email_from = getDolGlobalString('MAIN_INFO_SOCIETE_NOM') . ' <' . getDolGlobalString('MAIN_INFO_SOCIETE_MAIL') . '>'; } elseif (preg_match('/global_aliases_(\d+)/', $fromtype, $reg)) { $tmp = explode(',', getDolGlobalString('MAIN_INFO_SOCIETE_MAIL_ALIASES')); - $from = trim($tmp[((int) $reg[1] - 1)]); + $email_from = trim($tmp[((int) $reg[1] - 1)]); } elseif (preg_match('/senderprofile_(\d+)_(\d+)/', $fromtype, $reg)) { $sql = "SELECT rowid, label, email FROM " . MAIN_DB_PREFIX . "c_email_senderprofile WHERE rowid = " . (int) $reg[1]; $resql = $db->query($sql); $obj = $db->fetch_object($resql); if ($obj) { - $from = dol_string_nospecial($obj->label, ' ', array(",")) . ' <' . $obj->email . '>'; + $email_from = dol_string_nospecial($obj->label, ' ', array(",")) . ' <' . $obj->email . '>'; } else { - $from = ''; + $email_from = ''; } } else { - $from = dol_string_nospecial(GETPOST('fromname'), ' ', array(",")) . ' <' . GETPOST('frommail') . '>'; + $email_from = dol_string_nospecial(GETPOST('fromname'), ' ', array(",")) . ' <' . GETPOST('frommail') . '>'; } - $replyto = $from; + $replyto = $email_from; $subject = GETPOST('subject', 'restricthtml'); $message = GETPOST('message', 'restricthtml'); @@ -239,18 +239,18 @@ if (!$error && $massaction == 'confirm_presend_attendees') { // Send mail (substitutionarray must be done just before this) require_once DOL_DOCUMENT_ROOT . '/core/class/CMailFile.class.php'; - $mailfile = new CMailFile($subjectreplaced, $sendto, $from, $messagereplaced, array(), array(), array(), $sendtocc, $sendtobcc, 0, -1, '', '', "attendees_".$attendees->id, '', $sendcontext); + $mailfile = new CMailFile($subjectreplaced, $sendto, $email_from, $messagereplaced, array(), array(), array(), $sendtocc, $sendtobcc, 0, -1, '', '', "attendees_".$attendees->id, '', $sendcontext); if ($mailfile->error) { $resaction .= '
' . $mailfile->error . '
'; } else { $result = $mailfile->sendfile(); if ($result) { - $resaction .= $langs->trans('MailSuccessfulySent', $mailfile->getValidAddress($from, 2), $mailfile->getValidAddress($sendto, 2)) . '
'; // Must not contain " + $resaction .= $langs->trans('MailSuccessfulySent', $mailfile->getValidAddress($email_from, 2), $mailfile->getValidAddress($sendto, 2)) . '
'; // Must not contain " $error = 0; dol_syslog("Try to insert email event into agenda for objid=" . $attendees->id . " => objectobj=" . get_class($attendees)); - $actionmsg = $langs->transnoentities('MailSentByTo', $from, $sendto); + $actionmsg = $langs->transnoentities('MailSentByTo', $email_from, $sendto); if ($message) { if ($sendtocc) { $actionmsg = dol_concatdesc($actionmsg, $langs->transnoentities('Bcc') . ": " . $sendtocc); @@ -285,12 +285,12 @@ if (!$error && $massaction == 'confirm_presend_attendees') { } else { $langs->load("other"); if ($mailfile->error) { // @phpstan-ignore-line - $resaction .= $langs->trans('ErrorFailedToSendMail', $from, $sendto); + $resaction .= $langs->trans('ErrorFailedToSendMail', $email_from, $sendto); $resaction .= '
' . $mailfile->error . '
'; } elseif (getDolGlobalString('MAIN_DISABLE_ALL_MAILS')) { $resaction .= '
No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS
'; } else { - $resaction .= $langs->trans('ErrorFailedToSendMail', $from, $sendto) . '
(unhandled error)
'; + $resaction .= $langs->trans('ErrorFailedToSendMail', $email_from, $sendto) . '
(unhandled error)
'; } $nbignored++; } diff --git a/htdocs/eventorganization/eventorganizationindex.php b/htdocs/eventorganization/eventorganizationindex.php index bc7f6b2ead1..030c42952fc 100644 --- a/htdocs/eventorganization/eventorganizationindex.php +++ b/htdocs/eventorganization/eventorganizationindex.php @@ -44,7 +44,9 @@ $langs->loadLangs(array("eventorganization")); $action = GETPOST('action', 'aZ09'); $NBMAX = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); -$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); + +$max = getDolUserInt('MAIN_SIZE_SHORTLIST_LIMIT', getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5)); + $now = dol_now(); // Security check diff --git a/htdocs/expedition/card.php b/htdocs/expedition/card.php index 7e19580409b..753cb673962 100644 --- a/htdocs/expedition/card.php +++ b/htdocs/expedition/card.php @@ -2357,31 +2357,37 @@ if ($action == 'create' && $usercancreate) { print ''; print ''; if ($line->product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) { - if (isset($alreadyQtySetted[$line->fk_product][intval($warehouse_id)])) { - $deliverableQty = min($quantityToBeDelivered, $stock - $alreadyQtySetted[$line->fk_product][intval($warehouse_id)]); - } else { - if (!isset($alreadyQtySetted[$line->fk_product])) { - $alreadyQtySetted[$line->fk_product] = array(); + // For a virtual product (kit), $deliverableQty is already min($quantityToBeDelivered, $stock) computed above + // and the per-warehouse "stock" returned by loadStockForVirtualProduct() is $qtyWish, not a shared physical + // stock. The cross-line $alreadyQtySetted cap must not be applied for kits, otherwise the 2nd and next order + // lines of the same kit would be wrongly pre-filled with 0. + $tooltipClass = $tooltipTitle = ''; + if ($productChildrenNb <= 0) { + if (isset($alreadyQtySetted[$line->fk_product][intval($warehouse_id)])) { + $deliverableQty = min($quantityToBeDelivered, $stock - $alreadyQtySetted[$line->fk_product][intval($warehouse_id)]); + } else { + if (!isset($alreadyQtySetted[$line->fk_product])) { + $alreadyQtySetted[$line->fk_product] = array(); + } + + $deliverableQty = min($quantityToBeDelivered, $stock); } - $deliverableQty = min($quantityToBeDelivered, $stock); + if ($deliverableQty < 0) { + $deliverableQty = 0; + } + + if (!empty($alreadyQtySetted[$line->fk_product][intval($warehouse_id)])) { + $tooltipClass = ' classfortooltip'; + $tooltipTitle = $langs->trans('StockQuantitiesAlreadyAllocatedOnPreviousLines').' : '.$alreadyQtySetted[$line->fk_product][intval($warehouse_id)]; + } else { + $alreadyQtySetted[$line->fk_product][intval($warehouse_id)] = 0; + } + + $alreadyQtySetted[$line->fk_product][intval($warehouse_id)] = $deliverableQty + $alreadyQtySetted[$line->fk_product][intval($warehouse_id)]; } - if ($deliverableQty < 0) { - $deliverableQty = 0; - } - - $tooltipClass = $tooltipTitle = ''; - if (!empty($alreadyQtySetted[$line->fk_product][intval($warehouse_id)])) { - $tooltipClass = ' classfortooltip'; - $tooltipTitle = $langs->trans('StockQuantitiesAlreadyAllocatedOnPreviousLines') . ' : ' . $alreadyQtySetted[$line->fk_product][intval($warehouse_id)]; - } else { - $alreadyQtySetted[$line->fk_product][intval($warehouse_id)] = 0; - } - - $alreadyQtySetted[$line->fk_product][intval($warehouse_id)] = $deliverableQty + $alreadyQtySetted[$line->fk_product][intval($warehouse_id)]; - - $inputName = 'qtyl' . $indiceAsked . '_' . $subj; + $inputName = 'qtyl'.$indiceAsked.'_'.$subj; if (GETPOSTISSET($inputName)) { $deliverableQty = GETPOSTINT($inputName); } diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index c04b223976a..4213f64698d 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -537,7 +537,12 @@ class Expedition extends CommonObject // virtual products $line = $this->lines[$i]; if ($line->fk_product > 0) { - if (!isset($kits_list[$line->fk_product])) { + // We group kit lines by the fulfilled order line (fk_elementdet) and not by product id: when a same kit product + // is present on several distinct order lines, each order line must keep its own shipment line so the ordered + // qty stays consistent with the shipped qty (grouping by product would sum the shipped qty of all lines while + // keeping the ordered qty of a single line, resulting in a wrong/negative remaining qty to ship). Several + // warehouse pickings of the same order line still share the same fk_elementdet and are grouped together. + if (!isset($kits_list[$line->fk_elementdet])) { if (!is_object($line->product)) { $line_product = new Product($this->db); $result = $line_product->fetch($line->fk_product, '', '', '', 1, 1, 1); @@ -552,13 +557,13 @@ class Expedition extends CommonObject $line_product->get_sousproduits_arbo(); $prods_arbo = $line_product->get_arbo_each_prod($line->qty); if (count($prods_arbo) > 0) { - $kits_list[$line->fk_product] = array( + $kits_list[$line->fk_elementdet] = array( 'arbo' => $prods_arbo, 'total_qty' => $line->qty, ); } } else { - $kits_list[$line->fk_product]['total_qty'] += $line->qty; + $kits_list[$line->fk_elementdet]['total_qty'] += $line->qty; } } } @@ -566,20 +571,21 @@ class Expedition extends CommonObject } $kits_id_cached = array(); $sub_kits_id_cached = array(); + $sub_parents_cached = array(); for ($i = 0; $i < $num; $i++) { $line = $this->lines[$i]; if (empty($line->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) { $line_id = 0; - if (!isset($kits_id_cached[$line->fk_product])) { - if (!isset($line->detail_batch) || (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE'))) { // no batch management or is kit - $qty = isset($kits_list[$line->fk_product]) ? $kits_list[$line->fk_product]['total_qty'] : $line->qty; - $warehouse_id = (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) ? 0 : $line->entrepot_id; + if (!isset($kits_id_cached[$line->fk_elementdet])) { + if (!isset($line->detail_batch) || (isset($kits_list[$line->fk_elementdet]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE'))) { // no batch management or is kit + $qty = isset($kits_list[$line->fk_elementdet]) ? $kits_list[$line->fk_elementdet]['total_qty'] : $line->qty; + $warehouse_id = (isset($kits_list[$line->fk_elementdet]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) ? 0 : $line->entrepot_id; $line_id = $this->create_line($warehouse_id, $line->origin_line_id, $qty, $line->rang, $line->array_options, 0, $line->fk_product); if ($line_id <= 0) { $error++; } - if (isset($kits_list[$line->fk_product])) { - $kits_id_cached[$line->fk_product] = $line_id; + if (isset($kits_list[$line->fk_elementdet])) { + $kits_id_cached[$line->fk_elementdet] = $line_id; } } else { // with batch management if ($this->create_line_batch($line, $line->array_options) <= 0) { @@ -587,13 +593,12 @@ class Expedition extends CommonObject } } } else { - $line_id = $kits_id_cached[$line->fk_product]; + $line_id = $kits_id_cached[$line->fk_elementdet]; } // virtual products - if (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) { - $prods_arbo = $kits_list[$line->fk_product]['arbo']; - $total_qty = $kits_list[$line->fk_product]['total_qty']; + if (isset($kits_list[$line->fk_elementdet]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) { + $prods_arbo = $kits_list[$line->fk_elementdet]['arbo']; // get all children of virtual product $parent_line_id = $line_id; // parent line created @@ -621,10 +626,10 @@ class Expedition extends CommonObject if ($product_child_level != $level_last) { $parent_line_id = $line_id; // last line id $parent_product_id = $product_child_id; // last line id - if (isset($kits_id_cached[$parent_product_id])) { - $parent_line_id = $kits_id_cached[$parent_product_id]; + if (isset($sub_parents_cached[$line->fk_elementdet][$parent_product_id])) { + $parent_line_id = $sub_parents_cached[$line->fk_elementdet][$parent_product_id]; } else { - $kits_id_cached[$parent_product_id] = $parent_line_id; + $sub_parents_cached[$line->fk_elementdet][$parent_product_id] = $parent_line_id; } } @@ -651,7 +656,7 @@ class Expedition extends CommonObject } // create line for a child of virtual product - if (!isset($sub_kits_id_cached[$product_child_id]) || $warehouse_id > 0) { + if (!isset($sub_kits_id_cached[$line->fk_elementdet][$product_child_id]) || $warehouse_id > 0) { $line_id = $this->create_line($warehouse_id, ($parent_line_id ? 0 : $line->origin_line_id), $product_child_qty, $line->rang, $line->array_options, $parent_line_id, $product_child_id); if ($line_id <= 0) { $error++; @@ -661,7 +666,7 @@ class Expedition extends CommonObject // if kit or not manage stock (empty incdec) if (empty($warehouse_id)) { - $sub_kits_id_cached[$product_child_id] = $line_id; + $sub_kits_id_cached[$line->fk_elementdet][$product_child_id] = $line_id; } } @@ -1885,66 +1890,55 @@ class Expedition extends CommonObject } } + // No delete expedition if (!$error) { - // Delete linked object - $res = $this->deleteObjectLinked(); - if ($res < 0) { - $error++; - } + $sql = "SELECT rowid FROM ".$this->db->prefix()."expedition"; + $sql .= " WHERE rowid = ".((int) $this->id); - // No delete expedition - if (!$error) { - $sql = "SELECT rowid FROM ".$this->db->prefix()."expedition"; - $sql .= " WHERE rowid = ".((int) $this->id); - - if ($this->db->query($sql)) { - if (!empty($this->origin) && $this->origin_id > 0) { - $this->fetch_origin(); - $origin_object = $this->origin_object; - '@phan-var-force Facture|Commande $origin_object'; - /** @var Commande $origin_object */ - if ($origin_object->status == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress" - // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress" - $origin_object->loadExpeditions(); - if (count($origin_object->expeditions) <= 0) { - $origin_object->setStatut(Commande::STATUS_VALIDATED); - } + if ($this->db->query($sql)) { + if (!empty($this->origin) && $this->origin_id > 0) { + $this->fetch_origin(); + $origin_object = $this->origin_object; + '@phan-var-force Facture|Commande $origin_object'; + /** @var Commande $origin_object */ + if ($origin_object->status == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress" + // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress" + $origin_object->loadExpeditions(); + if (count($origin_object->expeditions) <= 0) { + $origin_object->setStatut(Commande::STATUS_VALIDATED); } } - - $this->db->commit(); - - // We delete PDFs - $ref = dol_sanitizeFileName($this->ref); - if (!empty($conf->expedition->dir_output)) { - $dir = $conf->expedition->dir_output.'/sending/'.$ref; - $file = $dir.'/'.$ref.'.pdf'; - if (file_exists($file)) { - if (!dol_delete_file($file)) { - return 0; - } - } - if (file_exists($dir)) { - if (!dol_delete_dir_recursive($dir)) { - $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir); - return 0; - } - } - } - - return 1; - } else { - $this->error = $this->db->lasterror()." - sql=$sql"; - $this->db->rollback(); - return -3; } + + $this->db->commit(); + + // We delete PDFs + $ref = dol_sanitizeFileName($this->ref); + if (!empty($conf->expedition->dir_output)) { + $dir = $conf->expedition->dir_output.'/sending/'.$ref; + $file = $dir.'/'.$ref.'.pdf'; + if (file_exists($file)) { + if (!dol_delete_file($file)) { + return 0; + } + } + if (file_exists($dir)) { + if (!dol_delete_dir_recursive($dir)) { + $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir); + return 0; + } + } + } + + return 1; } else { + $this->error = $this->db->lasterror()." - sql=$sql"; $this->db->rollback(); - return -2; + return -3; } } else { $this->db->rollback(); - return -1; + return -2; } } diff --git a/htdocs/expedition/class/expeditionligne.class.php b/htdocs/expedition/class/expeditionligne.class.php index da05933ba17..e2dba7b4dda 100644 --- a/htdocs/expedition/class/expeditionligne.class.php +++ b/htdocs/expedition/class/expeditionligne.class.php @@ -13,7 +13,7 @@ * Copyright (C) 2018 Nicolas ZABOURI * Copyright (C) 2018-2025 Frédéric France * Copyright (C) 2020 Lenin Rivas - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2025 Nick Fragoulis * * This program is free software; you can redistribute it and/or modify @@ -437,11 +437,11 @@ class ExpeditionLigne extends CommonObjectLine $sql .= ", description"; $sql .= ", rang"; $sql .= ") VALUES ("; - $sql .= $this->fk_expedition; - $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id); - $sql .= ", ".(empty($this->fk_elementdet) ? 'NULL' : $this->fk_elementdet); - $sql .= ", ".(empty($this->fk_parent) ? 'NULL' : $this->fk_parent); - $sql .= ", ".(empty($this->fk_product) ? 'NULL' : $this->fk_product); + $sql .= ((int) $this->fk_expedition); + $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : ((int) $this->entrepot_id)); + $sql .= ", ".(empty($this->fk_elementdet) ? 'NULL' : ((int) $this->fk_elementdet)); + $sql .= ", ".(empty($this->fk_parent) ? 'NULL' : ((int) $this->fk_parent)); + $sql .= ", ".(empty($this->fk_product) ? 'NULL' : ((int) $this->fk_product)); $sql .= ", '".(empty($this->element_type) ? 'order' : $this->db->escape($this->element_type))."'"; $sql .= ", ".price2num($this->qty, 'MS'); $sql .= ", ".((int) $this->fk_unit); @@ -817,7 +817,7 @@ class ExpeditionLigne extends CommonObjectLine if (!$error) { // update line $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET"; - $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null'); + $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? ((int) $this->entrepot_id) : 'null'); $sql .= " , qty = ".((float) price2num($qty, 'MS')); $sql .= " , fk_unit = ".((int) $this->fk_unit); $sql .= " WHERE rowid = ".((int) $this->id); diff --git a/htdocs/expedition/class/expeditionlinebatch.class.php b/htdocs/expedition/class/expeditionlinebatch.class.php index 362251c308c..5fe32b82257 100644 --- a/htdocs/expedition/class/expeditionlinebatch.class.php +++ b/htdocs/expedition/class/expeditionlinebatch.class.php @@ -1,7 +1,7 @@ * Copyright (C) 2013-2014 Cedric GROSS - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -159,13 +159,13 @@ class ExpeditionLineBatch extends CommonObject $sql .= ", fk_origin_stock"; $sql .= ", fk_warehouse"; $sql .= ") VALUES ("; - $sql .= $id_line_expdet; + $sql .= ((int) $id_line_expdet); $sql .= ", ".(!isset($this->sellby) || dol_strlen($this->sellby) == 0 ? 'NULL' : ("'".$this->db->idate($this->sellby))."'"); $sql .= ", ".(!isset($this->eatby) || dol_strlen($this->eatby) == 0 ? 'NULL' : ("'".$this->db->idate($this->eatby))."'"); $sql .= ", ".($this->batch == '' ? 'NULL' : ("'".$this->db->escape((string) $this->batch)."'")); - $sql .= ", ".(!isset($this->qty) ? ((!isset($this->dluo_qty)) ? 'NULL' : $this->dluo_qty) : $this->qty); // dluo_qty deprecated, use qty + $sql .= ", ".(!isset($this->qty) ? ((!isset($this->dluo_qty)) ? 'NULL' : ((float) $this->dluo_qty)) : ((float) $this->qty)); // dluo_qty deprecated, use qty $sql .= ", ".((int) $this->fk_origin_stock); - $sql .= ", ".(empty($this->fk_warehouse) ? 'NULL' : $this->fk_warehouse); + $sql .= ", ".(empty($this->fk_warehouse) ? 'NULL' : ((int) $this->fk_warehouse)); $sql .= ")"; dol_syslog(__METHOD__, LOG_DEBUG); diff --git a/htdocs/expedition/class/expeditionstats.class.php b/htdocs/expedition/class/expeditionstats.class.php index d02cc05c4e2..45446bddaf3 100644 --- a/htdocs/expedition/class/expeditionstats.class.php +++ b/htdocs/expedition/class/expeditionstats.class.php @@ -97,7 +97,7 @@ class ExpeditionStats extends Stats $this->where .= " c.fk_statut > 0"; // Not draft and not cancelled //$this->where.= " AND c.fk_soc = s.rowid AND c.entity = ".$conf->entity; - $this->where .= " AND c.entity = ".$conf->entity; + $this->where .= " AND c.entity = ".((int) $conf->entity); if ($this->socid) { $this->where .= " AND c.fk_soc = ".((int) $this->socid); diff --git a/htdocs/expedition/stats/index.php b/htdocs/expedition/stats/index.php index 856463fe709..265f73dfc9c 100644 --- a/htdocs/expedition/stats/index.php +++ b/htdocs/expedition/stats/index.php @@ -391,7 +391,7 @@ print ''.$langs->trans("NbOfSendings").'entity; +$sql.= " AND entity = ".((int) $conf->entity); $sql.= " GROUP BY dm DESC"; $resql=$db->query($sql); diff --git a/htdocs/expensereport/class/api_expensereports.class.php b/htdocs/expensereport/class/api_expensereports.class.php index 2f0d0dfd5bd..0d53a8760df 100644 --- a/htdocs/expensereport/class/api_expensereports.class.php +++ b/htdocs/expensereport/class/api_expensereports.class.php @@ -344,7 +344,7 @@ class ExpenseReports extends DolibarrApi $request_data->date, $request_data->comments, $request_data->fk_project, - $request_data->fk_c_exp_tax_cat, + (int) $request_data->fk_c_exp_tax_cat, $request_data->type, $request_data->fk_ecm_files ); @@ -414,7 +414,7 @@ class ExpenseReports extends DolibarrApi $request_data->value_unit, $request_data->date, $id, - $request_data->fk_c_exp_tax_cat, + (int) $request_data->fk_c_exp_tax_cat, $request_data->fk_ecm_files ); diff --git a/htdocs/expensereport/class/expensereport.class.php b/htdocs/expensereport/class/expensereport.class.php index 011c2e65b94..1f6185de5cc 100644 --- a/htdocs/expensereport/class/expensereport.class.php +++ b/htdocs/expensereport/class/expensereport.class.php @@ -921,7 +921,7 @@ class ExpenseReport extends CommonObject $sql .= " f.fk_user_approve"; $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as f"; $sql .= " WHERE f.rowid = ".((int) $id); - $sql .= " AND f.entity = ".$conf->entity; + $sql .= " AND f.entity = ".((int) $conf->entity); @@ -1373,7 +1373,7 @@ class ExpenseReport extends CommonObject // Validate $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element; $sql .= " SET ref = '".$this->db->escape($num)."',"; - $sql .= " fk_statut = ".self::STATUS_VALIDATED.","; + $sql .= " fk_statut = ".((int) self::STATUS_VALIDATED).","; $sql .= " date_valid = '".$this->db->idate($this->date_valid)."',"; $sql .= " fk_user_valid = ".((int) $user->id); $sql .= " WHERE rowid = ".((int) $this->id); @@ -1405,7 +1405,7 @@ class ExpenseReport extends CommonObject $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expensereport/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'expensereport/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'expensereport/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; @@ -1480,7 +1480,7 @@ class ExpenseReport extends CommonObject if ($this->status != self::STATUS_VALIDATED) { $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element; - $sql .= " SET fk_statut = ".self::STATUS_VALIDATED; + $sql .= " SET fk_statut = ".((int) self::STATUS_VALIDATED); $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog(get_class($this)."::set_save_from_refuse", LOG_DEBUG); @@ -1513,7 +1513,7 @@ class ExpenseReport extends CommonObject if ($this->status != self::STATUS_APPROVED) { $this->db->begin(); $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element; - $sql .= " SET ref = '".$this->db->escape($this->ref)."', fk_statut = ".self::STATUS_APPROVED.", fk_user_approve = ".((int) $fuser->id).","; + $sql .= " SET ref = '".$this->db->escape($this->ref)."', fk_statut = ".((int) self::STATUS_APPROVED).", fk_user_approve = ".((int) $fuser->id).","; $sql .= " date_approve='".$this->db->idate($now)."'"; $sql .= " WHERE rowid = ".((int) $this->id); if ($this->db->query($sql)) { @@ -1959,7 +1959,7 @@ class ExpenseReport extends CommonObject */ public function addline($qty = 0, $up = 0, $fk_c_type_fees = 0, $vatrate = 0, $date = '', $comments = '', $fk_project = 0, $fk_c_exp_tax_cat = 0, $type = 0, $fk_ecm_files = 0) { - global $langs, $mysoc; + global $mysoc; dol_syslog(get_class($this)."::addline qty=$qty, up=$up, fk_c_type_fees=$fk_c_type_fees, vatrate=$vatrate, date=$date, fk_project=$fk_project, type=$type, comments=$comments", LOG_DEBUG); diff --git a/htdocs/expensereport/class/expensereportline.class.php b/htdocs/expensereport/class/expensereportline.class.php index 4cbeacb4a7e..5d8d052bfb3 100644 --- a/htdocs/expensereport/class/expensereportline.class.php +++ b/htdocs/expensereport/class/expensereportline.class.php @@ -5,7 +5,7 @@ * Copyright (C) 2018 Nicolas ZABOURI * Copyright (c) 2018-2024 Frédéric France * Copyright (C) 2016-2020 Ferran Marcet - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024-2025 Frédéric France * Copyright (C) 2024 William Mead * @@ -275,8 +275,8 @@ class ExpenseReportLine extends CommonObjectLine $this->date = $objp->date; $this->dates = $this->db->jdate($objp->date); $this->value_unit = $objp->value_unit; - $this->fk_c_type_fees = $objp->fk_c_type_fees; - $this->fk_c_exp_tax_cat = $objp->fk_c_exp_tax_cat; + $this->fk_c_type_fees = (int) $objp->fk_c_type_fees; + $this->fk_c_exp_tax_cat = (int) $objp->fk_c_exp_tax_cat; $this->fk_projet = $objp->fk_project; // deprecated $this->fk_project = $objp->fk_project; $this->type_fees_code = $objp->type_fees_code; @@ -347,9 +347,9 @@ class ExpenseReportLine extends CommonObjectLine $sql .= ' total_ht, total_tva, total_ttc,'; $sql .= ' total_localtax1, total_localtax2,'; $sql .= ' date, rule_warning_message, fk_c_exp_tax_cat, fk_ecm_files)'; - $sql .= " VALUES (".$this->db->escape((string) $this->fk_expensereport).","; + $sql .= " VALUES (".((int) $this->fk_expensereport).","; $sql .= " ".((int) $this->fk_c_type_fees).","; - $sql .= " ".((int) (!empty($this->fk_project) && $this->fk_project > 0) ? $this->fk_project : ((!empty($this->fk_projet) && $this->fk_projet > 0) ? $this->fk_projet : 'null')).","; + $sql .= " ".((int) (!empty($this->fk_project) && $this->fk_project > 0) ? ((int) $this->fk_project) : ((!empty($this->fk_projet) && $this->fk_projet > 0) ? ((int) $this->fk_projet) : 'null')).","; $sql .= " ".((float) $this->vatrate).","; $sql .= " '".$this->db->escape(empty($this->vat_src_code) ? '' : $this->vat_src_code)."',"; $sql .= " ".((float) price2num($this->localtax1_tx)).","; @@ -491,8 +491,8 @@ class ExpenseReportLine extends CommonObjectLine $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'"; $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'"; $sql .= ", rule_warning_message='".$this->db->escape($this->rule_warning_message)."'"; - $sql .= ", fk_c_exp_tax_cat=".$this->db->escape((string) $this->fk_c_exp_tax_cat); - $sql .= ", fk_ecm_files=".($this->fk_ecm_files > 0 ? ((int) $this->fk_ecm_files) : 'null'); + $sql .= ", fk_c_exp_tax_cat = ".((int) $this->fk_c_exp_tax_cat); + $sql .= ", fk_ecm_files = ".($this->fk_ecm_files > 0 ? ((int) $this->fk_ecm_files) : 'null'); if ($this->fk_c_type_fees) { $sql .= ", fk_c_type_fees = ".((int) $this->fk_c_type_fees); } else { @@ -503,7 +503,7 @@ class ExpenseReportLine extends CommonObjectLine } else { $sql .= ", fk_projet=null"; } - $sql .= " WHERE rowid = ".((int) ($this->rowid ? $this->rowid : $this->id)); + $sql .= " WHERE rowid = ".((int) ($this->rowid ? $this->rowid : ((int) $this->id))); dol_syslog("ExpenseReportLine::update"); diff --git a/htdocs/expensereport/class/expensereportstats.class.php b/htdocs/expensereport/class/expensereportstats.class.php index 255f0eb00b6..f6d0ca98a7d 100644 --- a/htdocs/expensereport/class/expensereportstats.class.php +++ b/htdocs/expensereport/class/expensereportstats.class.php @@ -138,7 +138,7 @@ class ExpenseReportStats extends Stats { $sql = "SELECT MONTH(".$this->db->ifsql("e.".$this->db->sanitize($this->datetouse)." IS NULL", "e.date_create", "e.".$this->db->sanitize($this->datetouse)).") as dm, count(*)"; $sql .= " FROM ".$this->db->sanitize($this->from, 0, 1, 1); - $sql .= " WHERE YEAR(e.".$this->datetouse.") = ".((int) $year); + $sql .= " WHERE YEAR(e.".$this->db->sanitize($this->datetouse).") = ".((int) $year); $sql .= " AND ".$this->where; $sql .= " GROUP BY dm"; $sql .= $this->db->order('dm', 'DESC'); diff --git a/htdocs/expensereport/class/paymentexpensereport.class.php b/htdocs/expensereport/class/paymentexpensereport.class.php index cb72d0f26dd..49121e54bf8 100644 --- a/htdocs/expensereport/class/paymentexpensereport.class.php +++ b/htdocs/expensereport/class/paymentexpensereport.class.php @@ -2,7 +2,7 @@ /* Copyright (C) 2015-2017 Alexandre Spangaro * Copyright (C) 2018 Nicolas ZABOURI * Copyright (C) 2024-2025 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -206,7 +206,7 @@ class PaymentExpenseReport extends CommonObject if ($totalamount != 0) { $sql = "INSERT INTO ".MAIN_DB_PREFIX."payment_expensereport (fk_expensereport, datec, datep, amount,"; $sql .= " fk_typepayment, num_payment, note, fk_user_creat, fk_bank)"; - $sql .= " VALUES ($this->fk_expensereport, '".$this->db->idate($now)."',"; + $sql .= " VALUES (".((int) $this->fk_expensereport).", '".$this->db->idate($now)."',"; $sql .= " '".$this->db->idate($this->datep)."',"; $sql .= " ".price2num($totalamount).","; $sql .= " ".((int) $this->fk_typepayment).", '".$this->db->escape($this->num_payment)."', '".$this->db->escape($this->note_public)."', ".((int) $user->id).","; @@ -338,17 +338,17 @@ class PaymentExpenseReport extends CommonObject // Update request $sql = "UPDATE ".MAIN_DB_PREFIX."payment_expensereport SET"; - $sql .= " fk_expensereport=".(isset($this->fk_expensereport) ? $this->fk_expensereport : "null").","; + $sql .= " fk_expensereport=".(isset($this->fk_expensereport) ? ((int) $this->fk_expensereport) : "null").","; $sql .= " datec=".(dol_strlen($this->datec) != 0 ? "'".$this->db->idate($this->datec)."'" : 'null').","; $sql .= " tms=".(dol_strlen((string) $this->tms) != 0 ? "'".$this->db->idate($this->tms)."'" : 'null').","; $sql .= " datep=".(dol_strlen($this->datep) != 0 ? "'".$this->db->idate($this->datep)."'" : 'null').","; - $sql .= " amount=".(isset($this->amount) ? $this->amount : "null").","; - $sql .= " fk_typepayment=".(isset($this->fk_typepayment) ? $this->fk_typepayment : "null").","; + $sql .= " amount=".(isset($this->amount) ? ((float) $this->amount) : "null").","; + $sql .= " fk_typepayment=".(isset($this->fk_typepayment) ? ((int) $this->fk_typepayment) : "null").","; $sql .= " num_payment=".(isset($this->num_payment) ? "'".$this->db->escape($this->num_payment)."'" : "null").","; $sql .= " note=".(isset($this->note) ? "'".$this->db->escape($this->note)."'" : "null").","; - $sql .= " fk_bank=".(isset($this->fk_bank) ? $this->fk_bank : "null").","; - $sql .= " fk_user_creat=".(isset($this->fk_user_creat) ? $this->fk_user_creat : "null").","; - $sql .= " fk_user_modif=".(isset($this->fk_user_modif) ? $this->fk_user_modif : "null"); + $sql .= " fk_bank=".(isset($this->fk_bank) ? ((int) $this->fk_bank) : "null").","; + $sql .= " fk_user_creat=".(isset($this->fk_user_creat) ? ((int) $this->fk_user_creat) : "null").","; + $sql .= " fk_user_modif=".(isset($this->fk_user_modif) ? ((int) $this->fk_user_modif) : "null"); $sql .= " WHERE rowid=".((int) $this->id); $this->db->begin(); diff --git a/htdocs/expensereport/index.php b/htdocs/expensereport/index.php index 60e0f5be048..df9cab87e53 100644 --- a/htdocs/expensereport/index.php +++ b/htdocs/expensereport/index.php @@ -65,7 +65,7 @@ if (!$sortfield) { } $limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; -$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); +$max = getDolUserInt('MAIN_SIZE_SHORTLIST_LIMIT', getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5)); // Security check $socid = GETPOSTINT('socid'); diff --git a/htdocs/exports/class/export.class.php b/htdocs/exports/class/export.class.php index 642b4a2c32b..7d8238824ba 100644 --- a/htdocs/exports/class/export.class.php +++ b/htdocs/exports/class/export.class.php @@ -334,7 +334,7 @@ class Export { // phpcs:enable // Build the sql request - $sql = $this->array_export_sql_start[$indice]; + $sql = $this->array_export_sql_start[$indice]; // @phan-suppress-current-line SqlInjection $i = 0; //print_r($array_selected); @@ -343,7 +343,7 @@ class Export continue; // Field not selected } if (preg_match('/^none\./', $key)) { - continue; // A field that must not appears into SQL + continue; // A field that must not appear into SQL } if ($i > 0) { $sql .= ', '; @@ -357,9 +357,9 @@ class Export $newfield = $key; } - $sql .= $newfield; + $sql .= $newfield; // @phan-suppress-current-line SqlInjection } - $sql .= $this->array_export_sql_end[$indice]; + $sql .= $this->array_export_sql_end[$indice]; // @phan-suppress-current-line SqlInjection // Add the WHERE part. Filtering into sql if a filtering array is provided if (is_array($array_filterValue) && !empty($array_filterValue)) { @@ -370,21 +370,21 @@ class Export continue; } if ($value != '') { - $sqlWhere .= " AND ".$this->buildFilterQuery($this->array_export_TypeFields[$indice][$key], $key, $array_filterValue[$key]); + $sqlWhere .= " AND ".$this->buildFilterQuery($this->array_export_TypeFields[$indice][$key], $key, $array_filterValue[$key]); // @phan-suppress-current-line SqlInjection } } $sql .= $sqlWhere; } // Add the sort order - $sql .= $this->array_export_sql_order[$indice]; + $sql .= $this->array_export_sql_order[$indice]; // @phan-suppress-current-line SqlInjection // Add the HAVING part. if (is_array($array_filterValue) && !empty($array_filterValue)) { // Loop on each condition to add foreach ($array_filterValue as $key => $value) { if (preg_match('/GROUP_CONCAT/i', $key) and $value != '') { - $sql .= " HAVING ".$this->buildFilterQuery($this->array_export_TypeFields[$indice][$key], $key, $array_filterValue[$key]); + $sql .= " HAVING ".$this->buildFilterQuery($this->array_export_TypeFields[$indice][$key], $key, $array_filterValue[$key]); // @phan-suppress-current-line SqlInjection } } } @@ -576,7 +576,7 @@ class Export } $sql = "SELECT ".$this->db->sanitize($keyList)." as rowid, ".$this->db->sanitize($InfoFieldList[2])." as label".(empty($InfoFieldList[3]) ? "" : ", ".$this->db->sanitize($InfoFieldList[3])." as code"); if ($InfoFieldList[1] == 'c_stcomm') { - $sql = "SELECT id as id, ".$this->db->sanitize($keyList)." as rowid, ".$InfoFieldList[2]." as label".(empty($InfoFieldList[3]) ? "" : ", ".$this->db->sanitize($InfoFieldList[3]).' as code'); + $sql = "SELECT id as id, ".$this->db->sanitize($keyList)." as rowid, ".$this->db->sanitize($InfoFieldList[2])." as label".(empty($InfoFieldList[3]) ? "" : ", ".$this->db->sanitize($InfoFieldList[3]).' as code'); } if ($InfoFieldList[1] == 'c_country') { $sql = "SELECT ".$this->db->sanitize($keyList)." as rowid, ".$this->db->sanitize($InfoFieldList[2])." as label, code as code"; @@ -727,7 +727,7 @@ class Export $this->error = "ErrorBadParameter can't find dataset ".$datatoexport." into preload arrays this->array_export_code"; return -1; } - $sql = $this->build_sql($indice, $array_selected, $array_filterValue); + $sql = $this->build_sql($indice, $array_selected, $array_filterValue); // @phan-suppress-current-line SqlInjection } // Run the SQL diff --git a/htdocs/exports/export.php b/htdocs/exports/export.php index 86dd9aef764..48fc4002925 100644 --- a/htdocs/exports/export.php +++ b/htdocs/exports/export.php @@ -5,7 +5,7 @@ * Copyright (C) 2012 Charles-Fr BENKE * Copyright (C) 2015 Juanjo Menent * Copyright (C) 2024 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ /** * \file htdocs/exports/export.php * \ingroup export - * \brief Pages of export Wizard + * \brief Export Wizard pages */ require_once '../main.inc.php'; @@ -185,7 +185,7 @@ $result = restrictedArea($user, 'export'); * Actions */ -if ($action == 'selectfield' && $user->hasRight('export', 'creer')) { // Selection of field at step 2 +if ($action == 'selectfield' && $user->hasRight('export', 'creer')) { // Select field at step 2 $fieldsarray = $objexport->array_export_fields[0]; $fieldsentitiesarray = $objexport->array_export_entities[0]; $fieldsdependenciesarray = $objexport->array_export_dependencies[0]; @@ -209,7 +209,7 @@ if ($action == 'selectfield' && $user->hasRight('export', 'creer')) { // Sel var_dump($fieldsdependenciesarray);*/ $listofdependencies = array(); if (!empty($fieldsentitiesarray[$field]) && !empty($fieldsdependenciesarray[$fieldsentitiesarray[$field]])) { - // We found a dependency on the type of field + // Found a dependency on the field type $tmp = $fieldsdependenciesarray[$fieldsentitiesarray[$field]]; // $fieldsdependenciesarray=array('element'=>'fd.rowid') or array('element'=>array('fd.rowid','ab.rowid')) if (is_array($tmp)) { $listofdependencies = $tmp; @@ -217,7 +217,7 @@ if ($action == 'selectfield' && $user->hasRight('export', 'creer')) { // Sel $listofdependencies = array($tmp); } } elseif (!empty($field) && !empty($fieldsdependenciesarray[$field])) { - // We found a dependency on a dedicated field + // Found a dependency on a specific field $tmp = $fieldsdependenciesarray[$field]; // $fieldsdependenciesarray=array('fd.fieldx'=>'fd.rowid') or array('fd.fieldx'=>array('fd.rowid','ab.rowid')) if (is_array($tmp)) { $listofdependencies = $tmp; @@ -434,7 +434,7 @@ if ($step == 4 && $action == 'submitFormField' && $user->hasRight('export', 'lir accessforbidden(); } - // on boucle sur les champs selectionne pour recuperer la valeur + // loop over selected fields to retrieve the value if (is_array($objexport->array_export_TypeFields[0])) { $_SESSION["export_filtered_fields"] = array(); foreach ($objexport->array_export_TypeFields[0] as $code => $type) { // $code: s.fieldname $value: Text|Boolean|List:ccc @@ -566,7 +566,7 @@ if ($step == 2 && $datatoexport) { print $objexport->array_export_module[0]->getName(); print ''; - // Lot de donnees a exporter + // Dataset to export print ''.$langs->trans("DatasetToExport").''; print ''; $entity = preg_replace('/:.*$/', '', $objexport->array_export_icon[0]); @@ -692,7 +692,13 @@ if ($step == 2 && $datatoexport) { $tablename = getablenamefromfield($code, $sqlmaxforexport); $htmltext = ''.$langs->trans("Name").": ".$text.'
'; if (!empty($objexport->array_export_special[0][$code])) { - $htmltext .= ''.$langs->trans("ComputedField")." -> ".$langs->trans("Method")." : ".$objexport->array_export_special[0][$code]."
"; + $htmltext .= ''.$langs->trans("ComputedField")." -> ".$langs->trans("Method")." : "; + if (isset($objexport->array_export_special[0][$code]['method'])) { + $htmltext .= $objexport->array_export_special[0][$code]['method']; + } elseif (!is_array($objexport->array_export_special[0][$code])) { + $htmltext .= $objexport->array_export_special[0][$code]; + } + $htmltext .= "
"; } else { $htmltext .= ''.$langs->trans("Table")." -> ".$langs->trans("Field").": ".$tablename." -> ".preg_replace('/^.*\./', '', $code)."
"; } @@ -829,7 +835,7 @@ if ($step == 3 && $datatoexport) { print ''.$langs->trans("SelectFilterFields").'...

'; - // un formulaire en plus pour recuperer les filtres + // An extra form to get the filters print '
'; print ''; @@ -887,7 +893,13 @@ if ($step == 3 && $datatoexport) { $tablename = getablenamefromfield($code, $sqlmaxforexport); $htmltext = ''.$langs->trans("Name").': '.$text.'
'; if (!empty($objexport->array_export_special[0][$code])) { - $htmltext .= ''.$langs->trans("ComputedField")." -> ".$langs->trans("Method")." : ".$objexport->array_export_special[0][$code]."
"; + $htmltext .= ''.$langs->trans("ComputedField")." -> ".$langs->trans("Method")." : "; + if (isset($objexport->array_export_special[0][$code]['method'])) { + $htmltext .= $objexport->array_export_special[0][$code]['method']; + } elseif (!is_array($objexport->array_export_special[0][$code])) { + $htmltext .= $objexport->array_export_special[0][$code]; + } + $htmltext .= "
"; } else { $htmltext .= ''.$langs->trans("Table")." -> ".$langs->trans("Field").": ".$tablename." -> ".preg_replace('/^.*\./', '', $code)."
"; } @@ -989,7 +1001,7 @@ if ($step == 4 && $datatoexport) { print dolPrintHTML($titleofmodule); print ''; - // Lot de donnees a exporter + // Dataset to export print ''.$langs->trans("DatasetToExport").''; print ''; $entity = preg_replace('/:.*$/', '', $objexport->array_export_icon[0]); @@ -1098,7 +1110,13 @@ if ($step == 4 && $datatoexport) { $tablename = getablenamefromfield($code, $sqlmaxforexport); $htmltext = ''.$langs->trans("Name").': '.$text.'
'; if (!empty($objexport->array_export_special[0][$code])) { - $htmltext .= ''.$langs->trans("ComputedField")." -> ".$langs->trans("Method")." : ".$objexport->array_export_special[0][$code]."
"; + $htmltext .= ''.$langs->trans("ComputedField")." -> ".$langs->trans("Method")." : "; + if (isset($objexport->array_export_special[0][$code]['method'])) { + $htmltext .= $objexport->array_export_special[0][$code]['method']; + } elseif (!is_array($objexport->array_export_special[0][$code])) { + $htmltext .= $objexport->array_export_special[0][$code]; + } + $htmltext .= "
"; } else { $htmltext .= ''.$langs->trans("Table")." -> ".$langs->trans("Field").": ".$tablename." -> ".preg_replace('/^.*\./', '', $code)."
"; } @@ -1264,7 +1282,7 @@ if ($step == 5 && $datatoexport) { $head[$h][1] = $langs->trans("Step")." 2"; $h++; - // si le filtrage est parameter pour l'export ou pas + // whether filtering is a parameter for the export or not if ($usefilters && isset($objexport->array_export_TypeFields[0]) && is_array($objexport->array_export_TypeFields[0])) { $head[$h][0] = DOL_URL_ROOT.'/exports/export.php?step=3&datatoexport='.$datatoexport; $head[$h][1] = $langs->trans("Step")." 3"; @@ -1390,14 +1408,14 @@ if ($step == 5 && $datatoexport) { $htmltabloflibs .= '
'; print '
'; - print ''.$form->textwithpicto($langs->trans("NowClickToGenerateToBuildExportFile"), $htmltabloflibs, 1, 'help', '', 0, 2, 'helphonformat').''; + print '
'.$form->textwithpicto($langs->trans("NowClickToGenerateToBuildExportFile"), $htmltabloflibs, 1, 'help', '', 0, 2, 'helphonformat').'
'; //print $htmltabloflibs; print '
'; if ($sqlusedforexport && $user->admin) { - print info_admin($langs->trans("SQLUsedForExport").':
'.$sqlusedforexport, 0, 0, '1', '', 'TechnicalInformation').'
'; + print info_admin($langs->trans("SQLUsedForExport").':
'.$sqlusedforexport.'', 0, 0, 'info', '', 'TechnicalInformation').'
'; print '
'; } diff --git a/htdocs/fichinter/card-rec.php b/htdocs/fichinter/card-rec.php index e33090fad2c..02ff12a1a86 100644 --- a/htdocs/fichinter/card-rec.php +++ b/htdocs/fichinter/card-rec.php @@ -748,7 +748,7 @@ if ($action == 'create') { $num = count($object->lines); $i = 0; while ($i < $num) { - $type =0; + $type = 0; // Show product and description if (isset($object->lines[$i]->product_type)) { $type = $object->lines[$i]->product_type; @@ -808,7 +808,7 @@ if ($action == 'create') { $sql .= " , ".MAIN_DB_PREFIX."societe_commerciaux as sc"; } $sql .= " WHERE f.fk_soc = s.rowid"; - $sql .= " AND f.entity = ".$conf->entity; + $sql .= " AND f.entity = ".((int) $conf->entity); if (!empty($socid)) { $sql .= " AND s.rowid = ".((int) $socid); } @@ -840,7 +840,7 @@ if ($action == 'create') { print_barre_liste($langs->trans("RepeatableIntervention"), $page, $_SERVER['PHP_SELF'], "&socid=$socid", $sortfield, $sortorder, '', $num, '', 'intervention'); - print ''.$langs->trans("ToCreateAPredefinedIntervention").'

'; + print '
'.$langs->trans("ToCreateAPredefinedIntervention").'

'; $i = 0; print ''; diff --git a/htdocs/fichinter/card.php b/htdocs/fichinter/card.php index fb760baae9b..fcaa3787480 100644 --- a/htdocs/fichinter/card.php +++ b/htdocs/fichinter/card.php @@ -122,6 +122,7 @@ $result = restrictedArea($user, 'ficheinter', $id, 'fichinter'); $permissionnote = $user->hasRight('ficheinter', 'creer'); // Used by the include of actions_setnotes.inc.php $permissiondellink = $user->hasRight('ficheinter', 'creer'); // Used by the include of actions_dellink.inc.php $permissiontodelete = (($object->status == Fichinter::STATUS_DRAFT && $user->hasRight('ficheinter', 'creer')) || $user->hasRight('ficheinter', 'supprimer')); +$permissiontoread = $user->hasRight('ficheinter', 'lire'); $permissiontoadd = $user->hasRight('ficheinter', 'creer'); $permissiontoeditextra = $permissiontoadd; if (GETPOST('attribute', 'aZ09') && isset($extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')])) { @@ -881,7 +882,7 @@ if (empty($reshook)) { include DOL_DOCUMENT_ROOT.'/core/actions_sendmails.inc.php'; // Actions to build doc - $upload_dir = $conf->ficheinter->dir_output; + $upload_dir = getMultidirOutput($object); include DOL_DOCUMENT_ROOT.'/core/actions_builddoc.inc.php'; if ($action == 'update_extras' && $permissiontoeditextra) { @@ -2040,11 +2041,11 @@ if ($action == 'create') { * Built documents */ $filename = dol_sanitizeFileName($object->ref); - $filedir = $conf->ficheinter->dir_output."/".$filename; + $filedir = getMultidirOutput($object)."/".$filename; $urlsource = $_SERVER["PHP_SELF"]."?id=".$object->id; - $genallowed = $user->hasRight('ficheinter', 'lire'); + $genallowed = $permissiontoread; $delallowed = $permissiontoadd; - print $formfile->showdocuments('ficheinter', $filename, $filedir, $urlsource, $genallowed, $delallowed, $object->model_pdf, 1, 0, 0, 28, 0, '', '', '', $soc->default_lang); + print $formfile->showdocuments('ficheinter', $filename, $filedir, $urlsource, $genallowed, $delallowed, $object->model_pdf, 1, 0, 0, 28, 0, '', '', '', $soc->default_lang, '', $object); // Show links to link elements $tmparray = $form->showLinkToObjectBlock($object, array(), array('fichinter'), 1); @@ -2091,7 +2092,7 @@ if ($action == 'create') { // Presend form $modelmail = 'fichinter_send'; $defaulttopic = 'SendInterventionRef'; - $diroutput = $conf->ficheinter->dir_output; + $diroutput = $conf->ficheinter->multidir_output[$object->entity]; $trackid = 'int'.$object->id; include DOL_DOCUMENT_ROOT.'/core/tpl/card_presend.tpl.php'; diff --git a/htdocs/fichinter/class/fichinter.class.php b/htdocs/fichinter/class/fichinter.class.php index ff253f9794e..647ff014b18 100644 --- a/htdocs/fichinter/class/fichinter.class.php +++ b/htdocs/fichinter/class/fichinter.class.php @@ -42,7 +42,8 @@ require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php'; */ class Fichinter extends CommonObject { - use CommonSignedObject, CommonSubtotal; + use CommonSignedObject; + use CommonSubtotal; /** * @var string Prefix to check for any trigger code of any business class to prevent bad value for trigger code. @@ -351,7 +352,7 @@ class Fichinter extends CommonObject $sql .= ", note_public"; $sql .= ") "; $sql .= " VALUES ("; - $sql .= $this->socid; + $sql .= ((int) $this->socid); $sql .= ", '".$this->db->idate($now)."'"; $sql .= ", '".$this->db->escape($this->ref)."'"; $sql .= ", ".($this->ref_client ? "'".$this->db->escape($this->ref_client)."'" : "null"); @@ -661,7 +662,7 @@ class Fichinter extends CommonObject $sql .= ", date_valid = '".$this->db->idate($now)."'"; $sql .= ", fk_user_valid = ".($user->id > 0 ? (int) $user->id : "null"); $sql .= " WHERE rowid = ".((int) $this->id); - $sql .= " AND entity = ".((int) $this->entity); + $sql .= " AND entity IN (".getEntity('intervention').")"; $sql .= " AND fk_statut = 0"; @@ -697,7 +698,7 @@ class Fichinter extends CommonObject $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'ficheinter/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'ficheinter/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'ficheinter/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; @@ -772,7 +773,7 @@ class Fichinter extends CommonObject $sql .= " fk_user_modif = " . ((int) $user->id); $sql .= " WHERE rowid = " . ((int) $this->id); $sql .= " AND fk_statut > " . self::STATUS_DRAFT; - $sql .= " AND entity = " . ((int) $conf->entity); + $sql .= " AND entity IN (".getEntity('intervention').")"; if ($this->db->query($sql)) { if (!$notrigger) { diff --git a/htdocs/fichinter/class/fichinterrec.class.php b/htdocs/fichinter/class/fichinterrec.class.php index a66556bba76..0979a0e16fb 100644 --- a/htdocs/fichinter/class/fichinterrec.class.php +++ b/htdocs/fichinter/class/fichinterrec.class.php @@ -9,7 +9,7 @@ * Copyright (C) 2016-2018 Charlie Benke * Copyright (C) 2024 William Mead * Copyright (C) 2024-2026 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -570,16 +570,16 @@ class FichinterRec extends Fichinter $sql .= ", ".((int) $duration); //$sql.= ", ".(!empty($qty)? $qty :(!empty($duration)? $duration :"null")); //$sql.= ", ".price2num($txtva); - $sql .= ", ".(!empty($fk_product) ? $fk_product : "null"); + $sql .= ", ".(!empty($fk_product) ? ((int) $fk_product) : "null"); $sql .= ", ".((int) $product_type); - $sql .= ", ".(!empty($remise_percent) ? $remise_percent : "null"); + $sql .= ", ".(!empty($remise_percent) ? ((float) $remise_percent) : "null"); $sql .= ", '".price2num($pu_ht)."'"; $sql .= ", '".price2num($total_ht)."'"; $sql .= ", '".price2num($total_tva)."'"; $sql .= ", '".price2num($total_ttc)."'"; $sql .= ", ".(int) $rang; //$sql.= ", ".$special_code; - $sql .= ", ".(!empty($fk_unit) ? $fk_unit : "null"); + $sql .= ", ".(!empty($fk_unit) ? ((int) $fk_unit) : "null"); $sql .= ")"; dol_syslog(get_class($this)."::addLineRec", LOG_DEBUG); diff --git a/htdocs/fichinter/index.php b/htdocs/fichinter/index.php index c61841b6113..85352c807d1 100644 --- a/htdocs/fichinter/index.php +++ b/htdocs/fichinter/index.php @@ -63,7 +63,7 @@ if ($user->isExternalUser()) { // Load $resultboxes $resultboxes = FormOther::getBoxesArea($user, "20"); -$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); +$max = getDolUserInt('MAIN_SIZE_SHORTLIST_LIMIT', getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5)); /* diff --git a/htdocs/fourn/ajax/getSupplierPrices.php b/htdocs/fourn/ajax/getSupplierPrices.php index a56526b72ed..f6b38783b7e 100644 --- a/htdocs/fourn/ajax/getSupplierPrices.php +++ b/htdocs/fourn/ajax/getSupplierPrices.php @@ -38,7 +38,6 @@ if (!defined('NOREQUIRESOC')) { // Load Dolibarr environment require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php'; /** * @var Conf $conf * @var DoliDB $db @@ -46,6 +45,7 @@ require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php'; // init getSupplierPrices hook $hookmanager->initHooks(array('ajaxGetSupplierPrices')); diff --git a/htdocs/fourn/class/fournisseur.class.php b/htdocs/fourn/class/fournisseur.class.php index 20eb2ef661b..391cbf9ffbf 100644 --- a/htdocs/fourn/class/fournisseur.class.php +++ b/htdocs/fourn/class/fournisseur.class.php @@ -3,7 +3,7 @@ * Copyright (C) 2006 Laurent Destailleur * Copyright (C) 2005-2009 Regis Houssin * Copyright (C) 2011 Juanjo Menent - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2025 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -107,7 +107,7 @@ class Fournisseur extends Societe $sql = "SELECT count(pfp.rowid) as nb"; $sql .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp"; - $sql .= " WHERE pfp.entity = ".$conf->entity; + $sql .= " WHERE pfp.entity = ".((int) $conf->entity); $sql .= " AND pfp.fk_soc = ".((int) $this->id); $resql = $this->db->query($sql); diff --git a/htdocs/fourn/class/fournisseur.commande.class.php b/htdocs/fourn/class/fournisseur.commande.class.php index 6bc4fe53c8a..3ea66560e45 100644 --- a/htdocs/fourn/class/fournisseur.commande.class.php +++ b/htdocs/fourn/class/fournisseur.commande.class.php @@ -167,7 +167,7 @@ class CommandeFournisseur extends CommonOrder public $date_approve2; /** - * @var int|'' Date of the purchase order ordering + * @var int|''|null Date of the purchase order ordering */ public $date_commande; @@ -864,7 +864,7 @@ class CommandeFournisseur extends CommonOrder $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.$this->db->prefix()."ecm_files set filepath = 'fournisseur/commande/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; @@ -1553,7 +1553,7 @@ class CommandeFournisseur extends CommonOrder } $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur"; - $sql .= " SET fk_statut=".self::STATUS_ORDERSENT.", fk_input_method=".$methode.", date_commande='".$this->db->idate($date)."', "; + $sql .= " SET fk_statut=".self::STATUS_ORDERSENT.", fk_input_method=".((int) $methode).", date_commande='".$this->db->idate($date)."', "; $sql .= " note_private='".$this->db->escape((string) $newnoteprivate)."'"; $sql .= " WHERE rowid=".((int) $this->id); @@ -1674,10 +1674,10 @@ class CommandeFournisseur extends CommonOrder $sql .= ", ".self::STATUS_DRAFT; $sql .= ", ".((int) $this->source); $sql .= ", '".$this->db->escape(getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF'))."'"; - $sql .= ", ".($this->mode_reglement_id > 0 ? $this->mode_reglement_id : 'null'); + $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'null'); $sql .= ", ".(!empty($this->deposit_percent) ? "'" . $this->db->escape($this->deposit_percent) . "'" : "null"); - $sql .= ", ".($this->cond_reglement_id > 0 ? $this->cond_reglement_id : 'null'); - $sql .= ", ".($this->fk_account > 0 ? $this->fk_account : 'NULL'); + $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'null'); + $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL'); $sql .= ", ".(int) $this->fk_incoterms; $sql .= ", '".$this->db->escape($this->location_incoterms)."'"; $sql .= ", ".(int) $this->fk_multicurrency; @@ -1737,7 +1737,7 @@ class CommandeFournisseur extends CommonOrder } $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur"; - $sql .= " SET ref='(PROV".$this->id.")'"; + $sql .= " SET ref='(PROV".((int) $this->id).")'"; $sql .= " WHERE rowid=".((int) $this->id); dol_syslog(get_class($this)."::create", LOG_DEBUG); @@ -1849,24 +1849,24 @@ class CommandeFournisseur extends CommonOrder $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").","; $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").","; $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").","; - $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").","; + $sql .= " fk_soc=".(isset($this->socid) ? ((int) $this->socid) : "null").","; $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').","; $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').","; - $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").","; - $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").","; - $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").","; - $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").","; - $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").","; - $sql .= " fk_statut=".(isset($this->status) ? $this->status : "null").","; - $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").","; - $sql .= " fk_user_valid=".(isset($this->user_validation_id) && $this->user_validation_id > 0 ? $this->user_validation_id : "null").","; - $sql .= " fk_projet=".((!empty($this->fk_project) && $this->fk_project > 0) ? $this->fk_project : "null").","; - $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").","; - $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? strval($this->deposit_percent) : "null").","; - $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").","; + $sql .= " total_tva=".(isset($this->total_tva) ? ((float) $this->total_tva) : "null").","; + $sql .= " localtax1=".(isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null").","; + $sql .= " localtax2=".(isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null").","; + $sql .= " total_ht=".(isset($this->total_ht) ? ((float) $this->total_ht) : "null").","; + $sql .= " total_ttc=".(isset($this->total_ttc) ? ((float) $this->total_ttc) : "null").","; + $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : "null").","; + $sql .= " fk_user_author=".(isset($this->user_author_id) ? ((int) $this->user_author_id) : "null").","; + $sql .= " fk_user_valid=".(isset($this->user_validation_id) && $this->user_validation_id > 0 ? ((int) $this->user_validation_id) : "null").","; + $sql .= " fk_projet=".((!empty($this->fk_project) && $this->fk_project > 0) ? ((int) $this->fk_project) : "null").","; + $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null").","; + $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? ((float) $this->deposit_percent) : "null").","; + $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? ((int) $this->mode_reglement_id) : "null").","; $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').","; //$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").","; - $sql .= " fk_account=".($this->fk_account > 0 ? $this->fk_account : "null").","; + $sql .= " fk_account=".($this->fk_account > 0 ? ((int) $this->fk_account) : "null").","; //$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_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").","; @@ -1965,7 +1965,7 @@ class CommandeFournisseur extends CommonOrder $this->date = dol_now(); $this->date_creation = 0; $this->date_validation = 0; - $this->date_commande = 0; + $this->date_commande = null; $this->ref_supplier = ''; $this->user_approve_id = 0; $this->user_approve_id2 = 0; @@ -2391,7 +2391,7 @@ class CommandeFournisseur extends CommonOrder $sql = "INSERT INTO ".$this->db->prefix()."receptiondet_batch"; $sql .= " (fk_element, fk_product, qty, fk_entrepot, fk_user, datec, fk_elementdet, status, comment, eatby, sellby, batch, fk_reception) VALUES"; - $sql .= " ('".$this->id."','".$product."','".$qty."',".($entrepot > 0 ? "'".$entrepot."'" : "null").",'".$user->id."','".$this->db->idate($now)."','".$fk_commandefourndet."', ".$dispatchstatus.", '".$this->db->escape($comment)."', "; + $sql .= " ('".((int) $this->id)."','".((int) $product)."','".((float) $qty)."',".($entrepot > 0 ? "'".((int) $entrepot)."'" : "null").",'".((int) $user->id)."','".$this->db->idate($now)."','".((int) $fk_commandefourndet)."', ".((int) $dispatchstatus).", '".$this->db->escape($comment)."', "; $sql .= ($eatby ? "'".$this->db->idate($eatby)."'" : "null").", ".($sellby ? "'".$this->db->idate($sellby)."'" : "null").", ".($batch ? "'".$this->db->escape($batch)."'" : "null").", ".($fk_reception > 0 ? "'".$this->db->escape((string) $fk_reception)."'" : "null"); $sql .= ")"; @@ -3510,7 +3510,7 @@ class CommandeFournisseur extends CommonOrder if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) { $sql .= " JOIN ".$this->db->prefix()."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id); } - $sql .= " WHERE c.entity = ".$conf->entity; + $sql .= " WHERE c.entity = ".((int) $conf->entity); if ($mode === 'awaiting') { $sql .= " AND c.fk_statut IN (".self::STATUS_ORDERSENT.", ".self::STATUS_RECEIVED_PARTIALLY.")"; } else { diff --git a/htdocs/fourn/class/fournisseur.facture-rec.class.php b/htdocs/fourn/class/fournisseur.facture-rec.class.php index dffcd6ad9cd..5f08e5deb3b 100644 --- a/htdocs/fourn/class/fournisseur.facture-rec.class.php +++ b/htdocs/fourn/class/fournisseur.facture-rec.class.php @@ -913,10 +913,10 @@ class FactureFournisseurRec extends CommonInvoice $error = 0; $this->db->begin(); - $main = MAIN_DB_PREFIX.'facture_fourn_det_rec'; - $ef = $main."_extrafields"; + $sql_main_table = MAIN_DB_PREFIX.'facture_fourn_det_rec'; + $sql_ef_table = $sql_main_table."_extrafields"; - $sqlef = "DELETE FROM ".$ef." WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_facture_fourn = ". (int) $rowid .")"; + $sqlef = "DELETE FROM ".$sql_ef_table." WHERE fk_object IN (SELECT rowid FROM ".$sql_main_table." WHERE fk_facture_fourn = ". (int) $rowid .")"; $sql = "DELETE FROM ".MAIN_DB_PREFIX."facture_fourn_det_rec WHERE fk_facture_fourn = ". (int) $rowid; if ($this->db->query($sqlef) && $this->db->query($sql)) { diff --git a/htdocs/fourn/class/fournisseur.facture.class.php b/htdocs/fourn/class/fournisseur.facture.class.php index fa7ad0659fa..6798db80dff 100644 --- a/htdocs/fourn/class/fournisseur.facture.class.php +++ b/htdocs/fourn/class/fournisseur.facture.class.php @@ -2898,7 +2898,7 @@ class FactureFournisseur extends CommonInvoice */ public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0) { - global $langs, $conf, $user, $hookmanager; + global $langs, $user, $hookmanager; $result = ''; diff --git a/htdocs/fourn/class/fournisseur.facture.ligne.class.php b/htdocs/fourn/class/fournisseur.facture.ligne.class.php index e2fdaa94edc..20b164cd392 100644 --- a/htdocs/fourn/class/fournisseur.facture.ligne.class.php +++ b/htdocs/fourn/class/fournisseur.facture.ligne.class.php @@ -700,7 +700,7 @@ class SupplierInvoiceLine extends CommonObjectLine $sql .= ' info_bits, total_ht, tva, total_ttc, total_localtax1, total_localtax2, fk_unit'; $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc'; $sql .= ')'; - $sql .= " VALUES (".$this->fk_facture_fourn.","; + $sql .= " VALUES (".((int) $this->fk_facture_fourn).","; $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape((string) $this->fk_parent_line)."'" : "null").","; $product_label = !empty($this->product_label) @@ -717,7 +717,7 @@ class SupplierInvoiceLine extends CommonObjectLine $sql .= " ".price2num($this->localtax2_tx).","; $sql .= " '".$this->db->escape((string) $this->localtax1_type)."',"; $sql .= " '".$this->db->escape((string) $this->localtax2_type)."',"; - $sql .= ' '.((!empty($this->fk_product) && $this->fk_product > 0) ? $this->fk_product : "null").','; + $sql .= ' '.((!empty($this->fk_product) && $this->fk_product > 0) ? ((int) $this->fk_product) : "null").','; $sql .= " ".((int) $this->product_type).","; $sql .= " ".price2num($this->remise_percent).","; $sql .= ' '.(!empty($this->fk_remise_except) ? ((int) $this->fk_remise_except) : "null").','; @@ -725,7 +725,7 @@ class SupplierInvoiceLine extends CommonObjectLine $sql .= " ".(!empty($this->qty) ? price2num($this->total_ttc / $this->qty) : price2num($this->total_ttc)).","; $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").","; $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null").","; - $sql .= ' '.(!empty($this->fk_code_ventilation) ? $this->fk_code_ventilation : 0).','; + $sql .= ' '.(!empty($this->fk_code_ventilation) ? ((int) $this->fk_code_ventilation) : 0).','; $sql .= ' '.((int) $this->rang).','; $sql .= ' '.((int) $this->special_code).','; $sql .= " ".((int) $this->info_bits).","; diff --git a/htdocs/fourn/class/fournisseur.orderline.class.php b/htdocs/fourn/class/fournisseur.orderline.class.php index 9603c0f03a7..5054945b327 100644 --- a/htdocs/fourn/class/fournisseur.orderline.class.php +++ b/htdocs/fourn/class/fournisseur.orderline.class.php @@ -14,7 +14,7 @@ * Copyright (C) 2021 Josep Lluís Amador * Copyright (C) 2022 Gauthier VERDOL * Copyright (C) 2024 Solution Libre SAS - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 William Mead * Copyright (C) 2026 Pierre Ardoin * @@ -354,11 +354,11 @@ class CommandeFournisseurLigne extends CommonOrderLine $sql .= " total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_unit,"; $sql .= " fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc,"; $sql .= " fk_parent_line)"; - $sql .= " VALUES (".$this->fk_commande.", '".$this->db->escape($this->label)."','".$this->db->escape($this->desc)."',"; + $sql .= " VALUES (".((int) $this->fk_commande).", '".$this->db->escape($this->label)."','".$this->db->escape($this->desc)."',"; $sql .= " ".($this->date_start ? "'".$this->db->idate($this->date_start)."'" : "null").","; $sql .= " ".($this->date_end ? "'".$this->db->idate($this->date_end)."'" : "null").","; if ($this->fk_product) { - $sql .= $this->fk_product.","; + $sql .= ((int) $this->fk_product).","; } else { $sql .= "null,"; } @@ -385,7 +385,7 @@ class CommandeFournisseurLigne extends CommonOrderLine $sql .= ", ".($this->multicurrency_total_ht ? price2num($this->multicurrency_total_ht) : '0'); $sql .= ", ".($this->multicurrency_total_tva ? price2num($this->multicurrency_total_tva) : '0'); $sql .= ", ".($this->multicurrency_total_ttc ? price2num($this->multicurrency_total_ttc) : '0'); - $sql .= ", ".((!empty($this->fk_parent_line) && $this->fk_parent_line > 0) ? $this->fk_parent_line : 'null'); + $sql .= ", ".((!empty($this->fk_parent_line) && $this->fk_parent_line > 0) ? ((int) $this->fk_parent_line) : 'null'); $sql .= ")"; dol_syslog(get_class($this)."::insert", LOG_DEBUG); diff --git a/htdocs/fourn/class/fournisseur.product.class.php b/htdocs/fourn/class/fournisseur.product.class.php index cda2291ece2..a431ac1e50d 100644 --- a/htdocs/fourn/class/fournisseur.product.class.php +++ b/htdocs/fourn/class/fournisseur.product.class.php @@ -561,7 +561,7 @@ class ProductFournisseur extends Product $sql .= " barcode = ".(empty($barcode) ? 'NULL' : "'".$this->db->escape($barcode)."'").","; $sql .= " fk_barcode_type = ".(empty($fk_barcode_type) ? 'NULL' : "'".$this->db->escape((string) $fk_barcode_type)."'"); if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) { - $sql .= ", packaging = ".(empty($packaging) ? 1 : $packaging); + $sql .= ", packaging = ".(empty($packaging) ? 1 : ((float) $packaging)); } $sql .= " WHERE rowid = ".((int) $this->product_fourn_price_id); @@ -648,7 +648,7 @@ class ProductFournisseur extends Product $sql .= " ".((int) $availability).","; $sql .= " ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").","; $sql .= " ".((int) $newnpr).","; - $sql .= $conf->entity.","; + $sql .= ((int) $conf->entity).","; $sql .= ($delivery_time_days != '' ? ((int) $delivery_time_days) : 'null').","; $sql .= (empty($supplier_reputation) ? 'NULL' : "'".$this->db->escape($supplier_reputation)."'").","; $sql .= (empty($barcode) ? 'NULL' : "'".$this->db->escape($barcode)."'").","; diff --git a/htdocs/fourn/class/paiementfourn.class.php b/htdocs/fourn/class/paiementfourn.class.php index 5394baf756f..a22a2ce3654 100644 --- a/htdocs/fourn/class/paiementfourn.class.php +++ b/htdocs/fourn/class/paiementfourn.class.php @@ -6,10 +6,11 @@ * Copyright (C) 2010-2011 Juanjo Menent * Copyright (C) 2014 Marcos García * Copyright (C) 2018 Nicolas ZABOURI - * Copyright (C) 2018-2025 Frédéric France + * Copyright (C) 2018-2026 Frédéric France * Copyright (C) 2023 Joachim Kueter * Copyright (C) 2023 Sylvain Legrand * Copyright (C) 2024-2026 MDW + * Copyright (C) 2026 Lionel Vessiller * * 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 @@ -204,16 +205,31 @@ class PaiementFourn extends Paiement $this->error = $langs->trans('FailedToFoundTheConversionRateForInvoice'); return -1; } - if (empty($currencyofpayment)) { - $currencyofpayment = $this->multicurrency_code[$key]; + // Fallback: read invoice multicurrency code/tx if caller did not fill the arrays + $invoice_multicurrency_code = $this->multicurrency_code[$key] ?? ''; + $invoice_multicurrency_tx = $this->multicurrency_tx[$key] ?? ''; + if (empty($invoice_multicurrency_code) || empty($invoice_multicurrency_tx)) { + $tmparray = MultiCurrency::getInvoiceRate($key, 'facture_fourn'); + if ($tmparray !== false) { + if (empty($invoice_multicurrency_code)) { + $invoice_multicurrency_code = $tmparray['invoice_multicurrency_code']; + } + if (empty($invoice_multicurrency_tx)) { + $invoice_multicurrency_tx = $tmparray['invoice_multicurrency_tx']; + } + } } - if ($currencyofpayment != $this->multicurrency_code[$key]) { + + if (empty($currencyofpayment)) { + $currencyofpayment = $invoice_multicurrency_code; + } + if ($currencyofpayment != $invoice_multicurrency_code) { // If we have invoices with different currencies in the payment, we stop here $this->error = 'ErrorYouTryToPayInvoicesWithDifferentCurrenciesInSamePayment'; return -1; } if (empty($currencytxofpayment)) { - $currencytxofpayment = $this->multicurrency_tx[$key]; + $currencytxofpayment = $invoice_multicurrency_tx; } $totalamount_converted += $value_converted; @@ -340,18 +356,25 @@ class PaiementFourn extends Paiement } foreach ($amount_ht as $tva_tx => $xxx) { - $discount->amount_ht = abs($amount_ht[$tva_tx]); - $discount->amount_tva = abs($amount_tva[$tva_tx]); - $discount->amount_ttc = abs($amount_ttc[$tva_tx]); $discount->total_ht = abs($amount_ht[$tva_tx]); $discount->total_tva = abs($amount_tva[$tva_tx]); $discount->total_ttc = abs($amount_ttc[$tva_tx]); - $discount->multicurrency_amount_ht = abs($multicurrency_amount_ht[$tva_tx]); - $discount->multicurrency_amount_tva = abs($multicurrency_amount_tva[$tva_tx]); - $discount->multicurrency_amount_ttc = abs($multicurrency_amount_ttc[$tva_tx]); + + // keep compatibility + $discount->amount_ht = $discount->total_ht; + $discount->amount_tva = $discount->total_tva; + $discount->amount_ttc = $discount->total_ttc; + + // multi-currency $discount->multicurrency_total_ht = abs($multicurrency_amount_ht[$tva_tx]); $discount->multicurrency_total_tva = abs($multicurrency_amount_tva[$tva_tx]); $discount->multicurrency_total_ttc = abs($multicurrency_amount_ttc[$tva_tx]); + + // keep compatibility + $discount->multicurrency_amount_ht = $discount->multicurrency_total_ht; + $discount->multicurrency_amount_tva = $discount->multicurrency_total_tva; + $discount->multicurrency_amount_ttc = $discount->multicurrency_total_ttc; + $discount->tva_tx = abs((float) $tva_tx); $result = $discount->create($user); diff --git a/htdocs/fourn/commande/card.php b/htdocs/fourn/commande/card.php index 5aaef5333e9..cc222ea8043 100644 --- a/htdocs/fourn/commande/card.php +++ b/htdocs/fourn/commande/card.php @@ -2448,7 +2448,7 @@ if ($action == 'create') { } $morehtmlref .= $object->thirdparty->getNomUrl(1, 'supplier'); if (!getDolGlobalString('MAIN_DISABLE_OTHER_LINK') && $object->thirdparty->id > 0) { - $morehtmlref .= ' ('.$langs->trans("OtherOrders").')'; + $morehtmlref .= ' ('.$langs->trans("OtherOrders").')'; } } @@ -2878,16 +2878,21 @@ if ($action == 'create') { print dolGetButtonAction('', $langs->trans('Subtotal'), 'default', $url_button, '', true); } // Validate - if ($object->status == 0 && $num > 0) { - if ($usercanvalidate) { - $tmpbuttonlabel = $langs->trans('Validate'); - if ($usercanapprove && !getDolGlobalString('SUPPLIER_ORDER_NO_DIRECT_APPROVE')) { - $tmpbuttonlabel = $langs->trans("ValidateAndApprove"); - } + if ($object->status == 0) { + if ($num > 0) { + if ($usercanvalidate) { + $tmpbuttonlabel = $langs->trans('Validate'); + if ($usercanapprove && !getDolGlobalString('SUPPLIER_ORDER_NO_DIRECT_APPROVE')) { + $tmpbuttonlabel = $langs->trans("ValidateAndApprove"); + } - print ''; - print $tmpbuttonlabel; - print ''; + print ''; + print $tmpbuttonlabel; + print ''; + } + } else { + $langs->load("errors"); + print dolGetButtonAction($langs->trans("ErrorObjectMustHaveLinesToBeValidated", $object->ref), $langs->trans('Validate'), 'default', $_SERVER["PHP_SELF"] . '?action=validate&token=' . newToken() . '&id=' . $object->id, (string) $object->id, -1); } } // Create event diff --git a/htdocs/fourn/commande/index.php b/htdocs/fourn/commande/index.php index d10c428ae72..b85a4aa8571 100644 --- a/htdocs/fourn/commande/index.php +++ b/htdocs/fourn/commande/index.php @@ -48,7 +48,7 @@ $langs->loadLangs(array("suppliers", "orders")); // Initialize a technical object to manage hooks. Note that conf->hooks_modules contains array $hookmanager->initHooks(array('orderssuppliersindex')); -$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); +$max = getDolUserInt('MAIN_SIZE_SHORTLIST_LIMIT', getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5)); // Security check $orderid = GETPOST('orderid'); diff --git a/htdocs/fourn/commande/list.php b/htdocs/fourn/commande/list.php index 03185f6e11d..56c4c56358f 100644 --- a/htdocs/fourn/commande/list.php +++ b/htdocs/fourn/commande/list.php @@ -10,7 +10,7 @@ * Copyright (C) 2018-2022 Charlene Benke * Copyright (C) 2019 Nicolas Zabouri * Copyright (C) 2021-2026 Alexandre Spangaro - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 William Mead * * This program is free software; you can redistribute it and/or modify @@ -474,7 +474,7 @@ if (empty($reshook)) { $sql .= ", fk_target"; $sql .= ", targettype"; $sql .= ") VALUES ("; - $sql .= $id_order; + $sql .= ((int) $id_order); $sql .= ", '".$db->escape($objecttmp->origin)."'"; $sql .= ", ".((int) $objecttmp->id); $sql .= ", '".$db->escape($objecttmp->element)."'"; diff --git a/htdocs/fourn/facture/card.php b/htdocs/fourn/facture/card.php index 1e289038572..13a8789b770 100644 --- a/htdocs/fourn/facture/card.php +++ b/htdocs/fourn/facture/card.php @@ -15,6 +15,7 @@ * Copyright (C) 2023 Nick Fragoulis * Copyright (C) 2024-2026 MDW * Copyright (C) 2026 Vincent de Grandpré + * Copyright (C) 2026 Lionel Vessiller * * 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 @@ -724,6 +725,15 @@ if (empty($reshook)) { $discount->tva_tx = 0; $discount->vat_src_code = ''; + // multi-currency + $discount->multicurrency_code = $object->multicurrency_code; + $discount->multicurrency_tx = $object->multicurrency_tx; + $discount->multicurrency_total_ht = $discount->multicurrency_total_ttc = (float) price2num((float) $discount->amount_ttc * (float) $object->multicurrency_tx, 'MT'); + $discount->multicurrency_total_tva = 0; + // keep compatibility + $discount->multicurrency_amount_ht = $discount->multicurrency_amount_ttc = $discount->multicurrency_total_ttc; + $discount->multicurrency_amount_tva = 0; + $result = $discount->create($user); if ($result < 0) { $error++; @@ -734,9 +744,16 @@ if (empty($reshook)) { $discount->amount_ht = abs((float) $amount_ht[$tva_tx]); $discount->amount_tva = abs((float) $amount_tva[$tva_tx]); $discount->amount_ttc = abs((float) $amount_ttc[$tva_tx]); - $discount->multicurrency_amount_ht = abs((float) $multicurrency_amount_ht[$tva_tx]); - $discount->multicurrency_amount_tva = abs((float) $multicurrency_amount_tva[$tva_tx]); - $discount->multicurrency_amount_ttc = abs((float) $multicurrency_amount_ttc[$tva_tx]); + // multi-currency + $discount->multicurrency_code = $object->multicurrency_code; + $discount->multicurrency_tx = $object->multicurrency_tx; + $discount->multicurrency_total_ht = abs((float) $multicurrency_amount_ht[$tva_tx]); + $discount->multicurrency_total_tva = abs((float) $multicurrency_amount_tva[$tva_tx]); + $discount->multicurrency_total_ttc = abs((float) $multicurrency_amount_ttc[$tva_tx]); + // keep compatibility + $discount->multicurrency_amount_ht = abs((float) $discount->multicurrency_total_ht); + $discount->multicurrency_amount_tva = abs((float) $discount->multicurrency_total_tva); + $discount->multicurrency_amount_ttc = abs((float) $discount->multicurrency_total_ttc); // Clean vat code $reg = array(); @@ -955,6 +972,11 @@ if (empty($reshook)) { $fk_parent_line = 0; foreach ($facture_source->lines as $line) { + // Extrafields + if (method_exists($line, 'fetch_optionals')) { + $line->fetch_optionals(); + } + // Reset fk_parent_line for no child products and special product if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) { $fk_parent_line = 0; @@ -1003,6 +1025,19 @@ if (empty($reshook)) { } } } + + // Add link between credit note and origin objects + if (!empty($object->fk_facture_source) && $id > 0) { + $facture_source_link = new FactureFournisseur($db); + if ($facture_source_link->fetch($object->fk_facture_source) > 0) { + $facture_source_link->fetchObjectLinked(); + if (!empty($facture_source_link->linkedObjectsIds)) { + foreach ($facture_source_link->linkedObjectsIds as $sourcetype => $TIds) { + $object->add_object_linked($sourcetype, current($TIds)); + } + } + } + } } } elseif ($fac_recid > 0 && (GETPOSTINT('type') == FactureFournisseur::TYPE_STANDARD || GETPOSTINT('type') == FactureFournisseur::TYPE_DEPOSIT)) { // Standard invoice or Deposit invoice, created from a Predefined template invoice @@ -2670,6 +2705,9 @@ if ($action == 'create') { $optionsav .= '
'; $error = 0; @@ -177,21 +177,21 @@ if ($ok) { } print '
'; - +print '
'; print '
'; -print '
'; +print '
'; print 'Select a link "test" or "confirmed" to launch a reparation on the chosen option...'; print '
'; print '
'; - +print '
'; print ''; print ''; -print ''; -print ''; +print ''; +print ''; print ''; print ''; print ''; @@ -202,10 +202,10 @@ if ($dolibarr_main_db_character_set != 'utf8mb4') { } $sections = [ - 'Standard' => [ + 'Miscellaneous' => [ [ 'name' => 'standard', - 'info' => '' + 'info' => 'Miscellaneous fixes' ] ], 'Modules' => [ @@ -263,7 +263,7 @@ $sections = [ 'info' => 'Repair link between dispatch lines and supplier order lines' ] ], - 'Init data' => [ + 'Init empty data' => [ [ 'name' => 'set_empty_time_spent_amount', 'info' => 'Init empty time spent amount' @@ -282,14 +282,17 @@ $sections = [ 'name' => 'force_collation_from_conf_on_tables', 'info' => 'Force '.$conf->db->character_set.'/'.$conf->db->dolibarr_main_db_collation.' + row=dynamic, for mysql/mariadb only' ] - ], - 'Rebuild sequence' => [ + ] +]; + +if ($db->type == 'pgsql') { + $sections['Rebuild sequence'] = [ [ 'name' => 'rebuild_sequences', 'info' => 'For postgresql only' ] - ] -]; + ]; +} $conf->use_javascript_ajax = 1; @@ -318,6 +321,7 @@ foreach ($sections as $section => $options) { } } print '
OptionInformationLaunch testLaunch confirmed
'; +print '
'; print '
'; @@ -554,13 +558,13 @@ if ($ok && GETPOST('standard', 'alpha')) { $obj = $db->fetch_object($resql); $reg = array(); - if (preg_match('/MAIN_MODULE_([^_]+)_(.+)/i', $obj->name, $reg)) { + if (preg_match('/MAIN_MODULE_([^_\W]+)_(\w+)/i', $obj->name, $reg)) { $name = $reg[1]; $type = $reg[2]; $sql2 = "SELECT COUNT(*) as nb"; $sql2 .= " FROM ".MAIN_DB_PREFIX."const as c"; - $sql2 .= " WHERE name = 'MAIN_MODULE_".$name."'"; + $sql2 .= " WHERE name = 'MAIN_MODULE_".$db->sanitize($name)."'"; $sql2 .= " AND entity = ".((int) $obj->entity); $resql2 = $db->query($sql2); if ($resql2) { @@ -612,13 +616,13 @@ if ($ok && GETPOST('standard', 'alpha')) { $obj = $db->fetch_object($resql); $reg = array(); - if (preg_match('/^(.+)@(.+)$/i', $obj->file, $reg)) { + if (preg_match('/^(\w+)@(\w+)$/i', $obj->file, $reg)) { $name = $reg[1]; $module = $reg[2]; $sql2 = "SELECT COUNT(*) as nb"; $sql2 .= " FROM ".MAIN_DB_PREFIX."const as c"; - $sql2 .= " WHERE name = 'MAIN_MODULE_".strtoupper($module)."'"; + $sql2 .= " WHERE name = 'MAIN_MODULE_".strtoupper($module)."'"; // @phan-suppress-current-line SqlInjection $sql2 .= " AND entity = ".((int) $obj->entity); $sql2 .= " AND value <> 0"; $resql2 = $db->query($sql2); @@ -1256,7 +1260,7 @@ if ($ok && GETPOST('set_empty_time_spent_amount', 'alpha')) { if (GETPOST('set_empty_time_spent_amount') == 'confirmed') { $sql2 = "UPDATE ".MAIN_DB_PREFIX."element_time"; - $sql2 .= " SET thm = ".$obj->user_thm." WHERE thm IS NULL AND fk_user = ".((int) $obj->user_id); + $sql2 .= " SET thm = ".((float) $obj->user_thm)." WHERE thm IS NULL AND fk_user = ".((int) $obj->user_id); $resql2 = $db->query($sql2); if (!$resql2) { $error++; @@ -1354,13 +1358,13 @@ if ($ok && GETPOST('force_disable_of_modules_not_found', 'alpha')) { if (!$result) { print ' - File of '.$key.' ('.$reloffile.') NOT found, we disable the module.'; if (GETPOST('force_disable_of_modules_not_found') == 'confirmed') { - $sql2 = "DELETE FROM ".MAIN_DB_PREFIX."const WHERE name = 'MAIN_MODULE_".strtoupper($name)."_".strtoupper($key)."'"; + $sql2 = "DELETE FROM ".MAIN_DB_PREFIX."const WHERE name = 'MAIN_MODULE_".strtoupper($name)."_".strtoupper($key)."'"; // @phan-suppress-current-line SqlInjection $resql2 = $db->query($sql2); if (!$resql2) { $error++; dol_print_error($db); } - $sql3 = "DELETE FROM ".MAIN_DB_PREFIX."const WHERE name = 'MAIN_MODULE_".strtoupper($name)."'"; + $sql3 = "DELETE FROM ".MAIN_DB_PREFIX."const WHERE name = 'MAIN_MODULE_".strtoupper($name)."'"; // @phan-suppress-current-line SqlInjection $resql3 = $db->query($sql3); if (!$resql3) { $error++; @@ -1421,7 +1425,7 @@ if ($ok && GETPOST('clean_perm_table', 'alpha')) { $obj = $db->fetch_object($resql); if ($obj->id > 0) { print 'Found line with id '.$obj->id.', label "'.$obj->label.'" of module "'.$obj->module.'" to delete'; - if (GETPOST('clean_perm_table', 'alpha') == 'confirmed') { + if (GETPOST('clean_perm_table', 'alpha') == 'confirmed') { // If confirmed, proceed with deletion $sqldelete = "DELETE FROM ".MAIN_DB_PREFIX."rights_def WHERE id = ".((int) $obj->id); $resqldelete = $db->query($sqldelete); if (!$resqldelete) { @@ -1464,7 +1468,7 @@ if ($ok && GETPOST('clean_ecm_files_table', 'alpha')) { $obj = $db->fetch_object($resql); if ($obj->rowid > 0) { $filetocheck = DOL_DATA_ROOT.'/'.$obj->filepath.'/'.$obj->filename; - $nbfile++; + $nbfile++; // Count of files analyzed if (!dol_is_file($filetocheck) && !dol_is_file($filetocheck.'.noexe')) { $nbfiletodelete++; if ($nbfiletodelete <= $MAXTODELETE) { @@ -1782,8 +1786,8 @@ if ($ok && GETPOST('force_collation_from_conf_on_tables', 'alpha')) { print ''; print $table[0]; - $sql1 = "ALTER TABLE ".$table[0]." ROW_FORMAT=dynamic"; - $sql2 = "ALTER TABLE ".$table[0]." CONVERT TO CHARACTER SET ".$conf->db->character_set." COLLATE ".$conf->db->dolibarr_main_db_collation; + $sql1 = "ALTER TABLE ".$table[0]." ROW_FORMAT=dynamic"; // @phan-suppress-current-line SqlInjection + $sql2 = "ALTER TABLE ".$table[0]." CONVERT TO CHARACTER SET ".$conf->db->character_set." COLLATE ".$conf->db->dolibarr_main_db_collation; // @phan-suppress-current-line SqlInjection print ''; print ''; if ($force_collation_from_conf_on_tables == 'confirmed') { @@ -2054,11 +2058,11 @@ if ($ok && GETPOST('recalculateinvoicetotal') == 'confirmed') { FROM ".MAIN_DB_PREFIX."facturedet fd WHERE - fd.fk_facture = $obj->rowid"; + fd.fk_facture = ".((int) $obj->rowid); $ressql_calculs = $db->query($sql_calculs); while ($obj_calcul = $db->fetch_object($ressql_calculs)) { - // Calcul de la somme des paiements reçus - $sql_paiements = "SELECT SUM(amount) as somme from ".MAIN_DB_PREFIX."paiement_facture WHERE fk_facture = $obj->rowid"; + // Calculate the sum of received payments + $sql_paiements = "SELECT SUM(amount) as somme from ".MAIN_DB_PREFIX."paiement_facture WHERE fk_facture = ".((int) $obj->rowid); $montantPaiements = $db->fetch_object($db->query($sql_paiements))->somme; $totHt = ($obj_calcul->total_ht ? price2num($obj_calcul->total_ht, 'MT') : 0); $totTva = ($obj_calcul->total_tva ? price2num($obj_calcul->total_tva, 'MT') : 0); @@ -2068,15 +2072,15 @@ if ($ok && GETPOST('recalculateinvoicetotal') == 'confirmed') { $sql_maj = " UPDATE ".MAIN_DB_PREFIX."facture SET - total_ht = $totHt, - total_tva = $totTva, - localtax1 = $totLocal1, - localtax2 = $totLocal2, - total_ttc = $totTtc, + total_ht = ".((float) $totHt).", + total_tva = ".((float) $totTva).", + localtax1 = ".((float) $totLocal1).", + localtax2 = ".((float) $totLocal2).", + total_ttc = ".((float) $totTtc).", fk_statut = ".($totTtc == price2num($montantPaiements, 'MT') ? 2 : 1).", paid = ".($totTtc == price2num($montantPaiements, 'MT') ? 1 : 0)." WHERE - rowid = $obj->rowid"; + rowid = ".((int) $obj->rowid); $db->query($sql_maj); } $i++; diff --git a/htdocs/install/step2.php b/htdocs/install/step2.php index 383cec1df67..d751ba9f639 100644 --- a/htdocs/install/step2.php +++ b/htdocs/install/step2.php @@ -353,7 +353,7 @@ if ($action == "set") { // Test on permission not required. Already managed by fclose($fp); // If several requests, we loop on each - $listesql = explode(';', $buffer); + $listesql = explode(';', $buffer); // @phan-suppress-current-line SqlInjection foreach ($listesql as $req) { $buffer = trim($req); if ($buffer) { @@ -441,7 +441,7 @@ if ($action == "set") { // Test on permission not required. Already managed by //$buffer=preg_replace('/;\';/',";'§",$buffer); // If several requests, we loop on each of them - $listesql = explode('§', $buffer); + $listesql = explode('§', $buffer); // @phan-suppress-current-line SqlInjection foreach ($listesql as $buffer) { $buffer = trim($buffer); if ($buffer) { diff --git a/htdocs/install/step5.php b/htdocs/install/step5.php index 86796510c27..1521ce78c86 100644 --- a/htdocs/install/step5.php +++ b/htdocs/install/step5.php @@ -320,7 +320,7 @@ if ($action == "set" || empty($action) || preg_match('/upgrade/i', $action)) { } else { if ($result == -6) { //login or email already exists dolibarr_install_syslog('step5: AdminLoginAlreadyExists', LOG_WARNING); - print '
'.$newuser->error."

"; + print '
'.$newuser->error."
"; $success = 1; } else { dolibarr_install_syslog('step5: FailedToCreateAdminLogin '.$newuser->error, LOG_ERR); @@ -522,7 +522,7 @@ if ($action == "set") { if ($success) { if (!getDolGlobalString('MAIN_VERSION_LAST_UPGRADE') || (getDolGlobalString('MAIN_VERSION_LAST_UPGRADE') == DOL_VERSION)) { // Install is finished (database is on same version than files) - print '
'.$langs->trans("SystemIsInstalled")."
"; + print '
'.$langs->trans("SystemIsInstalled")."

"; // Create install.lock file // No need for the moment to create it automatically, creation by web assistant means permissions are given @@ -545,7 +545,7 @@ if ($action == "set") { } } if (empty($createlock)) { - print '
'.$langs->trans("WarningRemoveInstallDir")."
"; + print '
'.$langs->trans("WarningRemoveInstallDir")."
"; } print "
"; @@ -594,7 +594,7 @@ if ($action == "set") { } } if (empty($createlock)) { - print '
'.$langs->trans("WarningRemoveInstallDir")."
"; + print '
'.$langs->trans("WarningRemoveInstallDir")."
"; } // Delete the upgrade.unlock file it it exists diff --git a/htdocs/install/upgrade.php b/htdocs/install/upgrade.php index 04ff7f194b0..de3c60be078 100644 --- a/htdocs/install/upgrade.php +++ b/htdocs/install/upgrade.php @@ -295,10 +295,10 @@ if (!GETPOST('action', 'aZ09') || preg_match('/upgrade/i', GETPOST('action', 'aZ $values = $db->fetch_array($resql); if (is_array($values)) { $i = 0; - $createsql = $values[1]; + $createsql = $values[1]; // @phan-suppress-current-line SqlInjection $reg = array(); while (preg_match('/CONSTRAINT `(0_[0-9a-zA-Z]+|[_0-9a-zA-Z]+_ibfk_[0-9]+)`/i', $createsql, $reg) && $i < 100) { - $sqldrop = "ALTER TABLE ".$val." DROP FOREIGN KEY ".$reg[1]; + $sqldrop = "ALTER TABLE ".$db->sanitize($val)." DROP FOREIGN KEY ".$db->sanitize($reg[1]); $resqldrop = $db->query($sqldrop); if ($resqldrop) { print ''.$sqldrop.";\n"; @@ -335,7 +335,7 @@ if (!GETPOST('action', 'aZ09') || preg_match('/upgrade/i', GETPOST('action', 'aZ $filelist = array(); $i = 0; $ok = 0; - $from = '^'.preg_quote($newversionfrom, '/'); + $from_regex = '^'.preg_quote($newversionfrom, '/'); $to = preg_quote($newversionto.'.sql', '/').'$'; // Get files list @@ -354,7 +354,7 @@ if (!GETPOST('action', 'aZ09') || preg_match('/upgrade/i', GETPOST('action', 'aZ // Define which file to run foreach ($filesindir as $file) { - if (preg_match('/'.$from.'\-/i', $file)) { + if (preg_match('/'.$from_regex.'\-/i', $file)) { $filelist[] = $file; } elseif (preg_match('/\-'.$to.'/i', $file)) { // First test may be false if we migrate from x.y.* to x.y.* $filelist[] = $file; diff --git a/htdocs/install/upgrade2.php b/htdocs/install/upgrade2.php index a0236cff7cf..0124bbaf379 100644 --- a/htdocs/install/upgrade2.php +++ b/htdocs/install/upgrade2.php @@ -720,7 +720,7 @@ if (!GETPOST('action', 'aZ09') || preg_match('/upgrade/i', GETPOST('action', 'aZ 'MAIN_MODULE_RESOURCE' => 'noboxes', 'MAIN_MODULE_SALARIES' => 'newboxdefonly', 'MAIN_MODULE_SERVICE' => 'newboxdefonly', - 'MAIN_MODULE_SYSLOG' => 'newboxdefonly', + //'MAIN_MODULE_SYSLOG' => 'newboxdefonly', This enabled the module syslog, but we don't want to do that. 'MAIN_MODULE_SOCIETE' => 'newboxdefonly', 'MAIN_MODULE_STRIPE' => 'menuonly', 'MAIN_MODULE_TICKET' => 'newboxdefonly', @@ -735,6 +735,14 @@ if (!GETPOST('action', 'aZ09') || preg_match('/upgrade/i', GETPOST('action', 'aZ $error++; } + // Grant the supplier prices permissions added in 23.0 to users/groups that already have + // the matching product/service rights (ids resolved by name from the rights_def reloaded + // just above), so nobody loses access to supplier prices on upgrade. Idempotent. + $result = migrate_supplier_prices_permissions($db, $langs, $conf); + if ($result < 0) { + $error++; + } + // Reload menus (this must be always done, and only into last targeted version) // This reload the auguria menu.To reload a dynamic menu defined into module descriptor, see previours step $result = migrate_reload_menu($db, $langs, $conf); @@ -1363,7 +1371,7 @@ function migrate_links_transfert($db, $langs, $conf) $sql .= "fk_bank, url_id, url, label, type"; $sql .= ")"; $sql .= " VALUES ("; - $sql .= $obj->barowid.",".$obj->bbrowid.", '/compta/bank/line.php?rowid=', '(banktransfert)', 'banktransfert'"; + $sql .= ((int) $obj->barowid).",".((int) $obj->bbrowid).", '/compta/bank/line.php?rowid=', '(banktransfert)', 'banktransfert'"; $sql .= ")"; //print $sql.'
'; @@ -1654,9 +1662,9 @@ function migrate_paiementfourn_facturefourn($db, $langs, $conf) print ''.$select_obj->rowid.''.$select_obj->fk_facture_fourn.''.$select_obj->amount.''; $insert_sql = 'INSERT INTO '.MAIN_DB_PREFIX.'paiementfourn_facturefourn SET '; - $insert_sql .= ' fk_paiementfourn = \''.$select_obj->rowid.'\','; - $insert_sql .= ' fk_facturefourn = \''.$select_obj->fk_facture_fourn.'\','; - $insert_sql .= ' amount = \''.$select_obj->amount.'\''; + $insert_sql .= ' fk_paiementfourn = \''.((int) $select_obj->rowid).'\','; + $insert_sql .= ' fk_facturefourn = \''.((int) $select_obj->fk_facture_fourn).'\','; + $insert_sql .= ' amount = \''.((float) $select_obj->amount).'\''; $insert_resql = $db->query($insert_sql); if ($insert_resql) { @@ -2838,9 +2846,9 @@ function migrate_project_user_resp($db, $langs, $conf) $sql2 .= ") VALUES ("; $sql2 .= "'".$db->idate(dol_now())."'"; $sql2 .= ", '4'"; - $sql2 .= ", ".$obj->rowid; + $sql2 .= ", ".((int) $obj->rowid); $sql2 .= ", '160'"; - $sql2 .= ", ".$obj->fk_user_resp; + $sql2 .= ", ".((int) $obj->fk_user_resp); $sql2 .= ")"; if ($obj->fk_user_resp > 0) { @@ -2917,9 +2925,9 @@ function migrate_project_task_actors($db, $langs, $conf) $sql2 .= ") VALUES ("; $sql2 .= "'".$db->idate(dol_now())."'"; $sql2 .= ", '4'"; - $sql2 .= ", ".$obj->fk_project_task; + $sql2 .= ", ".((int) $obj->fk_project_task); $sql2 .= ", '180'"; - $sql2 .= ", ".$obj->fk_user; + $sql2 .= ", ".((int) $obj->fk_user); $sql2 .= ")"; $resql2 = $db->query($sql2); @@ -3269,9 +3277,9 @@ function migrate_shipping_delivery($db, $langs, $conf) $sqlInsert .= ", fk_target"; $sqlInsert .= ", targettype"; $sqlInsert .= ") VALUES ("; - $sqlInsert .= $obj->fk_expedition; + $sqlInsert .= ((int) $obj->fk_expedition); $sqlInsert .= ", 'shipping'"; - $sqlInsert .= ", ".$obj->rowid; + $sqlInsert .= ", ".((int) $obj->rowid); $sqlInsert .= ", 'delivery'"; $sqlInsert .= ")"; @@ -3953,7 +3961,7 @@ function migrate_remise_entity($db, $langs, $conf) $obj = $db->fetch_object($resql); $sqlUpdate = "UPDATE ".MAIN_DB_PREFIX."societe_remise SET"; - $sqlUpdate .= " entity = ".$obj->entity; + $sqlUpdate .= " entity = ".((int) $obj->entity); $sqlUpdate .= " WHERE rowid = ".((int) $obj->rowid); $result = $db->query($sqlUpdate); @@ -4074,6 +4082,115 @@ function migrate_remise_except_entity($db, $langs, $conf) print ''; } +/** + * Grant the "supplier prices" advanced permissions introduced in 23.0 (product/service + * read_supplier_prices and product write_supplier_prices) to the users and groups that already + * hold the matching product/service "read"/"create" rights. + * + * Before 23.0, access to supplier (purchase) prices followed the general product/service read and + * create rights. In "Advanced permissions" mode these are now gated by dedicated permissions that + * are not granted by default, so without this migration every user/group silently loses access to + * supplier prices on upgrade. Permission ids are resolved by name from llx_rights_def (populated by + * the module reload done just before this call) to avoid hardcoded permission ids. Idempotent. + * + * @param DoliDB $db Database handler + * @param Translate $langs Object langs + * @param Conf $conf Object conf + * @return int Return integer <0 if KO, >=0 if OK + */ +function migrate_supplier_prices_permissions($db, $langs, $conf) +{ + dolibarr_install_syslog("upgrade2::migrate_supplier_prices_permissions"); + + // Grant each target permission to holders of any of its source permissions. + // Format: array('to' => array(module, perms, subperms), 'from' => array(array(module, perms, subperms), ...)) + $rules = array( + array('to' => array('produit', 'product_advance', 'read_supplier_prices'), 'from' => array(array('produit', 'lire', ''), array('produit', 'creer', ''))), + array('to' => array('produit', 'product_advance', 'write_supplier_prices'), 'from' => array(array('produit', 'creer', ''))), + array('to' => array('service', 'service_advance', 'read_supplier_prices'), 'from' => array(array('service', 'lire', ''))), + ); + + $error = 0; + $db->begin(); + + foreach ($rules as $rule) { + // Resolve the target permission id by name (skip if the module/permission is not present) + $targetid = migrate_get_rights_def_id($db, $rule['to'][0], $rule['to'][1], $rule['to'][2]); + if ($targetid <= 0) { + continue; + } + + // Resolve the source permission ids by name + $sourceids = array(); + foreach ($rule['from'] as $from) { + $id = migrate_get_rights_def_id($db, $from[0], $from[1], $from[2]); + if ($id > 0) { + $sourceids[] = $id; + } + } + if (empty($sourceids)) { + continue; + } + $sourcelist = implode(',', $sourceids); + + // Grant to groups that hold a source permission but not yet the target + $sql = "INSERT INTO ".MAIN_DB_PREFIX."usergroup_rights (entity, fk_usergroup, fk_id)"; + $sql .= " SELECT DISTINCT gr.entity, gr.fk_usergroup, ".((int) $targetid); + $sql .= " FROM ".MAIN_DB_PREFIX."usergroup_rights as gr"; + $sql .= " WHERE gr.fk_id IN (".$db->sanitize($sourcelist).")"; + $sql .= " AND NOT EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."usergroup_rights as ex WHERE ex.fk_usergroup = gr.fk_usergroup AND ex.entity = gr.entity AND ex.fk_id = ".((int) $targetid).")"; + if (!$db->query($sql)) { + $error++; + break; + } + + // Grant to users that hold a source permission but not yet the target + $sql = "INSERT INTO ".MAIN_DB_PREFIX."user_rights (entity, fk_user, fk_id)"; + $sql .= " SELECT DISTINCT ur.entity, ur.fk_user, ".((int) $targetid); + $sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur"; + $sql .= " WHERE ur.fk_id IN (".$db->sanitize($sourcelist).")"; + $sql .= " AND NOT EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."user_rights as ex WHERE ex.fk_user = ur.fk_user AND ex.entity = ur.entity AND ex.fk_id = ".((int) $targetid).")"; + if (!$db->query($sql)) { + $error++; + break; + } + } + + if ($error) { + $db->rollback(); + return -1; + } + $db->commit(); + return 0; +} + +/** + * Return the permission id (llx_rights_def.id) matching a module/perms/subperms triplet, or 0 if none. + * + * @param DoliDB $db Database handler + * @param string $module Module (rights_def.module) + * @param string $perms First level permission (rights_def.perms) + * @param string $subperms Second level permission (rights_def.subperms), '' for none + * @return int Permission id, or 0 if not found + */ +function migrate_get_rights_def_id($db, $module, $perms, $subperms) +{ + $sql = "SELECT id FROM ".MAIN_DB_PREFIX."rights_def"; + $sql .= " WHERE module = '".$db->escape($module)."'"; + $sql .= " AND perms = '".$db->escape($perms)."'"; + if ($subperms === '') { + $sql .= " AND (subperms IS NULL OR subperms = '')"; + } else { + $sql .= " AND subperms = '".$db->escape($subperms)."'"; + } + $resql = $db->query($sql); + if (!$resql) { + return 0; + } + $obj = $db->fetch_object($resql); + return $obj ? (int) $obj->id : 0; +} + /** * Migrate to add entity value into llx_user_rights * @@ -5658,7 +5775,7 @@ function migrate_blockedlog_add_hmac_key() $hmac_secret_key = 'BLOCKEDLOGHMAC'.$randomsecret; // Example: 'BLOCKEDLOGHMACY3Ewx37RXbSd8gL9JV8p7Wqw7qvq2K2A' - $result = dolibarr_set_const($db, 'BLOCKEDLOG_HMAC_KEY', $hmac_secret_key, 'chaine', 0, 'The secret key for HMAC used for blockedlog record', 0); // Will encrypt the value using dolCrypt and store it. + $result = dolibarr_set_const($db, 'BLOCKEDLOG_HMAC_KEY', $hmac_secret_key, 'chaine', 0, 'The secret key for HMAC used for blockedlog record', $conf->entity); // Will encrypt the value using dolCrypt and store it. if ($result < 0) { dol_print_error($db); diff --git a/htdocs/intracommreport/card.php b/htdocs/intracommreport/card.php index f32e4250228..0d81894d2dd 100644 --- a/htdocs/intracommreport/card.php +++ b/htdocs/intracommreport/card.php @@ -2,6 +2,7 @@ /* Copyright (C) 2017 Laurent Destailleur * Copyright (C) ---Put here your own copyright and developer email--- * Copyright (C) 2024-2026 Frédéric France + * Copyright (C) 2026 MDW * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,7 +29,7 @@ * Terms * * DEB = Declaration d'Exchanges de Biens (FR) = Declaration of Exchange of Goods (EN) - * DES = Déclaration Européenne de Services (FR) = European Declaration of Services (EN) + * DES = Declaration Europeenne de Services (FR) = European Declaration of Services (EN) * * INTRACOMM: Douanes françaises (FR) = french customs (EN) - https://www.douane.gouv.fr/professionnels/commerce-international/import-export * diff --git a/htdocs/intracommreport/class/intracommreport.class.php b/htdocs/intracommreport/class/intracommreport.class.php index 93513905112..9826241e5b8 100644 --- a/htdocs/intracommreport/class/intracommreport.class.php +++ b/htdocs/intracommreport/class/intracommreport.class.php @@ -360,7 +360,7 @@ class IntracommReport extends CommonObject $declaration_des = $e->addChild('declaration_des'); $declaration_des->addChild('num_des', self::getDeclarationNumber($this->numero_declaration)); - $declaration_des->addChild('num_tvaFr', $mysoc->tva_intra ?? ''); // /^FR[a-Z0-9]{2}[0-9]{9}$/ // Doit faire 13 caractères + $declaration_des->addChild('num_tvaFr', $mysoc->tva_intra ?? ''); // /^FR[a-Z0-9]{2}[0-9]{9}$/ // Must be 13 characters $declaration_des->addChild('mois_des', (string) $period_month); $declaration_des->addChild('an_des', (string) $period_year); diff --git a/htdocs/knowledgemanagement/class/knowledgerecord.class.php b/htdocs/knowledgemanagement/class/knowledgerecord.class.php index 83c2dbd47a9..65e7551317c 100644 --- a/htdocs/knowledgemanagement/class/knowledgerecord.class.php +++ b/htdocs/knowledgemanagement/class/knowledgerecord.class.php @@ -645,14 +645,14 @@ class KnowledgeRecord extends CommonObject if (preg_match('/^[\(]?PROV/i', $this->ref)) { // Now we rename also files into index $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'knowledgerecord/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'knowledgerecord/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'knowledgerecord/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; $this->error = $this->db->lasterror(); } $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'knowledgerecord/".$this->db->escape($this->newref)."'"; - $sql .= " WHERE filepath = 'knowledgerecord/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; + $sql .= " WHERE filepath = 'knowledgerecord/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if (!$resql) { $error++; diff --git a/htdocs/knowledgemanagement/core/modules/knowledgemanagement/mod_knowledgerecord_standard.php b/htdocs/knowledgemanagement/core/modules/knowledgemanagement/mod_knowledgerecord_standard.php index f1d52de6edf..deb86e5b973 100644 --- a/htdocs/knowledgemanagement/core/modules/knowledgemanagement/mod_knowledgerecord_standard.php +++ b/htdocs/knowledgemanagement/core/modules/knowledgemanagement/mod_knowledgerecord_standard.php @@ -1,7 +1,7 @@ * Copyright (C) 2005-2009 Regis Houssin - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -91,11 +91,11 @@ class mod_knowledgerecord_standard extends ModeleNumRefKnowledgeRecord $max = ''; $posindice = strlen($this->prefix) + 6; - $sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".$posindice.") AS SIGNED)) as max"; + $sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".((int) $posindice).") AS SIGNED)) as max"; $sql .= " FROM ".MAIN_DB_PREFIX."knowledgemanagement_knowledgerecord"; $sql .= " WHERE ref LIKE '".$db->escape($this->prefix)."____-%'"; if ($object->ismultientitymanaged == 1) { - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); } elseif (!is_numeric($object->ismultientitymanaged)) { // TODO } @@ -129,11 +129,11 @@ class mod_knowledgerecord_standard extends ModeleNumRefKnowledgeRecord // first we get the max value $posindice = strlen($this->prefix) + 6; - $sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".$posindice.") AS SIGNED)) as max"; + $sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".((int) $posindice).") AS SIGNED)) as max"; $sql .= " FROM ".MAIN_DB_PREFIX."knowledgemanagement_knowledgerecord"; $sql .= " WHERE ref LIKE '".$db->escape($this->prefix)."____-%'"; if ($object->ismultientitymanaged == 1) { - $sql .= " AND entity = ".$conf->entity; + $sql .= " AND entity = ".((int) $conf->entity); } elseif (!is_numeric($object->ismultientitymanaged)) { // TODO } diff --git a/htdocs/langs/en_US/accountancy.lang b/htdocs/langs/en_US/accountancy.lang index 2e468b8789b..d8350655461 100644 --- a/htdocs/langs/en_US/accountancy.lang +++ b/htdocs/langs/en_US/accountancy.lang @@ -32,7 +32,7 @@ Chartofaccounts=Chart of accounts ChartOfSubaccounts=Chart of individual accounts ChartOfIndividualAccountsOfSubsidiaryLedger=Chart of individual accounts of the subsidiary ledger CurrentDedicatedAccountingAccount=Current dedicated account -AssignDedicatedAccountingAccount=New account to assign +AssignDedicatedAccountingAccount=Accouting account to assign InvoiceLabel=Invoice label OverviewOfAmountOfLinesNotBound=Overview of amount of lines not bound to an accounting account OverviewOfAmountOfLinesBound=Overview of amount of lines already bound to an accounting account @@ -487,7 +487,7 @@ GrandTotals = Grand Totals ErrorAccountancyDirectoryNotDefined = Accountancy directory is not defined ## Tools - Init accounting account on product / service InitAccountancy=Init accountancy -InitAccountancyDesc=This page can be used to initialize an accounting account on products and services that does not have accounting account defined for sales and purchases. +InitAccountancyDesc=This page can be used to initialize an accounting account on products and services that does not have predefined accounting accounts. DefaultBindingDesc=This page can be used to set the default accounts (from the chart of account) to use to link business objects with an account, like payment salaries, donation, taxes and VAT, when no specific account were already set. DefaultClosureDesc=This page can be used to set parameters used for accounting closures. Options=Options @@ -506,8 +506,8 @@ OptionModeProductBuyExportDesc=Show all products with accounting account for oth CleanFixHistory=Remove accounting code from lines that not exists into charts of account CleanHistory=Reset all bindings for selected year PredefinedGroups=Predefined groups -WithoutValidAccount=Without valid dedicated account -WithValidAccount=With valid dedicated account +WithoutValidAccount=Without valid default account +WithValidAccount=With a valid default account ValueNotIntoChartOfAccount=This value of accounting account does not exist into chart of account AccountRemovedFromGroup=Account removed from group SaleLocal=Local sale @@ -646,3 +646,4 @@ ReturnAccount=Return Clone=Clone AlreadyReturnedAccount=Entry n°%s has already been reversed SuccessReturnedAccount=Entry n°%s has been reversed +AccountancyCodeToSet=Accountancy code to set on products/services for diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 9417ad2b253..ffd74da4ead 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -27,6 +27,12 @@ FilesMissing=Missing files FilesUpdated=Updated files FilesModified=Modified files FilesAdded=Added files +ShowDiffWithOriginal=Show changes compared to the original file +DiffNotAvailableForBinaryFiles=Comparison is not available for binary files +CouldNotFetchOriginalFile=Could not retrieve the original file +OriginalFileChecksumMismatch=Warning: the retrieved original file does not match the expected checksum, the comparison below may not be reliable +NoDifferenceFound=No difference found +Loading=Loading FileCheckDolibarr=Files integrity AvailableOnlyOnPackagedVersions=Local integrity file is only available for packaged installations. Use the remote signature if unavailable. XmlNotFound=Application XML integrity file not found diff --git a/htdocs/langs/en_US/bills.lang b/htdocs/langs/en_US/bills.lang index 758c1dbce21..7df8f642140 100644 --- a/htdocs/langs/en_US/bills.lang +++ b/htdocs/langs/en_US/bills.lang @@ -242,6 +242,11 @@ AmountOfBillsHT=Amount of invoices (net of tax) AmountOfBillsByMonthHT=Amount of invoices by month (net of tax) UseSituationInvoices=Allow situation invoice UseSituationInvoicesCreditNote=Allow situation invoice credit note +SituationInvoiceMode=Situation invoice mode +SituationInvoiceModeCumulative=Cumulative mode (legacy) +SituationInvoiceModeProgressive=Progressive mode (recommended) +SituationInvoiceModeHelp=Cumulative mode (value 1) is the legacy implementation, flagged as unstable in the code. Progressive mode (value 2) is the recommended one. +SituationInvoiceModeWarning=Do not change this mode once situation invoices exist: amounts already stored would be interpreted differently. RetainedWarranty=Retained warranty RetainedWarrantyShort=Ret. warranty AllowedInvoiceForRetainedWarranty=Retained warranty usable on the following types of invoices diff --git a/htdocs/langs/en_US/blockedlog.lang b/htdocs/langs/en_US/blockedlog.lang index 4122a80ddad..e0eb8f1edcc 100644 --- a/htdocs/langs/en_US/blockedlog.lang +++ b/htdocs/langs/en_US/blockedlog.lang @@ -64,7 +64,7 @@ LNECertifiedPOSSystem=LNE Certified POS system LNECandidatePOSSystem=POS system candidate for the LNE certification LNECertifiedVersionFR=The version %s of module Immutable Logs is a certified version. More information on https://www.dolibarr.org/certifications.php LNECandidateVersionForCertificationFR=The version %s of module Immutable Logs is a candidate version to become a LNE certified version. Not yet certified. -NotCertifiedVersionFR=This version %s of application is technically ready to be compliant with the french VAT anti-fraud law (check any update of the law of your country), but is NOT the certified version, so you MUST get the compliant statement from an IT company to be allowed to use this software to collect payments from private individuals (ask your reseller or integrator to obtain this statement of compliance). +NotCertifiedVersionFR=This version %s of the immutable log module is technically ready to be compliant with the french VAT anti-fraud law (check any update of the law of your country). However, if you collect payments from private individuals and you are elligible to VAT, you can't use this software without a compliance statement ("French attestation"). So You MUST ask your reseller, integrator or hosting company to obtain this statement of compliance (Note: A list of companies that provide the statement for their customers may be available on https://partners.dolibarr.org. InvoiceGeneratedWithLNECertifiedPOSSystem=Invoice generated with a LNE certified POS system InvoiceGeneratedWithLNECandidatePOSSystem=Invoice generated with a candidate for the LNE certified POS system TotalTTCIfInvoiceSeeCompleteDataForDetail=Event amount. Total including tax if invoice. See the column "complete data" for details. @@ -137,7 +137,7 @@ NeedAThirdPartyStatement=You need a third-party statement to be compliant with l ForPeriodAndFilters=For the period and filters AddtionalInformation=Additional technical information StatusValid=Valid -NotCertified=Not certified (see statement below) +NotCertified=Need a compliance statement (see note below) ## logTypes logBILL_DELETE=Customer invoice logically deleted logBILL_PAYED=Customer invoice paid diff --git a/htdocs/langs/en_US/errors.lang b/htdocs/langs/en_US/errors.lang index bf383db72d9..fc3e537f1b4 100644 --- a/htdocs/langs/en_US/errors.lang +++ b/htdocs/langs/en_US/errors.lang @@ -443,6 +443,7 @@ WarningModuleAffiliatedToAReportedCompany=Warning, this module has been reported WarningModuleAffiliatedToAPiratPlatform=Be careful when getting a module (paid or free) from a suspicious non official platform like %s WarningDNSServerNotAvailable=Warning, DNS server not available WarningSecureKeyNotSet=The value fo the securekey must not be empty +WarningFullPathWasNotFoundSoWeUseAnAlternativeValue=Note: Full path was not found so we used the alternative value %s SwissQrOnlyVIR = SwissQR invoice can only be added on invoices set to be paid with credit transfer payments. SwissQrCreditorAddressInvalid = Creditor address is invalid (are ZIP and city set? (%s) diff --git a/htdocs/langs/en_US/orders.lang b/htdocs/langs/en_US/orders.lang index b5d66b9279b..7006f2ecda7 100644 --- a/htdocs/langs/en_US/orders.lang +++ b/htdocs/langs/en_US/orders.lang @@ -166,6 +166,7 @@ PDFEdisonDescription=A simple order model PDFProformaDescription=A complete Proforma invoice template CreateInvoiceForThisCustomer=Bill orders CreateInvoiceForThisSupplier=Bill orders +WarningOrderNotEligibleForInvoicing=Order %s has been skipped: its status does not allow creating an invoice CreateInvoiceForThisReceptions=Bill receptions NoOrdersToInvoice=No orders billable CloseProcessedOrdersAutomatically=Classify "Processed" all selected orders. diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index 97986e9da45..540ee6b976d 100644 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -1,5 +1,6 @@ # Dolibarr language file - Source file is en_US - products ProductRef=Product ref. +ServiceRef=Service ref. ProductLabel=Product label ProductLabelTranslated=Translated product label ProductDescription=Product description diff --git a/htdocs/langs/en_US/users.lang b/htdocs/langs/en_US/users.lang index f2fb7696115..45a1823ca8b 100644 --- a/htdocs/langs/en_US/users.lang +++ b/htdocs/langs/en_US/users.lang @@ -171,3 +171,4 @@ AtNextLogin=At next connection UserDoesNotHaveRightsToChangeHisPassword=This user doesn't have the rights to change his own password NotPossible=Not possible NoPasswordGenerationRuleConfigured=The engine or rule to generate password has not been set or is set to empty password in the setup page. +OnlyAdminUsersCanDeleteAdminUsers=Only admin users can delete admin users diff --git a/htdocs/langs/fr_FR/products.lang b/htdocs/langs/fr_FR/products.lang index dd0e1e8bde4..82c9fd13aae 100644 --- a/htdocs/langs/fr_FR/products.lang +++ b/htdocs/langs/fr_FR/products.lang @@ -1,5 +1,6 @@ # Dolibarr language file - Source file is en_US - products ProductRef=Réf. produit +ServiceRef=Réf. service ProductLabel=Libellé produit ProductLabelTranslated=Libellé produit traduit ProductDescription=Description du produit diff --git a/htdocs/loan/class/loan.class.php b/htdocs/loan/class/loan.class.php index 756b4c99c34..45767eb1eef 100644 --- a/htdocs/loan/class/loan.class.php +++ b/htdocs/loan/class/loan.class.php @@ -1,7 +1,7 @@ * Copyright (C) 2015-2026 Frédéric France - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -310,7 +310,7 @@ class Loan extends CommonObject $sql .= " '".$this->db->escape($this->account_interest)."',"; $sql .= " ".((int) $conf->entity).","; $sql .= " '".$this->db->idate($now)."',"; - $sql .= " ".(empty($this->fk_project) ? 'NULL' : $this->fk_project).","; + $sql .= " ".(empty($this->fk_project) ? 'NULL' : ((int) $this->fk_project)).","; $sql .= " ".((int) $user->id).","; $sql .= " '".price2num($newinsuranceamount)."'"; $sql .= ")"; diff --git a/htdocs/loan/class/loanschedule.class.php b/htdocs/loan/class/loanschedule.class.php index c66f243cb63..5ff930a65b5 100644 --- a/htdocs/loan/class/loanschedule.class.php +++ b/htdocs/loan/class/loanschedule.class.php @@ -1,7 +1,7 @@ * Copyright (C) 2018-2025 Frédéric France - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -209,7 +209,7 @@ class LoanSchedule extends CommonObject if ($totalamount != 0) { $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (fk_loan, datec, datep, amount_capital, amount_insurance, amount_interest,"; $sql .= " fk_typepayment, fk_user_creat, fk_bank)"; - $sql .= " VALUES (".$this->fk_loan.", '".$this->db->idate($now)."',"; + $sql .= " VALUES (".((int) $this->fk_loan).", '".$this->db->idate($now)."',"; $sql .= " '".$this->db->idate($this->datep)."',"; $sql .= " ".price2num($this->amount_capital).","; $sql .= " ".price2num($this->amount_insurance).","; @@ -357,14 +357,14 @@ class LoanSchedule extends CommonObject // Update request $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET"; - $sql .= " fk_loan=".(isset($this->fk_loan) ? $this->fk_loan : "null").","; + $sql .= " fk_loan=".(isset($this->fk_loan) ? ((int) $this->fk_loan) : "null").","; $sql .= " datec=".(dol_strlen($this->datec) != 0 ? "'".$this->db->idate($this->datec)."'" : 'null').","; $sql .= " tms=".(dol_strlen((string) $this->tms) != 0 ? "'".$this->db->idate($this->tms)."'" : 'null').","; $sql .= " datep=".(dol_strlen($this->datep) != 0 ? "'".$this->db->idate($this->datep)."'" : 'null').","; - $sql .= " amount_capital=".(isset($this->amount_capital) ? $this->amount_capital : "null").","; - $sql .= " amount_insurance=".(isset($this->amount_insurance) ? $this->amount_insurance : "null").","; - $sql .= " amount_interest=".(isset($this->amount_interest) ? $this->amount_interest : "null").","; - $sql .= " fk_typepayment=".(isset($this->fk_typepayment) ? $this->fk_typepayment : "null").","; + $sql .= " amount_capital=".(isset($this->amount_capital) ? ((float) $this->amount_capital) : "null").","; + $sql .= " amount_insurance=".(isset($this->amount_insurance) ? ((float) $this->amount_insurance) : "null").","; + $sql .= " amount_interest=".(isset($this->amount_interest) ? ((float) $this->amount_interest) : "null").","; + $sql .= " fk_typepayment=".(isset($this->fk_typepayment) ? ((int) $this->fk_typepayment) : "null").","; $sql .= " num_payment=".(isset($this->num_payment) ? "'".$this->db->escape($this->num_payment)."'" : "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").","; diff --git a/htdocs/loan/class/paymentloan.class.php b/htdocs/loan/class/paymentloan.class.php index c9f03a8e47e..9a41ad38c35 100644 --- a/htdocs/loan/class/paymentloan.class.php +++ b/htdocs/loan/class/paymentloan.class.php @@ -2,7 +2,7 @@ /* Copyright (C) 2014-2025 Alexandre Spangaro * Copyright (C) 2015-2024 Frédéric France * Copyright (C) 2020 Maxime DEMAREST - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * * 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 @@ -222,12 +222,12 @@ class PaymentLoan extends CommonObject if ($totalamount != 0) { $sql = "INSERT INTO ".MAIN_DB_PREFIX."payment_loan (fk_loan, datec, datep, amount_capital, amount_insurance, amount_interest,"; $sql .= " fk_typepayment, num_payment, note_private, note_public, fk_user_creat, fk_bank)"; - $sql .= " VALUES (".$this->chid.", '".$this->db->idate($now)."',"; + $sql .= " VALUES (".((int) $this->chid).", '".$this->db->idate($now)."',"; $sql .= " '".$this->db->idate($this->datep)."',"; $sql .= " ".price2num($this->amount_capital).","; $sql .= " ".price2num($this->amount_insurance).","; $sql .= " ".price2num($this->amount_interest).","; - $sql .= " ".((int) $this->paymenttype).", '".$this->db->escape($this->num_payment)."', '".$this->db->escape($this->note_private)."', '".$this->db->escape($this->note_public)."', ".$user->id.","; + $sql .= " ".((int) $this->paymenttype).", '".$this->db->escape($this->num_payment)."', '".$this->db->escape($this->note_private)."', '".$this->db->escape($this->note_public)."', ".((int) $user->id).","; $sql .= " 0)"; dol_syslog(get_class($this)."::create", LOG_DEBUG); @@ -372,14 +372,14 @@ class PaymentLoan extends CommonObject // Update request $sql = "UPDATE ".MAIN_DB_PREFIX."payment_loan SET"; - $sql .= " fk_loan=".(isset($this->fk_loan) ? $this->fk_loan : "null").","; + $sql .= " fk_loan=".(isset($this->fk_loan) ? ((int) $this->fk_loan) : "null").","; $sql .= " datec=".(dol_strlen($this->datec) != 0 ? "'".$this->db->idate($this->datec)."'" : 'null').","; $sql .= " tms=".(dol_strlen((string) $this->tms) != 0 ? "'".$this->db->idate($this->tms)."'" : 'null').","; $sql .= " datep=".(dol_strlen($this->datep) != 0 ? "'".$this->db->idate($this->datep)."'" : 'null').","; - $sql .= " amount_capital=".(isset($this->amount_capital) ? $this->amount_capital : "null").","; - $sql .= " amount_insurance=".(isset($this->amount_insurance) ? $this->amount_insurance : "null").","; - $sql .= " amount_interest=".(isset($this->amount_interest) ? $this->amount_interest : "null").","; - $sql .= " fk_typepayment=".(isset($this->fk_typepayment) ? $this->fk_typepayment : "null").","; + $sql .= " amount_capital=".(isset($this->amount_capital) ? ((float) $this->amount_capital) : "null").","; + $sql .= " amount_insurance=".(isset($this->amount_insurance) ? ((float) $this->amount_insurance) : "null").","; + $sql .= " amount_interest=".(isset($this->amount_interest) ? ((float) $this->amount_interest) : "null").","; + $sql .= " fk_typepayment=".(isset($this->fk_typepayment) ? ((int) $this->fk_typepayment) : "null").","; $sql .= " num_payment=".(isset($this->num_payment) ? "'".$this->db->escape($this->num_payment)."'" : "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").","; diff --git a/htdocs/loan/list.php b/htdocs/loan/list.php index 5871bab5b6c..5f190594934 100644 --- a/htdocs/loan/list.php +++ b/htdocs/loan/list.php @@ -3,6 +3,7 @@ * Copyright (C) 2015-2026 Frédéric France * Copyright (C) 2015 Juanjo Menent * Copyright (C) 2016 Laurent Destailleur + * Copyright (C) 2026 MDW * * 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 @@ -90,11 +91,11 @@ foreach ($object->fields as $key => $val) { if (!empty($val['visible'])) { $visible = (int) dol_eval((string) $val['visible'], 1); $arrayfields['t.'.$key] = array( - 'label'=>$val['label'], - 'checked'=>(($visible < 0) ? 0 : 1), - 'enabled'=>(abs($visible) != 3 && (bool) dol_eval((string) $val['enabled'], 1)), - 'position'=>$val['position'], - 'help'=> isset($val['help']) ? $val['help'] : '' + 'label' => $val['label'], + 'checked' => (($visible < 0) ? 0 : 1), + 'enabled' => (abs($visible) != 3 && (bool) dol_eval((string) $val['enabled'], 1)), + 'position' => $val['position'], + 'help' => isset($val['help']) ? $val['help'] : '' ); } } @@ -164,10 +165,10 @@ $sql .= " SUM(pl.amount_capital) as alreadypaid"; $sqlfields = $sql; // $sql fields to remove for count total $sql .= " FROM ".MAIN_DB_PREFIX."loan as l"; -$linktopl = " LEFT JOIN ".MAIN_DB_PREFIX."payment_loan AS pl ON l.rowid = pl.fk_loan"; -$sql .= $linktopl; +$sql_linktopl = " LEFT JOIN ".MAIN_DB_PREFIX."payment_loan AS pl ON l.rowid = pl.fk_loan"; +$sql .= $sql_linktopl; -$sql .= " WHERE l.entity = ".$conf->entity; +$sql .= " WHERE l.entity = ".((int) $conf->entity); if ($search_amount) { $sql .= natural_search("l.capital", $search_amount, 1); } @@ -184,7 +185,7 @@ $nbtotalofrecords = ''; if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { /* The fast and low memory method to get and count full list converts the sql into a sql count */ $sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql); - $sqlforcount = preg_replace('/'.preg_quote($linktopl, '/').'/', '', $sqlforcount); + $sqlforcount = preg_replace('/'.preg_quote($sql_linktopl, '/').'/', '', $sqlforcount); $sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount); $resql = $db->query($sqlforcount); if ($resql) { @@ -278,8 +279,8 @@ if (!empty($socid)) { $url .= '&socid='.$socid; } $newcardbutton = ''; -$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss'=>'reposition')); -$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanban'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss'=>'reposition')); +$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss' => 'reposition')); +$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanban'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss' => 'reposition')); $newcardbutton .= dolGetButtonTitleSeparator(); $newcardbutton .= dolGetButtonTitle($langs->trans('NewLoan'), '', 'fa fa-plus-circle', $url, '', $permissiontoadd); @@ -300,8 +301,8 @@ print ''; print ''; $newcardbutton = ''; -$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss'=>'reposition')); -$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanban'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss'=>'reposition')); +$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss' => 'reposition')); +$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanban'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss' => 'reposition')); $newcardbutton .= dolGetButtonTitleSeparator(); $newcardbutton .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', dol_buildpath('/loan/card.php', 1).'?action=create&backtopage='.urlencode($_SERVER['PHP_SELF']), '', $permissiontoadd); @@ -416,7 +417,7 @@ while ($i < $imaxinloop) { $object->ref = $obj->rowid; $object->label = $obj->label; $object->paid = $obj->paid; - $object->datestart= $db->jdate($obj->datestart); + $object->datestart = $db->jdate($obj->datestart); $object->dateend = $db->jdate($obj->dateend); if ($mode == 'kanban') { @@ -489,7 +490,7 @@ if ($num == 0) { $db->free($resql); -$parameters = array('arrayfields'=>$arrayfields, 'sql'=>$sql); +$parameters = array('arrayfields' => $arrayfields, 'sql' => $sql); $reshook = $hookmanager->executeHooks('printFieldListFooter', $parameters, $object, $action); // Note that $action and $object may have been modified by hook print $hookmanager->resPrint; diff --git a/htdocs/loan/payment/card.php b/htdocs/loan/payment/card.php index 23742c2d28b..d478682c238 100644 --- a/htdocs/loan/payment/card.php +++ b/htdocs/loan/payment/card.php @@ -242,7 +242,7 @@ if (empty($action) && $user->hasRight('loan', 'delete')) { if (!$disable_delete) { print dolGetButtonAction($langs->trans("Delete"), '', 'delete', $_SERVER["PHP_SELF"].'?id='.$id.'&action=delete&token='.newToken(), 'delete', 1); } else { - print dolGetButtonAction($langs->trans("CantRemovePaymentWithOneInvoicePaid"), $langs->trans("Delete"), 'delete', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=delete&token='.newToken(), 'delete', 0); + print dolGetButtonAction($langs->trans("CantRemovePaymentWithOneInvoicePaid"), $langs->trans("Delete"), 'delete', $_SERVER["PHP_SELF"].'?id='.$id.'&action=delete&token='.newToken(), 'delete', 0); } } diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index f47b8d7bf52..1ee8a5a337d 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -380,6 +380,7 @@ if ((!defined('NOCSRFCHECK') && empty($dolibarr_nocsrfcheck) && getDolGlobalInt( 'editfile', 'editvalidator', 'file_manager', + 'getCategories', 'history', 'presend', 'presend_addmessage', @@ -698,6 +699,7 @@ if (!defined('NOLOGIN')) { if (defined('MAIN_AUTHENTICATION_POST_METHOD')) { $allowedmethodtopostusername = constant('MAIN_AUTHENTICATION_POST_METHOD'); // Note a value of 2 is not compatible with some authentication methods that put username as GET parameter } + // Here, we are not already logged // TODO Remove use of $_COOKIE['login_dolibarr'] by replacing line with $usertotest = GETPOST("username", "alpha", $allowedmethodtopostusername); ? $usertotest = (!empty($_COOKIE['login_dolibarr']) ? preg_replace('/[^a-zA-Z0-9_@\-\.]/', '', $_COOKIE['login_dolibarr']) : GETPOST("username", "alpha", $allowedmethodtopostusername)); $passwordtotest = GETPOST('password', 'password', $allowedmethodtopostusername); @@ -914,6 +916,13 @@ if (!defined('NOLOGIN')) { if (!empty($user->conf->MAIN_LANG_DEFAULT)) { $langs->setDefaultLang($user->conf->MAIN_LANG_DEFAULT); } + + if ($entitytotest > 0 && $conf->entity != $entitytotest) { + // We asked to force login to $entitytotest that differs from default $conf->entity, and we succeed, so + // we must force conf->entity to the new value, so the rest of the code that load $user->loadRights() and + // set $_SESSION['dol_entity'] will be done in correct environment. + $conf->entity = $entitytotest; + } } } else { // We are already into an authenticated session @@ -1199,7 +1208,7 @@ if (!defined('NOLOGIN')) { $conf->liste_limit = getDolUserInt('MAIN_SIZE_LISTE_LIMIT'); // Can be 0 } if ((int) $conf->liste_limit <= 0) { - // Mode automatic. Similar code than into conf.class.php + // Mode automatic. $conf->liste_limit = getListLimitFromScreenHeight(); } // Overwrite main_checkbox_left_column from user setup @@ -1315,7 +1324,7 @@ if (!defined('NOLOGIN')) { accessforbidden("ErrorLoginDisabled"); } - // Load permissions + // Load permissions for entity = $conf->entity $user->loadRights(); } @@ -2692,7 +2701,8 @@ function top_menu_user($hideloginname = 0, $urllogout = '') jQuery(document).on("click", function(event) { if (jQuery("#topmenu-login-dropdown").hasClass("open")) { if (!$(event.target).closest("#topmenu-login-dropdown").length) { - /* console.log("click close login - we click outside"); */ + console.log("click close login - we click outside"); + // Hide the dropdown. closeTopMenuLoginDropdown(); } } @@ -2782,6 +2792,18 @@ function top_menu_ai() $aijsurl = DOL_URL_ROOT.'/ai/js/ai_assistant.js?v='.urlencode((string) ($aijsver ? $aijsver : DOL_VERSION)); $html .= '