diff --git a/.github/workflows/exakat.yml b/.github/workflows/exakat.yml index 90ba405e061..861f6ccd4b0 100644 --- a/.github/workflows/exakat.yml +++ b/.github/workflows/exakat.yml @@ -13,5 +13,5 @@ jobs: - name: Exakat uses: docker://exakat/exakat-ga with: - ignore_rules: 'Performances/PrePostIncrement,Functions/WrongNumberOfArguments,Variables/UndefinedVariable,Classes/DontUnsetProperties,Classes/NonPpp,Classes/StaticMethodsCalledFromObject,Classes/UseClassOperator,Functions/UsesDefaultArguments,Php/NoClassInGlobal,Php/ShouldUseCoalesce,Structures/MergeIfThen,Structures/ElseIfElseif,Structures/RepeatedPrint,Structures/UselessParenthesis' + ignore_rules: 'Classes/UseInstanceof,Performances/PrePostIncrement,Functions/WrongNumberOfArguments,Variables/UndefinedVariable,Classes/DontUnsetProperties,Classes/NonPpp,Classes/StaticMethodsCalledFromObject,Classes/UseClassOperator,Functions/UsesDefaultArguments,Php/NoClassInGlobal,Php/ShouldUseCoalesce,Structures/MergeIfThen,Structures/ElseIfElseif,Structures/RepeatedPrint,Structures/UselessParenthesis,Structures/SwitchWithoutDefault,Structures/ShouldMakeTernary,Structures/UseConstant' ignore_dirs: '/htdocs/includes,/htdocs/build,/htdocs/dev,/htdocs/doc,/htdocs/scripts,/htdocs/test' \ No newline at end of file diff --git a/ChangeLog b/ChangeLog index 62f28b9bd6e..ca5834f63f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,7 +23,7 @@ Following changes may create regressions for some external modules, but were nec you must now also include declaration of the Trait 'CommonIncoterm' in your class. All incoterm functions were moved into this Trait. * The GETPOST(..., 'alpha') has now the same behaviour than GETPOST(..., 'alphanohtml') so no html will be allowed. Use GETPOST(..., 'restricthtml') to accept HTML. * If you have links in your code with '&action=delete' as a parameter, you must also add '&token='.newToken() as another parameter to avoid CSRF protection errors. - +* The API addPayment for api_invoice has evolved to accept amount into a foreign currency. You must provide array(amount=>X,mutlicurrency_ammount=>Y) instead of amount. ***** ChangeLog for 12.0.3 compared to 12.0.2 ***** FIX: 10.0 - when the mime file name is different from the filesystem name, the attachment name should be the mime filename diff --git a/dev/initdata/purge-data.php b/dev/initdata/purge-data.php index 183140af202..e6a67aa73c8 100755 --- a/dev/initdata/purge-data.php +++ b/dev/initdata/purge-data.php @@ -26,7 +26,7 @@ $sapi_type = php_sapi_name(); $script_file = basename(__FILE__); -$path=dirname(__FILE__).'/'; +$path=__DIR__.'/'; // Test si mode batch if (substr($sapi_type, 0, 3) == 'cgi') { diff --git a/htdocs/admin/dict.php b/htdocs/admin/dict.php index 1ac1e0f4ad4..e40a84db0ec 100644 --- a/htdocs/admin/dict.php +++ b/htdocs/admin/dict.php @@ -91,11 +91,7 @@ $hookmanager->initHooks(array('admin')); // Put here declaration of dictionaries properties // Sort order to show dictionary (0 is space). All other dictionaries (added by modules) will be at end of this. -if (! empty($conf->global->THIRDPARTY_ENABLE_PROSPECTION_ON_ALTERNATIVE_ADRESSES)) { - $taborder = array(9, 0, 4, 3, 2, 0, 1, 8, 19, 16, 39, 27, 40, 38, 0, 5, 11, 0, 32, 33, 34, 0, 6, 0, 29, 0, 7, 24, 28, 17, 35, 36, 0, 10, 23, 12, 13, 0, 14, 0, 22, 20, 18, 21, 0, 15, 30, 0, 37, 0, 25, 0); -} else { - $taborder = array(9, 0, 4, 3, 2, 0, 1, 8, 19, 16, 27, 38, 0, 5, 11, 0, 32, 33, 34, 0, 6, 0, 29, 0, 7, 24, 28, 17, 35, 36, 0, 10, 23, 12, 13, 0, 14, 0, 22, 20, 18, 21, 0, 15, 30, 0, 37, 0, 25, 0); -} +$taborder = array(9, 0, 4, 3, 2, 0, 1, 8, 19, 16, 39, 27, 40, 38, 0, 5, 11, 0, 32, 33, 34, 0, 6, 0, 29, 0, 7, 24, 28, 17, 35, 36, 0, 10, 23, 12, 13, 0, 14, 0, 22, 20, 18, 21, 0, 15, 30, 0, 37, 0, 25, 0); // Name of SQL tables of dictionaries $tabname = array(); @@ -133,7 +129,6 @@ $tabname[30] = MAIN_DB_PREFIX."c_format_cards"; $tabname[32] = MAIN_DB_PREFIX."c_hrm_public_holiday"; $tabname[33] = MAIN_DB_PREFIX."c_hrm_department"; $tabname[34] = MAIN_DB_PREFIX."c_hrm_function"; - $tabname[35] = MAIN_DB_PREFIX."c_exp_tax_cat"; $tabname[36] = MAIN_DB_PREFIX."c_exp_tax_range"; $tabname[37] = MAIN_DB_PREFIX."c_units"; @@ -485,8 +480,8 @@ $tabcond[35] = !empty($conf->expensereport->enabled); $tabcond[36] = !empty($conf->expensereport->enabled); $tabcond[37] = !empty($conf->product->enabled); $tabcond[38] = !empty($conf->socialnetworks->enabled); -$tabcond[39] = (! empty($conf->societe->enabled) && empty($conf->global->SOCIETE_DISABLE_PROSPECTS)); -$tabcond[40] = ! empty($conf->societe->enabled); +$tabcond[39] = (!empty($conf->societe->enabled) && empty($conf->global->SOCIETE_DISABLE_PROSPECTS) && !empty($conf->global->THIRDPARTY_ENABLE_PROSPECTION_ON_ALTERNATIVE_ADRESSES)); +$tabcond[40] = (!empty($conf->societe->enabled) && !empty($conf->global->THIRDPARTY_ENABLE_PROSPECTION_ON_ALTERNATIVE_ADRESSES)); // List of help for fields $tabhelp = array(); diff --git a/htdocs/admin/security_file.php b/htdocs/admin/security_file.php index 4b665123e4f..daa43110529 100644 --- a/htdocs/admin/security_file.php +++ b/htdocs/admin/security_file.php @@ -154,6 +154,9 @@ if (ini_get('safe_mode') && !empty($conf->global->MAIN_ANTIVIRUS_COMMAND)) } } print ''; +if (defined('MAIN_ANTIVIRUS_COMMAND')) { + print '
'.$langs->trans("ValueIsForcedBySystem").''; +} print ""; print ''; @@ -165,6 +168,9 @@ print ''.$langs->trans("AntiVirusParamExample").''; print ''; print ''; +if (defined('MAIN_ANTIVIRUS_PARAM')) { + print '
'.$langs->trans("ValueIsForcedBySystem").''; +} print ""; print ''; diff --git a/htdocs/admin/system/dolibarr.php b/htdocs/admin/system/dolibarr.php index 57d288dfb4c..5db0da25635 100644 --- a/htdocs/admin/system/dolibarr.php +++ b/htdocs/admin/system/dolibarr.php @@ -1,5 +1,5 @@ +/* Copyright (C) 2005-2020 Laurent Destailleur * Copyright (C) 2007 Rodolphe Quiedeville * Copyright (C) 2007-2012 Regis Houssin * @@ -19,7 +19,7 @@ /** * \file htdocs/admin/system/dolibarr.php - * \brief Page to show Dolibarr informations + * \brief Page to show Dolibarr information */ require '../../main.inc.php'; @@ -365,7 +365,7 @@ foreach ($configfileparameters as $key => $value) print ''.$newkey.''; // Value print ""; - if ($newkey == 'dolibarr_main_db_pass') { + if (in_array($newkey, array('dolibarr_main_db_pass', 'dolibarr_main_auth_ldap_admin_pass'))) { if (empty($dolibarr_main_prod)) { print ''; } diff --git a/htdocs/admin/system/modules.php b/htdocs/admin/system/modules.php index c909084f1d6..28dcb6641ef 100644 --- a/htdocs/admin/system/modules.php +++ b/htdocs/admin/system/modules.php @@ -56,11 +56,13 @@ $arrayfields = array( 'name'=>array('label'=>$langs->trans("Modules"), 'checked'=>1, 'position'=>10), 'version'=>array('label'=>$langs->trans("Version"), 'checked'=>1, 'position'=>20), 'id'=>array('label'=>$langs->trans("IdModule"), 'checked'=>1, 'position'=>30), + 'module_position'=>array('label'=>$langs->trans("Position"), 'checked'=>1, 'position'=>35), 'permission'=>array('label'=>$langs->trans("IdPermissions"), 'checked'=>1, 'position'=>40) ); $arrayfields = dol_sort_array($arrayfields, 'position'); + /* * Actions */ @@ -129,6 +131,7 @@ foreach ($modules as $key=>$module) { $newModule->name = $module->getName(); $newModule->version = $module->getVersion(); $newModule->id = $key; + $newModule->module_position = $module->module_position; $alt = $module->name.' - '.$modules_files[$key]; @@ -223,6 +226,10 @@ if ($arrayfields['id']['checked']) { print ''; print ''; } +if ($arrayfields['module_position']['checked']) { + print ''; + print ''; +} if ($arrayfields['permission']['checked']) { print ''; print ''; @@ -247,6 +254,9 @@ if ($arrayfields['version']['checked']) { if ($arrayfields['id']['checked']) { print_liste_field_titre($arrayfields['id']['label'], $_SERVER["PHP_SELF"], "id", "", "", "", $sortfield, $sortorder); } +if ($arrayfields['module_position']['checked']) { + print_liste_field_titre($arrayfields['module_position']['label'], $_SERVER["PHP_SELF"], "module_position", "", "", "", $sortfield, $sortorder); +} if ($arrayfields['permission']['checked']) { print_liste_field_titre($arrayfields['permission']['label'], $_SERVER["PHP_SELF"], "permission", "", "", "", $sortfield, $sortorder); } @@ -273,6 +283,8 @@ if ($sortfield == "id" && $sortorder == "desc") usort($moduleList, "compareIdDes if ($sortfield == "permission" && $sortorder == "asc") usort($moduleList, "comparePermissionIdsAsc"); if ($sortfield == "permission" && $sortorder == "desc") usort($moduleList, "comparePermissionIdsDesc"); +$moduleList = dol_sort_array($moduleList, 'module_position'); + foreach ($moduleList as $module) { print ''; @@ -291,6 +303,10 @@ foreach ($moduleList as $module) { print ''.$module->id.''; } + if ($arrayfields['module_position']['checked']) { + print ''.$module->module_position.''; + } + if ($arrayfields['permission']['checked']) { $idperms = ''; diff --git a/htdocs/admin/tools/dolibarr_export.php b/htdocs/admin/tools/dolibarr_export.php index f1a95a48e9f..27efdb3c52f 100644 --- a/htdocs/admin/tools/dolibarr_export.php +++ b/htdocs/admin/tools/dolibarr_export.php @@ -147,7 +147,7 @@ print ''; print $langs->trans("DatabaseName").' : '.$dolibarr_main_db_name.'
'; print ''; print ''; -print ''; +print ''; print ''; print ''; print ''; } + +/** + * Return a file on output using a lo memory. + * It can return very large files with no need of memory. + * WARNING: This close output buffers. + * + * @param string $fullpath_original_file_osencoded Full path of file to return. + * @param int $method -1 automatic, 0=readfile, 1=fread, 2=stream_copy_to_stream + * @return void + */ +function readfileLowMemory($fullpath_original_file_osencoded, $method = -1) +{ + global $conf; + + if ($method == -1) { + $method = 0; + if (! empty($conf->global->MAIN_FORCE_READFILE_WITH_FREAD)) $method = 1; + if (! empty($conf->global->MAIN_FORCE_READFILE_WITH_STREAM_COPY)) $method = 2; + } + + // Be sure we don't have output buffering enabled to have readfile working correctly + while (ob_get_level()) ob_end_flush(); + + // Solution 0 + if ($method == 0) { + readfile($fullpath_original_file_osencoded); + } + // Solution 1 + elseif ($method == 1) { + $handle = fopen($fullpath_original_file_osencoded, "rb"); + while (!feof($handle)) { + print fread($handle, 8192); + } + fclose($handle); + } + // Solution 2 + elseif ($method == 2) { + $handle1 = fopen($fullpath_original_file_osencoded, "rb"); + $handle2 = fopen("php://output", "wb"); + stream_copy_to_stream($handle1, $handle2); + fclose($handle1); + fclose($handle2); + } +} diff --git a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php index 6827f573402..2af8fcc9bfb 100644 --- a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php @@ -353,7 +353,7 @@ class pdf_crabe extends ModelePDFFactures // Set certificate $cert = empty($user->conf->CERTIFICATE_CRT) ? '' : $user->conf->CERTIFICATE_CRT; - // If use has no certificate, we try to take the company one + // If user has no certificate, we try to take the company one if (!$cert) { $cert = empty($conf->global->CERTIFICATE_CRT) ? '' : $conf->global->CERTIFICATE_CRT; } diff --git a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php index 8e41ae46574..bab3bbd9e71 100644 --- a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php @@ -369,7 +369,7 @@ class pdf_sponge extends ModelePDFFactures // Set certificate $cert = empty($user->conf->CERTIFICATE_CRT) ? '' : $user->conf->CERTIFICATE_CRT; - // If use has no certificate, we try to take the company one + // If user has no certificate, we try to take the company one if (!$cert) { $cert = empty($conf->global->CERTIFICATE_CRT) ? '' : $conf->global->CERTIFICATE_CRT; } diff --git a/htdocs/core/modules/modBom.class.php b/htdocs/core/modules/modBom.class.php index 7258541a7da..78b4efa1567 100644 --- a/htdocs/core/modules/modBom.class.php +++ b/htdocs/core/modules/modBom.class.php @@ -54,7 +54,7 @@ class modBom extends DolibarrModules // It is used to group modules by family in module setup page $this->family = "products"; // Module position in the family on 2 digits ('01', '10', '20', ...) - $this->module_position = '60'; + $this->module_position = '65'; // Gives the possibility for the module, to provide his own family info and position of this family (Overwrite $this->family and $this->module_position. Avoid this) //$this->familyinfo = array('myownfamily' => array('position' => '01', 'label' => $langs->trans("MyOwnFamily"))); diff --git a/htdocs/core/modules/modCashDesk.class.php b/htdocs/core/modules/modCashDesk.class.php index b27a1ae30e4..2863760b138 100644 --- a/htdocs/core/modules/modCashDesk.class.php +++ b/htdocs/core/modules/modCashDesk.class.php @@ -46,12 +46,11 @@ class modCashDesk extends DolibarrModules $this->rights_class = 'cashdesk'; $this->family = "portal"; - $this->module_position = '55'; + $this->module_position = '59'; // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module) $this->name = preg_replace('/^mod/i', '', get_class($this)); $this->description = "CashDesk module"; - $this->revision = '1.27'; $this->version = 'dolibarr'; $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); diff --git a/htdocs/core/modules/modDeplacement.class.php b/htdocs/core/modules/modDeplacement.class.php index d89aac28e98..349fde7eba7 100644 --- a/htdocs/core/modules/modDeplacement.class.php +++ b/htdocs/core/modules/modDeplacement.class.php @@ -45,7 +45,7 @@ class modDeplacement extends DolibarrModules $this->numero = 75; $this->family = "hr"; - $this->module_position = '41'; + $this->module_position = '43'; // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module) $this->name = preg_replace('/^mod/i', '', get_class($this)); $this->description = "Gestion des notes de frais et deplacements"; // Si traduction Module75Desc non trouvee diff --git a/htdocs/core/modules/modEmailCollector.class.php b/htdocs/core/modules/modEmailCollector.class.php index 86bf582edf2..1e00f28d40f 100644 --- a/htdocs/core/modules/modEmailCollector.class.php +++ b/htdocs/core/modules/modEmailCollector.class.php @@ -52,7 +52,7 @@ class modEmailCollector extends DolibarrModules // It is used to group modules by family in module setup page $this->family = "interface"; // Module position in the family on 2 digits ('01', '10', '20', ...) - $this->module_position = '12'; + $this->module_position = '23'; // Gives the possibility to the module, to provide his own family info and position of this family (Overwrite $this->family and $this->module_position. Avoid this) //$this->familyinfo = array('myownfamily' => array('position' => '01', 'label' => $langs->trans("MyOwnFamily"))); diff --git a/htdocs/core/modules/modMailing.class.php b/htdocs/core/modules/modMailing.class.php index ae475836bb5..3f410a1e3f2 100644 --- a/htdocs/core/modules/modMailing.class.php +++ b/htdocs/core/modules/modMailing.class.php @@ -48,7 +48,7 @@ class modMailing extends DolibarrModules // It is used to group modules by family in module setup page $this->family = "interface"; // Module position in the family on 2 digits ('01', '10', '20', ...) - $this->module_position = '11'; + $this->module_position = '22'; // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module) $this->name = preg_replace('/^mod/i', '', get_class($this)); diff --git a/htdocs/core/modules/modMrp.class.php b/htdocs/core/modules/modMrp.class.php index 73df7523843..b7901e38fd7 100644 --- a/htdocs/core/modules/modMrp.class.php +++ b/htdocs/core/modules/modMrp.class.php @@ -52,7 +52,7 @@ class modMrp extends DolibarrModules // It is used to group modules by family in module setup page $this->family = "products"; // Module position in the family on 2 digits ('01', '10', '20', ...) - $this->module_position = '62'; + $this->module_position = '66'; // Gives the possibility for the module, to provide his own family info and position of this family (Overwrite $this->family and $this->module_position. Avoid this) //$this->familyinfo = array('myownfamily' => array('position' => '01', 'label' => $langs->trans("MyOwnFamily"))); // Module label (no space allowed), used if translation string 'ModuleMrpName' not found (Mrp is name of module). diff --git a/htdocs/core/modules/modProduct.class.php b/htdocs/core/modules/modProduct.class.php index 5237f5f9b4b..e1a1a51160a 100644 --- a/htdocs/core/modules/modProduct.class.php +++ b/htdocs/core/modules/modProduct.class.php @@ -51,7 +51,7 @@ class modProduct extends DolibarrModules $this->numero = 50; $this->family = "products"; - $this->module_position = '25'; + $this->module_position = '26'; // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module) $this->name = preg_replace('/^mod/i', '', get_class($this)); $this->description = "Product management"; diff --git a/htdocs/core/modules/modRecruitment.class.php b/htdocs/core/modules/modRecruitment.class.php index 6d994ac1071..8e282a9513d 100644 --- a/htdocs/core/modules/modRecruitment.class.php +++ b/htdocs/core/modules/modRecruitment.class.php @@ -52,7 +52,7 @@ class modRecruitment extends DolibarrModules // It is used to group modules by family in module setup page $this->family = "hr"; // Module position in the family on 2 digits ('01', '10', '20', ...) - $this->module_position = '51'; + $this->module_position = '44'; // Gives the possibility for the module, to provide his own family info and position of this family (Overwrite $this->family and $this->module_position. Avoid this) //$this->familyinfo = array('myownfamily' => array('position' => '01', 'label' => $langs->trans("MyOwnFamily"))); // Module label (no space allowed), used if translation string 'ModuleRecruitmentName' not found (Recruitment is name of module). diff --git a/htdocs/core/modules/modWebServicesClient.class.php b/htdocs/core/modules/modWebServicesClient.class.php index d61052c0e1b..720a756a828 100644 --- a/htdocs/core/modules/modWebServicesClient.class.php +++ b/htdocs/core/modules/modWebServicesClient.class.php @@ -41,7 +41,7 @@ class modWebServicesClient extends DolibarrModules $this->numero = 2660; $this->family = "interface"; - $this->module_position = '26'; + $this->module_position = '25'; // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module) $this->name = preg_replace('/^mod/i', '', get_class($this)); $this->description = "Enable the web service client to call external supplier web services"; diff --git a/htdocs/core/tpl/objectline_edit.tpl.php b/htdocs/core/tpl/objectline_edit.tpl.php index 8aa02b2f522..bf5b87d0037 100644 --- a/htdocs/core/tpl/objectline_edit.tpl.php +++ b/htdocs/core/tpl/objectline_edit.tpl.php @@ -309,21 +309,21 @@ if (!empty($extrafields)) rights->margins->creer) { -?> + ?> /* Some js test when we click on button "Add" */ jQuery(document).ready(function() { global->DISPLAY_MARGIN_RATES)) { ?> - $("input[name='np_marginRate']:first").blur(function(e) { - return checkFreeLine(e, "np_marginRate"); - }); - global->DISPLAY_MARK_RATES)) { ?> - $("input[name='np_markRate']:first").blur(function(e) { - return checkFreeLine(e, "np_markRate"); - }); - }); @@ -373,7 +373,7 @@ if (! empty($usemargins) && $user->rights->margins->creer) return true; } - diff --git a/htdocs/document.php b/htdocs/document.php index ab3ad20a8f8..bc0cc1eb933 100644 --- a/htdocs/document.php +++ b/htdocs/document.php @@ -261,10 +261,11 @@ if (!$attachment && !empty($conf->global->MAIN_USE_EXIF_ROTATION) && image_forma $readfile = !$imgres; } +if (is_object($db)) $db->close(); + +// Send file now if ($readfile) { header('Content-Length: '.dol_filesize($fullpath_original_file)); - readfile($fullpath_original_file_osencoded); + readfileLowMemory($fullpath_original_file_osencoded); } - -if (is_object($db)) $db->close(); diff --git a/htdocs/expedition/stats/index.php b/htdocs/expedition/stats/index.php index fcccb545a33..9521cd46d10 100644 --- a/htdocs/expedition/stats/index.php +++ b/htdocs/expedition/stats/index.php @@ -269,8 +269,12 @@ foreach ($data as $val) print ''; print ''; print ''; /*print ''; diff --git a/htdocs/fichinter/card.php b/htdocs/fichinter/card.php index b6d7ab39336..650fdc2be15 100644 --- a/htdocs/fichinter/card.php +++ b/htdocs/fichinter/card.php @@ -587,7 +587,7 @@ if (empty($reshook)) } $object->fetch_thirdparty(); - $desc = GETPOST('np_desc', 'alpha'); + $desc = GETPOST('np_desc', 'restricthtml'); $date_inter = dol_mktime(GETPOST('dihour', 'int'), GETPOST('dimin', 'int'), 0, GETPOST('dimonth', 'int'), GETPOST('diday', 'int'), GETPOST('diyear', 'int')); $duration = convertTime2Seconds(GETPOST('durationhour', 'int'), GETPOST('durationmin', 'int')); @@ -1534,7 +1534,7 @@ if ($action == 'create') // editeur wysiwyg if (empty($conf->global->FICHINTER_EMPTY_LINE_DESC)) { require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; - $doleditor = new DolEditor('np_desc', GETPOST('np_desc', 'alpha'), '', 100, 'dolibarr_details', '', false, true, $conf->global->FCKEDITOR_ENABLE_DETAILS, ROWS_2, '90%'); + $doleditor = new DolEditor('np_desc', GETPOST('np_desc', 'restricthtml'), '', 100, 'dolibarr_details', '', false, true, $conf->global->FCKEDITOR_ENABLE_DETAILS, ROWS_2, '90%'); $doleditor->Create(); } print ''; diff --git a/htdocs/install/mysql/migration/12.0.0-13.0.0.sql b/htdocs/install/mysql/migration/12.0.0-13.0.0.sql index ffce6b110dc..88c17cc0619 100644 --- a/htdocs/install/mysql/migration/12.0.0-13.0.0.sql +++ b/htdocs/install/mysql/migration/12.0.0-13.0.0.sql @@ -30,6 +30,9 @@ -- Missing in v12 or lower +ALTER TABLE llx_payment_salary MODIFY COLUMN ref varchar(30) NULL; +ALTER TABLE llx_payment_various MODIFY COLUMN ref varchar(30) NULL; + ALTER TABLE llx_prelevement_bons ADD COLUMN type varchar(16) DEFAULT 'debit-order'; ALTER TABLE llx_prelevement_facture_demande ADD INDEX idx_prelevement_facture_demande_fk_facture (fk_facture); diff --git a/htdocs/install/mysql/migration/repair.sql b/htdocs/install/mysql/migration/repair.sql index aa5ab65d3ff..c9c916ed3ad 100644 --- a/htdocs/install/mysql/migration/repair.sql +++ b/htdocs/install/mysql/migration/repair.sql @@ -383,6 +383,7 @@ delete from llx_commande_fournisseur_dispatch where fk_commandefourndet = 0 or f delete from llx_menu where menu_handler = 'smartphone'; update llx_expedition set date_valid = date_creation where fk_statut = 1 and date_valid IS NULL; +update llx_expedition set date_valid = NOW() where fk_statut = 1 and date_valid IS NULL; -- Detect bad consistency between duraction_effective of a task and sum of time of tasks -- select pt.rowid, pt.duration_effective, SUM(ptt.task_duration) as y from llx_projet_task as pt, llx_projet_task_time as ptt where ptt.fk_task = pt.rowid group by pt.rowid, pt.duration_effective having pt.duration_effective <> y; diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 1e64a5bfd04..8d64fe1a8d6 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -439,7 +439,7 @@ ExtrafieldParamHelpPassword=Leaving this field blank means this value will be st ExtrafieldParamHelpselect=List of values must be lines with format key,value (where key can't be '0')

