diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b9aab4928c1..eb71e312e5b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ --- -exclude: (?x)^( htdocs/includes/ckeditor/.*|(\.(?!github/workflows)[^/]*/.*))$ +exclude: (?x)^( htdocs/includes/ckeditor/.*|htdocs/public/includes/ckeditor/.*|(\.(?!github/workflows)[^/]*/.*))$ repos: # Several miscellaneous checks and fix (on yaml files, end of files fix) - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/dev/build/makepack-dolibarr.pl b/dev/build/makepack-dolibarr.pl index df8c6377c87..feb6f104f15 100755 --- a/dev/build/makepack-dolibarr.pl +++ b/dev/build/makepack-dolibarr.pl @@ -114,8 +114,8 @@ if ( !$ENV{"DESTIBETARC"} || !$ENV{"DESTISTABLE"} ) { print "On Linux:\n"; print "export DESTIBETARC='/tmp'; export DESTISTABLE='/tmp';\n"; print "On Windows:\n"; - print "set DESTIBETARC=c:/tmp\n"; - print "set DESTISTABLE=c:/tmp\n"; + print "set DESTIBETARC=c:/temp\n"; + print "set DESTISTABLE=c:/temp\n"; print "\n"; print "Example in .bashrc:\n"; print diff --git a/htdocs/accountancy/bookkeeping/list.php b/htdocs/accountancy/bookkeeping/list.php index 183bf316f81..150492e9bfc 100644 --- a/htdocs/accountancy/bookkeeping/list.php +++ b/htdocs/accountancy/bookkeeping/list.php @@ -51,7 +51,7 @@ require_once DOL_DOCUMENT_ROOT.'/accountancy/class/lettering.class.php'; */ // Load translation files required by the page -$langs->loadLangs(array("accountancy", "categories", "compta")); +$langs->loadLangs(array("accountancy", "categories", "compta", "other")); // Get Parameters $socid = GETPOSTINT('socid'); diff --git a/htdocs/accountancy/bookkeeping/listbyaccount.php b/htdocs/accountancy/bookkeeping/listbyaccount.php index ec25d198658..16f3edf1b9a 100644 --- a/htdocs/accountancy/bookkeeping/listbyaccount.php +++ b/htdocs/accountancy/bookkeeping/listbyaccount.php @@ -2,7 +2,7 @@ /* Copyright (C) 2016 Neil Orley * Copyright (C) 2013-2016 Olivier Geffroy * Copyright (C) 2013-2020 Florian Henry - * Copyright (C) 2013-2025 Alexandre Spangaro + * Copyright (C) 2013-2026 Alexandre Spangaro * Copyright (C) 2018-2025 Frédéric France * Copyright (C) 2024-2025 MDW * Copyright (C) 2025 Nicolas Barrouillet @@ -49,7 +49,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; */ // Load translation files required by the page -$langs->loadLangs(array("accountancy", "categories", "compta")); +$langs->loadLangs(array("accountancy", "categories", "compta", "other")); $journal_code = GETPOST('code_journal', 'alpha'); $account = GETPOST("account", 'int'); diff --git a/htdocs/admin/accounting.php b/htdocs/admin/accounting.php index 49db0a149bc..18ca40e7ab4 100644 --- a/htdocs/admin/accounting.php +++ b/htdocs/admin/accounting.php @@ -75,7 +75,7 @@ print "
\n"; $texttoshow = $langs->trans("AccountancySetupDoneFromAccountancyMenu", '{s1}'.$langs->transnoentitiesnoconv("Accounting").' - '.$langs->transnoentitiesnoconv("Setup").'{s2}'); $texttoshow = str_replace('{s1}', '', $texttoshow); $texttoshow = str_replace('{s2}', ''.img_picto("", "url", 'class="paddingleft"'), $texttoshow); -print ''.$texttoshow."
\n"; +print '
'.$texttoshow."

