From 643309dc6ad280ad8fee15cceaaccc97f557aa9c Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 24 Jan 2021 15:50:19 +0100 Subject: [PATCH 01/17] Update doc --- ChangeLog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index aff7be3c35e..7282e2e3423 100644 --- a/ChangeLog +++ b/ChangeLog @@ -238,7 +238,7 @@ Following changes may create regressions for some external modules, but were nec * All properties ->titre have been renamed into ->title * Property $paiementid in API 'api_supplier_invoices.php' has been renamed into into $payment_mode_id * Property 'num_paiement' has been renamed 'num_payment' everywhere for better code consistency. -* The deprecated subsitution key __SIGNATURE__ has been removed. Use __USER_SIGNATURE__ if you used the old syntax in your email templates. +* The deprecated subsitution key __SIGNATURE__ has been removed. Replace it with __USER_SIGNATURE__ if you used the old syntax in your email templates. * The hidden option HOLIDAY_MORE_PUBLIC_HOLIDAYS has been removed. Use instead the dictionary table if you need to define custom days of holiday. * If you build a class that implement CommonObject to use the incoterm properties or methods (->fk_incoterm, ->label_incoterm, ->location_incoterm), you must now also include declaration of the Trait 'CommonIncoterm' in your class. All incoterm functions were moved into this Trait. @@ -251,6 +251,7 @@ Following changes may create regressions for some external modules, but were nec * Context for hook showSocinfoOnPrint has been moved from "showsocinfoonprint" to "main" * Library htdocs/includes/phpoffice/phpexcel as been removed (replaced with htdocs/includes/phpoffice/PhpSpreadsheet) * Databse transaction in your triggers must be correctly balanced (one close for one open). If not, an error will be returned by the trigger, even if trigger did return error code. +* Dolibarr v13 is still compatible with any PHP version between 5.6.0 and 7.4.*; Unit tests are OK with PHP 8.0 but some warnings or troubles may appears with PHP 8.0. ***** ChangeLog for 12.0.4 compared to 12.0.3 ***** From 56d13c96f4483257f0538f041557908d78419e2b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 24 Jan 2021 15:51:14 +0100 Subject: [PATCH 02/17] Doc --- ChangeLog | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7282e2e3423..a6f0d9d7086 100644 --- a/ChangeLog +++ b/ChangeLog @@ -182,8 +182,7 @@ NEW: add option "If the feature to manage kits of module Stock is used, show det For developers: --------------- -NEW: add __MEMBER_TYPE__ substitution key -NEW: add __TYPE__ substitution key +NEW: add __MEMBER_TYPE__ and __TYPE__ substitution key NEW: add function dolButtonToOpenUrlInDialogPopup() to be able to open page into a popup NEW: show line number on intervention card (via MAIN_VIEW_LINE_NUMBER) NEW: Add some fields to link website page to an other object @@ -198,7 +197,6 @@ NEW: allow to edit "demand reason" field though API NEW: fetch contact by email with REST API NEW: field ref_ext in llx_commandedet NEW: fields ref_ext for Attributes and Combinations -NEW: get state by REST API NEW: get state dictionnary by REST API NEW: improve Product API for variant products NEW: OAuth SCOPE for Admin SDK From 84bab7169e827b115853e40478e7ab8971a921e3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 24 Jan 2021 15:52:17 +0100 Subject: [PATCH 03/17] Document --- ChangeLog | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index a6f0d9d7086..f4afde7fb45 100644 --- a/ChangeLog +++ b/ChangeLog @@ -194,16 +194,10 @@ NEW: can add event to log into blockedlog module with a constant NEW: add property cssview when declaring fields of an object NEW: Can use dynamic code into the 'enabled' property of DAO fields NEW: allow to edit "demand reason" field though API -NEW: fetch contact by email with REST API NEW: field ref_ext in llx_commandedet NEW: fields ref_ext for Attributes and Combinations -NEW: get state dictionnary by REST API -NEW: improve Product API for variant products NEW: OAuth SCOPE for Admin SDK -NEW: retrieve discount from invoice from API NEW: standardizes API thirdparties by email like other object -NEW: Thirdparty REST API: endpoint to set price level -NEW: use new category API for project list view NEW: Triggers Attributes and Attributes values NEW: added incoterms data into the substitution array NEW: add send context for ticket @@ -219,6 +213,12 @@ NEW: API can update a payment NEW: API get member by thirdparty NEW: API get thirdparty by barcode NEW: API get users by email / login +NEW: fetch contact by email with REST API +NEW: get state dictionnary by REST API +NEW: improve Product API for variant products +NEW: retrieve discount from invoice from API +NEW: Thirdparty REST API: endpoint to set price level +NEW: use new category API for project list view HOOKs NEW: Hook on propal card From b4be97ec157a9b4f50b5a90ea558ca1c0dfb6e98 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 25 Jan 2021 18:59:04 +0100 Subject: [PATCH 04/17] Draft code --- htdocs/core/modules/modSociete.class.php | 14 ++++++++++++-- htdocs/langs/en_US/admin.lang | 3 ++- htdocs/societe/list.php | 6 ++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/htdocs/core/modules/modSociete.class.php b/htdocs/core/modules/modSociete.class.php index 1bb0c61b83e..e5a3c584adb 100644 --- a/htdocs/core/modules/modSociete.class.php +++ b/htdocs/core/modules/modSociete.class.php @@ -197,15 +197,25 @@ class modSociete extends DolibarrModules $this->rights[$r][3] = 0; // La permission est-elle une permission par defaut $this->rights[$r][4] = 'export'; - // 262 : Resteindre l'acces des commerciaux + // 262 : Restrict access to sales representative $r++; $this->rights[$r][0] = 262; - $this->rights[$r][1] = 'Read all third parties by internal users (otherwise only if commercial contact). Not effective for external users (limited to themselves).'; + $this->rights[$r][1] = 'Read all third parties (and their objects) by internal users (otherwise only if commercial contact). Not effective for external users (limited to themselves).'; $this->rights[$r][2] = 'r'; $this->rights[$r][3] = 0; $this->rights[$r][4] = 'client'; $this->rights[$r][5] = 'voir'; + /* + $r++; + $this->rights[$r][0] = 263; + $this->rights[$r][1] = 'Read all third parties (without their objects) by internal users (otherwise only if commercial contact). Not effective for external users (limited to themselves).'; + $this->rights[$r][2] = 'r'; + $this->rights[$r][3] = 0; + $this->rights[$r][4] = 'client'; + $this->rights[$r][5] = 'readallthirdparties_advance'; + */ + $r++; $this->rights[$r][0] = 281; // id de la permission $this->rights[$r][1] = 'Read contacts'; // libelle de la permission diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 2cf34ace170..b9ee83f44af 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -805,7 +805,8 @@ PermissionAdvanced253=Create/modify internal/external users and permissions Permission254=Create/modify external users only Permission255=Modify other users password Permission256=Delete or disable other users -Permission262=Extend access to all third parties (not only third parties for which that user is a sale representative).
Not effective for external users (always limited to themselves for proposals, orders, invoices, contracts, etc.).
Not effective for projects (only rules on project permissions, visibility and assignment matters). +Permission262=Extend access to all third parties AND their objects (not only third parties for which the user is a sale representative).
Not effective for external users (always limited to themselves for proposals, orders, invoices, contracts, etc.).
Not effective for projects (only rules on project permissions, visibility and assignment matters). +Permission263=Extend access to all third parties WITHOUT their objects (not only third parties for which the user is a sale representative).
Not effective for external users (always limited to themselves for proposals, orders, invoices, contracts, etc.).
Not effective for projects (only rules on project permissions, visibility and assignment matters). Permission271=Read CA Permission272=Read invoices Permission273=Issue invoices diff --git a/htdocs/societe/list.php b/htdocs/societe/list.php index 33875fb67da..d3991d06c0c 100644 --- a/htdocs/societe/list.php +++ b/htdocs/societe/list.php @@ -436,9 +436,11 @@ if (!empty($search_categ_sup)) $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX."categorie_f $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX."c_stcomm as st ON s.fk_stcomm = st.id"; // We'll need this table joined to the select in order to filter by sale if ($search_sale == -2) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid"; -elseif ($search_sale || (!$user->rights->societe->client->voir && !$socid)) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; +//elseif ($search_sale || (empty($user->rights->societe->client->voir) && (empty($conf->global->MAIN_USE_ADVANCED_PERMS) || empty($user->rights->societe->client->readallthirdparties_advance)) && !$socid)) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; +elseif ($search_sale || (empty($user->rights->societe->client->voir) && !$socid)) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; $sql .= " WHERE s.entity IN (".getEntity('societe').")"; -if (!$user->rights->societe->client->voir && !$socid) $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".$user->id; +//if (empty($user->rights->societe->client->voir) && (empty($conf->global->MAIN_USE_ADVANCED_PERMS) || empty($user->rights->societe->client->readallthirdparties_advance)) && !$socid) $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".$user->id; +if (empty($user->rights->societe->client->voir) && !$socid) $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".$user->id; if ($search_sale && $search_sale != -2) $sql .= " AND s.rowid = sc.fk_soc"; // Join for the needed table to filter by sale if (!$user->rights->fournisseur->lire) $sql .= " AND (s.fournisseur <> 1 OR s.client <> 0)"; // client=0, fournisseur=0 must be visible if ($search_sale == -2) $sql .= " AND sc.fk_user IS NULL"; From 79574faabec9587ef1b9c2083dc739921623de55 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 25 Jan 2021 19:12:14 +0100 Subject: [PATCH 05/17] Fix phpcs --- htdocs/workstation/class/workstationresource.class.php | 2 +- htdocs/workstation/class/workstationusergroup.class.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/workstation/class/workstationresource.class.php b/htdocs/workstation/class/workstationresource.class.php index 7b0dcf96002..698bba47d29 100644 --- a/htdocs/workstation/class/workstationresource.class.php +++ b/htdocs/workstation/class/workstationresource.class.php @@ -37,7 +37,7 @@ class WorkstationResource extends CommonObject /** * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor. */ - public $fields = array( + public $fields = array( 'fk_workstation' => array ('type' => 'integer'), 'fk_resource' => array ('type' => 'integer') ); diff --git a/htdocs/workstation/class/workstationusergroup.class.php b/htdocs/workstation/class/workstationusergroup.class.php index 83ef89973c8..920fb896bdc 100644 --- a/htdocs/workstation/class/workstationusergroup.class.php +++ b/htdocs/workstation/class/workstationusergroup.class.php @@ -36,7 +36,7 @@ class WorkstationUserGroup extends CommonObject /** * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor. */ - public $fields = array( + public $fields = array( 'fk_workstation' => array ('type' => 'integer'), 'fk_usergroup' => array ('type' => 'integer') ); From 6a12de741f2b79c78fe3addc6d007b15db009070 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 25 Jan 2021 22:46:09 +0100 Subject: [PATCH 06/17] FIX Report by Ricardo Matias --- htdocs/core/lib/functions.lib.php | 12 ++++----- htdocs/main.inc.php | 15 +++++++++++ htdocs/user/class/user.class.php | 41 ++++++++++++++++--------------- htdocs/user/home.php | 2 +- test/phpunit/SecurityTest.php | 28 +++++++++++++++++---- 5 files changed, 66 insertions(+), 32 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 68c8775fd81..7fd4a416790 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -686,7 +686,7 @@ function checkVal($out = '', $check = 'alphanohtml', $filter = null, $options = $out = dol_string_nohtmltag($out, 0); } break; - case 'alphawithlgt': // No " and no ../ but we keep < > tags. Can be used for email string like "Name " + case 'alphawithlgt': // No " and no ../ but we keep balanced < > tags with no special chars inside. Can be used for email string like "Name " if (!is_array($out)) { // '"' is dangerous because param in url can close the href= or src= and add javascript functions. // '../' is dangerous because it allows dir transversals @@ -5762,7 +5762,7 @@ function picto_required() * @param string $stringtoclean String to clean * @param integer $removelinefeed 1=Replace all new lines by 1 space, 0=Only ending new lines are removed others are replaced with \n, 2=Ending new lines are removed but others are kept with a same number of \n than nb of
when there is both "...
\n..." * @param string $pagecodeto Encoding of input/output string - * @param integer $strip_tags 0=Use internal strip, 1=Use strip_tags() php function (bugged when text contains a < char that is not for a html tag) + * @param integer $strip_tags 0=Use internal strip, 1=Use strip_tags() php function (bugged when text contains a < char that is not for a html tag or when tags is not closed like '0000-021 - $temp = preg_replace($pattern, "", $temp); // pass 1 - // $temp after pass 1: 0000-021 - $temp = preg_replace($pattern, "", $temp); // pass 2 - // $temp after pass 2: 0000-021 + $temp = preg_replace($pattern, "", $temp); // pass 1 - $temp after pass 1: 0000-021 + $temp = preg_replace($pattern, "", $temp); // pass 2 - $temp after pass 2: 0000-021 + // removed '<' into non closing html tags like 'error=alert(1) to bypass test on onerror + $tmpval = preg_replace('/<[^<]+>/', '', $val); + // List of dom events is on https://www.w3schools.com/jsref/dom_obj_event.asp + $inj += preg_match('/onmouse([a-z]*)\s*=/i', $tmpval); // onmousexxx can be set on img or any html tag like + $inj += preg_match('/ondrag([a-z]*)\s*=/i', $tmpval); // + $inj += preg_match('/ontouch([a-z]*)\s*=/i', $tmpval); // + $inj += preg_match('/on(abort|afterprint|beforeprint|beforeunload|blur|canplay|canplaythrough|change|click|contextmenu|copy|cut)\s*=/i', $tmpval); + $inj += preg_match('/on(dblclick|drop|durationchange|ended|error|focus|focusin|focusout|hashchange|input|invalid)\s*=/i', $tmpval); + $inj += preg_match('/on(keydown|keypress|keyup|load|loadeddata|loadedmetadata|loadstart|offline|online|pagehide|pageshow)\s*=/i', $tmpval); + $inj += preg_match('/on(paste|pause|play|playing|progress|ratechange|resize|reset|scroll|search|seeking|select|show|stalled|start|submit|suspend)\s*=/i', $tmpval); + $inj += preg_match('/on(timeupdate|toggle|unload|volumechange|waiting)\s*=/i', $tmpval); + //$inj += preg_match('/on[A-Z][a-z]+\*=/', $val); // To lock event handlers onAbort(), ... $inj += preg_match('/:|:|:/i', $val); // refused string ':' encoded (no reason to have it encoded) to lock 'javascript:...' $inj += preg_match('/javascript\s*:/i', $val); diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 1aac46933f2..8f8f8c67ca8 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -2398,53 +2398,54 @@ class User extends CommonObject $label .= '
'; $label .= img_picto('', $this->picto).' '.$langs->trans("User").''; $label .= ' '.$this->getLibStatut(4); - $label .= '
'.$langs->trans('Name').': '.$this->getFullName($langs, ''); + $label .= '
'.$langs->trans('Name').': '.dol_string_nohtmltag($this->getFullName($langs, '')); if (!empty($this->login)) { - $label .= '
'.$langs->trans('Login').': '.$this->login; + $label .= '
'.$langs->trans('Login').': '.dol_string_nohtmltag($this->login); } if (!empty($this->job)) { - $label .= '
'.$langs->trans("Job").': '.$this->job; + $label .= '
'.$langs->trans("Job").': '.dol_string_nohtmltag($this->job); } - $label .= '
'.$langs->trans("Email").': '.$this->email; + $label .= '
'.$langs->trans("Email").': '.dol_string_nohtmltag($this->email); if (!empty($this->phone)) { - $label .= '
'.$langs->trans("Phone").': '.$this->phone; + $label .= '
'.$langs->trans("Phone").': '.dol_string_nohtmltag($this->phone); } if (!empty($this->admin)) { $label .= '
'.$langs->trans("Administrator").': '.yn($this->admin); } + $company = ''; if (!empty($this->socid)) { // Add thirdparty for external users $thirdpartystatic = new Societe($db); $thirdpartystatic->fetch($this->socid); if (empty($hidethirdpartylogo)) { $companylink = ' '.$thirdpartystatic->getNomUrl(2, (($option == 'nolink') ? 'nolink' : '')); // picto only of company } - $company = ' ('.$langs->trans("Company").': '.$thirdpartystatic->name.')'; + $company = ' ('.$langs->trans("Company").': '.dol_string_nohtmltag($thirdpartystatic->name).')'; } $type = ($this->socid ? $langs->trans("External").$company : $langs->trans("Internal")); - $label .= '
'.$langs->trans("Type").': '.$type; + $label .= '
'.$langs->trans("Type").': '.dol_string_nohtmltag($type); $label .= '
'; if ($infologin > 0) { $label .= '
'; $label .= '
'.$langs->trans("Session").''; - $label .= '
'.$langs->trans("IPAddress").': '.$_SERVER["REMOTE_ADDR"]; + $label .= '
'.$langs->trans("IPAddress").': '.dol_string_nohtmltag(getUserRemoteIP()); if (!empty($conf->global->MAIN_MODULE_MULTICOMPANY)) { - $label .= '
'.$langs->trans("ConnectedOnMultiCompany").': '.$conf->entity.' (user entity '.$this->entity.')'; + $label .= '
'.$langs->trans("ConnectedOnMultiCompany").': '.$conf->entity.' (User entity '.$this->entity.')'; } - $label .= '
'.$langs->trans("AuthenticationMode").': '.$_SESSION["dol_authmode"].(empty($dolibarr_main_demo) ? '' : ' (demo)'); + $label .= '
'.$langs->trans("AuthenticationMode").': '.dol_string_nohtmltag($_SESSION["dol_authmode"].(empty($dolibarr_main_demo) ? '' : ' (demo)')); $label .= '
'.$langs->trans("ConnectedSince").': '.dol_print_date($this->datelastlogin, "dayhour", 'tzuser'); $label .= '
'.$langs->trans("PreviousConnexion").': '.dol_print_date($this->datepreviouslogin, "dayhour", 'tzuser'); - $label .= '
'.$langs->trans("CurrentTheme").': '.$conf->theme; - $label .= '
'.$langs->trans("CurrentMenuManager").': '.$menumanager->name; + $label .= '
'.$langs->trans("CurrentTheme").': '.dol_string_nohtmltag($conf->theme); + $label .= '
'.$langs->trans("CurrentMenuManager").': '.dol_string_nohtmltag($menumanager->name); $s = picto_from_langcode($langs->getDefaultLang()); - $label .= '
'.$langs->trans("CurrentUserLanguage").': '.($s ? $s.' ' : '').$langs->getDefaultLang(); - $label .= '
'.$langs->trans("Browser").': '.$conf->browser->name.($conf->browser->version ? ' '.$conf->browser->version : '').' ('.$_SERVER['HTTP_USER_AGENT'].')'; - $label .= '
'.$langs->trans("Layout").': '.$conf->browser->layout; - $label .= '
'.$langs->trans("Screen").': '.$_SESSION['dol_screenwidth'].' x '.$_SESSION['dol_screenheight']; + $label .= '
'.$langs->trans("CurrentUserLanguage").': '.dol_string_nohtmltag(($s ? $s.' ' : '').$langs->getDefaultLang()); + $label .= '
'.$langs->trans("Browser").': '.dol_string_nohtmltag($conf->browser->name.($conf->browser->version ? ' '.$conf->browser->version : '').' ('.$_SERVER['HTTP_USER_AGENT'].')'); + $label .= '
'.$langs->trans("Layout").': '.dol_string_nohtmltag($conf->browser->layout); + $label .= '
'.$langs->trans("Screen").': '.dol_string_nohtmltag($_SESSION['dol_screenwidth'].' x '.$_SESSION['dol_screenheight']); if ($conf->browser->layout == 'phone') { $label .= '
'.$langs->trans("Phone").': '.$langs->trans("Yes"); } if (!empty($_SESSION["disablemodules"])) { - $label .= '
'.$langs->trans("DisabledModules").':
'.join(', ', explode(',', $_SESSION["disablemodules"])); + $label .= '
'.$langs->trans("DisabledModules").':
'.dol_string_nohtmltag(join(', ', explode(',', $_SESSION["disablemodules"]))); } } if ($infologin < 0) { @@ -2508,12 +2509,12 @@ class User extends CommonObject } if ($withpictoimg > -2 && $withpictoimg != 2) { if (empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) { - $result .= ''; + $result .= ''; } if ($mode == 'login') { - $result .= dol_trunc($this->login, $maxlen); + $result .= dol_string_nohtmltag(dol_trunc($this->login, $maxlen)); } else { - $result .= $this->getFullName($langs, '', ($mode == 'firstelselast' ? 3 : ($mode == 'firstname' ? 2 : -1)), $maxlen); + $result .= dol_string_nohtmltag($this->getFullName($langs, '', ($mode == 'firstelselast' ? 3 : ($mode == 'firstname' ? 2 : -1)), $maxlen)); } if (empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) { $result .= ''; diff --git a/htdocs/user/home.php b/htdocs/user/home.php index 4d215685f53..e294be6a219 100644 --- a/htdocs/user/home.php +++ b/htdocs/user/home.php @@ -128,7 +128,7 @@ if ($resql) print ''; print ''; print ''; - print ''; + print ''."\n"; $i = 0; while ($i < $num && $i < $max) diff --git a/test/phpunit/SecurityTest.php b/test/phpunit/SecurityTest.php index 8709c66f15d..8cfa5d10db5 100644 --- a/test/phpunit/SecurityTest.php +++ b/test/phpunit/SecurityTest.php @@ -186,7 +186,11 @@ class SecurityTest extends PHPUnit\Framework\TestCase $_SERVER["PHP_SELF"]='/DIR WITH SPACE/htdocs/admin/index.php?mainmenu=home&leftmenu=setup&username=weservices'; $result=testSqlAndScriptInject($_SERVER["PHP_SELF"], 2); - $this->assertEquals($expectedresult, $result, 'Error on testSqlAndScriptInject 1a'); + $this->assertEquals($expectedresult, $result, 'Error on testSqlAndScriptInject expected 0a'); + + $test = 'This is a < inside string with < and > also and tag like before the >'; + $result=testSqlAndScriptInject($test, 0); + $this->assertEquals($expectedresult, $result, 'Error on testSqlAndScriptInject expected 0b'); // Should detect XSS $expectedresult=1; @@ -275,6 +279,10 @@ class SecurityTest extends PHPUnit\Framework\TestCase $test="onerror=alert(1)"; $result=testSqlAndScriptInject($test, 0); $this->assertGreaterThanOrEqual($expectedresult, $result, 'Error on testSqlAndScriptInject jjj'); + + $test="rror=alert(document.location)"; + $result=testSqlAndScriptInject($test, 0); + $this->assertGreaterThanOrEqual($expectedresult, $result, 'Error on testSqlAndScriptInject kkk'); } /** @@ -299,7 +307,9 @@ class SecurityTest extends PHPUnit\Framework\TestCase $_GET["param5"]="a_1-b"; $_POST["param6"]="">objnotdefined\''; $_POST["param11"]=' Name '; @@ -363,10 +373,18 @@ class SecurityTest extends PHPUnit\Framework\TestCase print __METHOD__." result=".$result."\n"; $this->assertEquals('"c:\this is a path~1\aaan" abcdef', $result); - // With alphanohtml, we must convert the html entities like n - $result=GETPOST("param8", 'alphanohtml'); + // With alphanohtml, we must convert the html entities like n and disable all entities + $result=GETPOST("param8a", 'alphanohtml'); print __METHOD__." result=".$result."\n"; - $this->assertEquals("HackerassertEquals("Hackersvg onload='console.log(123)'", $result); + + $result=GETPOST("param8b", 'alphanohtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals('img src=x onerror=alert(document.location) t=', $result, 'Test a string with non closing html tag with alphanohtml'); + + $result=GETPOST("param8c", 'alphanohtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals($_POST['param8c'], $result, 'Test a string with non closing html tag with alphanohtml'); $result=GETPOST("param9", 'alphanohtml'); print __METHOD__." result=".$result."\n"; From 13378897a876c38a4abbd17d16dabcd5982afe88 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 25 Jan 2021 22:46:09 +0100 Subject: [PATCH 07/17] FIX Report by Ricardo Matias Conflicts: test/phpunit/SecurityTest.php --- htdocs/core/lib/functions.lib.php | 12 +- htdocs/main.inc.php | 15 +++ htdocs/user/class/user.class.php | 41 +++---- htdocs/user/home.php | 2 +- test/phpunit/SecurityTest.php | 178 ++++++++++++++++-------------- 5 files changed, 141 insertions(+), 107 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 4a639c69a69..142d91565d6 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -686,7 +686,7 @@ function checkVal($out = '', $check = 'alphanohtml', $filter = null, $options = $out = dol_string_nohtmltag($out, 0); } break; - case 'alphawithlgt': // No " and no ../ but we keep < > tags. Can be used for email string like "Name " + case 'alphawithlgt': // No " and no ../ but we keep balanced < > tags with no special chars inside. Can be used for email string like "Name " if (!is_array($out)) { // '"' is dangerous because param in url can close the href= or src= and add javascript functions. // '../' is dangerous because it allows dir transversals @@ -5762,7 +5762,7 @@ function picto_required() * @param string $stringtoclean String to clean * @param integer $removelinefeed 1=Replace all new lines by 1 space, 0=Only ending new lines are removed others are replaced with \n, 2=Ending new lines are removed but others are kept with a same number of \n than nb of
when there is both "...
\n..." * @param string $pagecodeto Encoding of input/output string - * @param integer $strip_tags 0=Use internal strip, 1=Use strip_tags() php function (bugged when text contains a < char that is not for a html tag) + * @param integer $strip_tags 0=Use internal strip, 1=Use strip_tags() php function (bugged when text contains a < char that is not for a html tag or when tags is not closed like '0000-021
- $temp = preg_replace($pattern, "", $temp); // pass 1 - // $temp after pass 1: 0000-021 - $temp = preg_replace($pattern, "", $temp); // pass 2 - // $temp after pass 2: 0000-021 + $temp = preg_replace($pattern, "", $temp); // pass 1 - $temp after pass 1: 0000-021 + $temp = preg_replace($pattern, "", $temp); // pass 2 - $temp after pass 2: 0000-021 + // removed '<' into non closing html tags like 'error=alert(1) to bypass test on onerror + $tmpval = preg_replace('/<[^<]+>/', '', $val); + // List of dom events is on https://www.w3schools.com/jsref/dom_obj_event.asp + $inj += preg_match('/onmouse([a-z]*)\s*=/i', $tmpval); // onmousexxx can be set on img or any html tag like + $inj += preg_match('/ondrag([a-z]*)\s*=/i', $tmpval); // + $inj += preg_match('/ontouch([a-z]*)\s*=/i', $tmpval); // + $inj += preg_match('/on(abort|afterprint|beforeprint|beforeunload|blur|canplay|canplaythrough|change|click|contextmenu|copy|cut)\s*=/i', $tmpval); + $inj += preg_match('/on(dblclick|drop|durationchange|ended|error|focus|focusin|focusout|hashchange|input|invalid)\s*=/i', $tmpval); + $inj += preg_match('/on(keydown|keypress|keyup|load|loadeddata|loadedmetadata|loadstart|offline|online|pagehide|pageshow)\s*=/i', $tmpval); + $inj += preg_match('/on(paste|pause|play|playing|progress|ratechange|resize|reset|scroll|search|seeking|select|show|stalled|start|submit|suspend)\s*=/i', $tmpval); + $inj += preg_match('/on(timeupdate|toggle|unload|volumechange|waiting)\s*=/i', $tmpval); + //$inj += preg_match('/on[A-Z][a-z]+\*=/', $val); // To lock event handlers onAbort(), ... $inj += preg_match('/:|:|:/i', $val); // refused string ':' encoded (no reason to have it encoded) to lock 'javascript:...' $inj += preg_match('/javascript\s*:/i', $val); diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 89c13670890..241057047e5 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -2430,53 +2430,54 @@ class User extends CommonObject $label .= '
'; $label .= img_picto('', $this->picto).' '.$langs->trans("User").''; $label .= ' '.$this->getLibStatut(4); - $label .= '
'.$langs->trans('Name').': '.$this->getFullName($langs, ''); + $label .= '
'.$langs->trans('Name').': '.dol_string_nohtmltag($this->getFullName($langs, '')); if (!empty($this->login)) { - $label .= '
'.$langs->trans('Login').': '.$this->login; + $label .= '
'.$langs->trans('Login').': '.dol_string_nohtmltag($this->login); } if (!empty($this->job)) { - $label .= '
'.$langs->trans("Job").': '.$this->job; + $label .= '
'.$langs->trans("Job").': '.dol_string_nohtmltag($this->job); } - $label .= '
'.$langs->trans("Email").': '.$this->email; + $label .= '
'.$langs->trans("Email").': '.dol_string_nohtmltag($this->email); if (!empty($this->phone)) { - $label .= '
'.$langs->trans("Phone").': '.$this->phone; + $label .= '
'.$langs->trans("Phone").': '.dol_string_nohtmltag($this->phone); } if (!empty($this->admin)) { $label .= '
'.$langs->trans("Administrator").': '.yn($this->admin); } + $company = ''; if (!empty($this->socid)) { // Add thirdparty for external users $thirdpartystatic = new Societe($db); $thirdpartystatic->fetch($this->socid); if (empty($hidethirdpartylogo)) { $companylink = ' '.$thirdpartystatic->getNomUrl(2, (($option == 'nolink') ? 'nolink' : '')); // picto only of company } - $company = ' ('.$langs->trans("Company").': '.$thirdpartystatic->name.')'; + $company = ' ('.$langs->trans("Company").': '.dol_string_nohtmltag($thirdpartystatic->name).')'; } $type = ($this->socid ? $langs->trans("External").$company : $langs->trans("Internal")); - $label .= '
'.$langs->trans("Type").': '.$type; + $label .= '
'.$langs->trans("Type").': '.dol_string_nohtmltag($type); $label .= '
'; if ($infologin > 0) { $label .= '
'; $label .= '
'.$langs->trans("Session").''; - $label .= '
'.$langs->trans("IPAddress").': '.$_SERVER["REMOTE_ADDR"]; + $label .= '
'.$langs->trans("IPAddress").': '.dol_string_nohtmltag(getUserRemoteIP()); if (!empty($conf->global->MAIN_MODULE_MULTICOMPANY)) { - $label .= '
'.$langs->trans("ConnectedOnMultiCompany").': '.$conf->entity.' (user entity '.$this->entity.')'; + $label .= '
'.$langs->trans("ConnectedOnMultiCompany").': '.$conf->entity.' (User entity '.$this->entity.')'; } - $label .= '
'.$langs->trans("AuthenticationMode").': '.$_SESSION["dol_authmode"].(empty($dolibarr_main_demo) ? '' : ' (demo)'); + $label .= '
'.$langs->trans("AuthenticationMode").': '.dol_string_nohtmltag($_SESSION["dol_authmode"].(empty($dolibarr_main_demo) ? '' : ' (demo)')); $label .= '
'.$langs->trans("ConnectedSince").': '.dol_print_date($this->datelastlogin, "dayhour", 'tzuser'); $label .= '
'.$langs->trans("PreviousConnexion").': '.dol_print_date($this->datepreviouslogin, "dayhour", 'tzuser'); - $label .= '
'.$langs->trans("CurrentTheme").': '.$conf->theme; - $label .= '
'.$langs->trans("CurrentMenuManager").': '.$menumanager->name; + $label .= '
'.$langs->trans("CurrentTheme").': '.dol_string_nohtmltag($conf->theme); + $label .= '
'.$langs->trans("CurrentMenuManager").': '.dol_string_nohtmltag($menumanager->name); $s = picto_from_langcode($langs->getDefaultLang()); - $label .= '
'.$langs->trans("CurrentUserLanguage").': '.($s ? $s.' ' : '').$langs->getDefaultLang(); - $label .= '
'.$langs->trans("Browser").': '.$conf->browser->name.($conf->browser->version ? ' '.$conf->browser->version : '').' ('.$_SERVER['HTTP_USER_AGENT'].')'; - $label .= '
'.$langs->trans("Layout").': '.$conf->browser->layout; - $label .= '
'.$langs->trans("Screen").': '.$_SESSION['dol_screenwidth'].' x '.$_SESSION['dol_screenheight']; + $label .= '
'.$langs->trans("CurrentUserLanguage").': '.dol_string_nohtmltag(($s ? $s.' ' : '').$langs->getDefaultLang()); + $label .= '
'.$langs->trans("Browser").': '.dol_string_nohtmltag($conf->browser->name.($conf->browser->version ? ' '.$conf->browser->version : '').' ('.$_SERVER['HTTP_USER_AGENT'].')'); + $label .= '
'.$langs->trans("Layout").': '.dol_string_nohtmltag($conf->browser->layout); + $label .= '
'.$langs->trans("Screen").': '.dol_string_nohtmltag($_SESSION['dol_screenwidth'].' x '.$_SESSION['dol_screenheight']); if ($conf->browser->layout == 'phone') { $label .= '
'.$langs->trans("Phone").': '.$langs->trans("Yes"); } if (!empty($_SESSION["disablemodules"])) { - $label .= '
'.$langs->trans("DisabledModules").':
'.join(', ', explode(',', $_SESSION["disablemodules"])); + $label .= '
'.$langs->trans("DisabledModules").':
'.dol_string_nohtmltag(join(', ', explode(',', $_SESSION["disablemodules"]))); } } if ($infologin < 0) { @@ -2540,12 +2541,12 @@ class User extends CommonObject } if ($withpictoimg > -2 && $withpictoimg != 2) { if (empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) { - $result .= ''; + $result .= ''; } if ($mode == 'login') { - $result .= dol_trunc($this->login, $maxlen); + $result .= dol_string_nohtmltag(dol_trunc($this->login, $maxlen)); } else { - $result .= $this->getFullName($langs, '', ($mode == 'firstelselast' ? 3 : ($mode == 'firstname' ? 2 : -1)), $maxlen); + $result .= dol_string_nohtmltag($this->getFullName($langs, '', ($mode == 'firstelselast' ? 3 : ($mode == 'firstname' ? 2 : -1)), $maxlen)); } if (empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) { $result .= ''; diff --git a/htdocs/user/home.php b/htdocs/user/home.php index 4d215685f53..e294be6a219 100644 --- a/htdocs/user/home.php +++ b/htdocs/user/home.php @@ -128,7 +128,7 @@ if ($resql) print '
'.$langs->trans("LastUsersCreated", min($num, $max)).''.$langs->trans("FullList").'
'; print ''; print ''; - print ''; + print ''."\n"; $i = 0; while ($i < $num && $i < $max) diff --git a/test/phpunit/SecurityTest.php b/test/phpunit/SecurityTest.php index c38cf49d018..f485102e06e 100644 --- a/test/phpunit/SecurityTest.php +++ b/test/phpunit/SecurityTest.php @@ -171,7 +171,11 @@ class SecurityTest extends PHPUnit\Framework\TestCase $_SERVER["PHP_SELF"]='/DIR WITH SPACE/htdocs/admin/index.php?mainmenu=home&leftmenu=setup&username=weservices'; $result=testSqlAndScriptInject($_SERVER["PHP_SELF"], 2); - $this->assertEquals($expectedresult, $result, 'Error on testSqlAndScriptInject 1a'); + $this->assertEquals($expectedresult, $result, 'Error on testSqlAndScriptInject expected 0a'); + + $test = 'This is a < inside string with < and > also and tag like before the >'; + $result=testSqlAndScriptInject($test, 0); + $this->assertEquals($expectedresult, $result, 'Error on testSqlAndScriptInject expected 0b'); // Should detect XSS $expectedresult=1; @@ -260,6 +264,10 @@ class SecurityTest extends PHPUnit\Framework\TestCase $test="onerror=alert(1)"; $result=testSqlAndScriptInject($test, 0); $this->assertGreaterThanOrEqual($expectedresult, $result, 'Error on testSqlAndScriptInject jjj'); + + $test="rror=alert(document.location)"; + $result=testSqlAndScriptInject($test, 0); + $this->assertGreaterThanOrEqual($expectedresult, $result, 'Error on testSqlAndScriptInject kkk'); } /** @@ -270,106 +278,116 @@ class SecurityTest extends PHPUnit\Framework\TestCase public function testGETPOST() { global $conf,$user,$langs,$db; - $conf=$this->savconf; - $user=$this->savuser; - $langs=$this->savlangs; - $db=$this->savdb; + $conf=$this->savconf; + $user=$this->savuser; + $langs=$this->savlangs; + $db=$this->savdb; - $_COOKIE["id"]=111; - $_GET["param1"]="222"; - $_POST["param1"]="333"; - $_GET["param2"]='a/b#e(pr)qq-rr\cc'; - $_GET["param3"]='"na/b#e(pr)qq-rr\cc'; // Same than param2 + " and n - $_GET["param4"]='../dir'; - $_GET["param5"]="a_1-b"; - $_POST["param6"]="">objnotdefined\''; - $_POST["param11"]=' Name '; + $_COOKIE["id"]=111; + $_GET["param1"]="222"; + $_POST["param1"]="333"; + $_GET["param2"]='a/b#e(pr)qq-rr\cc'; + $_GET["param3"]='"na/b#e(pr)qq-rr\cc'; // Same than param2 + " and n + $_GET["param4"]='../dir'; + $_GET["param5"]="a_1-b"; + $_POST["param6"]="">objnotdefined\''; + $_POST["param11"]=' Name '; - $result=GETPOST('id', 'int'); // Must return nothing - print __METHOD__." result=".$result."\n"; - $this->assertEquals($result, ''); + $result=GETPOST('id', 'int'); // Must return nothing + print __METHOD__." result=".$result."\n"; + $this->assertEquals($result, ''); - $result=GETPOST("param1", 'int'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals($result, 222, 'Test on param1 with no 3rd param'); + $result=GETPOST("param1", 'int'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals($result, 222, 'Test on param1 with no 3rd param'); - $result=GETPOST("param1", 'int', 2); - print __METHOD__." result=".$result."\n"; - $this->assertEquals($result, 333, 'Test on param1 with 3rd param = 2'); + $result=GETPOST("param1", 'int', 2); + print __METHOD__." result=".$result."\n"; + $this->assertEquals($result, 333, 'Test on param1 with 3rd param = 2'); - // Test alpha - $result=GETPOST("param2", 'alpha'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals($result, $_GET["param2"], 'Test on param2'); + // Test alpha + $result=GETPOST("param2", 'alpha'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals($result, $_GET["param2"], 'Test on param2'); - $result=GETPOST("param3", 'alpha'); // Must return string sanitized from char " - print __METHOD__." result=".$result."\n"; - $this->assertEquals($result, 'na/b#e(pr)qq-rr\cc', 'Test on param3'); + $result=GETPOST("param3", 'alpha'); // Must return string sanitized from char " + print __METHOD__." result=".$result."\n"; + $this->assertEquals($result, 'na/b#e(pr)qq-rr\cc', 'Test on param3'); - $result=GETPOST("param4", 'alpha'); // Must return string sanitized from ../ - print __METHOD__." result=".$result."\n"; - $this->assertEquals($result, 'dir'); + $result=GETPOST("param4", 'alpha'); // Must return string sanitized from ../ + print __METHOD__." result=".$result."\n"; + $this->assertEquals($result, 'dir'); - // Test aZ09 - $result=GETPOST("param1", 'aZ09'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals($result, $_GET["param1"]); + // Test aZ09 + $result=GETPOST("param1", 'aZ09'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals($result, $_GET["param1"]); - $result=GETPOST("param2", 'aZ09'); // Must return '' as string contains car not in aZ09 definition - print __METHOD__." result=".$result."\n"; - $this->assertEquals($result, ''); + $result=GETPOST("param2", 'aZ09'); // Must return '' as string contains car not in aZ09 definition + print __METHOD__." result=".$result."\n"; + $this->assertEquals($result, ''); - $result=GETPOST("param3", 'aZ09'); // Must return '' as string contains car not in aZ09 definition - print __METHOD__." result=".$result."\n"; - $this->assertEquals($result, ''); + $result=GETPOST("param3", 'aZ09'); // Must return '' as string contains car not in aZ09 definition + print __METHOD__." result=".$result."\n"; + $this->assertEquals($result, ''); - $result=GETPOST("param4", 'aZ09'); // Must return '' as string contains car not in aZ09 definition - print __METHOD__." result=".$result."\n"; - $this->assertEquals('', $result); + $result=GETPOST("param4", 'aZ09'); // Must return '' as string contains car not in aZ09 definition + print __METHOD__." result=".$result."\n"; + $this->assertEquals('', $result); - $result=GETPOST("param5", 'aZ09'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals($_GET["param5"], $result); + $result=GETPOST("param5", 'aZ09'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals($_GET["param5"], $result); - $result=GETPOST("param6", 'alpha'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals('>', $result); + $result=GETPOST("param6", 'alpha'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals('>', $result); - $result=GETPOST("param6", 'nohtml'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals('">', $result); + $result=GETPOST("param6", 'nohtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals('">', $result); - // With restricthtml we must remove html open/close tag and content but not htmlentities like n - $result=GETPOST("param7", 'restricthtml'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals('"c:\this is a path~1\aaan" abcdef', $result); + // With restricthtml we must remove html open/close tag and content but not htmlentities like n + $result=GETPOST("param7", 'restricthtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals('"c:\this is a path~1\aaan" abcdef', $result); - // With alphanohtml, we must convert the html entities like n - $result=GETPOST("param8", 'alphanohtml'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals("HackerassertEquals("Hackersvg onload='console.log(123)'", $result); - $result=GETPOST("param9", 'alphanohtml'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals($_POST["param9"], $result); + $result=GETPOST("param8b", 'alphanohtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals('img src=x onerror=alert(document.location) t=', $result, 'Test a string with non closing html tag with alphanohtml'); - $result=GETPOST("param10", 'alphanohtml'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals($_POST["param9"], $result, 'We should get param9 after processing param10'); + $result=GETPOST("param8c", 'alphanohtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals($_POST['param8c'], $result, 'Test a string with non closing html tag with alphanohtml'); - $result=GETPOST("param11", 'alphanohtml'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals("Name", $result, 'Test an email string with alphanohtml'); + $result=GETPOST("param9", 'alphanohtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals($_POST["param9"], $result); - $result=GETPOST("param11", 'alphawithlgt'); - print __METHOD__." result=".$result."\n"; - $this->assertEquals(trim($_POST["param11"]), $result, 'Test an email string with alphawithlgt'); + $result=GETPOST("param10", 'alphanohtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals($_POST["param9"], $result, 'We should get param9 after processing param10'); - return $result; + $result=GETPOST("param11", 'alphanohtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals("Name", $result, 'Test an email string with alphanohtml'); + + $result=GETPOST("param11", 'alphawithlgt'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals(trim($_POST["param11"]), $result, 'Test an email string with alphawithlgt'); + + return $result; } /** From 8db80ff44dadfb76b55771e7a244a3d7a6445fcb Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 26 Jan 2021 11:19:13 +0100 Subject: [PATCH 08/17] Fix reponsive --- htdocs/admin/mails.php | 8 +++++++- htdocs/core/lib/admin.lib.php | 10 +++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/htdocs/admin/mails.php b/htdocs/admin/mails.php index 5e32a637c8f..88ae8964c1b 100644 --- a/htdocs/admin/mails.php +++ b/htdocs/admin/mails.php @@ -532,6 +532,7 @@ if ($action == 'edit') print ''.$langs->trans("EMailsDesc")."
\n"; print "
\n"; + print '
'; // You can use div-table-responsive-no-min if you dont need reserved height for your table print '
'.$langs->trans("LastUsersCreated", min($num, $max)).''.$langs->trans("FullList").'
'; print ''; @@ -552,10 +553,12 @@ if ($action == 'edit') } print '
'.$langs->trans("Parameter").''.$langs->trans("Value").'
'; + print ''; if (empty($conf->global->MAIN_DISABLE_ALL_MAILS)) { print '
'; + print '
'; // You can use div-table-responsive-no-min if you dont need reserved height for your table print ''; print ''; @@ -663,6 +666,7 @@ if ($action == 'edit') } print '
'.$langs->trans("MAIN_MAIL_SENDMODE").'
'; + print '
'; if ($conf->global->MAIN_MAIL_SENDMODE == 'mail' && empty($conf->global->MAIN_HIDE_WARNING_TO_ENCOURAGE_SMTP_SETUP)) { print info_admin($langs->trans("WarningPHPMail").'
'.$langs->trans("WarningPHPMailA").'
'.$langs->trans("WarningPHPMailB").'
'.$langs->trans("WarningPHPMailC").'

