diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 68fe9de68d6..d7463d62172 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -26,8 +26,9 @@ Default **language here is english**. So please prepare your contributions in en 1. [Fork](https://help.github.com/articles/fork-a-repo) the [GitHub repository](https://github.com/Dolibarr/dolibarr). 2. Clone your fork. 3. Choose a branch(See the [Branches](#branches) section below). -4. Commit and push your changes. -5. [Make a pull request](https://help.github.com/articles/creating-a-pull-request). +4. Read our developer documentation on the [Dolibarr Wiki](https://wiki.dolibarr.org/index.php?title=Developer_documentation). +5. Commit and push your changes. +6. [Make a pull request](https://help.github.com/articles/creating-a-pull-request). ### Branches diff --git a/.travis.yml b/.travis.yml index b48a3667bb0..5d7eb7a1678 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,7 +57,7 @@ jobs: php: nightly env: DB=mysql - stage: PHP Dev - if: type = push AND branch = 14.0 + if: type = push AND branch = 15.0 php: nightly env: DB=mysql @@ -411,6 +411,12 @@ script: php upgrade.php 13.0.0 14.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade13001400.log php upgrade2.php 13.0.0 14.0.0 > $TRAVIS_BUILD_DIR/upgrade13001400-2.log php step5.php 13.0.0 14.0.0 > $TRAVIS_BUILD_DIR/upgrade13001400-3.log + php upgrade.php 14.0.0 15.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade14001500.log + php upgrade2.php 14.0.0 15.0.0 > $TRAVIS_BUILD_DIR/upgrade14001500-2.log + php step5.php 14.0.0 15.0.0 > $TRAVIS_BUILD_DIR/upgrade14001500-3.log + php upgrade.php 15.0.0 16.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade15001600.log + php upgrade2.php 15.0.0 16.0.0 > $TRAVIS_BUILD_DIR/upgrade15001600-2.log + php step5.php 15.0.0 16.0.0 > $TRAVIS_BUILD_DIR/upgrade15001600-3.log ls -alrt $TRAVIS_BUILD_DIR/ - | diff --git a/ChangeLog b/ChangeLog index 8c52db8af69..030650d5863 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,7 +15,7 @@ NEW: Add option to disable globaly some notifications emails. NEW: #18326 Workflow: Close order on shipment closing. NEW: #18401 Add __NEWREF__ subtitute to get new object reference. NEW: #18403 Add __URL_SHIPMENT__ substitute to get the URL of a shipment -NEW: #18689 REST API module: add api key generate / modify right. +NEW: #18689 REST API module: add api key generate / modify pemrission. NEW: #18663 Make "L'Annuaire des Entreprises" the default provider for SIREN verification for French thirdparties. NEW: #18046 Add tags on ticket/categories NEW: #18326 Workflow: Close order on shipment closing. @@ -109,6 +109,13 @@ NEW: External backups can be downloaded from the "About info page". NEW: Add massaction to switch status on sale / on purchase of a product. + Modules +NEW: Stable module Knowledge Management +NEW: Experimental module Event Organization Management +NEW: Experimental module Workstations Management +NEW: Development of module Partnership Management + + For developers: --------------- @@ -116,6 +123,7 @@ NEW: Introduce method hasRight NEW: Can use textarea field into a confirm popup. NEW: Can use the result_mode of mysqli driver. Save memory for list count NEW: #18319 REST API - Shipment: Add 'close' action / endpoint / POST method. +NEW: Add API /approve and /makeOrder for purchase orders. NEW: add action trigger for member excluded NEW: add option MAIN_IBAN_IS_NEVER_MANDATORY, MAIN_IBAN_NOT_MANDATORY, PROPAL_NOT_BILLABLE, PROPAL_REOPEN_UNSIGNED_ONLY, PROPOSAL_ARE_NOT_BILLABLE, TICKETS_MESSAGE_FORCE_MAIL NEW: Add code codebar column on serial/lot structure @@ -138,6 +146,8 @@ NEW: Hidden option API_DISABLE_COMPRESSION is now visible in API setup page. NEW: Add hook printUnderHeaderPDFline on invoice PDF templates (can be used for example to add a barcode or more information on header of invoices). Following changes may create regressions for some external modules, but were necessary to make Dolibarr better: +* ALL EXTERNAL MODULES THAT WERE NOT CORRECTLY DEVELOPPED WILL NOT WORK ON V15 (All modules that forgot to manage the security token field + into forms will be broken. The security token field is expected since Dolibarr v9 but a lot of external modules did not implement it). * Update hook 'printOriginObjectLine', removed check on product type and special code. Need now reshook. * Old deprecated module "SimplePOS" has been completely removed. Use module "TakePOS" is you need a Point Of Sale. * The method static ActionComm::getActions($db, ...) is no more static. Use $actioncomm->getActions(...) instead (without $db param). diff --git a/README.md b/README.md index b0f7a2c3761..4e120a4cb91 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@  [](https://php.net/) [](https://github.com/Dolibarr/dolibarr) +[](https://bestpractices.coreinfrastructure.org/projects/5521) Dolibarr ERP & CRM is a modern software package that helps manage your organization's activity (contacts, suppliers, invoices, orders, stocks, agenda…). diff --git a/SECURITY.md b/SECURITY.md index 9c28e2874b9..cadd4a23791 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,13 +6,14 @@ This file contains some policies about the security reports on Dolibarr ERP CRM | Version | Supported | | ---------- | ---------------------- | -| <= 14.0.1 | :x: | -| >= 14.0.2+ | :white_check_mark: except CSRF attacks| +| <= 14.0.4 | :x: | +| >= 14.0.5+ | :white_check_mark: except CSRF attacks| | >= develop | :white_check_mark: | ## Reporting a Vulnerability -To report a vulnerability, please use GitHub security advisory at [https://github.com/Dolibarr/dolibarr/security/advisories/new](https://github.com/Dolibarr/dolibarr/security/advisories/new) (if you have permissions) or alternatively send an email to security@dolibarr.org (for everybody) +To report a vulnerability, for a private report, please use GitHub security advisory at [https://github.com/Dolibarr/dolibarr/security/advisories/new](https://github.com/Dolibarr/dolibarr/security/advisories/new) (if you have permissions). +Alternatively send an email to security@dolibarr.org (for everybody) ## Hunting vulnerabilities on Dolibarr @@ -66,7 +67,7 @@ Scope is the web application (back office) and the APIs. * Remote code execution (RCE) * Local files access and manipulation (LFI, RFI, XXE, SSRF, XSPA) * Code injections (HTML, JS, SQL, PHP, ...) -* Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose or into module "Web site" when permission to edit website content is allowed). +* Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose) and except into module "Web site" when permission to edit website content is allowed (injecting any data in this case is allowed too). * Cross-Site Requests Forgery (CSRF) with real security impact (when using GET URLs, CSRF are qualified only for creating, updating or deleting data from pages restricted to admin users) * Open redirect * Broken authentication & session management diff --git a/build/docker/Dockerfile b/build/docker/Dockerfile index eb8e3ade6dc..ead2a8af1c5 100644 --- a/build/docker/Dockerfile +++ b/build/docker/Dockerfile @@ -39,15 +39,13 @@ RUN chmod +x /usr/local/bin/docker-run.sh RUN pecl install xdebug && docker-php-ext-enable xdebug RUN echo 'zend_extension="/usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so"' >> ${PHP_INI_DIR}/php.ini -RUN echo 'xdebug.remote_autostart=1' >> ${PHP_INI_DIR}/php.ini -RUN echo 'xdebug.remote_enable=1' >> ${PHP_INI_DIR}/php.ini -RUN echo 'xdebug.default_enable=1' >> ${PHP_INI_DIR}/php.ini -#RUN echo 'xdebug.remote_host=docker.host' >> ${PHP_INI_DIR}/php.ini -RUN echo 'xdebug.remote_port=9000' >> ${PHP_INI_DIR}/php.ini -RUN echo 'xdebug.remote_connect_back=1' >> ${PHP_INI_DIR}/php.ini -RUN echo 'xdebug.profiler_enable=0' >> ${PHP_INI_DIR}/php.ini -RUN echo 'xdebug.remote_log="/tmp/xdebug.log"' >> ${PHP_INI_DIR}/php.ini -#RUN echo 'localhost docker.host' >> /etc/hosts +RUN echo 'xdebug.mode=debug' >> ${PHP_INI_DIR}/php.ini +RUN echo 'xdebug.start_with_request=yes' >> ${PHP_INI_DIR}/php.ini +RUN echo 'xdebug.client_host=host.docker.internal' >> ${PHP_INI_DIR}/php.ini +RUN echo 'xdebug.client_port=9003' >> ${PHP_INI_DIR}/php.ini +RUN echo 'xdebug.discover_client_host=true' >> ${PHP_INI_DIR}/php.ini +#RUN echo 'xdebug.log="/tmp/xdebug.log"' >> ${PHP_INI_DIR}/php.ini +RUN echo 'xdebug.idekey="netbeans-xdebug"' >> ${PHP_INI_DIR}/php.ini # set up sendmail config, to use maildev RUN echo "account default" > /etc/msmtprc diff --git a/build/docker/docker-compose.yml b/build/docker/docker-compose.yml index b72118de5fb..8994043cd8a 100644 --- a/build/docker/docker-compose.yml +++ b/build/docker/docker-compose.yml @@ -48,6 +48,7 @@ services: - external-pod extra_hosts: - "localhost.localdomain:127.0.0.1" + - "host.docker.internal:host-gateway" mail: image: maildev/maildev diff --git a/htdocs/accountancy/admin/journals_list.php b/htdocs/accountancy/admin/journals_list.php index 8af707c4626..7f000290e1c 100644 --- a/htdocs/accountancy/admin/journals_list.php +++ b/htdocs/accountancy/admin/journals_list.php @@ -58,8 +58,8 @@ $listoffset = GETPOST('listoffset', 'alpha'); $listlimit = GETPOST('listlimit', 'int') > 0 ?GETPOST('listlimit', 'int') : 1000; $active = 1; -$sortfield = GETPOST("sortfield", 'alpha'); -$sortorder = GETPOST("sortorder", 'alpha'); +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int'); if (empty($page) || $page == -1) { $page = 0; diff --git a/htdocs/accountancy/admin/productaccount.php b/htdocs/accountancy/admin/productaccount.php index 8503f3acfd6..847891c949b 100644 --- a/htdocs/accountancy/admin/productaccount.php +++ b/htdocs/accountancy/admin/productaccount.php @@ -80,8 +80,8 @@ if (empty($accounting_product_mode)) { } $limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : (empty($conf->global->ACCOUNTING_LIMIT_LIST_VENTILATION) ? $conf->liste_limit : $conf->global->ACCOUNTING_LIMIT_LIST_VENTILATION); -$sortfield = GETPOST("sortfield", 'alpha'); -$sortorder = GETPOST("sortorder", 'alpha'); +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int'); if (empty($page) || $page == -1) { $page = 0; diff --git a/htdocs/accountancy/bookkeeping/thirdparty_lettering_customer.php b/htdocs/accountancy/bookkeeping/thirdparty_lettering_customer.php index 29a0171fe08..a563b653ac1 100644 --- a/htdocs/accountancy/bookkeeping/thirdparty_lettering_customer.php +++ b/htdocs/accountancy/bookkeeping/thirdparty_lettering_customer.php @@ -48,8 +48,8 @@ $socid = GETPOSTINT("socid"); // if ($user->socid) $socid=$user->socid; $limit = GETPOSTISSET('limit') ? GETPOST('limit', 'int') : $conf->liste_limit; -$sortfield = GETPOST("sortfield", 'alpha'); -$sortorder = GETPOST("sortorder", 'alpha'); +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int'); if (empty($page) || $page == - 1) { $page = 0; @@ -157,7 +157,7 @@ $sql = "SELECT bk.rowid, bk.doc_date, bk.doc_type, bk.doc_ref, "; $sql .= " bk.subledger_account, bk.numero_compte , bk.label_compte, bk.debit, "; $sql .= " bk.credit, bk.montant, bk.sens, bk.code_journal, bk.piece_num, bk.lettering_code"; $sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as bk"; -$sql .= " WHERE (bk.subledger_account = '".$db->escape($object->code_compta)."' AND bk.numero_compte = '".$db->escape($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER)."' )"; +$sql .= " WHERE (bk.subledger_account = '".$db->escape($object->code_compta)."' AND bk.numero_compte = '".$db->escape($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER)."' )"; /* if (dol_strlen($search_date_start) || dol_strlen($search_date_end)) { diff --git a/htdocs/accountancy/bookkeeping/thirdparty_lettering_supplier.php b/htdocs/accountancy/bookkeeping/thirdparty_lettering_supplier.php index d62a1e9fc25..5c315bee9fc 100644 --- a/htdocs/accountancy/bookkeeping/thirdparty_lettering_supplier.php +++ b/htdocs/accountancy/bookkeeping/thirdparty_lettering_supplier.php @@ -48,8 +48,8 @@ $socid = GETPOSTINT("socid"); $limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit; -$sortfield = GETPOST("sortfield", 'alpha'); -$sortorder = GETPOST("sortorder", 'alpha'); +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int'); if (empty($page) || $page == - 1) { $page = 0; diff --git a/htdocs/accountancy/class/accountingaccount.class.php b/htdocs/accountancy/class/accountingaccount.class.php index a42439b1597..a4dcac8c1ad 100644 --- a/htdocs/accountancy/class/accountingaccount.class.php +++ b/htdocs/accountancy/class/accountingaccount.class.php @@ -29,6 +29,7 @@ require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; + /** * Class to manage accounting accounts */ @@ -566,7 +567,7 @@ class AccountingAccount extends CommonObject /** * Information on record * - * @param int $id of record + * @param int $id ID of record * @return void */ public function info($id) @@ -850,8 +851,8 @@ class AccountingAccount extends CommonObject // Level 3 (define $code_t): Search suggested account for this thirdparty (similar code exists in page index.php to make automatic binding) if (!empty($conf->global->ACCOUNTANCY_USE_PRODUCT_ACCOUNT_ON_THIRDPARTY)) { - if (!empty($buyer->code_compta)) { - $code_t = $buyer->code_compta; + if (!empty($buyer->code_compta_product)) { + $code_t = $buyer->code_compta_product; $suggestedid = $accountingAccount['thirdparty']; $suggestedaccountingaccountfor = 'thridparty'; } diff --git a/htdocs/accountancy/class/bookkeeping.class.php b/htdocs/accountancy/class/bookkeeping.class.php index 42fb42b89fa..e96cf9e0997 100644 --- a/htdocs/accountancy/class/bookkeeping.class.php +++ b/htdocs/accountancy/class/bookkeeping.class.php @@ -2114,15 +2114,26 @@ class BookKeepingLine public $montant; /** - * @var float Amount + * @var float Amount */ public $amount; + /** + * @var float Multicurrency amount + */ + public $multicurrency_amount; + + /** + * @var float Multicurrency code + */ + public $multicurrency_code; + /** * @var string Sens */ public $sens; public $lettering_code; + public $date_lettering; /** * @var int ID @@ -2153,4 +2164,9 @@ class BookKeepingLine * @var integer|string $date_validation; */ public $date_validation; + + /** + * @var integer|string $date_lim_reglement; + */ + public $date_lim_reglement; } diff --git a/htdocs/accountancy/customer/index.php b/htdocs/accountancy/customer/index.php index b7ec854fd7c..7ce226f99d8 100644 --- a/htdocs/accountancy/customer/index.php +++ b/htdocs/accountancy/customer/index.php @@ -153,9 +153,9 @@ if ($action == 'validatehistory') { $sql .= " co.code as country_code, co.label as country_label,"; $sql .= " s.tva_intra,"; if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) { - $sql .= " spe.accountancy_code_sell as company_code_sell"; + $sql .= " spe.accountancy_code_sell as company_code_sell"; // accounting code for product but stored on thirdparty } else { - $sql .= " s.accountancy_code_sell as company_code_sell"; + $sql .= " s.accountancy_code_sell as company_code_sell"; // accounting code for product but stored on thirdparty } $sql .= " FROM ".MAIN_DB_PREFIX."facture as f"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc"; @@ -215,7 +215,7 @@ if ($action == 'validatehistory') { $thirdpartystatic->email = $objp->email; $thirdpartystatic->country_code = $objp->country_code; $thirdpartystatic->tva_intra = $objp->tva_intra; - $thirdpartystatic->code_compta = $objp->company_code_sell; + $thirdpartystatic->code_compta_product = $objp->company_code_sell; // The accounting account for product stored on thirdparty object (for level3 suggestion) $product_static->ref = $objp->product_ref; $product_static->id = $objp->product_id; diff --git a/htdocs/accountancy/customer/list.php b/htdocs/accountancy/customer/list.php index 582877d3599..56f904e79ef 100644 --- a/htdocs/accountancy/customer/list.php +++ b/htdocs/accountancy/customer/list.php @@ -539,13 +539,14 @@ if ($result) { $thirdpartystatic->client = $objp->client; $thirdpartystatic->fournisseur = $objp->fournisseur; $thirdpartystatic->code_client = $objp->code_client; + $thirdpartystatic->code_compta = $objp->code_compta_client; // For backward compatibility $thirdpartystatic->code_compta_client = $objp->code_compta_client; $thirdpartystatic->code_fournisseur = $objp->code_fournisseur; $thirdpartystatic->code_compta_fournisseur = $objp->code_compta_fournisseur; $thirdpartystatic->email = $objp->email; $thirdpartystatic->country_code = $objp->country_code; $thirdpartystatic->tva_intra = $objp->tva_intra; - $thirdpartystatic->code_compta_company = $objp->company_code_sell; + $thirdpartystatic->code_compta_product = $objp->company_code_sell; // The accounting account for product stored on thirdparty object (for level3 suggestion) $product_static->ref = $objp->product_ref; $product_static->id = $objp->product_id; diff --git a/htdocs/accountancy/supplier/index.php b/htdocs/accountancy/supplier/index.php index 51c9e66cebe..70434a470f1 100644 --- a/htdocs/accountancy/supplier/index.php +++ b/htdocs/accountancy/supplier/index.php @@ -211,7 +211,7 @@ if ($action == 'validatehistory') { $thirdpartystatic->email = $objp->email; $thirdpartystatic->country_code = $objp->country_code; $thirdpartystatic->tva_intra = $objp->tva_intra; - $thirdpartystatic->code_compta = $objp->company_code_sell; + $thirdpartystatic->code_compta_product = $objp->company_code_buy; // The accounting account for product stored on thirdparty object (for level3 suggestion) $product_static->ref = $objp->product_ref; $product_static->id = $objp->product_id; @@ -230,7 +230,7 @@ if ($action == 'validatehistory') { $facture_static->ref = $objp->ref; $facture_static->id = $objp->facid; $facture_static->type = $objp->ftype; - $facture_static->datef = $objp->datef; + $facture_static->date = $objp->datef; $facture_static_det->id = $objp->rowid; $facture_static_det->total_ht = $objp->total_ht; diff --git a/htdocs/accountancy/supplier/list.php b/htdocs/accountancy/supplier/list.php index f88c1dd8ccc..6364ce914c6 100644 --- a/htdocs/accountancy/supplier/list.php +++ b/htdocs/accountancy/supplier/list.php @@ -547,7 +547,7 @@ if ($result) { $thirdpartystatic->email = $objp->email; $thirdpartystatic->country_code = $objp->country_code; $thirdpartystatic->tva_intra = $objp->tva_intra; - $thirdpartystatic->code_compta_company = $objp->company_code_buy; + $thirdpartystatic->code_compta_product = $objp->company_code_buy; // The accounting account for product stored on thirdparty object (for level3 suggestion) $product_static->ref = $objp->product_ref; $product_static->id = $objp->product_id; diff --git a/htdocs/adherents/agenda.php b/htdocs/adherents/agenda.php index 4b59039d313..cb942ba3176 100644 --- a/htdocs/adherents/agenda.php +++ b/htdocs/adherents/agenda.php @@ -39,8 +39,8 @@ $langs->loadLangs(array("companies", "members")); $id = GETPOST('id', 'int') ?GETPOST('id', 'int') : GETPOST('rowid', 'int'); $limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : $conf->liste_limit; -$sortfield = GETPOST("sortfield", 'alpha'); -$sortorder = GETPOST("sortorder", 'alpha'); +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int'); if (empty($page) || $page == -1) { $page = 0; diff --git a/htdocs/adherents/card.php b/htdocs/adherents/card.php index d7d72f70540..a2e3779d52b 100644 --- a/htdocs/adherents/card.php +++ b/htdocs/adherents/card.php @@ -949,7 +949,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { print load_fiche_titre($langs->trans("NewMember"), '', $object->picto); if ($conf->use_javascript_ajax) { - print "\n".''; print ''; } - print '
| '; - $out .= $langs->trans("GroupEmails"); - $out .= ' | '; - $out .= ' withoptiononeemailperrecipient > 0 ? ' checked="checked"' : '').'> '; - $out .= ''; - $out .= ''; - $out .= ' - '; - $out .= $langs->trans("WarningIfYouCheckOneRecipientPerEmail"); - $out .= ''; - $out .= ' | ||||||
| '; + $out .= $langs->trans("GroupEmails"); + $out .= ' | '; + $out .= ' withoptiononeemailperrecipient > 0 ? ' checked="checked"' : '').'> '; + $out .= ''; + $out .= ''; + $out .= ' - '; + $out .= $langs->trans("WarningIfYouCheckOneRecipientPerEmail"); + $out .= ''; + $out .= ' | ||||||
| ' . $this->langs->trans("Parameter") . ' | '; + $out .= '' . $this->langs->trans("Parameter") . ' | '; $out .= '' . $this->langs->trans("Value") . ' | '; $out .= '|||||
'."\n";
- print $langs->trans('SelectMailModel').': '.$formmail->selectarray('modelmailselected', $modelmail_array, $this->param['models_id'], 1);
+ print $langs->trans('SelectMailModel').': '.$formmail->selectarray('modelmailselected', $modelmail_array, $this->param['models_id'], 1, 0, "", "", 0, 0, 0, '', 'minwidth200');
if ($user->admin) {
print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
}
diff --git a/htdocs/core/class/link.class.php b/htdocs/core/class/link.class.php
index eaf4804b213..f69dcb2874d 100644
--- a/htdocs/core/class/link.class.php
+++ b/htdocs/core/class/link.class.php
@@ -249,7 +249,7 @@ class Link extends CommonObject
$resql = $this->db->query($sql);
if ($resql) {
$num = $this->db->num_rows($resql);
- dol_syslog(get_class($this)."::fetchAll ".$num."records", LOG_DEBUG);
+ dol_syslog(get_class($this)."::fetchAll num=".((int) $num), LOG_DEBUG);
if ($num > 0) {
while ($obj = $this->db->fetch_object($resql)) {
$link = new Link($this->db);
diff --git a/htdocs/core/class/translate.class.php b/htdocs/core/class/translate.class.php
index c092690a505..f881447cd67 100644
--- a/htdocs/core/class/translate.class.php
+++ b/htdocs/core/class/translate.class.php
@@ -555,9 +555,9 @@ class Translate
* Return translated value of key for special keys ("Currency...", "Civility...", ...).
* Search in lang file, then into database. Key must be any complete entry into lang file: CurrencyEUR, ...
* If not found, return key.
- * The string return is not formated (translated with transnoentitiesnoconv)
- * NOTE: To avoid infinite loop (getLabelFromKey->transnoentities->getTradFromKey), if you modify this function,
- * check that getLabelFromKey is not called with same value than input.
+ * The string return is not formated (translated with transnoentitiesnoconv).
+ * NOTE: To avoid infinite loop (getLabelFromKey->transnoentities->getTradFromKey->getLabelFromKey), if you modify this function,
+ * check that getLabelFromKey is never called with the same value than $key.
*
* @param string $key Key to translate
* @return string Translated string (translated with transnoentitiesnoconv)
@@ -585,7 +585,7 @@ class Translate
$newstr = $this->getLabelFromKey($db, $reg[1], 'c_lead_status', 'code', 'label');
} elseif (preg_match('/^OrderSource([0-9A-Z]+)$/i', $key, $reg)) {
// TODO OrderSourceX must be replaced with content of table llx_c_input_reason or llx_c_input_method
- //$newstr=$this->getLabelFromKey($db,$reg[1],'c_ordersource','code','label');
+ //$newstr=$this->getLabelFromKey($db,$reg[1],'llx_c_input_reason','code','label');
}
/* Disabled. There is too many cases where translation of $newstr is not defined is normal (like when output with setEventMessage an already translated string)
@@ -945,9 +945,9 @@ class Translate
*
* @param DoliDB $db Database handler
* @param string $key Translation key to get label (key in language file)
- * @param string $tablename Table name without prefix
- * @param string $fieldkey Field for key
- * @param string $fieldlabel Field for label
+ * @param string $tablename Table name without prefix. This value must always be a hardcoded string and not a value coming from user input.
+ * @param string $fieldkey Field for key. This value must always be a hardcoded string and not a value coming from user input.
+ * @param string $fieldlabel Field for label. This value must always be a hardcoded string and not a value coming from user input.
* @param string $keyforselect Use another value than the translation key for the where into select
* @param int $filteronentity Use a filter on entity
* @return string Label in UTF8 (but without entities)
@@ -959,10 +959,15 @@ class Translate
if ($key == '') {
return '';
}
+ // Test should be useless because the 3 variables are never set from user input but we keep it in case of.
+ if (preg_match('/[^0-9A-Z_]/i', $tablename) || preg_match('/[^0-9A-Z_]/i', $fieldkey) || preg_match('/[^0-9A-Z_]/i', $fieldlabel)) {
+ $this->error = 'Bad value for parameter tablename, fieldkey or fieldlabel';
+ return -1;
+ }
//print 'param: '.$key.'-'.$keydatabase.'-'.$this->trans($key); exit;
- // Check if a translation is available (this can call getTradFromKey)
+ // Check if a translation is available (Note: this can call getTradFromKey that can call getLabelFromKey)
$tmp = $this->transnoentitiesnoconv($key);
if ($tmp != $key && $tmp != 'ErrorBadValueForParamNotAString') {
return $tmp; // Found in language array
@@ -973,6 +978,7 @@ class Translate
return $this->cache_labels[$tablename][$key]; // Found in cache
}
+ // Not found in loaded language file nor in cache. So we will take the label into database.
$sql = "SELECT ".$fieldlabel." as label";
$sql .= " FROM ".MAIN_DB_PREFIX.$tablename;
$sql .= " WHERE ".$fieldkey." = '".$db->escape($keyforselect ? $keyforselect : $key)."'";
diff --git a/htdocs/core/class/utils.class.php b/htdocs/core/class/utils.class.php
index e081c59b14b..b408985a92d 100644
--- a/htdocs/core/class/utils.class.php
+++ b/htdocs/core/class/utils.class.php
@@ -399,21 +399,23 @@ class Utils
if ($execmethod == 2) { // With this method, there is no way to get the return code, only output
$handlein = popen($fullcommandclear, 'r');
$i = 0;
- while (!feof($handlein)) {
- $i++; // output line number
- $read = fgets($handlein);
- // Exclude warning line we don't want
- if ($i == 1 && preg_match('/Warning.*Using a password/i', $read)) {
- continue;
- }
- fwrite($handle, $read);
- if (preg_match('/'.preg_quote('-- Dump completed').'/i', $read)) {
- $ok = 1;
- } elseif (preg_match('/'.preg_quote('SET SQL_NOTES=@OLD_SQL_NOTES').'/i', $read)) {
- $ok = 1;
+ if ($handlein) {
+ while (!feof($handlein)) {
+ $i++; // output line number
+ $read = fgets($handlein);
+ // Exclude warning line we don't want
+ if ($i == 1 && preg_match('/Warning.*Using a password/i', $read)) {
+ continue;
+ }
+ fwrite($handle, $read);
+ if (preg_match('/'.preg_quote('-- Dump completed').'/i', $read)) {
+ $ok = 1;
+ } elseif (preg_match('/'.preg_quote('SET SQL_NOTES=@OLD_SQL_NOTES').'/i', $read)) {
+ $ok = 1;
+ }
}
+ pclose($handlein);
}
- pclose($handlein);
}
diff --git a/htdocs/core/class/validate.class.php b/htdocs/core/class/validate.class.php
index 389bb708e82..61251167c21 100644
--- a/htdocs/core/class/validate.class.php
+++ b/htdocs/core/class/validate.class.php
@@ -55,18 +55,17 @@ class Validate
{
global $langs;
- if ($outputLang) {
+ if (empty($outputLang)) {
$this->outputLang = $langs;
} else {
$this->outputLang = $outputLang;
}
- if (!is_object($this->outputLang) || !method_exists($outputLang, 'load')) {
+ if (!is_object($this->outputLang) || !method_exists($this->outputLang, 'load')) {
return false;
}
- /** @var Translate $outputLang */
- $outputLang->load('validate');
+ $this->outputLang->loadLangs(array('validate', 'errors'));
$this->db = $db;
}
@@ -229,6 +228,21 @@ class Validate
return true;
}
+ /**
+ * Check numeric validity
+ *
+ * @param mixed $string to validate
+ * @return boolean Validity is ok or not
+ */
+ public function isNumeric($string)
+ {
+ if (!is_numeric($string)) {
+ $this->error = $this->outputLang->trans('RequireValidNumeric');
+ return false;
+ }
+ return true;
+ }
+
/**
* Check for boolean validity
*
diff --git a/htdocs/core/customreports.php b/htdocs/core/customreports.php
index e380dea8403..fafd1c015e8 100644
--- a/htdocs/core/customreports.php
+++ b/htdocs/core/customreports.php
@@ -56,7 +56,7 @@ if (!defined('USE_CUSTOM_REPORT_AS_INCLUDE')) {
}
$search_yaxis = GETPOST('search_yaxis', 'array');
- $search_graph = GETPOST('search_graph', 'none');
+ $search_graph = GETPOST('search_graph', 'restricthtml');
// Load variable for pagination
$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
diff --git a/htdocs/core/db/DoliDB.class.php b/htdocs/core/db/DoliDB.class.php
index daa5f657549..4476228d509 100644
--- a/htdocs/core/db/DoliDB.class.php
+++ b/htdocs/core/db/DoliDB.class.php
@@ -62,11 +62,26 @@ abstract class DoliDB implements Database
/** @var string Last error number. For example: 'DB_ERROR_RECORD_ALREADY_EXISTS', '12345', ... */
public $lasterrno;
+ /** @var string If we need to set a prefix specific to the database so it can be reused (when defined instead of MAIN_DB_PREFIX) to forge requests */
+ public $prefix_db;
+
/** @var bool Status */
public $ok;
/** @var string */
public $error;
+
+
+ /**
+ * Return the DB prefix
+ *
+ * @return string The DB prefix
+ */
+ public function prefix()
+ {
+ return (empty($this->prefix_db) ? MAIN_DB_PREFIX : $this->prefix_db);
+ }
+
/**
* Format a SQL IF
*
diff --git a/htdocs/core/get_menudiv.php b/htdocs/core/get_menudiv.php
index 77b0416956b..597031945f1 100644
--- a/htdocs/core/get_menudiv.php
+++ b/htdocs/core/get_menudiv.php
@@ -121,12 +121,13 @@ print '
display: none;
}
- a.alilevel0 {
+ a.alilevel0, span.spanlilevel0 {
background-image: url(\''.DOL_URL_ROOT.'/theme/'.urlencode($conf->theme).'/img/next.png\') !important;
background-repeat: no-repeat !important;
background-position-x: 10px;
background-position-y: 16px;
padding: 1em 15px 1em 40px;
+ display: block;
}
li.lilevel0 font.vsmenudisabled {
background-repeat: no-repeat !important;
diff --git a/htdocs/core/js/lib_notification.js.php b/htdocs/core/js/lib_notification.js.php
index f4a4e59526a..af5952f6026 100644
--- a/htdocs/core/js/lib_notification.js.php
+++ b/htdocs/core/js/lib_notification.js.php
@@ -64,7 +64,7 @@ print "jQuery(document).ready(function () {\n";
//print " console.log('referrer=".dol_escape_js($_SERVER['HTTP_REFERER'])."');\n";
print ' var nowtime = Date.now();';
-print ' var time_auto_update = '.$conf->global->MAIN_BROWSER_NOTIFICATION_FREQUENCY.';'."\n"; // Always defined
+print ' var time_auto_update = '.max(1, getDolGlobalInt('MAIN_BROWSER_NOTIFICATION_FREQUENCY')).';'."\n"; // Always defined
print ' var time_js_next_test;'."\n";
?>
diff --git a/htdocs/core/lib/admin.lib.php b/htdocs/core/lib/admin.lib.php
index e806d9b5957..448677002df 100644
--- a/htdocs/core/lib/admin.lib.php
+++ b/htdocs/core/lib/admin.lib.php
@@ -240,6 +240,7 @@ function run_sql($sqlfile, $silent = 1, $entity = '', $usesavepoint = 1, $handle
if (empty($nocommentremoval)) {
$buf = preg_replace('/([,;ERLT\)])\s*--.*$/i', '\1', $buf); //remove comment from a line that not start with -- before add it to the buffer
}
+ if ($buffer) $buffer .= ' ';
$buffer .= trim($buf);
}
@@ -639,7 +640,7 @@ function modules_prepare_head($nbofactivatedmodules, $nboftotalmodules)
if ($nbofactivatedmodules <= (empty($conf->global->MAIN_MIN_NB_ENABLED_MODULE_FOR_WARNING) ? 1 : $conf->global->MAIN_MIN_NB_ENABLED_MODULE_FOR_WARNING)) { // If only minimal initial modules enabled)
//$head[$h][1] = $form->textwithpicto($langs->trans("AvailableModules"), $desc);
$head[$h][1] = $langs->trans("AvailableModules");
- $head[$h][1] .= $form->textwithpicto('', $langs->trans("YouMustEnableOneModule").'. '.$desc.'', 1, warning); + $head[$h][1] .= $form->textwithpicto('', $langs->trans("YouMustEnableOneModule").'. '.$desc.'', 1, 'warning'); } else { //$head[$h][1] = $langs->trans("AvailableModules").$form->textwithpicto(''.$nbofactivatedmodules.' / '.$nboftotalmodules.'', $desc, 1, 'help', '', 1, 3); $head[$h][1] = $langs->trans("AvailableModules").''.$nbofactivatedmodules.' / '.$nboftotalmodules.''; diff --git a/htdocs/core/lib/company.lib.php b/htdocs/core/lib/company.lib.php index 4ce7d4691a4..2902430bf82 100644 --- a/htdocs/core/lib/company.lib.php +++ b/htdocs/core/lib/company.lib.php @@ -916,8 +916,8 @@ function show_contacts($conf, $langs, $db, $object, $backtopage = '') $form = new Form($db); $optioncss = GETPOST('optioncss', 'alpha'); - $sortfield = GETPOST("sortfield", 'alpha'); - $sortorder = GETPOST("sortorder", 'alpha'); + $sortfield = GETPOST('sortfield', 'aZ09comma'); + $sortorder = GETPOST('sortorder', 'aZ09comma'); $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int'); $search_status = GETPOST("search_status", 'int'); diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index 7b5cc92c75d..c70ea041e0a 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -2270,13 +2270,13 @@ function dol_most_recent_file($dir, $regexfilter = '', $excludefilter = array('( /** * Security check when accessing to a document (used by document.php, viewimage.php and webservices to get documents). - * TODO Replace code that set $accesallowed by a call to restrictedArea() + * TODO Replace code that set $accessallowed by a call to restrictedArea() * * @param string $modulepart Module of document ('module', 'module_user_temp', 'module_user' or 'module_temp'). Exemple: 'medias', 'invoice', 'logs', 'tax-vat', ... * @param string $original_file Relative path with filename, relative to modulepart. * @param string $entity Restrict onto entity (0=no restriction) * @param User $fuser User object (forced) - * @param string $refname Ref of object to check permission for external users (autodetect if not provided) + * @param string $refname Ref of object to check permission for external users (autodetect if not provided) or for hierarchy * @param string $mode Check permission for 'read' or 'write' * @return mixed Array with access information : 'accessallowed' & 'sqlprotectagainstexternals' & 'original_file' (as a full path name) * @see restrictedArea() @@ -2423,6 +2423,30 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, $accessallowed = 1; } $original_file = $conf->fournisseur->facture->dir_output.'/'.$original_file; + } elseif (($modulepart == 'holiday') && !empty($conf->holiday->dir_output)) { + if ($fuser->rights->holiday->{$read} || preg_match('/^specimen/i', $original_file)) { + $accessallowed = 1; + // If we known $id of holiday, call checkUserAccessToObject to check permission on properties and hierarchy of leave request + if ($refname && !preg_match('/^specimen/i', $original_file)) { + include_once DOL_DOCUMENT_ROOT.'/holiday/class/holiday.class.php'; + $tmpholiday = new Holiday($db); + $tmpholiday->fetch('', $refname); + $accessallowed = checkUserAccessToObject($user, array('holiday'), $tmpholiday, 'holiday', '', '', 'rowid', ''); + } + } + $original_file = $conf->holiday->dir_output.'/'.$original_file; + } elseif (($modulepart == 'expensereport') && !empty($conf->expensereport->dir_output)) { + if ($fuser->rights->expensereport->{$lire} || preg_match('/^specimen/i', $original_file)) { + $accessallowed = 1; + // If we known $id of expensereport, call checkUserAccessToObject to check permission on properties and hierarchy of expense report + if ($refname && !preg_match('/^specimen/i', $original_file)) { + include_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php'; + $tmpexpensereport = new ExpenseReport($db); + $tmpexpensereport->fetch('', $refname); + $accessallowed = checkUserAccessToObject($user, array('expensereport'), $tmpexpensereport, 'expensereport', '', '', 'rowid', ''); + } + } + $original_file = $conf->expensereport->dir_output.'/'.$original_file; } elseif (($modulepart == 'apercuexpensereport') && !empty($conf->expensereport->dir_output)) { // Wrapping pour les apercu supplier invoice if ($fuser->rights->expensereport->{$lire}) { @@ -2686,7 +2710,7 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, include_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; $tmptask = new Task($db); $tmptask->fetch('', $refname); - $accessallowed = checkUserAccessToObject($user, array('projet_task'), $tmptask->id, 'projet&project', '', '', 'rowid', ''); + $accessallowed = checkUserAccessToObject($user, array('projet_task'), $tmptask->id, 'projet_task&project', '', '', 'rowid', ''); } } $original_file = $conf->projet->dir_output.'/'.$original_file; @@ -2971,9 +2995,9 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, } $ret = array( - 'accessallowed' => $accessallowed, - 'sqlprotectagainstexternals'=>$sqlprotectagainstexternals, - 'original_file'=>$original_file + 'accessallowed' => ($accessallowed ? 1 : 0), + 'sqlprotectagainstexternals' => $sqlprotectagainstexternals, + 'original_file' => $original_file ); return $ret; diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index ed871204cae..2eb1802d303 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -17,6 +17,7 @@ * Copyright (C) 2019 Thibault Foucart '; - print ' '."\n";
+ print ' '."\n";
if ($addformmessage) {
print '';
print ' '; diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index 28723ab87db..4375d14430b 100644 --- a/htdocs/core/lib/price.lib.php +++ b/htdocs/core/lib/price.lib.php @@ -404,7 +404,7 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt } } - // Recal function using the multicurrency price as reference price. We must set param $multicurrency_tx to 1 to avoid infinite loop. + // 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, ''); if ($multicurrency_code) { diff --git a/htdocs/core/lib/project.lib.php b/htdocs/core/lib/project.lib.php index e9e06708f7c..90a67991413 100644 --- a/htdocs/core/lib/project.lib.php +++ b/htdocs/core/lib/project.lib.php @@ -679,22 +679,23 @@ function projectLinesa(&$inc, $parent, &$lines, &$level, $var, $showproject, &$t // Title of task if (count($arrayfields) > 0 && !empty($arrayfields['t.label']['checked'])) { - print ' ';
+ $labeltoshow = '';
if ($showlineingray) {
- print '';
+ $labeltoshow .= '';
}
//else print '';
for ($k = 0; $k < $level; $k++) {
- print ' | ';
+ $labeltoshow .= ' ';
}
if ($showlineingray) {
- print '';
+ $labeltoshow .= '';
}
- //else print '';
+ print '';
}
- print $lines[$i]->label;
+ $labeltoshow .= dol_escape_htmltag($lines[$i]->label);
for ($k = 0; $k < $level; $k++) {
- print ' ';
+ $labeltoshow .= '';
+ print $labeltoshow;
print " | \n";
}
@@ -822,29 +823,7 @@ function projectLinesa(&$inc, $parent, &$lines, &$level, $var, $showproject, &$t
}
}
- // Contacts of tasks. Disabled, because available by default just after
- /*
- if (!empty($conf->global->PROJECT_SHOW_CONTACTS_IN_LIST)) {
- print '';
- foreach (array('internal', 'external') as $source) {
- $tab = $lines[$i]->liste_contact(-1, $source);
- $num = count($tab);
- if (!empty($num)) {
- foreach ($tab as $contacttask) {
- //var_dump($contacttask);
- if ($source == 'internal') {
- $c = new User($db);
- } else {
- $c = new Contact($db);
- }
- $c->fetch($contacttask['id']);
- print $c->getNomUrl(1).' ('.$contacttask['libelle'].') | ';
- }*/
- if (count($arrayfields) > 0 && !empty($arrayfields['c.assigned']['checked'])) {
+ if (count($arrayfields) > 0 && !empty($arrayfields['t.budget_amount']['checked'])) {
print ''; - } - } - } - print ' ';
print price($lines[$i]->budget_amount, 0, $langs, 1, 0, 0, $conf->currency);
$total_budget_amount += $lines[$i]->budget_amount;
@@ -854,10 +833,11 @@ function projectLinesa(&$inc, $parent, &$lines, &$level, $var, $showproject, &$t
// Contacts of task
if (count($arrayfields) > 0 && !empty($arrayfields['c.assigned']['checked'])) {
print ' | ';
+ $ifisrt = 1;
foreach (array('internal', 'external') as $source) {
$tab = $lines[$i]->liste_contact(-1, $source);
- $num = count($tab);
- if (!empty($num)) {
+ $numcontact = count($tab);
+ if (!empty($numcontact)) {
foreach ($tab as $contacttask) {
//var_dump($contacttask);
if ($source == 'internal') {
@@ -867,14 +847,19 @@ function projectLinesa(&$inc, $parent, &$lines, &$level, $var, $showproject, &$t
}
$c->fetch($contacttask['id']);
if (!empty($c->photo)) {
- print $c->getNomUrl(-2).' ';
+ if (get_class($c) == 'User') {
+ print $c->getNomUrl(-2, '', 0, 0, 24, 1, '', ($ifisrt ? '' : 'notfirst'));
+ } else {
+ print $c->getNomUrl(-2, '', 0, '', -1, 0, ($ifisrt ? '' : 'notfirst'));
+ }
} else {
if (get_class($c) == 'User') {
- print $c->getNomUrl(2, '', 0, 0, 24, 1);//.' ';
+ print $c->getNomUrl(2, '', 0, 0, 24, 1, '', ($ifisrt ? '' : 'notfirst'));
} else {
- print $c->getNomUrl(2);//.' ';
+ print $c->getNomUrl(2, '', 0, '', -1, 0, ($ifisrt ? '' : 'notfirst'));
}
}
+ $ifisrt = 0;
}
}
}
diff --git a/htdocs/core/lib/security.lib.php b/htdocs/core/lib/security.lib.php
index 4f08f2a4141..4a20fbf96f2 100644
--- a/htdocs/core/lib/security.lib.php
+++ b/htdocs/core/lib/security.lib.php
@@ -626,24 +626,30 @@ function restrictedArea($user, $features, $objectid = 0, $tableandshare = '', $f
}
/**
- * Check access by user to object is ok.
- * This function is also called by restrictedArea that check before if module is enabled and if permission of user for $action is ok.
+ * Check that access by a given user to an object is ok.
+ * This function is also called by restrictedArea() that check before if module is enabled and if permission of user for $action is ok.
*
- * @param User $user User to check
- * @param array $featuresarray Features/modules to check. Example: ('user','service','member','project','task',...)
- * @param int|string $objectid 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 modume. Param not used if objectid is null (optional).
- * @param 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)
- * @param string $dbt_select Field name for select if not rowid. Not used if objectid is null (optional)
- * @param string $parenttableforentity Parent table for entity. Example 'fk_website@website'
- * @return bool True if user has access, False otherwise
+ * @param User $user User to check
+ * @param array $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 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 $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)
+ * @param string $dbt_select Field name for select if not rowid. Not used if objectid is null (optional)
+ * @param string $parenttableforentity Parent table for entity. Example 'fk_website@website'
+ * @return bool True if user has access, False otherwise
* @see restrictedArea()
*/
-function checkUserAccessToObject($user, array $featuresarray, $objectid = 0, $tableandshare = '', $feature2 = '', $dbt_keyfield = '', $dbt_select = 'rowid', $parenttableforentity = '')
+function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tableandshare = '', $feature2 = '', $dbt_keyfield = '', $dbt_select = 'rowid', $parenttableforentity = '')
{
global $db, $conf;
+ if (is_object($object)) {
+ $objectid = $object->id;
+ } else {
+ $objectid = $object; // $objectid can be X or 'X,Y,Z'
+ }
+
//dol_syslog("functions.lib:restrictedArea $feature, $objectid, $dbtablename, $feature2, $dbt_socfield, $dbt_select, $isdraft");
//print "user_id=".$user->id.", features=".join(',', $featuresarray).", feature2=".$feature2.", objectid=".$objectid;
//print ", tableandshare=".$tableandshare.", dbt_socfield=".$dbt_keyfield.", dbt_select=".$dbt_select." | "; @@ -656,7 +662,7 @@ function checkUserAccessToObject($user, array $featuresarray, $objectid = 0, $ta foreach ($featuresarray as $feature) { $sql = ''; - //var_dump($feature); + //var_dump($feature);exit; // For backward compatibility if ($feature == 'member') { @@ -669,11 +675,15 @@ function checkUserAccessToObject($user, array $featuresarray, $objectid = 0, $ta $feature = 'projet_task'; } + $checkonentitydone = 0; + + // Array to define rules of checks to do $check = array('adherent', 'banque', 'bom', 'don', 'mrp', 'user', 'usergroup', 'payment', 'payment_supplier', 'product', 'produit', 'service', 'produit|service', 'categorie', 'resource', 'expensereport', 'holiday', 'salaries', 'website'); // Test on entity only (Objects with no link to company) $checksoc = array('societe'); // Test for societe object $checkother = array('contact', 'agenda'); // Test on entity + link to third party on field $dbt_keyfield. Allowed if link is empty (Ex: contacts...). $checkproject = array('projet', 'project'); // Test for project object $checktask = array('projet_task'); // Test for task object + $checkhierarchy = array('expensereport', 'holiday'); $nocheck = array('barcode', 'stock'); // No test //$checkdefault = 'all other not already defined'; // Test on entity + link to third party on field $dbt_keyfield. Not allowed if link is empty (Ex: invoice, orders...). @@ -714,10 +724,12 @@ function checkUserAccessToObject($user, array $featuresarray, $objectid = 0, $ta $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; } } - } elseif (in_array($feature, $checksoc)) { // We check feature = checksoc + $checkonentitydone = 1; + } + if (in_array($feature, $checksoc)) { // We check feature = checksoc // If external user: Check permission for external users if ($user->socid > 0) { - if ($user->socid <> $objectid) { + if ($user->socid != $objectid) { return false; } } elseif (!empty($conf->societe->enabled) && ($user->rights->societe->lire && empty($user->rights->societe->client->voir))) { @@ -736,7 +748,10 @@ function checkUserAccessToObject($user, array $featuresarray, $objectid = 0, $ta $sql .= " WHERE s.rowid IN (".$db->sanitize($objectid, 1).")"; $sql .= " AND s.entity IN (".getEntity($sharedelement, 1).")"; } - } elseif (in_array($feature, $checkother)) { // Test on entity + link to thirdparty. Allowed if link is empty (Ex: contacts...). + + $checkonentitydone = 1; + } + if (in_array($feature, $checkother)) { // 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.".$dbt_select.") as nb"; @@ -758,25 +773,19 @@ function checkUserAccessToObject($user, array $featuresarray, $objectid = 0, $ta $sql .= " WHERE dbt.".$dbt_select." IN (".$db->sanitize($objectid, 1).")"; $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; } - if ($feature == 'agenda') { - // Also check owner or attendee for users without allactions->read - if ($objectid > 0 && empty($user->rights->agenda->allactions->read)) { - require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php'; - $action = new ActionComm($db); - $action->fetch($objectid); - if ($action->authorid != $user->id && $action->userownerid != $user->id && !(array_key_exists($user->id, $action->userassigned))) { - return false; - } - } - } - } elseif (in_array($feature, $checkproject)) { + + $checkonentitydone = 1; + } + if (in_array($feature, $checkproject)) { if (!empty($conf->projet->enabled) && empty($user->rights->projet->all->lire)) { + $projectid = $objectid; + include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; $projectstatic = new Project($db); $tmps = $projectstatic->getProjectsAuthorizedForUser($user, 0, 1, 0); $tmparray = explode(',', $tmps); - if (!in_array($objectid, $tmparray)) { + if (!in_array($projectid, $tmparray)) { return false; } } else { @@ -785,16 +794,21 @@ function checkUserAccessToObject($user, array $featuresarray, $objectid = 0, $ta $sql .= " WHERE dbt.".$dbt_select." IN (".$db->sanitize($objectid, 1).")"; $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; } - } elseif (in_array($feature, $checktask)) { + + $checkonentitydone = 1; + } + if (in_array($feature, $checktask)) { if (!empty($conf->projet->enabled) && empty($user->rights->projet->all->lire)) { $task = new Task($db); $task->fetch($objectid); + $projectid = $task->fk_project; include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; $projectstatic = new Project($db); $tmps = $projectstatic->getProjectsAuthorizedForUser($user, 0, 1, 0); + $tmparray = explode(',', $tmps); - if (!in_array($task->fk_project, $tmparray)) { + if (!in_array($projectid, $tmparray)) { return false; } } else { @@ -803,7 +817,10 @@ function checkUserAccessToObject($user, array $featuresarray, $objectid = 0, $ta $sql .= " WHERE dbt.".$dbt_select." IN (".$db->sanitize($objectid, 1).")"; $sql .= " AND dbt.entity IN (".getEntity($sharedelement, 1).")"; } - } elseif (!in_array($feature, $nocheck)) { // By default (case of $checkdefault), we check on object entity + link to third party on field $dbt_keyfield + + $checkonentitydone = 1; + } + if (!$checkonentitydone && !in_array($feature, $nocheck)) { // 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)) { @@ -845,11 +862,47 @@ function checkUserAccessToObject($user, array $featuresarray, $objectid = 0, $ta } //print $sql; + // For events, check on users assigned to event + if ($feature === 'agenda') { + // Also check owner or attendee for users without allactions->read + if ($objectid > 0 && empty($user->rights->agenda->allactions->read)) { + require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php'; + $action = new ActionComm($db); + $action->fetch($objectid); + if ($action->authorid != $user->id && $action->userownerid != $user->id && !(array_key_exists($user->id, $action->userassigned))) { + return false; + } + } + } + + // 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)) { + $childids = $user->getAllChildIds(1); + $useridtocheck = 0; + if ($feature == 'holiday') { + $useridtocheck = $object->fk_user; + if (!in_array($useridtocheck, $childids)) { + return false; + } + $useridtocheck = $object->fk_validator; + if (!in_array($useridtocheck, $childids)) { + return false; + } + } + if ($feature == 'expensereport') { + $useridtocheck = $object->fk_user_author; + if (!in_array($useridtocheck, $childids)) { + return false; + } + } + } + if ($sql) { $resql = $db->query($sql); if ($resql) { $obj = $db->fetch_object($resql); - if (!$obj || $obj->nb < count(explode(',', $objectid))) { + if (!$obj || $obj->nb < count(explode(',', $objectid))) { // error if we found 0 or less record than nb of id provided return false; } } else { diff --git a/htdocs/core/login/functions_dolibarr.php b/htdocs/core/login/functions_dolibarr.php index f102cd2f358..292ce1b44f5 100644 --- a/htdocs/core/login/functions_dolibarr.php +++ b/htdocs/core/login/functions_dolibarr.php @@ -2,6 +2,7 @@ /* Copyright (C) 2007-2015 Laurent Destailleur | ||