\n"; print "
\n"; llxFooter(); diff --git a/htdocs/admin/modules.php b/htdocs/admin/modules.php index 3625d91c40a..c0b166d4730 100644 --- a/htdocs/admin/modules.php +++ b/htdocs/admin/modules.php @@ -62,6 +62,7 @@ require_once DOL_DOCUMENT_ROOT.'/admin/remotestore/class/externalModules.class.p $langs->loadLangs(array("errors", "admin", "modulebuilder")); $action = GETPOST('action', 'aZ09'); +$page = GETPOSTINT('page'); $page_y = GETPOSTINT('page_y'); $optioncss = GETPOST('optioncss', 'aZ09'); $sortfield = GETPOST('sortfield', 'aZ09'); @@ -520,7 +521,7 @@ $i = 0; // is a sequencer of modules found $j = 0; // j is module number. Automatically affected if module number not defined. $modNameLoaded = array(); -//if ($mode == 'common' || $mode == 'commonkanban') { + // Load $modules (required for the badge count) foreach ($modulesdir as $dir) { // Load modules attributes in arrays (name, numero, orders) from dir directory @@ -1213,7 +1214,7 @@ if ($mode == 'common' || $mode == 'commonkanban') { print "\n"; // Desc - print ''; + print ''; print nl2br($objMod->getDesc()); print "\n"; diff --git a/htdocs/comm/action/class/actioncomm.class.php b/htdocs/comm/action/class/actioncomm.class.php index c16892fe2ea..5dfd1defc05 100644 --- a/htdocs/comm/action/class/actioncomm.class.php +++ b/htdocs/comm/action/class/actioncomm.class.php @@ -821,6 +821,11 @@ class ActionComm extends CommonObject $result = $this->create($fuser); if ($result < 0) { $error++; + } else { + $resultcat = $this->cloneCategories($objFrom->id, $this->id); + if ($resultcat < 0) { + $error++; + } } if (!$error) { diff --git a/htdocs/compta/bank/card.php b/htdocs/compta/bank/card.php index 922317c51fe..f347c6a3a78 100644 --- a/htdocs/compta/bank/card.php +++ b/htdocs/compta/bank/card.php @@ -703,7 +703,7 @@ if ($action == 'create') { print ''; // Type - print ''; + print ''; print ''; // Currency @@ -739,10 +739,13 @@ if ($action == 'create') { print ''; print ''; print ''; @@ -869,15 +872,15 @@ if ($action == 'create') { print nl2br($object->owner_address); print "\n"; - print ''; + print ''; print ''; - print ''; + print ''; print ''; - print ''; + print ''; print ''; } // Ref customer diff --git a/htdocs/core/ajax/getnews.php b/htdocs/core/ajax/getnews.php deleted file mode 100644 index 1f364879f2e..00000000000 --- a/htdocs/core/ajax/getnews.php +++ /dev/null @@ -1,58 +0,0 @@ - - */ - -// Just for display errors in editor -ini_set('display_errors', 1); - -if (!defined('NOTOKENRENEWAL')) { - define('NOTOKENRENEWAL', '1'); // Disables token renewal -} -if (!defined('NOREQUIREMENU')) { - define('NOREQUIREMENU', '1'); -} -if (!defined('NOREQUIREHTML')) { - define('NOREQUIREHTML', '1'); -} -if (!defined('NOREQUIREAJAX')) { - define('NOREQUIREAJAX', '1'); -} -if (!defined('NOREQUIRESOC')) { - define('NOREQUIRESOC', '1'); -} -require_once '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/website/class/websitepage.class.php'; - -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Translate $langs - * @var User $user - */ - -top_httphead(); - -if ($_SERVER['REQUEST_METHOD'] == 'POST' && GETPOSTISSET('selectedIds')) { - $selectedIds = json_decode(GETPOST('selectedIds'), true); - - $websitepage = new WebsitePage($db); - $selectedPosts = array(); - - foreach ($selectedIds as $id) { - $blog = new WebsitePage($db); - $blog->fetch($id); - - $selectedPosts[] = array( - 'id' => $blog->id, - 'title' => $blog->title, - 'description' => $blog->description, - 'date_creation' => $blog->date_creation, - 'image' => $blog->image, - ); - } - - print json_encode($selectedPosts); -} else { - print json_encode(array('error' => 'Invalid request')); -} diff --git a/htdocs/core/ajax/mailtemplate.php b/htdocs/core/ajax/mailtemplate.php index d1b112a0870..5f6c583412f 100644 --- a/htdocs/core/ajax/mailtemplate.php +++ b/htdocs/core/ajax/mailtemplate.php @@ -52,6 +52,7 @@ require_once '../lib/files.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/website.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; $langs->load("mails"); @@ -156,10 +157,11 @@ if (GETPOSTISSET('template')) { $content); + $template = GETPOST('template', 'alpha'); // Get list of selected news or products - $selectedPostsStr = GETPOST('selectedPosts', 'alpha'); + $selectedIdsStr = GETPOST('selectedPosts', 'alpha'); //$selectedPosts = array(); - $selectedPosts = json_decode($selectedPostsStr); + $selectedIds = json_decode($selectedIdsStr); /*if (is_array($selectedPostsStr)) { $selectedPosts = explode(',', $selectedPostsStr); }*/ @@ -172,28 +174,43 @@ if (GETPOSTISSET('template')) { } } */ - if (is_array($selectedPosts) && !empty($selectedPosts)) { + if (!empty($selectedIds) && !empty($template) && is_array($selectedIds) ) { $newsList = ''; + $productList = ''; + foreach ($selectedIds as $Id) { + if ($template == "news") { + $post = getNewsDetailsById($Id); - foreach ($selectedPosts as $postId) { - $post = getNewsDetailsById($postId); + $newsList .= '
+
+

' . (empty($post['title']) ? '' : dol_htmlentitiesbr($post['title'])) . '

+

' . (empty($post['description']) ? '' : dol_htmlentitiesbr($post['description'])) . '

+ Created By: ' . dol_htmlentitiesbr(empty($post['user_fullname']) ? '' : $post['user_fullname']) . ' +
+ ' . dol_print_date((empty($post['date_creation']) ? dol_now() : $post['date_creation']), 'daytext', 'tzserver', $langs) . ' +
+
+ ' . (!empty($post['image']) ? 'Image' : 'Gray rectangle') . ' +
+
'; + } elseif ($template == "product") { + $product = getProductForEmailTemplate($Id); - $newsList .= '
-
-

' . htmlentities(empty($post['title']) ? '' : $post['title']) . '

-

' . htmlentities(empty($post['description']) ? '' : $post['description']) . '

