diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000000..fbe406e6c46 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,16 @@ +# Workflow run order + +To reduce run minutes, the following order is put in place: + +On PR & Merge, always run: + +- pre-commit; +- phan. + +When both succeed, start: + +- phpstan; +- Windows-ci; +- travis. + +See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-a-workflow-based-on-the-conclusion-of-another-workflow diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000000..39edbc3496b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: "CI" + +on: [push, pull_request] +jobs: + pre-commit: + uses: ./.github/workflows/pre-commit.yml + secrets: inherit + with: + gh_event: ${{ github.event_name }} + phan: + uses: ./.github/workflows/phan.yml + secrets: inherit + with: + gh_event: ${{ github.event_name }} + phpstan: + uses: ./.github/workflows/phpstan.yml + secrets: inherit + needs: [pre-commit, phan] + with: + gh_event: ${{ github.event_name }} + windows-ci: + needs: [pre-commit, phan] + secrets: inherit + uses: ./.github/workflows/windows-ci.yml + with: + gh_event: ${{ github.event_name }} + gh-travis: # Runs travis script on github runner (not on travis) + if: false + # needs: [pre-commit, phan] + # needs: [windows-ci] + secrets: inherit + uses: ./.github/workflows/gh-travis.yml + with: + gh_event: ${{ github.event_name }} + + +# Note (not tested, from https://github.com/orgs/community/discussions/38361) +# To cancel jobs if one failes, the following action may help +# - if: "failure()" +# uses: "andymckay/cancel-action@0.3" diff --git a/.github/workflows/gh-travis.yml b/.github/workflows/gh-travis.yml new file mode 100644 index 00000000000..30c10765f07 --- /dev/null +++ b/.github/workflows/gh-travis.yml @@ -0,0 +1,49 @@ +--- +# This runs a travis script inside a github runner +name: Travis +# Controls when the workflow will run +on: + # push: + # pull_request: + workflow_call: + inputs: + gh_event: + required: true + type: string + workflow_dispatch: + +concurrency: + group: travis-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref + }} + cancel-in-progress: true +env: + gh_event: ${{ inputs.gh_event || github.event_name }} + GITHUB_JSON: ${{ toJSON(github) }} # Helps in debugging Github Action +# 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 + gh-travis: + # The type of runner that the job will run on + runs-on: ubuntu-latest + strategy: + fail-fast: false + # matrix: + # php-version: + # # PHPStan requires PHP >= 7.2. + # #- "7.2" + # - "8.2" + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - name: Checkout travis file + uses: actions/checkout@v4 + - name: Run .travis.yml build script + uses: ktomk/run-travis-yml@v1 + with: + # run-job: travis # name of a job in travis file + allow-failure: false + # file: .travis.yml + # steps: | # Default: setup, before_install, install, before_script, script, after_script, before_deploy + # install + # script + # env: + # TRAVIS_PHP_VERSION: ${{ matrix.php-version }} diff --git a/.github/workflows/phan.yml b/.github/workflows/phan.yml index 67bec077a13..f233c24907c 100644 --- a/.github/workflows/phan.yml +++ b/.github/workflows/phan.yml @@ -1,16 +1,22 @@ --- on: - pull_request: - push: - schedule: - # execute once a day, the 1st - - cron: 10 9 * * * + # pull_request: + # push: + # schedule: + # # execute once a day, the 1st + # - cron: 10 9 * * * + workflow_call: + inputs: + gh_event: + required: true + type: string workflow_dispatch: + concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: phan-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: - # Do pull analysis on schedule or manual dispatch + gh_event: ${{ inputs.gh_event || github.event_name }} PHAN_CONFIG: > ${{ ( github.event.schedule || github.event_name == 'workflow_dispatch' ) @@ -21,6 +27,7 @@ env: PHAN_MIN_PHP: 7.0 PHAN_QUICK: ${{ github.event.schedule && '' || '--quick' }} GITHUB_JSON: ${{ toJSON(github) }} # Helps in debugging Github Action + name: phan jobs: phan: diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index cbe0f4b2732..c638ee83e50 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -1,16 +1,25 @@ +--- # This is a basic workflow to check code with PHPSTAN tool - -name: "PHPStan" - +name: PHPStan # Controls when the workflow will run -on: [push, pull_request] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true +on: + # push: + # pull_request: + workflow_call: + inputs: + gh_event: + required: true + type: string + workflow_dispatch: +concurrency: + group: stan-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref + }} + cancel-in-progress: true env: - CACHE_KEY_PART: ${{ 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 + 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 # 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 @@ -23,7 +32,7 @@ jobs: php-version: # PHPStan requires PHP >= 7.2. #- "7.2" - - "8.2" + - '8.2' # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it @@ -34,9 +43,10 @@ jobs: id: setup-php uses: shivammathur/setup-php@v2 with: - php-version: "${{ matrix.php-version }}" + php-version: ${{ matrix.php-version }} tools: phpstan, cs2pr - extensions: calendar, json, imagick, gd, zip, mbstring, intl, opcache, imap, mysql, pgsql, sqlite3, ldap, xml, mcrypt + extensions: calendar, json, imagick, gd, zip, mbstring, intl, opcache, imap, + mysql, pgsql, sqlite3, ldap, xml, mcrypt # Restore old cache - name: Restore phpstan cache @@ -44,7 +54,8 @@ jobs: uses: actions/cache/restore@v4 with: path: ./.github/tmp - key: phpstan-cache-${{ matrix.php-version }}-${{ env.CACHE_KEY_PART }}-${{ github.run_id }} + key: phpstan-cache-${{ matrix.php-version }}-${{ env.CACHE_KEY_PART }}-${{ + github.run_id }} restore-keys: | phpstan-cache-${{ matrix.php-version }}-${{ env.CACHE_KEY_PART }}- phpstan-cache-${{ matrix.php-version }}-${{ github.head_ref }}- @@ -61,12 +72,13 @@ jobs: # continue-on-error: true # Save cache - - name: "Save phpstan cache" + - name: Save phpstan cache uses: actions/cache/save@v4 if: ${{ success() || ( ! cancelled() && steps.cache.outputs.cache-hit != 'true' ) }} with: path: ./.github/tmp - key: phpstan-cache-${{ matrix.php-version }}-${{ env.CACHE_KEY_PART }}-${{ github.run_id }} + key: phpstan-cache-${{ matrix.php-version }}-${{ env.CACHE_KEY_PART }}-${{ + github.run_id }} - name: Provide phpstan log as artifact uses: actions/upload-artifact@v4 if: ${{ always() }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 671a266c767..f113a8a7a36 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -1,8 +1,17 @@ --- name: pre-commit on: - pull_request: - push: + # pull_request: + # push: + workflow_call: + inputs: + gh_event: + required: true + type: string + workflow_dispatch: + +env: + gh_event: ${{ inputs.gh_event || github.event_name }} jobs: pre-commit: runs-on: ubuntu-latest @@ -20,7 +29,7 @@ jobs: - name: Get all changed php files (if PR) id: changed-php uses: tj-actions/changed-files@v43 - if: github.event_name == 'pull_request' + if: env.gh_event == 'pull_request' with: files: | **.php @@ -58,7 +67,7 @@ jobs: # - name: Get all changed php files (if PR) # id: changed-php # uses: tj-actions/changed-files@v43 - # if: github.event_name == 'pull_request' + # if: env.gh_event == 'pull_request' # with: # files: | # **.php @@ -72,7 +81,7 @@ jobs: steps.changed-php.outputs.any_changed == 'true' || ( - github.event_name == 'push' + env.gh_event == 'push' && ( github.event.ref == 'refs/heads/develop' || endsWith(github.event.ref, '.0') @@ -94,7 +103,7 @@ jobs: - name: Run some pre-commit hooks on all files on push to "main" branches if: | - github.event_name == 'push' + env.gh_event == 'push' && ( github.event.ref == 'refs/heads/develop' || endsWith(github.event.ref, '.0') diff --git a/.github/workflows/windows-ci.yaml b/.github/workflows/windows-ci.yml similarity index 94% rename from .github/workflows/windows-ci.yaml rename to .github/workflows/windows-ci.yml index cb467055156..f85ad2535be 100644 --- a/.github/workflows/windows-ci.yaml +++ b/.github/workflows/windows-ci.yml @@ -2,19 +2,26 @@ name: Win CI # yamllint disable-line rule:truthy on: - push: - pull_request: + # push: + # pull_request: + workflow_call: + inputs: + gh_event: + required: true + type: string workflow_dispatch: + concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref + group: win-ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: + gh_event: ${{ inputs.gh_event || github.event_name }} PHPUNIT_LOG: phpunit_tests.log DOLIBARR_LOG: documents/dolibarr.log PHPSERVER_LOG: phpserver.log PHPSERVER_DOMAIN_PORT: 127.0.0.1:8000 # could be 127.0.0.1:8000 if config modified - CACHE_KEY_PART: ${{ github.event_name == 'pull_request' && format('{0}-{1}', github.base_ref, github.head_ref) || github.ref_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 }} PHP_INI_SCAN_DIR: C:\myphpini CKEY: win-ci-2 GITHUB_JSON: ${{ toJSON(github) }} # Helps in debugging Github Action diff --git a/htdocs/admin/syslog.php b/htdocs/admin/syslog.php index 71dc5df45f6..5475e20d0d1 100644 --- a/htdocs/admin/syslog.php +++ b/htdocs/admin/syslog.php @@ -3,6 +3,7 @@ * Copyright (C) 2005-2009 Regis Houssin * Copyright (C) 2007 Rodolphe Quiedeville * Copyright (C) 2013 Juanjo Menent + * Copyright (C) 2024 Frédéric France * * 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 @@ -112,11 +113,16 @@ if ($action == 'set') { dolibarr_del_const($db, 'SYSLOG_HANDLERS', -1); // To be sure there is not a setup into another entity dolibarr_set_const($db, 'SYSLOG_HANDLERS', json_encode($activeModules), 'chaine', 0, '', 0); - + $error = 0; + $errors = []; // Check configuration foreach ($activeModules as $modulename) { $module = new $modulename(); - $error = $module->checkConfiguration(); + $res = $module->checkConfiguration(); + if (!$res) { + $error++; + $errors = array_merge($errors, $module->errors); + } } @@ -125,7 +131,7 @@ if ($action == 'set') { setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); } else { $db->rollback(); - setEventMessages($error, $errors, 'errors'); + setEventMessages('', $errors, 'errors'); } } diff --git a/htdocs/blockedlog/admin/blockedlog_list.php b/htdocs/blockedlog/admin/blockedlog_list.php index fb8f92d9e12..2604e35f362 100644 --- a/htdocs/blockedlog/admin/blockedlog_list.php +++ b/htdocs/blockedlog/admin/blockedlog_list.php @@ -379,7 +379,7 @@ if (GETPOST('withtab', 'alpha')) { // Add $param from extra fields //include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; -print '
'; +print ''; print ''; print '
'; diff --git a/htdocs/comm/action/card.php b/htdocs/comm/action/card.php index 833fa00ef57..cf1ba00ba66 100644 --- a/htdocs/comm/action/card.php +++ b/htdocs/comm/action/card.php @@ -1916,9 +1916,9 @@ if ($id > 0) { */ print ''; $tzforfullday = getDolGlobalString('MAIN_STORE_FULL_EVENT_IN_GMT'); - print $form->selectDate($datep ? $datep : $object->datep, 'ap', 1, 1, 0, "action", 1, 1, 0, 'fulldaystart', '', '', '', 1, '', '', $object->fulldayevent ? ($tzforfullday ? $tzforfullday : 'tzuserrel') : 'tzuserrel'); + print $form->selectDate($datep ? $datep : $object->datep, 'ap', 1, 1, 0, "action", 1, 2, 0, 'fulldaystart', '', '', '', 1, '', '', $object->fulldayevent ? ($tzforfullday ? $tzforfullday : 'tzuserrel') : 'tzuserrel'); print '     -     '; - print $form->selectDate($datef ? $datef : $object->datef, 'p2', 1, 1, 1, "action", 1, 0, 0, 'fulldayend', '', '', '', 1, '', '', $object->fulldayevent ? ($tzforfullday ? $tzforfullday : 'tzuserrel') : 'tzuserrel'); + print $form->selectDate($datef ? $datef : $object->datef, 'p2', 1, 1, 1, "action", 1, 2, 0, 'fulldayend', '', '', '', 1, '', '', $object->fulldayevent ? ($tzforfullday ? $tzforfullday : 'tzuserrel') : 'tzuserrel'); print ''; print ' '; diff --git a/htdocs/comm/action/list.php b/htdocs/comm/action/list.php index 3abd25ae840..106be0c0b25 100644 --- a/htdocs/comm/action/list.php +++ b/htdocs/comm/action/list.php @@ -729,7 +729,7 @@ $url = DOL_URL_ROOT.'/comm/action/card.php?action=create'; $url .= '&apyear='.$tmpforcreatebutton['year'].'&apmonth='.$tmpforcreatebutton['mon'].'&apday='.$tmpforcreatebutton['mday'].'&aphour='.$tmpforcreatebutton['hours'].'&apmin='.$tmpforcreatebutton['minutes']; $url .= '&backtopage='.urlencode($_SERVER["PHP_SELF"].($newparam ? '?'.$newparam : '')); -$newcardbutton = dolGetButtonTitle($langs->trans('AddAction'), '', 'fa fa-plus-circle', $url, '', $user->rights->agenda->myactions->create || $user->hasRight('agenda', 'allactions', 'create')); +$newcardbutton = dolGetButtonTitle($langs->trans('AddAction'), '', 'fa fa-plus-circle', $url, '', $user->hasRight('agenda', 'myactions', 'create') || $user->hasRight('agenda', 'allactions', 'create')); $param .= '&mode='.urlencode($mode); diff --git a/htdocs/comm/mailing/class/advtargetemailing.class.php b/htdocs/comm/mailing/class/advtargetemailing.class.php index e97f3d6df66..28bdb0b02d8 100644 --- a/htdocs/comm/mailing/class/advtargetemailing.class.php +++ b/htdocs/comm/mailing/class/advtargetemailing.class.php @@ -584,6 +584,9 @@ class AdvanceTargetingMailing extends CommonObject if (!empty($arrayquery['cust_saleman']) && count($arrayquery['cust_saleman']) > 0) { $sqlwhere[] = " (saleman.fk_user IN (".$this->db->sanitize(implode(',', $arrayquery['cust_saleman']))."))"; } + if (!empty($arrayquery['cust_state']) && count($arrayquery['cust_state']) > 0) { + $sqlwhere[] = " (t.fk_departement IN (".$this->db->sanitize(implode(',', $arrayquery['cust_state']))."))"; + } if (!empty($arrayquery['cust_country']) && count($arrayquery['cust_country']) > 0) { $sqlwhere[] = " (t.fk_pays IN (".$this->db->sanitize(implode(',', $arrayquery['cust_country']))."))"; } @@ -825,6 +828,12 @@ class AdvanceTargetingMailing extends CommonObject if (!empty($arrayquery['cust_saleman']) && count($arrayquery['cust_saleman']) > 0) { $sqlwhere[] = " (saleman.fk_user IN (".$this->db->sanitize(implode(',', $arrayquery['cust_saleman']))."))"; } + //if (!empty($arrayquery['cust_state'])) { + // $sqlwhere[] = $this->transformToSQL('tsd.nom', $arrayquery['cust_state']); + //} + if (!empty($arrayquery['cust_state']) && count($arrayquery['cust_state']) > 0) { + $sqlwhere[] = " (t.fk_departement IN (".$this->db->sanitize(implode(',', $arrayquery['cust_state']))."))"; + } if (!empty($arrayquery['cust_country']) && count($arrayquery['cust_country']) > 0) { $sqlwhere[] = " (ts.fk_pays IN (".$this->db->sanitize(implode(',', $arrayquery['cust_country']))."))"; } diff --git a/htdocs/comm/mailing/class/html.formadvtargetemailing.class.php b/htdocs/comm/mailing/class/html.formadvtargetemailing.class.php index 70f7c9ba975..16ba79dba54 100644 --- a/htdocs/comm/mailing/class/html.formadvtargetemailing.class.php +++ b/htdocs/comm/mailing/class/html.formadvtargetemailing.class.php @@ -87,6 +87,67 @@ class FormAdvTargetEmailing extends Form return $this->advMultiselectarray($htmlname, $options_array, $selected_array); } + /** + * Return combo list of activated countries, into language of user + * + * @param string $htmlname of html select object + * @param array $selected_array or Code or Label of preselected country + * @return string HTML string with select + */ + public function multiselectState($htmlname = 'state_id', $selected_array = array()) + { + global $conf, $langs; + + $langs->load("dict"); + $maxlength = 0; + + $out = ''; + $stateArray = array(); + $label = array(); + + $options_array = array(); + + $sql = "SELECT d.rowid as rowid, d.code_departement as code, d.nom as department, r.nom as region"; + $sql .= " FROM ".MAIN_DB_PREFIX."c_departements d"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_regions r on d.fk_region=r.code_region"; + $sql .= " WHERE d.active = 1 AND d.code_departement<>'' AND r.code_region<>''"; + //$sql .= " ORDER BY r.nom ASC, d.nom ASC"; + + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + if ($num) { + $foundselected = false; + + while ($i < $num) { + $obj = $this->db->fetch_object($resql); + $stateArray [$i] ['rowid'] = $obj->rowid; + $stateArray [$i] ['code'] = $obj->code; + $stateArray [$i] ['label'] = $obj->region.'/'.$obj->department; + $label[$i] = $stateArray[$i]['label']; + $i++; + } + + $array1_sort_order = SORT_ASC; + array_multisort($label, $array1_sort_order, $stateArray); + + foreach ($stateArray as $row) { + $label = dol_trunc($row['label'], $maxlength, 'middle'); + if ($row['code']) { + $label .= ' ('.$row['code'].')'; + } + + $options_array[$row['rowid']] = $label; + } + } + } else { + dol_print_error($this->db); + } + + return $this->advMultiselectarray($htmlname, $options_array, $selected_array); + } + /** * Return combo list of activated countries, into language of user * diff --git a/htdocs/compta/accounting-files.php b/htdocs/compta/accounting-files.php index 05351d41713..36b12bf6b36 100644 --- a/htdocs/compta/accounting-files.php +++ b/htdocs/compta/accounting-files.php @@ -72,7 +72,7 @@ $date_stopMonth = GETPOSTINT('date_stopmonth'); $date_stopYear = GETPOSTINT('date_stopyear'); $date_stop = dol_mktime(23, 59, 59, $date_stopMonth, $date_stopDay, $date_stopYear, 'tzuserrel'); $action = GETPOST('action', 'aZ09'); -$projectid = (GETPOSTINT('projectid') ? GETPOSTINT('projectid') : 0); +$projectid = GETPOSTINT('projectid'); // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context $hookmanager->initHooks(array('comptafileslist', 'globallist')); @@ -667,7 +667,7 @@ foreach ($listofchoices as $choice => $val) { $disabled = ' disabled'; } $checked = (((!GETPOSTISSET('search') && $action != 'searchfiles') || GETPOST($choice)) ? ' checked="checked"' : ''); - print '