'.$langs->trans("WarningPHPMailD"), 0, 0, 'warning'); @@ -670,11 +674,12 @@ if ($action == 'edit') print '
'; + print '
'; // You can use div-table-responsive-no-min if you dont need reserved height for your table print ''; print ''; // From - print ''; + print ''; print ''; @@ -746,6 +751,7 @@ if ($action == 'edit') print ''; print '
'.$langs->trans("OtherOptions").'
'.$langs->trans("MAIN_MAIL_EMAIL_FROM", ini_get('sendmail_from') ?ini_get('sendmail_from') : $langs->transnoentities("Undefined")).'
'.$langs->trans("MAIN_MAIL_EMAIL_FROM", ini_get('sendmail_from') ?ini_get('sendmail_from') : $langs->transnoentities("Undefined")).''.$conf->global->MAIN_MAIL_EMAIL_FROM; if (!empty($conf->global->MAIN_MAIL_EMAIL_FROM) && !isValidEmail($conf->global->MAIN_MAIL_EMAIL_FROM)) print img_warning($langs->trans("ErrorBadEMail")); print '
'.$langs->trans("MAIN_MAIL_ENABLED_USER_DEST_SELECT").''.yn($conf->global->MAIN_MAIL_ENABLED_USER_DEST_SELECT).'
'; + print '
'; } print dol_get_fiche_end(); diff --git a/htdocs/core/lib/admin.lib.php b/htdocs/core/lib/admin.lib.php index f3faeda4b3a..ce9ffb31697 100644 --- a/htdocs/core/lib/admin.lib.php +++ b/htdocs/core/lib/admin.lib.php @@ -1870,11 +1870,6 @@ function email_admin_prepare_head() } } - $head[$h][0] = DOL_URL_ROOT."/admin/mails_templates.php"; - $head[$h][1] = $langs->trans("EMailTemplates"); - $head[$h][2] = 'templates'; - $h++; - if (!empty($user->admin) && (empty($_SESSION['leftmenu']) || $_SESSION['leftmenu'] != 'email_templates')) { $head[$h][0] = DOL_URL_ROOT."/admin/mails_senderprofile_list.php"; $head[$h][1] = $langs->trans("EmailSenderProfiles"); @@ -1882,6 +1877,11 @@ function email_admin_prepare_head() $h++; } + $head[$h][0] = DOL_URL_ROOT."/admin/mails_templates.php"; + $head[$h][1] = $langs->trans("EMailTemplates"); + $head[$h][2] = 'templates'; + $h++; + complete_head_from_modules($conf, $langs, null, $head, $h, 'email_admin', 'remove'); return $head; From c417f730b91a9b103a6e0989ece8d9f4a9d3d437 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 26 Jan 2021 11:31:48 +0100 Subject: [PATCH 09/17] Clean language file --- htdocs/langs/en_US/mrp.lang | 2 -- 1 file changed, 2 deletions(-) diff --git a/htdocs/langs/en_US/mrp.lang b/htdocs/langs/en_US/mrp.lang index 9f0ddf152db..5ca0dbc6981 100644 --- a/htdocs/langs/en_US/mrp.lang +++ b/htdocs/langs/en_US/mrp.lang @@ -83,8 +83,6 @@ Workstations=Workstations WorkstationsDescription=Workstations management WorkstationSetup = Workstations setup WorkstationSetupPage = Workstations setup page -WorkstationAbout = About Workstation -WorkstationAboutPage = Workstations about page WorkstationList=Workstation list WorkstationCreate=Add new workstation ConfirmEnableWorkstation=Are you sure you want to enable workstation %s ? From 6313fb8be0d5b9953d98e4c766a2cfc597efdf67 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 26 Jan 2021 11:38:07 +0100 Subject: [PATCH 10/17] Fix lang --- htdocs/langs/en_US/stocks.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/langs/en_US/stocks.lang b/htdocs/langs/en_US/stocks.lang index ace97c85c3f..ad4946ee9f9 100644 --- a/htdocs/langs/en_US/stocks.lang +++ b/htdocs/langs/en_US/stocks.lang @@ -62,7 +62,7 @@ EnhancedValueOfWarehouses=Warehouses value UserWarehouseAutoCreate=Create a user warehouse automatically when creating a user AllowAddLimitStockByWarehouse=Manage also value for minimum and desired stock per pairing (product-warehouse) in addition to the value for minimum and desired stock per product RuleForWarehouse=Rule for warehouses -WarehouseAskWarehouseDuringPropal=Set a warehouse on Sale propal +WarehouseAskWarehouseDuringPropal=Set a warehouse on Commercial proposals WarehouseAskWarehouseDuringOrder=Set a warehouse on Sale orders UserDefaultWarehouse=Set a warehouse on Users MainDefaultWarehouse=Default warehouse From 19dd36aa5901b78169a97b07ee4b8ccd2f3f7949 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 26 Jan 2021 11:45:29 +0100 Subject: [PATCH 11/17] Trans --- htdocs/langs/en_US/admin.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index b9ee83f44af..ee7b0052b7c 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -349,7 +349,7 @@ UpdateServerOffline=Update server offline WithCounter=Manage a counter GenericMaskCodes=You may enter any numbering mask. In this mask, the following tags could be used:
{000000} corresponds to a number which will be incremented on each %s. Enter as many zeros as the desired length of the counter. The counter will be completed by zeros from the left in order to have as many zeros as the mask.
{000000+000} same as previous but an offset corresponding to the number to the right of the + sign is applied starting on first %s.
{000000@x} same as previous but the counter is reset to zero when month x is reached (x between 1 and 12, or 0 to use the early months of fiscal year defined in your configuration, or 99 to reset to zero every month). If this option is used and x is 2 or higher, then sequence {yy}{mm} or {yyyy}{mm} is also required.
{dd} day (01 to 31).
{mm} month (01 to 12).
{yy}, {yyyy} or {y} year over 2, 4 or 1 numbers.
GenericMaskCodes2={cccc} the client code on n characters
{cccc000} the client code on n characters is followed by a counter dedicated for customer. This counter dedicated to customer is reset at same time than global counter.
{tttt} The code of third party type on n characters (see menu Home - Setup - Dictionary - Types of third parties). If you add this tag, the counter will be different for each type of third party.
-GenericMaskCodes3=All other characters in the mask will remain intact (except * or ? in 13th position in EAN13).
Spaces are not allowed.
In EAN13 the last character after the last } in 13th position
should be * or ? it will be replaced by the calculated key.
+GenericMaskCodes3=All other characters in the mask will remain intact (except * or ? in 13th position in EAN13).
Spaces are not allowed.
In EAN13, the last character after the last } in 13th position should be * or ? . It will be replaced by the calculated key.
GenericMaskCodes4a=Example on the 99th %s of the third party TheCompany, with date 2007-01-31:
GenericMaskCodes4b=Example on third party created on 2007-03-01:
GenericMaskCodes4c=Example on product created on 2007-03-01:
From 977e4dde865f3ccf1ab8618af8bc7fbdd1c71ec1 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 26 Jan 2021 12:08:54 +0100 Subject: [PATCH 12/17] Fix regression --- htdocs/core/lib/functions.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 142d91565d6..d2e919e49fe 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -5786,7 +5786,7 @@ function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = $temp = preg_replace($pattern, "", $temp); // pass 1 - $temp after pass 1:
0000-021 $temp = preg_replace($pattern, "", $temp); // pass 2 - $temp after pass 2: 0000-021 // removed '<' into non closing html tags like ' Date: Tue, 26 Jan 2021 14:24:32 +0100 Subject: [PATCH 13/17] Look and feel v14 --- htdocs/projet/activity/perday.php | 8 ++++---- htdocs/projet/activity/permonth.php | 8 ++++---- htdocs/projet/activity/perweek.php | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/htdocs/projet/activity/perday.php b/htdocs/projet/activity/perday.php index a4a37f22a31..c582507ec50 100644 --- a/htdocs/projet/activity/perday.php +++ b/htdocs/projet/activity/perday.php @@ -504,19 +504,19 @@ $moreforfilter .= '
'; $moreforfilter .= '
'; $includeonly = 'hierarchyme'; if (empty($user->rights->user->user->lire)) $includeonly = array($user->id); -$moreforfilter .= img_picto($langs->trans('User'), 'user').$form->select_dolusers($search_usertoprocessid ? $search_usertoprocessid : $usertoprocess->id, 'search_usertoprocessid', $user->rights->user->user->lire ? 0 : 0, null, 0, $includeonly, null, 0, 0, 0, '', 0, '', 'maxwidth200 marginleftonly'); +$moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('User'), 'user').$form->select_dolusers($search_usertoprocessid ? $search_usertoprocessid : $usertoprocess->id, 'search_usertoprocessid', $user->rights->user->user->lire ? 0 : 0, null, 0, $includeonly, null, 0, 0, 0, '', 0, '', 'maxwidth200 marginleftonly'); $moreforfilter .= '
'; if (empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { $moreforfilter .= '
'; $moreforfilter .= '
'; - $moreforfilter .= img_picto($langs->trans('Project'), 'project').''; + $moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('Project'), 'project').''; $moreforfilter .= '
'; $moreforfilter .= '
'; $moreforfilter .= '
'; - $moreforfilter .= img_picto($langs->trans('ThirdParty'), 'company').''; + $moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('ThirdParty'), 'company').''; $moreforfilter .= '
'; } @@ -594,7 +594,7 @@ if (!empty($arrayfields['t.progress']['checked'])) if ($usertoprocess->id == $user->id) print ''.$langs->trans("TimeSpentByYou").''; else print ''.$langs->trans("TimeSpentByUser").'';*/ print ''.$langs->trans("TimeSpent").'
'.$langs->trans("Everybody").''; -print ''.$langs->trans("TimeSpent").($usertoprocess->firstname ? '
'.dol_trunc($usertoprocess->firstname, 10).'' : '').''; +print ''.$langs->trans("TimeSpent").($usertoprocess->firstname ? '
'.$usertoprocess->getNomUrl(-2).''.dol_trunc($usertoprocess->firstname, 10).'' : '').''; print ''.$langs->trans("HourStart").''; // By default, we can edit only tasks we are assigned to diff --git a/htdocs/projet/activity/permonth.php b/htdocs/projet/activity/permonth.php index 9a95f8abf79..626634b13cd 100644 --- a/htdocs/projet/activity/permonth.php +++ b/htdocs/projet/activity/permonth.php @@ -429,19 +429,19 @@ $moreforfilter .= '
'; $moreforfilter .= '
'; $includeonly = 'hierachyme'; if (empty($user->rights->user->user->lire)) $includeonly = array($user->id); -$moreforfilter .= img_picto($langs->trans('User'), 'user').$form->select_dolusers($search_usertoprocessid ? $search_usertoprocessid : $usertoprocess->id, 'search_usertoprocessid', $user->rights->user->user->lire ? 0 : 0, null, 0, $includeonly, null, 0, 0, 0, '', 0, '', 'maxwidth200'); +$moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('User'), 'user').$form->select_dolusers($search_usertoprocessid ? $search_usertoprocessid : $usertoprocess->id, 'search_usertoprocessid', $user->rights->user->user->lire ? 0 : 0, null, 0, $includeonly, null, 0, 0, 0, '', 0, '', 'maxwidth200'); $moreforfilter .= '
'; if (empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { $moreforfilter .= '
'; $moreforfilter .= '
'; - $moreforfilter .= img_picto($langs->trans('Project'), 'project').''; + $moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('Project'), 'project').''; $moreforfilter .= '
'; $moreforfilter .= '
'; $moreforfilter .= '
'; - $moreforfilter .= img_picto($langs->trans('ThirdParty'), 'company').''; + $moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('ThirdParty'), 'company').''; $moreforfilter .= '
'; } @@ -488,7 +488,7 @@ print ''.$langs->trans("ProgressDeclared"). if ($usertoprocess->id == $user->id) print ''.$langs->trans("TimeSpentByYou").''; else print ''.$langs->trans("TimeSpentByUser").'';*/ print ''.$langs->trans("TimeSpent").'
'.$langs->trans("Everybody").''; -print ''.$langs->trans("TimeSpent").($usertoprocess->firstname ? '
'.dol_trunc($usertoprocess->firstname, 10).'' : '').''; +print ''.$langs->trans("TimeSpent").($usertoprocess->firstname ? '
'.$usertoprocess->getNomUrl(-2).''.dol_trunc($usertoprocess->firstname, 10).'' : '').''; foreach ($TWeek as $week_number) { diff --git a/htdocs/projet/activity/perweek.php b/htdocs/projet/activity/perweek.php index c29486302af..9dff5fbc0f6 100644 --- a/htdocs/projet/activity/perweek.php +++ b/htdocs/projet/activity/perweek.php @@ -559,19 +559,19 @@ $moreforfilter .= '
'; $moreforfilter .= '
'; $includeonly = 'hierarchyme'; if (empty($user->rights->user->user->lire)) $includeonly = array($user->id); -$moreforfilter .= img_picto($langs->trans('User'), 'user').$form->select_dolusers($search_usertoprocessid ? $search_usertoprocessid : $usertoprocess->id, 'search_usertoprocessid', $user->rights->user->user->lire ? 0 : 0, null, 0, $includeonly, null, 0, 0, 0, '', 0, '', 'maxwidth200'); +$moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('User'), 'user').$form->select_dolusers($search_usertoprocessid ? $search_usertoprocessid : $usertoprocess->id, 'search_usertoprocessid', $user->rights->user->user->lire ? 0 : 0, null, 0, $includeonly, null, 0, 0, 0, '', 0, '', 'maxwidth200'); $moreforfilter .= '
'; if (empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { $moreforfilter .= '
'; $moreforfilter .= '
'; - $moreforfilter .= img_picto($langs->trans('Project'), 'project').''; + $moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('Project'), 'project').''; $moreforfilter .= '
'; $moreforfilter .= '
'; $moreforfilter .= '
'; - $moreforfilter .= img_picto($langs->trans('ThirdParty'), 'company').''; + $moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('ThirdParty'), 'company').''; $moreforfilter .= '
'; } @@ -652,7 +652,7 @@ if (!empty($arrayfields['t.progress']['checked'])) if ($usertoprocess->id == $user->id) print ''.$langs->trans("TimeSpentByYou").''; else print ''.$langs->trans("TimeSpentByUser").'';*/ print ''.$langs->trans("TimeSpent").'
'.$langs->trans("Everybody").''; -print ''.$langs->trans("TimeSpent").($usertoprocess->firstname ? '
'.dol_trunc($usertoprocess->firstname, 10).'' : '').''; +print ''.$langs->trans("TimeSpent").($usertoprocess->firstname ? '
'.$usertoprocess->getNomUrl(-2).''.dol_trunc($usertoprocess->firstname, 10).'' : '').''; for ($idw = 0; $idw < 7; $idw++) { From 1637b6af7dd0cfea6efb9f18ca879cbf0cfdf147 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 26 Jan 2021 15:16:06 +0100 Subject: [PATCH 14/17] Look and feel v14 --- htdocs/core/lib/project.lib.php | 2 ++ htdocs/projet/activity/perday.php | 2 +- htdocs/projet/activity/perweek.php | 2 +- htdocs/theme/eldy/global.inc.php | 3 +++ htdocs/theme/md/style.css.php | 3 +++ 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/htdocs/core/lib/project.lib.php b/htdocs/core/lib/project.lib.php index d7c7c53f8de..c5467c08357 100644 --- a/htdocs/core/lib/project.lib.php +++ b/htdocs/core/lib/project.lib.php @@ -1118,6 +1118,7 @@ function projectLinesPerDay(&$inc, $parent, $fuser, $lines, &$level, &$projectsr $projectstatic->ref = $lines[$i]->projectref; $projectstatic->title = $lines[$i]->projectlabel; $projectstatic->public = $lines[$i]->public; + $projectstatic->status = $lines[$i]->projectstatus; $taskstatic->id = $lines[$i]->id; $taskstatic->ref = ($lines[$i]->ref ? $lines[$i]->ref : $lines[$i]->id); @@ -1491,6 +1492,7 @@ function projectLinesPerWeek(&$inc, $firstdaytoshow, $fuser, $parent, $lines, &$ $projectstatic->title = $lines[$i]->projectlabel; $projectstatic->public = $lines[$i]->public; $projectstatic->thirdparty_name = $lines[$i]->thirdparty_name; + $projectstatic->status = $lines[$i]->projectstatus; $taskstatic->id = $lines[$i]->id; $taskstatic->ref = ($lines[$i]->ref ? $lines[$i]->ref : $lines[$i]->id); diff --git a/htdocs/projet/activity/perday.php b/htdocs/projet/activity/perday.php index c582507ec50..c6b38390ac9 100644 --- a/htdocs/projet/activity/perday.php +++ b/htdocs/projet/activity/perday.php @@ -3,7 +3,7 @@ * Copyright (C) 2004-2016 Laurent Destailleur * Copyright (C) 2005-2010 Regis Houssin * Copyright (C) 2010 François Legastelois - * Copyright (C) 2018 Frédéric France + * Copyright (C) 2018 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 diff --git a/htdocs/projet/activity/perweek.php b/htdocs/projet/activity/perweek.php index 9dff5fbc0f6..f001a73d0f4 100644 --- a/htdocs/projet/activity/perweek.php +++ b/htdocs/projet/activity/perweek.php @@ -3,7 +3,7 @@ * Copyright (C) 2004-2015 Laurent Destailleur * Copyright (C) 2005-2010 Regis Houssin * Copyright (C) 2010 François Legastelois - * Copyright (C) 2018 Frédéric France + * Copyright (C) 2018 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 diff --git a/htdocs/theme/eldy/global.inc.php b/htdocs/theme/eldy/global.inc.php index eaa1bf8c2e2..c4f862863e1 100644 --- a/htdocs/theme/eldy/global.inc.php +++ b/htdocs/theme/eldy/global.inc.php @@ -2577,6 +2577,9 @@ img.userphotosmall { /* size for user photo in lists */ img.userphoto[alt="Gravatar avatar"], img.photouserphoto.dropdown-user-image[alt="Gravatar avatar"] { background: #fff; } +form[name="addtime"] img.userphoto { + border: 1px solid #444; +} .span-icon-user { background-image: url(); background-repeat: no-repeat; diff --git a/htdocs/theme/md/style.css.php b/htdocs/theme/md/style.css.php index f0e4d593f00..a9fbc589a12 100644 --- a/htdocs/theme/md/style.css.php +++ b/htdocs/theme/md/style.css.php @@ -2568,6 +2568,9 @@ img.userphotosmall { /* size for user photo in lists */ img.userphoto[alt="Gravatar avatar"] { background: #fff; } +form[name="addtime"] img.userphoto { + border: 1px solid #444; +} .span-icon-user { background-image: url(); background-repeat: no-repeat; From 69344a71703eddf3b07da829685f6b600f9526d4 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 26 Jan 2021 16:33:47 +0100 Subject: [PATCH 15/17] Fix create temp dir --- htdocs/includes/odtphp/odf.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/htdocs/includes/odtphp/odf.php b/htdocs/includes/odtphp/odf.php index 88fa70c5646..bc15530a777 100644 --- a/htdocs/includes/odtphp/odf.php +++ b/htdocs/includes/odtphp/odf.php @@ -820,11 +820,13 @@ IMG; // Export to PDF using LibreOffice if ($conf->global->MAIN_ODT_AS_PDF == 'libreoffice') { + dol_mkdir($conf->user->dir_temp); // We must be sure the directory exists and is writable + // Install prerequisites: apt install soffice libreoffice-common libreoffice-writer // using windows libreoffice that must be in path // using linux/mac libreoffice that must be in path // Note PHP Config "fastcgi.impersonate=0" must set to 0 - Default is 1 - $command ='soffice --headless -env:UserInstallation=file:"//'.$conf->user->dir_temp.'" --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); + $command ='soffice --headless -env:UserInstallation=file:\''.$conf->user->dir_temp.'\' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); } elseif (preg_match('/unoconv/', $conf->global->MAIN_ODT_AS_PDF)) { From 23c0a302633ff145a0f725ba0fd3bcad8f1b0403 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 26 Jan 2021 16:40:57 +0100 Subject: [PATCH 16/17] Use a better temp directory --- htdocs/includes/odtphp/odf.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/htdocs/includes/odtphp/odf.php b/htdocs/includes/odtphp/odf.php index bc15530a777..7b165b53142 100644 --- a/htdocs/includes/odtphp/odf.php +++ b/htdocs/includes/odtphp/odf.php @@ -821,12 +821,13 @@ IMG; if ($conf->global->MAIN_ODT_AS_PDF == 'libreoffice') { dol_mkdir($conf->user->dir_temp); // We must be sure the directory exists and is writable + dol_mkdir($conf->user->dir_temp.'/odtaspdf'); // We create a subdir because the soffice change pemrissions on it // Install prerequisites: apt install soffice libreoffice-common libreoffice-writer // using windows libreoffice that must be in path // using linux/mac libreoffice that must be in path // Note PHP Config "fastcgi.impersonate=0" must set to 0 - Default is 1 - $command ='soffice --headless -env:UserInstallation=file:\''.$conf->user->dir_temp.'\' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); + $command ='soffice --headless -env:UserInstallation=file:\''.$conf->user->dir_temp.'/odtaspdf\' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); } elseif (preg_match('/unoconv/', $conf->global->MAIN_ODT_AS_PDF)) { From 0de06b7ef4ca24852fcf09c68916012ff7645d9f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 26 Jan 2021 16:49:54 +0100 Subject: [PATCH 17/17] Fix odttopdf generation --- htdocs/includes/odtphp/odf.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/htdocs/includes/odtphp/odf.php b/htdocs/includes/odtphp/odf.php index 7b165b53142..0f5861caf68 100644 --- a/htdocs/includes/odtphp/odf.php +++ b/htdocs/includes/odtphp/odf.php @@ -821,7 +821,10 @@ IMG; if ($conf->global->MAIN_ODT_AS_PDF == 'libreoffice') { dol_mkdir($conf->user->dir_temp); // We must be sure the directory exists and is writable - dol_mkdir($conf->user->dir_temp.'/odtaspdf'); // We create a subdir because the soffice change pemrissions on it + + // We delete and recreate a subdir because the soffice may have change pemrissions on it + dol_delete_dir_recursive($conf->user->dir_temp.'/odtaspdf'); + dol_mkdir($conf->user->dir_temp.'/odtaspdf'); // Install prerequisites: apt install soffice libreoffice-common libreoffice-writer // using windows libreoffice that must be in path