- Created By: ' . htmlentities(empty($post['user_fullname']) ? '' : $post['user_fullname']) . ' -
- ' . dol_print_date((empty($post['date_creation']) ? dol_now() : $post['date_creation']), 'daytext', 'tzserver', $langs) . ' -
-
- ' . (!empty($post['image']) ? 'Image' : 'Gray rectangle') . ' -
-
'; + $productList .= '
+
+ ' . (!empty($product['image']) ? dol_htmlentitiesbr($product['image']) : 'Gray rectangle') . ' +
+
+

'.(dol_htmlentitiesbr($product["ref"])).(empty($product["label"]) ? '' : ' - '.dol_htmlentitiesbr($product["label"])).'

+

'. (empty($product['description']) ? '' : dol_htmlentitiesbr($product['description'])) .'

+
+
+ '; + } } $content = str_replace('__NEWS_LIST__', $newsList, $content); - $content = str_replace('__PRODUCT_SELECTED__', $newsList, $content); + $content = str_replace('__PRODUCT_SELECTED__', $productList, $content); } else { $content = str_replace('__NEWS_LIST__', $langs->trans("SelectSomeArticlesOrEnterYourOwnContent"), $content); $content = str_replace('__PRODUCT_SELECTED__', $langs->trans("SelectOneArticleOrEnterYourOwnContent"), $content); diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index d3b056dac9d..d90974d72d3 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -11529,9 +11529,12 @@ abstract class CommonObject require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; $categorystatic = new Categorie($this->db); - $sql = "INSERT INTO ".$this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type])." (fk_categorie, fk_product)"; - $sql .= " SELECT fk_categorie, $toId FROM ".$this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]); - $sql .= " WHERE fk_product = ".((int) $fromId); + $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]); + $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]); + + $sql = "INSERT INTO ".$tablename." (fk_categorie, ".$fkname.")"; + $sql .= " SELECT fk_categorie, $toId FROM ".$tablename; + $sql .= " WHERE ".$fkname." = ".((int) $fromId); if (!$this->db->query($sql)) { $this->error = $this->db->lasterror(); diff --git a/htdocs/core/class/extrafields.class.php b/htdocs/core/class/extrafields.class.php index 9d411eb916e..47d78d4c598 100644 --- a/htdocs/core/class/extrafields.class.php +++ b/htdocs/core/class/extrafields.class.php @@ -1146,6 +1146,10 @@ class ExtraFields $form = new Form($this->db); } + if ($mode == 1) { // When field is used for search input into a list, we limit its size. + $morecss = 'maxwidth125'; + } + $parameters = array( 'key' => $key, 'value' => &$value, diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 0fa3ad42b72..ee2e69d7087 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -1578,11 +1578,28 @@ class FormMail extends Form } } + // Fetch Product / Services + $productArray = array(); + if (isModEnabled('product') || isModEnabled('service')) { + include_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php'; + $form = new Form($this->db); + $arrayofproduct = $form->select_produits_list(0, 'product-select', '', 0, 0, '', 1, 2, 1); + if (!empty($arrayofproduct)) { + foreach ($arrayofproduct as $product) { + $productArray[$product["key"]] = array( + 'id' => $product["key"], + 'label' => $product["value"].' - '.dol_trunc($product["label2"], 40), + 'labelhtml' => $product["value"].' - '.dol_trunc($product["label2"], 40), + ); + } + } + } + // Use the multiselect array function to create the dropdown $out .= ''; @@ -1592,7 +1609,7 @@ class FormMail extends Form $out .= ''; } @@ -1612,6 +1629,7 @@ class FormMail extends Form $(".template-option").removeClass("selected"); $(this).addClass("selected"); + $(".select-template").val("").trigger("change"); if (template === "news") { $("#post-dropdown-container").show(); @@ -1659,46 +1677,38 @@ class FormMail extends Form updateSelectedPostsContent(contentHtml, selectedIds); }); + $("#product-select").change(function() { + var selectedIds = $(this).val(); + var contentHtml = $(".template-option.selected").data("content"); + + updateSelectedPostsContent(contentHtml, selectedIds); + }); function updateSelectedPostsContent(contentHtml, selectedIds) { var csrfToken = "' .newToken().'"; + template = $(".template-option.selected").data("template"); + var subject = $("#subject").val(); $.ajax({ type: "POST", - url: "'.dol_buildpath('/core/ajax/getnews.php', 1).'", + url: "'.dol_buildpath('/core/ajax/mailtemplate.php', 1).'", data: { - selectedIds: JSON.stringify(selectedIds), - token : csrfToken + token: csrfToken, + template: template, + subject: subject, + selectedPosts: JSON.stringify(selectedIds) }, success: function(response) { - var selectedPosts = JSON.parse(response); - var subject = $("#subject").val(); - contentHtml = contentHtml.replace(/__SUBJECT__/g, subject); - template = $(".template-option.selected").data("template"); - $.ajax({ - type: "POST", - url: "'.dol_buildpath('/core/ajax/mailtemplate.php', 1).'", - data: { - token: csrfToken, - template: template, - subject: subject, - selectedPosts: JSON.stringify(selectedIds) - }, - success: function(response) { - jQuery("#'.$htmlContent.'").val(response); - var editorInstance = CKEDITOR.instances["'.$htmlContent.'"]; - if (editorInstance) { - editorInstance.setData(response); - } - }, - error: function(xhr, status, error) { - console.error("An error occurred: " + xhr.responseText); - } - }); + jQuery("#'.$htmlContent.'").val(response); + var editorInstance = CKEDITOR.instances["'.$htmlContent.'"]; + if (editorInstance) { + editorInstance.setData(response); + } }, error: function(xhr, status, error) { console.error("An error occurred: " + xhr.responseText); } }); + } }); '; diff --git a/htdocs/core/class/html.formsetup.class.php b/htdocs/core/class/html.formsetup.class.php index 52db1ca8d34..00afb746200 100644 --- a/htdocs/core/class/html.formsetup.class.php +++ b/htdocs/core/class/html.formsetup.class.php @@ -167,6 +167,7 @@ class FormSetup if ($editMode) { $out .= 'formAttributes) . ' >'; + $out .= ''; // generate hidden values from $this->formHiddenInputs if (!empty($this->formHiddenInputs) && is_array($this->formHiddenInputs)) { diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index b3928e8798b..fb9959f5943 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -1925,16 +1925,21 @@ function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1, $includequotes * * @param string $str String to clean * @param string $newstr String to replace bad chars with - * @param int $unaccent 1=Remove also accent (default), 0 do not remove them + * @param int $unaccent 1=Remove also accent, 0 do not remove them + * @param int $allowdash 1=Allow dash char after a space and before a string, 0 do not allow * @return string String cleaned * * @see dol_string_nospecial(), dol_string_unaccent(), dol_sanitizeFileName() */ -function dol_sanitizePathName($str, $newstr = '_', $unaccent = 1) +function dol_sanitizePathName($str, $newstr = '_', $unaccent = 0, $allowdash = 0) { // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file - // Char '>' '<' '|' '$' and ';' are special chars for shells. - // Chars '--' can be used into filename to inject special parameters like --use-compress-program to make command with file as parameter making remote execution of command + // Char '>' '<' '|' '$' ';' and '`' are special chars for shells. + // Char '?' and '*' are for wild card chars. + // Char '"' is dangerous. + // Char '°' is just not expected. + // Chars '-' and '--' can be used into filename to inject special parameters like --use-compress-program to make command with file as parameter making remote execution of command + // Chars '--' and '~' can be used for path transversal $filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°', '$', ';', '`'); $tmp = $str; @@ -1942,10 +1947,13 @@ function dol_sanitizePathName($str, $newstr = '_', $unaccent = 1) $tmp = dol_string_unaccent($tmp); } $tmp = dol_string_nospecial($tmp, $newstr, $filesystem_forbidden_chars); - $tmp = preg_replace('/\-\-+/', '_', $tmp); - $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp); - $tmp = preg_replace('/\s+\-$/', '', $tmp); - $tmp = str_replace('..', '', $tmp); + $tmp = preg_replace('/\-\-+/', $newstr, $tmp); + if (empty($allowdash)) { + $tmp = preg_replace('/\s+\-([^\s])/', ' '.$newstr.'$1', $tmp); + $tmp = preg_replace('/\s+\-$/', '', $tmp); + } + $tmp = str_replace('..', $newstr, $tmp); + $tmp = str_replace('~', $newstr, $tmp); return $tmp; } diff --git a/htdocs/core/lib/product.lib.php b/htdocs/core/lib/product.lib.php index 73f05ff68d7..1a044aa5379 100644 --- a/htdocs/core/lib/product.lib.php +++ b/htdocs/core/lib/product.lib.php @@ -1024,3 +1024,42 @@ function measuring_units_cubed($unit) $measuring_units[99] = 89; // inch -> inch3 return $measuring_units[$unit]; } + +/** + * Retrieve and return product for mail template. + * + * @param int $id The ID of the product to retrieve. + * @return array|int<-1,-1> Return array if OK, -1 if KO + */ +function getProductForEmailTemplate($id) +{ + require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; + global $db, $conf; + + $productarray = array(); + $sql = "SELECT p.rowid as id, p.ref, p.label, p.description, p.entity"; + $sql .= " FROM ".MAIN_DB_PREFIX."product as p"; + $sql .= " WHERE p.entity IN (".getEntity('product').")"; + $sql .= " AND p.rowid = ".((int) $id); + + $resql = $db->query($sql); + + if ($resql) { + $productarray = $db->fetch_array($resql); + } else { + dol_print_error($db); + return -1; + } + + $object = new Product($db); + $result = $object->fetch($id); + if ($result < 0) { + dol_print_error($db, $object->error, $object->errors); + } + $entity = (empty($object->entity) ? $conf->entity : $object->entity); + $productarray["image"] = $object->show_photos('product', $conf->product->multidir_output[$entity], 1, 1, 0, 0, 0, 120, 160, 1, ''); + if ($object->nbphoto <= 0) { + $productarray["image"] = ""; + } + return $productarray; +} diff --git a/htdocs/core/modules/DolibarrModules.class.php b/htdocs/core/modules/DolibarrModules.class.php index 660b8a7bb0c..9eea62c01fd 100644 --- a/htdocs/core/modules/DolibarrModules.class.php +++ b/htdocs/core/modules/DolibarrModules.class.php @@ -2683,7 +2683,7 @@ class DolibarrModules // Can not be abstract, because we need to instantiate it $return .= '
- '.$this->getName().' + '.$this->getName().' '.nl2br($this->getDesc()).''; $return .= '
'; diff --git a/htdocs/core/modules/modAccounting.class.php b/htdocs/core/modules/modAccounting.class.php index 214cec094e7..401def34490 100644 --- a/htdocs/core/modules/modAccounting.class.php +++ b/htdocs/core/modules/modAccounting.class.php @@ -83,7 +83,7 @@ class modAccounting extends DolibarrModules [ "MAIN_BANK_ACCOUNTANCY_CODE_ALWAYS_REQUIRED", "chaine", - "1", + "0", "With this constants on, bank account number is always required", 0, 'current', 1 ], [ diff --git a/htdocs/hrm/skill_tab.php b/htdocs/hrm/skill_tab.php index 5a9cda8b5c6..19fa1e37403 100644 --- a/htdocs/hrm/skill_tab.php +++ b/htdocs/hrm/skill_tab.php @@ -457,7 +457,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea include DOL_DOCUMENT_ROOT . '/core/tpl/extrafields_view.tpl.php'; } else { // Login - print '
'; + print ''; if (!empty($object->ldap_sid) && $object->statut == 0) { print '
'.$langs->trans("AccountType").'
'.$langs->trans("AccountType").''.$object->type_lib[$object->type].'
'.$langs->trans("AccountancyCode").''; if (isModEnabled('accounting')) { - $accountingaccount = new AccountingAccount($db); - $accountingaccount->fetch(0, $object->account_number, 1); - - print $accountingaccount->getNomUrl(0, 1, 1, '', 1); + if (empty($object->account_number)) { + print img_warning($langs->trans("Mandatory")); + } else { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $object->account_number, 1); + print $accountingaccount->getNomUrl(0, 1, 1, '', 1); + } } else { print $object->account_number; } @@ -752,11 +755,11 @@ if ($action == 'create') { if (isModEnabled('accounting')) { print '
'.$langs->trans("AccountancyJournal").''; - - if ($object->fk_accountancy_journal > 0) { + if (empty($object->fk_accountancy_journal)) { + print img_warning($langs->trans("Mandatory")); + } elseif ($object->fk_accountancy_journal > 0) { $accountingjournal = new AccountingJournal($db); $accountingjournal->fetch($object->fk_accountancy_journal); - print $accountingjournal->getNomUrl(0, 1, 1, '', 1); } print '
'.$langs->trans("BankAccountOwnerZip").'
'.$langs->trans("BankAccountOwnerZip").''.dol_escape_htmltag($object->owner_zip); print '
'.$langs->trans("BankAccountOwnerTown").'
'.$langs->trans("BankAccountOwnerTown").''.dol_escape_htmltag($object->owner_town); print '
'.$langs->trans("BankAccountOwnerCountry").'
'.$langs->trans("BankAccountOwnerCountry").''; $object->owner_country_code = dol_getIdFromCode($db, $object->owner_country_id, 'c_country', 'rowid', 'code'); $langs->load("dict"); diff --git a/htdocs/compta/bank/list.php b/htdocs/compta/bank/list.php index 0f7dc103859..0c47d4466d1 100644 --- a/htdocs/compta/bank/list.php +++ b/htdocs/compta/bank/list.php @@ -712,12 +712,16 @@ foreach ($accounts as $key => $type) { // Account number if (!empty($arrayfields['b.account_number']['checked'])) { print ''; - if (isModEnabled('accounting') && !empty($objecttmp->account_number)) { - $accountingaccount = new AccountingAccount($db); - $accountingaccount->fetch(0, $objecttmp->account_number, 1); - print ''; - print $accountingaccount->getNomUrl(0, 1, 1, '', 0); - print ''; + if (isModEnabled('accounting')) { + if (empty($objecttmp->account_number)) { + print img_warning($langs->trans("Mandatory")); + } else { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $objecttmp->account_number, 1); + print ''; + print $accountingaccount->getNomUrl(0, 1, 1, '', 0); + print ''; + } } else { print ''.$objecttmp->account_number.''; } diff --git a/htdocs/compta/facture/invoicetemplate_list.php b/htdocs/compta/facture/invoicetemplate_list.php index 0b0610c0f88..2fe68b3fc52 100644 --- a/htdocs/compta/facture/invoicetemplate_list.php +++ b/htdocs/compta/facture/invoicetemplate_list.php @@ -402,6 +402,11 @@ if ($search_date_when_end) { // Add where from extra fields include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; +// Add where from hooks +$parameters = array(); +$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object, $action); // Note that $action and $object may have been modified by hook +$sql .= $hookmanager->resPrint; + if ($search_all) { $sql .= " AND EXISTS (SELECT fdc.rowid FROM ".MAIN_DB_PREFIX."facturedet_rec as fdc WHERE f.rowid = fdc.fk_facture ".natural_search(array_keys($fieldstosearchall), $search_all).")"; } @@ -854,6 +859,12 @@ if (!empty($arrayfields['f.tms']['checked'])) { } // Extra fields include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php'; + +// Hook fields +$parameters = array('arrayfields'=>$arrayfields, 'param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder, 'totalarray'=>$totalarray); +$reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook +print $hookmanager->resPrint; + if (!empty($arrayfields['status']['checked'])) { print_liste_field_titre($arrayfields['status']['label'], $_SERVER['PHP_SELF'], "f.suspended,f.frequency", "", $param, '', $sortfield, $sortorder, 'center '); $totalarray['nbfield']++; diff --git a/htdocs/compta/facture/list.php b/htdocs/compta/facture/list.php index 7c6d65266c9..9863e20bf41 100644 --- a/htdocs/compta/facture/list.php +++ b/htdocs/compta/facture/list.php @@ -1627,7 +1627,7 @@ if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER_IN_LIST')) { // Ref if (!empty($arrayfields['f.ref']['checked'])) { print ''; - print ''; + print ''; print '
'.$langs->trans("Login").'
'.$langs->trans("Login").''; print $langs->trans("LoginAccountDisableInDolibarr"); diff --git a/htdocs/includes/odtphp/odf.php b/htdocs/includes/odtphp/odf.php index 118b1fb87bb..9b7ce2eaf10 100644 --- a/htdocs/includes/odtphp/odf.php +++ b/htdocs/includes/odtphp/odf.php @@ -873,8 +873,8 @@ IMG; * * @param string $name Name of ODT file to generate before generating PDF * @param int $dooutputfordownload Output the file content to make the download - * @throws OdfException - * @return void + * @throws OdfException + * @return void */ public function exportAsAttachedPDF($name = "", $dooutputfordownload = 1) { @@ -888,12 +888,11 @@ IMG; $execmethod = (getDolGlobalString('MAIN_EXEC_USE_POPEN') ? 2 : 1); // 1 or 2 // Method 1 sometimes hang the server. - // Export to PDF using LibreOffice if (getDolGlobalString('MAIN_ODT_AS_PDF') == 'libreoffice') { dol_mkdir($conf->user->dir_temp); // We must be sure the directory exists and is writable - // We delete and recreate a subdir because the soffice may have change pemrissions on it + // We delete and recreate a subdir because the soffice may have change permissions on it $countdeleted = 0; dol_delete_dir_recursive($conf->user->dir_temp.'/odtaspdf', 0, 0, 0, $countdeleted, 0, 1); dol_mkdir($conf->user->dir_temp.'/odtaspdf'); @@ -902,8 +901,10 @@ IMG; // using windows libreoffice that must be in path // using linux/mac libreoffice that must be in path // Note PHP Config "fastcgi.impersonate=0" must set to 0 - Default is 1 - $command ='soffice --headless -env:UserInstallation=file:'.(getDolGlobalString('MAIN_ODT_ADD_SLASH_FOR_WINDOWS') ? '///' : '').'\''.$conf->user->dir_temp.'/odtaspdf\' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); + $command ='soffice --headless -env:UserInstallation=file:'.escapeshellarg((getDolGlobalString('MAIN_ODT_ADD_SLASH_FOR_WINDOWS') ? '///' : '').dol_sanitizePathName($conf->user->dir_temp).'/odtaspdf').' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); } elseif (preg_match('/unoconv/', getDolGlobalString('MAIN_ODT_AS_PDF'))) { + // This feature is now disabled by default. Must set var in conf.php to allow it. + global $dolibarr_main_allow_unoconv; // If issue with unoconv, see https://github.com/dagwieers/unoconv/issues/87 // MAIN_ODT_AS_PDF should be "sudo -u unoconv /usr/bin/unoconv" and userunoconv must have sudo to be root by adding file /etc/sudoers.d/unoconv with content www-data ALL=(unoconv) NOPASSWD: /usr/bin/unoconv . @@ -927,18 +928,22 @@ IMG; // If it fails: // - set shell of user to bash instead of nologin. // - set permission to read/write to user on home directory /var/www so user can create the libreoffice , dconf and .cache dir and files then set permission back - - $command = getDolGlobalString('MAIN_ODT_AS_PDF').' '.escapeshellcmd($name); - //$command = '/usr/bin/unoconv -vvv '.escapeshellcmd($name); + if (!empty($dolibarr_main_allow_unoconv)) { + $command = dol_sanitizePathName(getDolGlobalString('MAIN_ODT_AS_PDF'), '_', 0, 1).' '.escapeshellarg($name); + //$command = '/usr/bin/unoconv -vvv '.escapeshellcmd($name); + } else { + throw new OdfException('Use of the unoconv method is deprecated. Try to use "libreoffice" method instead of set $dolibarr_main_allow_unoconv to 1 in conf.php for backward compatibility.'); + } } else { // deprecated old method using odt2pdf.sh (native, jodconverter, ...) - $tmpname=preg_replace('/\.odt/i', '', $name); + $tmpname = dol_sanitizePathName(preg_replace('/\.odt/i', '', $name)); if (getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT')) { - $command = getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT').'/scripts/odt2pdf/odt2pdf.sh '.escapeshellcmd($tmpname).' '.(is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF'))?'jodconverter':getDolGlobalString('MAIN_ODT_AS_PDF')); + $paramodt2pdf = (is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF')) ? 'jodconverter' : getDolGlobalString('MAIN_ODT_AS_PDF')); + $paramodt2pdf = dol_sanitizePathName($paramodt2pdf); + $command = dol_sanitizePathName(getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT')).'/scripts/odt2pdf/odt2pdf.sh '.escapeshellarg($tmpname).' '.escapeshellarg($paramodt2pdf); } else { - dol_syslog(get_class($this).'::exportAsAttachedPDF is used but the constant MAIN_DOL_SCRIPTS_ROOT with path to script directory was not defined.', LOG_WARNING); - $command = '../../scripts/odt2pdf/odt2pdf.sh '.escapeshellcmd($tmpname).' '.(is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF'))?'jodconverter':getDolGlobalString('MAIN_ODT_AS_PDF')); + throw new OdfException('Use of the ODT to PDF convertion with odt2pdf.sh script is deprecated when option MAIN_DOL_SCRIPTS_ROOT to define path of scripts directory is no set.'); } } @@ -946,6 +951,7 @@ IMG; //$command = DOL_DOCUMENT_ROOT.'/includes/odtphp/odt2pdf.sh '.$name.' '.$dirname; dol_syslog(get_class($this).'::exportAsAttachedPDF $execmethod='.$execmethod.' Run command='.$command, LOG_DEBUG); + // TODO Use: // $outputfile = DOL_DATA_ROOT.'/odt2pdf.log'; // $result = $utils->executeCLI($command, $outputfile); and replace test on $execmethod. @@ -953,17 +959,17 @@ IMG; // $errorstring will be $result['output'] $retval=0; $output_arr=array(); if ($execmethod == 1) { - exec($command, $output_arr, $retval); + exec(escapeshellcmd($command), $output_arr, $retval); } if ($execmethod == 2) { $outputfile = DOL_DATA_ROOT.'/odt2pdf.log'; - $ok=0; $handle = fopen($outputfile, 'w'); if ($handle) { dol_syslog(get_class($this)."Run command ".$command, LOG_DEBUG); + dol_syslog(get_class($this)."escapeshellcmd(command) = ".escapeshellcmd($command), LOG_DEBUG); fwrite($handle, $command."\n"); - $handlein = popen($command, 'r'); + $handlein = popen(escapeshellcmd($command), 'r'); while (!feof($handlein)) { $read = fgets($handlein); fwrite($handle, $read); @@ -987,6 +993,7 @@ IMG; if (getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { $name = preg_replace('/\.od(x|t)/i', '', $name); + header('Content-type: application/pdf'); header('Content-Disposition: attachment; filename="' . basename($name) . '.pdf"'); readfile($name . ".pdf"); @@ -1008,7 +1015,7 @@ IMG; foreach ($output_arr as $line) { $errorstring.= $line."
"; } - throw new OdfException('ODT to PDF convert fail (option MAIN_ODT_AS_PDF is '.$conf->global->MAIN_ODT_AS_PDF.', command was '.$command.', retval='.$retval.') : ' . $errorstring); + throw new OdfException('ODT to PDF convert fail (option MAIN_ODT_AS_PDF is '.getDolGlobalString('MAIN_ODT_AS_PDF').', command was '.$command.', retval='.$retval.') : ' . $errorstring); } } } diff --git a/htdocs/install/check.php b/htdocs/install/check.php index 7529d7faaf1..e7aa7f4d302 100644 --- a/htdocs/install/check.php +++ b/htdocs/install/check.php @@ -448,8 +448,8 @@ if (!file_exists($conffile)) { // Show title if (getDolGlobalString('MAIN_VERSION_LAST_UPGRADE') || getDolGlobalString('MAIN_VERSION_LAST_INSTALL')) { - print $langs->trans("VersionLastUpgrade").' '.getDolGlobalString('MAIN_VERSION_LAST_UPGRADE', getDolGlobalString('MAIN_VERSION_LAST_INSTALL')).'   -   '; - print $langs->trans("VersionProgram").' '.DOL_VERSION.''; + print $langs->trans("VersionLastUpgrade").' '.getDolGlobalString('MAIN_VERSION_LAST_UPGRADE', getDolGlobalString('MAIN_VERSION_LAST_INSTALL')).'   -   '; + print $langs->trans("VersionProgram").' '.DOL_VERSION.''; //print ' '.img_warning($langs->trans("RunningUpdateProcessMayBeRequired")); print '
'; print '
'; diff --git a/htdocs/install/default.css b/htdocs/install/default.css index 40318827804..60a290944e7 100644 --- a/htdocs/install/default.css +++ b/htdocs/install/default.css @@ -45,7 +45,7 @@ } .no-bottom { - padding-bottom: 0; + padding-bottom: 0 !important; } .small { diff --git a/htdocs/install/step5.php b/htdocs/install/step5.php index 0d8545945f1..d2dd1521432 100644 --- a/htdocs/install/step5.php +++ b/htdocs/install/step5.php @@ -178,7 +178,7 @@ if ($action == "set") { // Test on permissions not required here $morehtml = ''; -pHeader($langs->trans("DolibarrSetup"), "step5", 'set', '', '', 'main-inside main-inside-borderbottom'); +pHeader($langs->trans("DolibarrSetup"), "step5", 'set', '', '', 'main-inside main-inside-borderbottom no-bottom'); print '
'; // Test if we can run a first install process diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 06b5f726c1e..701cd0b2409 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -7313,51 +7313,6 @@ class Product extends CommonObject $return .= ''; return $return; } - - /** - * Retrieve and display products. - * - * @param int $limit The maximum number of results to return. - * @return array>|int return array if OK, -1 if KO - */ - public function getProductsToPreviewInEmail($limit) - { - - if (!is_numeric($limit)) { - return -1; - } - - $sql = "SELECT p.rowid, p.ref, p.label, p.description, p.entity, ef.filename - FROM ".MAIN_DB_PREFIX."product AS p - JOIN ".MAIN_DB_PREFIX."ecm_files AS ef ON p.rowid = ef.src_object_id - WHERE ef.entity IN (".getEntity('product').") - AND (ef.filename LIKE '%.png' OR ef.filename LIKE '%.jpeg' OR ef.filename LIKE '%.svg') - GROUP BY p.rowid, p.ref, p.label, p.description, p.entity, ef.filename - ORDER BY p.datec ASC - LIMIT " . ((int) $limit); - - $resql = $this->db->query($sql); - $products = array(); - - if ($resql) { - while ($obj = $this->db->fetch_object($resql)) { - $products[] = array( - 'rowid' => $obj->rowid, - 'ref' => $obj->ref, - 'label' => $obj->label, - 'description' => $obj->description, - 'entity' => $obj->entity, - 'filename' => $obj->filename - ); - } - } else { - dol_print_error($this->db); - } - if (empty($products)) { - return -1; - } - return $products; - } } /** diff --git a/htdocs/theme/eldy/global.inc.php b/htdocs/theme/eldy/global.inc.php index 2a6fea2a8a6..f706812bdaf 100644 --- a/htdocs/theme/eldy/global.inc.php +++ b/htdocs/theme/eldy/global.inc.php @@ -773,6 +773,9 @@ input.pageplusone { .opacitytransp { opacity: 0; } +.noopacity { + opacity: unset !important; +} .colorwhite { color: var(--colorwhite); } diff --git a/htdocs/theme/md/style.css.php b/htdocs/theme/md/style.css.php index c005dc018fe..a46f0eede06 100644 --- a/htdocs/theme/md/style.css.php +++ b/htdocs/theme/md/style.css.php @@ -944,6 +944,9 @@ input.pageplusone { .opacitytransp { opacity: 0; } +.noopacity { + opacity: unset !important; +} .colorwhite { color: var(--colorwhite); } diff --git a/scripts/odt2pdf/odt2pdf.sh b/scripts/odt2pdf/odt2pdf.sh index d895e11e12e..164d954fe71 100755 --- a/scripts/odt2pdf/odt2pdf.sh +++ b/scripts/odt2pdf/odt2pdf.sh @@ -1,11 +1,11 @@ #!/bin/bash -# @copyright GPL License 2010 - Vikas Mahajan - http://vikasmahajan.wordpress.com -# @copyright GPL License 2013 - Florian HEnry - florian.henry@open-concept.pro -# @copyright GPL License 2017 - Laurent Destailleur - eldy@users.sourceforge.net -# @copyright GPL License 2019 - Camille Lafitte - cam.lafit@azerttyu.net -# Copyright (C) 2024 MDW +# Copyright (C) 2010 Vikas Mahajan http://vikasmahajan.wordpress.com +# Copyright (C) 2013 Florian HEnry florian.henry@open-concept.pro +# Copyright (C) 2017 Laurent Destailleur eldy@users.sourceforge.net +# Copyright (C) 2019 Camille Lafitte cam.lafit@azerttyu.net +# Copyright (C) 2024 MDW # -# Convert an ODT into a PDF using "native" or "jodconverter" or "pyodconverter" or "unoconv" tool. +# Convert an ODT into a PDF using "native" or "jodconverter" or "pyodconverter". # Dolibarr variable MAIN_ODT_AS_PDF must be defined ... # to value "libreoffice" to call soffice native exporter feature (in such a case, this script is useless) # or value "unoconv" to call unoconv CLI tool after ODT generation. @@ -21,8 +21,7 @@ if [ "$1" = "" ] || [ "$2" = "" ] then - echo "Usage: odt2pdf.sh fullfilename [native|unoconv|jodconverter|pyodconverter|pathtojodconverterjar|pandoc]" - echo "Example: odt2pdf.sh myfile unoconv" + echo "Usage: odt2pdf.sh fullfilename [native|jodconverter|pyodconverter|pathtojodconverterjar|pandoc]" echo "Example: odt2pdf.sh myfile ~/jodconverter/jodconverter-cli-2.2.2.jar" exit fi diff --git a/test/phpunit/FunctionsLibTest.php b/test/phpunit/FunctionsLibTest.php index eda5e73efe2..3226e512cd2 100644 --- a/test/phpunit/FunctionsLibTest.php +++ b/test/phpunit/FunctionsLibTest.php @@ -2046,4 +2046,44 @@ class FunctionsLibTest extends CommonClassTest $this->assertEquals("1", $result[0]); $this->assertEquals("0", $result[1]); } + + + + /** + * testDolSanitizePathName + * + * @return void + */ + public function testDolSanitizePathName() + { + global $conf,$user,$langs,$db; + $conf = $this->savconf; + $user = $this->savuser; + $langs = $this->savlangs; + $db = $this->savdb; + + $s = '../aéa/bbb ccc/ddd'; + $result = dol_sanitizePathName($s); + $this->assertEquals('_/aéa/bbb ccc/ddd', $result); + + $s = '../aéa/bbb ccc/ddd'; + $result = dol_sanitizePathName($s, '_', 1); + $this->assertEquals('_/aea/bbb ccc/ddd', $result); + + $s = 'C:\ccc/d\'d"d$'; + $result = dol_sanitizePathName($s); + $this->assertEquals('C:\ccc/d\'d_d_', $result); + + $s = 'C:\ccc/d\'d"d$'; + $result = dol_sanitizePathName($s); + $this->assertEquals('C:\ccc/d\'d_d_', $result); + + $s = '/aaa/bbb -a -b'; + $result = dol_sanitizePathName($s); + $this->assertEquals('/aaa/bbb _a _b', $result); + + $s = '/aaa/bbb -a -b'; + $result = dol_sanitizePathName($s, '_', 0, 1); + $this->assertEquals('/aaa/bbb -a -b', $result); + } }