for example:
1,value1
2,value2
code3,value3
...

In order to have the list depending on another complementary attribute list:
1,value1|options_parent_list_code:parent_key
2,value2|options_parent_list_code:parent_key

In order to have the list depending on another list:
1,value1|parent_list_code:parent_key
2,value2|parent_list_code:parent_key ExtrafieldParamHelpcheckbox=List of values must be lines with format key,value (where key can't be '0')

for example:
1,value1
2,value2
3,value3
... ExtrafieldParamHelpradio=List of values must be lines with format key,value (where key can't be '0')

for example:
1,value1
2,value2
3,value3
... -ExtrafieldParamHelpsellist=List of values comes from a table
Syntax: table_name:label_field:id_field::filter
Example: c_typent:libelle:id::filter

- idfilter is necessarly a primary int key
- filter can be a simple test (eg active=1) to display only active value
You can also use $ID$ in filter witch is the current id of current object
To do a SELECT in filter use $SEL$
if you want to filter on extrafields use syntax extra.fieldcode=... (where field code is the code of extrafield)

In order to have the list depending on another complementary attribute list:
c_typent:libelle:id:options_parent_list_code|parent_column:filter

In order to have the list depending on another list:
c_typent:libelle:id:parent_list_code|parent_column:filter +ExtrafieldParamHelpsellist=List of values comes from a table
Syntax: table_name:label_field:id_field::filter
Example: c_typent:libelle:id::filter

- id_field is necessarly a primary int key
- filter can be a simple test (eg active=1) to display only active value
You can also use $ID$ in filter which is the current id of current object
To use a SELECT into the filter use the keyword $SEL$ to bypass anti-injection protection.
if you want to filter on extrafields use syntax extra.fieldcode=... (where field code is the code of extrafield)

In order to have the list depending on another complementary attribute list:
c_typent:libelle:id:options_parent_list_code|parent_column:filter

In order to have the list depending on another list:
c_typent:libelle:id:parent_list_code|parent_column:filter ExtrafieldParamHelpchkbxlst=List of values comes from a table
Syntax: table_name:label_field:id_field::filter
Example: c_typent:libelle:id::filter

filter can be a simple test (eg active=1) to display only active value
You can also use $ID$ in filter witch is the current id of current object
To do a SELECT in filter use $SEL$
if you want to filter on extrafields use syntax extra.fieldcode=... (where field code is the code of extrafield)

In order to have the list depending on another complementary attribute list:
c_typent:libelle:id:options_parent_list_code|parent_column:filter

In order to have the list depending on another list:
c_typent:libelle:id:parent_list_code|parent_column:filter ExtrafieldParamHelplink=Parameters must be ObjectName:Classpath
Syntax: ObjectName:Classpath ExtrafieldParamHelpSeparator=Keep empty for a simple separator
Set this to 1 for a collapsing separator (open by default for new session, then status is kept for each user session)
Set this to 2 for a collapsing separator (collapsed by default for new session, then status is kept fore each user session) @@ -1221,7 +1221,8 @@ RestoreDesc=To restore a Dolibarr backup, two steps are required. RestoreDesc2=Restore the backup file (zip file for example) of the "documents" directory to a new Dolibarr installation or into this current documents directory (%s). RestoreDesc3=Restore the database structure and data from a backup dump file into the database of the new Dolibarr installation or into the database of this current installation (%s). Warning, once the restore is complete, you must use a login/password, that existed from the backup time/installation to connect again.
To restore a backup database into this current installation, you can follow this assistant. RestoreMySQL=MySQL import -ForcedToByAModule= This rule is forced to %s by an activated module +ForcedToByAModule=This rule is forced to %s by an activated module +ValueIsForcedBySystem=This value is forced by the system. You can't change it. PreviousDumpFiles=Existing backup files PreviousArchiveFiles=Existing archive files WeekStartOnDay=First day of the week diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index 1344287044e..ef7b5e33a7d 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -378,10 +378,18 @@ if ((!defined('NOCSRFCHECK') && empty($dolibarr_nocsrfcheck) && !empty($conf->gl in_array(GETPOST('action', 'aZ09'), array('add', 'addtimespent', 'update', 'install', 'delete', 'deleteprof', 'deletepayment'))) { if (!GETPOSTISSET('token')) { - dol_syslog("--- Access to ".$_SERVER["PHP_SELF"]." refused by CSRFCHECK_WITH_TOKEN protection. Token not provided."); - print "Access to this page this way (POST method or page with CSRFCHECK_WITH_TOKEN on or having a sensible value for action parameter) is refused by CSRF protection in main.inc.php. Token not provided.\n"; - print "If you access your server behind a proxy using url rewriting, you might check that all HTTP header is propagated (or add the line \$dolibarr_nocsrfcheck=1 into your conf.php file or MAIN_SECURITY_CSRF_WITH_TOKEN to 0 into setup).\n"; - die; + if (GETPOST('uploadform')) { + dol_syslog("--- Access to ".$_SERVER["PHP_SELF"]." refused. File size too large."); + $langs->loadLangs(array("errors", "install")); + print $langs->trans("ErrorFileSizeTooLarge").' '; + print $langs->trans("ErrorGoBackAndCorrectParameters"); + die; + } else { + dol_syslog("--- Access to ".$_SERVER["PHP_SELF"]." refused by CSRFCHECK_WITH_TOKEN protection. Token not provided."); + print "Access to this page this way (POST method or page with CSRFCHECK_WITH_TOKEN on or having a sensible value for action parameter) is refused by CSRF protection in main.inc.php. Token not provided.\n"; + print "If you access your server behind a proxy using url rewriting, you might check that all HTTP header is propagated (or add the line \$dolibarr_nocsrfcheck=1 into your conf.php file or MAIN_SECURITY_CSRF_WITH_TOKEN to 0 into setup).\n"; + die; + } } } diff --git a/htdocs/product/card.php b/htdocs/product/card.php index 62b7d78b031..4970679eb87 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -18,6 +18,7 @@ * Copyright (C) 2017 Josep Lluís Amador * Copyright (C) 2019 Frédéric France * Copyright (C) 2019-2020 Thibault FOUCART + * Copyright (C) 2020 Pierre Ardoin * * 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 @@ -2387,7 +2388,11 @@ if ($action != 'create' && $action != 'edit' && $action != 'delete') // Documents $objectref = dol_sanitizeFileName($object->ref); $relativepath = $comref.'/'.$objectref.'.pdf'; - $filedir = $conf->product->dir_output.'/'.$objectref; + if (!empty($conf->product->multidir_output[$object->entity])) { + $filedir = $conf->product->multidir_output[$object->entity].'/'.$objectref; //Check repertories of current entities + } else { + $filedir = $conf->product->dir_output.'/'.$objectref; + } $urlsource = $_SERVER["PHP_SELF"]."?id=".$object->id; $genallowed = $usercanread; $delallowed = $usercancreate; diff --git a/htdocs/product/inventory/lib/inventory.lib.php b/htdocs/product/inventory/lib/inventory.lib.php index da2b626ab5c..47fa1e53510 100644 --- a/htdocs/product/inventory/lib/inventory.lib.php +++ b/htdocs/product/inventory/lib/inventory.lib.php @@ -73,48 +73,3 @@ function inventoryPrepareHead(&$inventory, $title = 'Inventory', $get = '') array(dol_buildpath('/product/inventory/inventory.php?id='.$inventory->id.$get, 1), $langs->trans('Inventory'), 'inventory') ); } - - - -/** - * Define head array for tabs of inventory tools setup pages - * - * @param Inventory $inventory Object inventory - * - * @return string html of products - */ -function inventorySelectProducts(&$inventory) -{ - global $conf, $db, $langs; - - $except_product_id = array(); - - foreach ($inventory->Inventorydet as $Inventorydet) - { - $except_product_id[] = $Inventorydet->fk_product; - } - - ob_start(); - $form = new Form($db); - $form->select_produits(-1, 'fk_product'); - - $TChildWarehouses = array($inventory->fk_warehouse); - $e = new Entrepot($db); - $e->fetch($inventory->fk_warehouse); - if (method_exists($e, 'get_children_warehouses')) $e->get_children_warehouses($e->id, $TChildWarehouses); - - $Tab = array(); - $sql = 'SELECT rowid, label - FROM '.MAIN_DB_PREFIX.'entrepot WHERE rowid IN('.implode(', ', $TChildWarehouses).')'; - if (method_exists($e, 'get_children_warehouses')) $sql .= ' ORDER BY fk_parent'; - $resql = $db->query($sql); - while ($res = $db->fetch_object($resql)) { - $Tab[$res->rowid] = $res->label; - } - print '   '; - print $langs->trans('Warehouse').' : '.$form::selectarray('fk_warehouse', $Tab); - - $select_html = ob_get_clean(); - - return $select_html; -} diff --git a/htdocs/takepos/index.php b/htdocs/takepos/index.php index bad5f112d21..dd96a8ae333 100644 --- a/htdocs/takepos/index.php +++ b/htdocs/takepos/index.php @@ -44,6 +44,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; $place = (GETPOST('place', 'aZ09') ? GETPOST('place', 'aZ09') : 0); // $place is id of table for Bar or Restaurant or multiple sales $action = GETPOST('action', 'aZ09'); $setterminal = GETPOST('setterminal', 'int'); +$setcurrency = GETPOST('setcurrency', 'aZ09'); if ($_SESSION["takeposterminal"] == "") { @@ -57,6 +58,11 @@ if ($setterminal > 0) setcookie("takeposterminal", $setterminal, (time() + (86400 * 354)), '/', null, false, true); // Permanent takeposterminal var in a cookie } +if ($setcurrency!="") +{ + $_SESSION["takeposcustomercurrency"] = $setcurrency; +} + $_SESSION["urlfrom"] = '/takepos/index.php'; $langs->loadLangs(array("bills", "orders", "commercial", "cashdesk", "receiptprinter", "banks")); @@ -725,14 +731,10 @@ function CashReport(rowid) $.colorbox({href:"../compta/cashcontrol/report.php?id="+rowid+"&contextpage=takepos", width:"60%", height:"90%", transition:"none", iframe:"true", title:"trans("CashReport"); ?>"}); } -// Popup to select the terminal to use -function TerminalsDialog() +// TakePOS Popup +function ModalBox(ModalID) { - var modal = document.getElementById("ModalTerminal"); - var span = document.getElementsByClassName("close")[0]; - span.onclick = function() { - modal.style.display = "none"; - } + var modal = document.getElementById(ModalID); modal.style.display = "block"; } @@ -767,7 +769,7 @@ $( document ).ready(function() { //IF NO TERMINAL SELECTED if ($_SESSION["takeposterminal"] == "") { - print "TerminalsDialog();"; + print "ModalBox('ModalTerminal');"; } if ($conf->global->TAKEPOS_CONTROL_CASH_OPENING) { @@ -798,7 +800,7 @@ if (empty($conf->global->TAKEPOS_HIDE_HEAD_BAR)) {
@@ -841,7 +849,7 @@ if (empty($conf->global->TAKEPOS_HIDE_HEAD_BAR)) { + +
diff --git a/htdocs/takepos/invoice.php b/htdocs/takepos/invoice.php index f560fee0b44..d3ec86a7268 100644 --- a/htdocs/takepos/invoice.php +++ b/htdocs/takepos/invoice.php @@ -1090,7 +1090,16 @@ if ($placeid > 0) } else $htmlforlines .= $line->qty; $htmlforlines .= ''; - $htmlforlines .= '
'; + $htmlforlines .= ''; } $htmlforlines .= ''."\n"; $htmlforlines .= $htmlsupplements[$line->id]; diff --git a/htdocs/takepos/receipt.php b/htdocs/takepos/receipt.php index c7ca395a316..f32e4d82061 100644 --- a/htdocs/takepos/receipt.php +++ b/htdocs/takepos/receipt.php @@ -182,6 +182,16 @@ if ($conf->global->TAKEPOS_SHOW_CUSTOMER) multicurrency->enabled) && $_SESSION["takeposcustomercurrency"]!="" && $conf->currency!=$_SESSION["takeposcustomercurrency"]) { + //Only show customer currency if multicurrency module is enabled, if currency selected and if this currency selected is not the same as main currency + include_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php'; + $multicurrency = new MultiCurrency($db); + $multicurrency->fetch(0, $_SESSION["takeposcustomercurrency"]); + echo ''; +} + if ($conf->global->TAKEPOS_PRINT_PAYMENT_METHOD) { $sql = "SELECT p.pos_change as pos_change, p.datep as date, p.fk_paiement, p.num_paiement as num, pf.amount as amount, pf.multicurrency_amount,"; $sql .= " cp.code"; diff --git a/htdocs/takepos/send.php b/htdocs/takepos/send.php index ba1e3f5e5ad..f618b260db1 100644 --- a/htdocs/takepos/send.php +++ b/htdocs/takepos/send.php @@ -60,10 +60,12 @@ if ($action=="send") $model_id = $conf->global->TAKEPOS_EMAIL_TEMPLATE_INVOICE; $arraydefaultmessage = $formmail->getEMailTemplate($db, 'facture_send', $user, $outputlangs, $model_id); $subject = $arraydefaultmessage->topic; + ob_start(); // turn on output receipt include 'receipt.php'; $receipt = ob_get_contents(); // get the contents of the output buffer ob_end_clean(); + $msg="".$arraydefaultmessage->content."
".$receipt.""; $sendto=$email; $from=$mysoc->email;
'; @@ -160,10 +160,10 @@ if (in_array($type, array('mysql', 'mysqli'))) { print ''; print '
'; print '
'; - print ''; + print ''; print '
'; } elseif (in_array($type, array('pgsql'))) { - print '
'; + print '
'; print ''; print '
'; } else { diff --git a/htdocs/commande/class/api_orders.class.php b/htdocs/commande/class/api_orders.class.php index 2202ad5b67e..7ccbd8950b5 100644 --- a/htdocs/commande/class/api_orders.class.php +++ b/htdocs/commande/class/api_orders.class.php @@ -544,9 +544,10 @@ class Orders extends DolibarrApi * Unlink a contact type of given order * * @param int $id Id of order to update - * @param int $rowid Row key of the contact in the array contact_ids. + * @param int $contactid Id of contact + * @param string $type Type of the contact (BILLING, SHIPPING, CUSTOMER). * - * @url DELETE {id}/contact/{rowid} + * @url DELETE {id}/contact/{contactid}/{type} * * @return int * @@ -554,7 +555,7 @@ class Orders extends DolibarrApi * @throws RestException 404 * @throws RestException 500 */ - public function deleteContact($id, $rowid) + public function deleteContact($id, $contactid, $type) { if (!DolibarrApiAccess::$user->rights->commande->creer) { throw new RestException(401); @@ -569,10 +570,16 @@ class Orders extends DolibarrApi throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); } - $result = $this->commande->delete_linked_contact($rowid); + $contacts = $this->commande->liste_contact(); - if (!$result) { - throw new RestException(500, 'Error when deleted the contact'); + foreach ($contacts as $contact) { + if ($contact['id'] == $contactid && $contact['code'] == $type) { + $result = $this->commande->delete_contact($contact['rowid']); + + if (!$result) { + throw new RestException(500, 'Error when deleted the contact'); + } + } } return array( diff --git a/htdocs/compta/facture/card-rec.php b/htdocs/compta/facture/card-rec.php index a11f0f7c800..113068c447c 100644 --- a/htdocs/compta/facture/card-rec.php +++ b/htdocs/compta/facture/card-rec.php @@ -1365,6 +1365,8 @@ if ($action == 'create') // Only on template invoices $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = $langs->trans("DateNextInvoiceBeforeGen").' ('.$langs->trans("Example").': '.dol_print_date(($object->date_when ? $object->date_when : dol_now()), 'dayhour').')'; $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = $langs->trans("DateNextInvoiceAfterGen").' ('.$langs->trans("Example").': '.dol_print_date(dol_time_plus_duree(($object->date_when ? $object->date_when : dol_now()), $object->frequency, $object->unit_frequency), 'dayhour').')'; + $substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $object->nb_gen_done; + $substitutionarray['__INVOICE_COUNTER_MAX__'] = $object->nb_gen_max; $htmltext = ''.$langs->trans("FollowingConstantsWillBeSubstituted").':
'; foreach ($substitutionarray as $key => $val) diff --git a/htdocs/compta/facture/class/api_invoices.class.php b/htdocs/compta/facture/class/api_invoices.class.php index cc1b7fe57ed..1c423a2e459 100644 --- a/htdocs/compta/facture/class/api_invoices.class.php +++ b/htdocs/compta/facture/class/api_invoices.class.php @@ -477,8 +477,9 @@ class Invoices extends DolibarrApi * * @param int $id Id of invoice to update * @param int $rowid Row key of the contact in the array contact_ids. + * @param string $type Type of the contact (BILLING, SHIPPING, CUSTOMER). * - * @url DELETE {id}/contact/{rowid} + * @url DELETE {id}/contact/{rowid}/{type} * * @return array * @@ -486,7 +487,7 @@ class Invoices extends DolibarrApi * @throws RestException 404 * @throws RestException 500 */ - public function deleteContact($id, $rowid) + public function deleteContact($id, $rowid, $type) { if (!DolibarrApiAccess::$user->rights->facture->creer) { throw new RestException(401); @@ -502,10 +503,17 @@ class Invoices extends DolibarrApi throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); } - $result = $this->invoice->delete_contact($rowid); - if ($result < 0) { - throw new RestException(500, 'Error when deleted the contact'); - } + $contacts = $this->invoice->liste_contact(); + + foreach ($contacts as $contact) { + if ($contact['id'] == $rowid && $contact['code'] == $type) { + $result = $this->invoice->delete_contact($contact['rowid']); + + if (!$result) { + throw new RestException(500, 'Error when deleted the contact'); + } + } + } return $this->_cleanObjectDatas($this->invoice); } @@ -1397,28 +1405,29 @@ class Invoices extends DolibarrApi /** * Add a payment to pay partially or completely one or several invoices. * Warning: Take care that all invoices are owned by the same customer. - * Example of value for parameter arrayofamounts: {"1": "99.99", "2": "10"} + * Example of value for parameter arrayofamounts: {"1": {"amount": "99.99", "multicurrency_amount": ""}, "2": {"amount": "", "multicurrency_amount": "10"}} * * @param array $arrayofamounts {@from body} Array with id of invoices with amount to pay for each invoice * @param string $datepaye {@from body} Payment date {@type timestamp} - * @param int $paymentid {@from body} Payment mode Id {@min 1} - * @param string $closepaidinvoices {@from body} Close paid invoices {@choice yes,no} - * @param int $accountid {@from body} Account Id {@min 1} - * @param string $num_payment {@from body} Payment number (optional) - * @param string $comment {@from body} Note private (optional) - * @param string $chqemetteur {@from body} Payment issuer (mandatory if paiementcode = 'CHQ') - * @param string $chqbank {@from body} Issuer bank name (optional) + * @param int $paymentid {@from body} Payment mode Id {@min 1} + * @param string $closepaidinvoices {@from body} Close paid invoices {@choice yes,no} + * @param int $accountid {@from body} Account Id {@min 1} + * @param string $num_payment {@from body} Payment number (optional) + * @param string $comment {@from body} Note private (optional) + * @param string $chqemetteur {@from body} Payment issuer (mandatory if paiementcode = 'CHQ') + * @param string $chqbank {@from body} Issuer bank name (optional) + * @param string $ref_ext {@from body} External reference (optional) + * @param bool $accepthigherpayment {@from body} Accept higher payments that it remains to be paid (optional) * * @url POST /paymentsdistributed * * @return int Payment ID - * * @throws RestException 400 * @throws RestException 401 * @throws RestException 403 * @throws RestException 404 */ - public function addPaymentDistributed($arrayofamounts, $datepaye, $paymentid, $closepaidinvoices, $accountid, $num_payment = '', $comment = '', $chqemetteur = '', $chqbank = '') + public function addPaymentDistributed($arrayofamounts, $datepaye, $paymentid, $closepaidinvoices, $accountid, $num_payment = '', $comment = '', $chqemetteur = '', $chqbank = '', $ref_ext = '', $accepthigherpayment = false) { global $conf; @@ -1451,7 +1460,7 @@ class Invoices extends DolibarrApi $multicurrency_amounts = array(); // Loop on each invoice to pay - foreach ($arrayofamounts as $id => $amount) + foreach ($arrayofamounts as $id => $amountarray) { $result = $this->invoice->fetch($id); if (!$result) { @@ -1459,33 +1468,52 @@ class Invoices extends DolibarrApi throw new RestException(404, 'Invoice ID '.$id.' not found'); } - // Calculate amount to pay - $totalpaye = $this->invoice->getSommePaiement(); - $totalcreditnotes = $this->invoice->getSumCreditNotesUsed(); - $totaldeposits = $this->invoice->getSumDepositsUsed(); - $resteapayer = price2num($this->invoice->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits, 'MT'); - if ($amount != 'remain') - { - if ($amount > $resteapayer) - { - $this->db->rollback(); - throw new RestException(400, 'Payment amount on invoice ID '.$id.' ('.$amount.') is higher than remain to pay ('.$resteapayer.')'); - } - $resteapayer = $amount; + if (($amountarray["amount"] == "remain" || $amountarray["amount"] > 0) && ($amountarray["multicurrency_amount"] == "remain" || $amountarray["multicurrency_amount"] > 0)) { + $this->db->rollback(); + throw new RestException(400, 'Payment in both currency '.$id.' ( amount: '.$amountarray["amount"].', multicurrency_amount: '.$amountarray["multicurrency_amount"].')'); } - // Clean parameters amount if payment is for a credit note - if ($this->invoice->type == Facture::TYPE_CREDIT_NOTE) { - $resteapayer = price2num($resteapayer, 'MT'); - $amounts[$id] = -$resteapayer; + + $is_multicurrency = 0; + $total_ttc = $this->invoice->total_ttc; + + if ($amountarray["multicurrency_amount"] > 0 || $amountarray["multicurrency_amount"] == "remain") { + $is_multicurrency = 1; + $total_ttc = $this->invoice->multicurrency_total_ttc; + } + + // Calculate amount to pay + $totalpaye = $this->invoice->getSommePaiement($is_multicurrency); + $totalcreditnotes = $this->invoice->getSumCreditNotesUsed($is_multicurrency); + $totaldeposits = $this->invoice->getSumDepositsUsed($is_multicurrency); + $remainstopay = $amount = price2num($total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits, 'MT'); + + if (!$is_multicurrency && $amountarray["amount"] != 'remain') + { + $amount = price2num($amountarray["amount"], 'MT'); + } + + if ($is_multicurrency && $amountarray["multicurrency_amount"] != 'remain') + { + $amount = price2num($amountarray["multicurrency_amount"], 'MT'); + } + + if ($amount > $remainstopay && $accepthigherpayment == false) { + $this->db->rollback(); + throw new RestException(400, 'Payment amount on invoice ID '.$id.' ('.$amount.') is higher than remain to pay ('.$remainstopay.')'); + } + + if ($this->invoice->type == Facture::TYPE_CREDIT_NOTE) { + $amount = -$amount; + } + + if ($is_multicurrency) { + $amounts[$id] = null; // Multicurrency - $newvalue = price2num($this->invoice->multicurrency_total_ttc, 'MT'); - $multicurrency_amounts[$id] = -$newvalue; + $multicurrency_amounts[$id] = $amount; } else { - $resteapayer = price2num($resteapayer, 'MT'); - $amounts[$id] = $resteapayer; + $amounts[$id] = $amount; // Multicurrency - $newvalue = price2num($this->invoice->multicurrency_total_ttc, 'MT'); - $multicurrency_amounts[$id] = $newvalue; + $multicurrency_amounts[$id] = null; } } @@ -1498,7 +1526,7 @@ class Invoices extends DolibarrApi $paymentobj->paiementcode = dol_getIdFromCode($this->db, $paymentid, 'c_paiement', 'id', 'code', 1); $paymentobj->num_payment = $num_payment; $paymentobj->note_private = $comment; - + $paymentobj->ref_ext = $ref_ext; $payment_id = $paymentobj->create(DolibarrApiAccess::$user, ($closepaidinvoices == 'yes' ? 1 : 0)); // This include closing invoices if ($payment_id < 0) { diff --git a/htdocs/compta/facture/class/facture.class.php b/htdocs/compta/facture/class/facture.class.php index 4110e5ad26d..8ec349c79f4 100644 --- a/htdocs/compta/facture/class/facture.class.php +++ b/htdocs/compta/facture/class/facture.class.php @@ -458,7 +458,7 @@ class Facture extends CommonInvoice $nextdatewhen = null; $previousdaynextdatewhen = null; - // Create invoice from a template invoice + // Create invoice from a template recurring invoice if ($this->fac_rec > 0) { $this->fk_fac_rec_source = $this->fac_rec; diff --git a/htdocs/conf/conf.php.example b/htdocs/conf/conf.php.example index 6b1b5e0ea0a..02cc5bae399 100644 --- a/htdocs/conf/conf.php.example +++ b/htdocs/conf/conf.php.example @@ -287,6 +287,18 @@ $dolibarr_cron_allow_cli='0'; // Examples: '-1' (sending by cli is forbidden) // $dolibarr_mailing_limit_sendbycli='0'; +// MAIN_ANTIVIRUS_COMMAND (as a constant) +// Force a value for the antivirus command line tool so setup for admin user interface has no effect. +// Default value: '' +// Example: '/usr/bin/clamdscan'; +// define('MAIN_ANTIVIRUS_COMMAND', '/usr/bin/clamdscan'); + +// MAIN_ANTIVIRUS_PARAM (as a constant) +// Force a value for the antivirus parameters on command line so setup for admin user interface has no effect. +// Default value: '' +// Example: '--fdpass'; +// define('MAIN_ANTIVIRUS_PARAM', '--fdpass'); + //################## // Other diff --git a/htdocs/core/boxes/modules_boxes.php b/htdocs/core/boxes/modules_boxes.php index 80589e4c4f3..fd179dec7b6 100644 --- a/htdocs/core/boxes/modules_boxes.php +++ b/htdocs/core/boxes/modules_boxes.php @@ -170,29 +170,6 @@ class ModeleBoxes // Can't be abtract as it is instantiated to build "empty" box } } - - /** - * Standard method to get content of a box - * - * @param array $head Array with properties of box title - * @param array $contents Array with properties of box lines - * - * @return string - */ - public function outputBox($head = null, $contents = null) - { - global $langs, $user, $conf; - - // Trick to get result into a var from a function that makes print instead of return - // TODO Replace ob_start with param nooutput=1 into showBox - ob_start(); - $result = $this->showBox($head, $contents); - $output = ob_get_contents(); - ob_end_clean(); - - return $output; - } - /** * Standard method to show a box (usage by boxes not mandatory, a box can still use its own showBox function) * diff --git a/htdocs/core/class/conf.class.php b/htdocs/core/class/conf.class.php index 8cfeea86d95..32d7b145a41 100644 --- a/htdocs/core/class/conf.class.php +++ b/htdocs/core/class/conf.class.php @@ -684,6 +684,9 @@ class Conf // If we are in develop mode, we activate the option MAIN_SECURITY_CSRF_WITH_TOKEN to 1 if not already defined. if (!isset($this->global->MAIN_SECURITY_CSRF_WITH_TOKEN) && $this->global->MAIN_FEATURES_LEVEL >= 2) $this->global->MAIN_SECURITY_CSRF_WITH_TOKEN = 1; + if (defined('MAIN_ANTIVIRUS_COMMAND')) $this->global->MAIN_ANTIVIRUS_COMMAND = constant('MAIN_ANTIVIRUS_COMMAND'); + if (defined('MAIN_ANTIVIRUS_PARAM')) $this->global->MAIN_ANTIVIRUS_PARAM = constant('MAIN_ANTIVIRUS_PARAM'); + // For backward compatibility if (isset($this->product)) $this->produit = $this->product; if (isset($this->facture)) $this->invoice = $this->facture; diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 84960b21832..3f4938360e0 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -1916,13 +1916,16 @@ class Form * 'warehouseclosed' = count products from closed warehouses, * 'warehouseinternal' = count products from warehouses for internal correct/transfer only * @param array $selected_combinations Selected combinations. Format: array([attrid] => attrval, [...]) - * @return void + * @param string $nooutput No print, return the output into a string + * @return void|string */ - public function select_produits($selected = '', $htmlname = 'productid', $filtertype = '', $limit = 20, $price_level = 0, $status = 1, $finished = 2, $selected_input_value = '', $hidelabel = 0, $ajaxoptions = array(), $socid = 0, $showempty = '1', $forcecombo = 0, $morecss = '', $hidepriceinlabel = 0, $warehouseStatus = '', $selected_combinations = array()) + public function select_produits($selected = '', $htmlname = 'productid', $filtertype = '', $limit = 20, $price_level = 0, $status = 1, $finished = 2, $selected_input_value = '', $hidelabel = 0, $ajaxoptions = array(), $socid = 0, $showempty = '1', $forcecombo = 0, $morecss = '', $hidepriceinlabel = 0, $warehouseStatus = '', $selected_combinations = array(), $nooutput = 0) { // phpcs:enable global $langs, $conf; + $out = ''; + // check parameters $price_level = (!empty($price_level) ? $price_level : 0); if (is_null($ajaxoptions)) $ajaxoptions = array(); @@ -1962,100 +1965,103 @@ class Form if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES) && !empty($socid)) { $urloption .= '&socid='.$socid; } - print ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT.'/product/ajax/products.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 0, $ajaxoptions); + $out .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT.'/product/ajax/products.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 0, $ajaxoptions); if (!empty($conf->variants->enabled)) { - ?> + $out .= ' - trans("RefOrLabel").' : '; + + if (empty($hidelabel)) $out .= $langs->trans("RefOrLabel").' : '; elseif ($hidelabel > 1) { $placeholder = ' placeholder="'.$langs->trans("RefOrLabel").'"'; if ($hidelabel == 2) { - print img_picto($langs->trans("Search"), 'search'); + $out .= img_picto($langs->trans("Search"), 'search'); } } - print 'global->PRODUCT_SEARCH_AUTOFOCUS) ? 'autofocus' : '').' />'; + $out .= 'global->PRODUCT_SEARCH_AUTOFOCUS) ? 'autofocus' : '').' />'; if ($hidelabel == 3) { - print img_picto($langs->trans("Search"), 'search'); + $out .= img_picto($langs->trans("Search"), 'search'); } } else { - print $this->select_produits_list($selected, $htmlname, $filtertype, $limit, $price_level, '', $status, $finished, 0, $socid, $showempty, $forcecombo, $morecss, $hidepriceinlabel, $warehouseStatus); + $out .= $this->select_produits_list($selected, $htmlname, $filtertype, $limit, $price_level, '', $status, $finished, 0, $socid, $showempty, $forcecombo, $morecss, $hidepriceinlabel, $warehouseStatus); } + + if (empty($nooutput)) print $out; + else return $out; } // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps /** - * Return list of products for a customer + * Return list of products for a customer. + * Called by select_produits. * * @param int $selected Preselected product * @param string $htmlname Name of select html @@ -2262,7 +2268,7 @@ class Form $sql .= $this->db->plimit($limit, 0); // Build output string - dol_syslog(get_class($this)."::select_produits_list search product", LOG_DEBUG); + dol_syslog(get_class($this)."::select_produits_list search products", LOG_DEBUG); $result = $this->db->query($sql); if ($result) { @@ -2308,7 +2314,7 @@ class Form $sql .= " WHERE fk_product_price=".$objp->price_rowid; $sql .= " ORDER BY quantity ASC"; - dol_syslog(get_class($this)."::select_produits_list search price by qty", LOG_DEBUG); + dol_syslog(get_class($this)."::select_produits_list search prices by qty", LOG_DEBUG); $result2 = $this->db->query($sql); if ($result2) { diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 7264c83b152..2b98bac30c6 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -114,6 +114,9 @@ class FormFile if (empty($usewithoutform)) // Try to avoid this and set instead the form by the caller. { + // Add a param as GET parameter to detect when POST were cleaned by PHP because a file larger than post_max_size + $url .= (strpos('?', $url) === false ? '?' : '&').'uploadform=1'; + $out .= '
'; $out .= ''; $out .= ''; diff --git a/htdocs/core/class/html.formother.class.php b/htdocs/core/class/html.formother.class.php index dc55d0931f7..a6c458fc67b 100644 --- a/htdocs/core/class/html.formother.class.php +++ b/htdocs/core/class/html.formother.class.php @@ -1162,7 +1162,7 @@ class FormOther //print 'box_order '.$boxactivated[$ii]->box_order.'
'; // Show box $box->loadBox($box_max_lines); - $boxlista .= $box->outputBox(); + $boxlista .= $box->showBox(null, null, 1); } } @@ -1171,7 +1171,7 @@ class FormOther $emptybox->box_id = 'A'; $emptybox->info_box_head = array(); $emptybox->info_box_contents = array(); - $boxlista .= $emptybox->outputBox(array(), array()); + $boxlista .= $emptybox->showBox(array(), array(), 1); } $boxlista .= "\n"; @@ -1189,7 +1189,7 @@ class FormOther //print 'box_order '.$boxactivated[$ii]->box_order.'
'; // Show box $box->loadBox($box_max_lines); - $boxlistb .= $box->outputBox(); + $boxlistb .= $box->showBox(null, null, 1); } } @@ -1198,7 +1198,7 @@ class FormOther $emptybox->box_id = 'B'; $emptybox->info_box_head = array(); $emptybox->info_box_contents = array(); - $boxlistb .= $emptybox->outputBox(array(), array()); + $boxlistb .= $emptybox->showBox(array(), array(), 1); } $boxlistb .= "\n"; diff --git a/htdocs/core/lib/admin.lib.php b/htdocs/core/lib/admin.lib.php index 9227d9a1cfd..038b4be27a6 100644 --- a/htdocs/core/lib/admin.lib.php +++ b/htdocs/core/lib/admin.lib.php @@ -1689,8 +1689,11 @@ function phpinfo_array() { ob_start(); phpinfo(); + $phpinfostring = ob_get_contents(); + ob_end_clean(); + $info_arr = array(); - $info_lines = explode("\n", strip_tags(ob_get_clean(), "

")); // end of ob_start() + $info_lines = explode("\n", strip_tags($phpinfostring, "

")); $cat = "General"; foreach ($info_lines as $line) { diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index aecf488d2b7..7f2fd4ce70d 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -1075,13 +1075,11 @@ function dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disable } } - if ($reshook < 0) // At least one blocking error returned by one hook - { + if ($reshook < 0) { // At least one blocking error returned by one hook $errmsg = join(',', $hookmanager->errors); if (empty($errmsg)) $errmsg = 'ErrorReturnedBySomeHooks'; // Should not occurs. Added if hook is bugged and does not set ->errors when there is error. return $errmsg; - } elseif (empty($reshook)) - { + } elseif (empty($reshook)) { // The file functions must be in OS filesystem encoding. $src_file_osencoded = dol_osencode($src_file); $file_name_osencoded = dol_osencode($file_name); @@ -1535,6 +1533,8 @@ function dol_add_file_process($upload_dir, $allowoverwrite = 0, $donotupdatesess $nbok = 0; for ($i = 0; $i < $nbfile; $i++) { + if (empty($TFile['name'][$i])) continue; // For example, when submitting a form with no file name + // Define $destfull (path to file including filename) and $destfile (only filename) $destfull = $upload_dir."/".$TFile['name'][$i]; $destfile = $TFile['name'][$i]; diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 4fe156b249b..3af3f7c2562 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -8919,3 +8919,47 @@ function addSummaryTableLine($tableColumnCount, $num, $nbofloop = 0, $total = 0, print '

'; - if ($year) print ''.$year.''; - else print $langs->trans("ValidationDateNotDefinedEvenIfShipmentValidated"); + if ($year) { + print ''.$year.''; + } else { + // Technical error that should not happen + print 'Error: validation date of shipment is not defined. This looks strange because shipment is validated. Try to run /install/repair.php?standard=confirmed'; + } print ''.$val['nb'].''.price(price2num($val['total'],'MT'),1).''.price($line->total_ttc).''; + $htmlforlines .= price($line->total_ttc); + if (!empty($conf->multicurrency->enabled) && $_SESSION["takeposcustomercurrency"]!="" && $conf->currency!=$_SESSION["takeposcustomercurrency"]) { + //Only show customer currency if multicurrency module is enabled, if currency selected and if this currency selected is not the same as main currency + include_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php'; + $multicurrency = new MultiCurrency($db); + $multicurrency->fetch(0, $_SESSION["takeposcustomercurrency"]); + $htmlforlines .= ' ('.price($line->total_ttc*$multicurrency->rate->rate).' '.$_SESSION["takeposcustomercurrency"].')'; + } + $htmlforlines .= '
trans("TotalTTC").''.price($object->total_ttc, 1, '', 1, - 1, - 1, $conf->currency)."\n"; ?>
'; + if ($gift!=1) echo ''.$langs->trans("TotalTTC").' '.$_SESSION["takeposcustomercurrency"].''.price($object->total_ttc*$multicurrency->rate->rate, 1, '', 1, - 1, - 1, $_SESSION["takeposcustomercurrency"])."\n"; + echo '