diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cf752e4abb2..2ac96991403 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -137,6 +137,8 @@ repos: files: \.(php)$ args: [--standard=dev/setup/codesniffer/ruleset.xml] - id: php-cs + exclude: | + (?x)^(htdocs/includes/.*)$ files: \.(php)$ args: [ diff --git a/dev/build/generate_filelist_xml.php b/dev/build/generate_filelist_xml.php index 5911ed1ed01..6d8eb2d198f 100755 --- a/dev/build/generate_filelist_xml.php +++ b/dev/build/generate_filelist_xml.php @@ -272,137 +272,72 @@ $checksumconcat = array(); fputs($fp, ''."\n"); -// TODO Use this array to make the scan +// Array of dir/files to include in the section $arrayofunalterablefiles = array( - array('dir' => dirname(__FILE__).'/../../htdocs/blockedlog', 'files' => 'all', 'regextoinclude' => '(\.php|\.sql)$', 'regextoexclude' => ''), - array('dir' => dirname(__FILE__).'/../../htdocs/install/mysql/tables', 'files' => 'all', 'regextoinclude' => 'llx_blockedlog.*(\.php|\.sql)$'), - array('dir' => dirname(__FILE__).'/../../htdocs/core/triggers', 'files' => 'interface_50_modBlockedlog_ActionsBlockedLog.class.php'), - array('dir' => dirname(__FILE__).'/../../htdocs/core/class', 'files' => 'interfaces.class.php'), - array('dir' => dirname(__FILE__).'/../../htdocs/core/class', 'files' => 'commontrigger.class.php'), - array('dir' => dirname(__FILE__).'/../../htdocs/takepos', 'files' => 'receipt.php') + array('dir' => dirname(__FILE__).'/../../htdocs/', 'file' => 'version.inc.php'), + array('dir' => dirname(__FILE__).'/../../htdocs/blockedlog', 'file' => 'all', 'regextoinclude' => '(\.php|\.sql)$', 'regextoexclude' => ''), + array('dir' => dirname(__FILE__).'/../../htdocs/install/mysql/tables', 'file' => 'all', 'regextoinclude' => 'llx_blockedlog.*(\.php|\.sql)$', 'regextoexclude' => ''), + array('dir' => dirname(__FILE__).'/../../htdocs/core/triggers', 'file' => 'interface_50_modBlockedlog_ActionsBlockedLog.class.php'), + array('dir' => dirname(__FILE__).'/../../htdocs/core/class', 'file' => 'all', 'regextoinclude' => '(interfaces.class.php|commontrigger.class.php)$', 'regextoexclude' => ''), + array('dir' => dirname(__FILE__).'/../../htdocs/takepos', 'file' => 'receipt.php') ); -$regextoinclude = '(\.php|\.sql)$'; -$regextoexclude = ''; // Exclude dirs -$files = dol_dir_list(dirname(__FILE__).'/../../htdocs/blockedlog', 'files', 1, $regextoinclude, $regextoexclude, 'fullname'); -$dir = ''; -foreach ($files as $filetmp) { - $file = $filetmp['fullname']; - $newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); - $newdir = str_replace(dirname(__FILE__).'/../../htdocs', '', dirname($file)); - if ($newdir != $dir) { +foreach ($arrayofunalterablefiles as $entry) { + if ($entry['file'] == 'all') { + $regextoinclude = $entry['regextoinclude']; + $regextoexclude = $entry['regextoexclude']; + $files = dol_dir_list($entry['dir'], 'files', 1, $regextoinclude, $regextoexclude, 'fullname'); + $dir = ''; + foreach ($files as $filetmp) { + $file = $filetmp['fullname']; + $newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); + $newdir = str_replace(dirname(__FILE__).'/../../htdocs', '', dirname($file)); + if ($newdir != $dir) { + if ($needtoclose) { + fputs($fp, ' '."\n"); + $needtoclose = 0; + } + fputs($fp, ' '."\n"); + $dir = $newdir; + $needtoclose = 1; + } + if (filetype($file) == "file") { + $md5 = md5_file($file); + $checksumconcat[] = $md5; + fputs($fp, ' '.$md5.''."\n"); + } + } if ($needtoclose) { fputs($fp, ' '."\n"); $needtoclose = 0; } - fputs($fp, ' '."\n"); - $dir = $newdir; - $needtoclose = 1; - } - if (filetype($file) == "file") { - $md5 = md5_file($file); - $checksumconcat[] = $md5; - fputs($fp, ' '.$md5.''."\n"); - } -} -if ($needtoclose) { - fputs($fp, ' '."\n"); - $needtoclose = 0; -} -// Add the SQL file -$regextoinclude = 'llx_blockedlog.*(\.php|\.sql)$'; -$regextoexclude = ''; // Exclude dirs -$files = dol_dir_list(dirname(__FILE__).'/../../htdocs/install/mysql/tables', 'files', 0, $regextoinclude, $regextoexclude, 'fullname'); -foreach ($files as $filetmp) { - $file = $filetmp['fullname']; - $newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); - $newdir = str_replace(dirname(__FILE__).'/../../htdocs', '', dirname($file)); - if ($newdir != $dir) { + } else { + $file = $entry['dir'].'/'.$entry['file']; + $newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); + $newdir = str_replace(dirname(__FILE__).'/../../htdocs', '', dirname($file)); + if (!file_exists($file)) { + print "Error file ".$file." does not exists."; + exit(1); + } + if ($newdir != $dir) { + if ($needtoclose) { + fputs($fp, ' '."\n"); + $needtoclose = 0; + } + fputs($fp, ' '."\n"); + $dir = $newdir; + $needtoclose = 1; + } + if (filetype($file) == "file") { + $md5 = md5_file($file); + $checksumconcat[] = $md5; + fputs($fp, ' '.$md5.''."\n"); + } if ($needtoclose) { fputs($fp, ' '."\n"); $needtoclose = 0; } - fputs($fp, ' '."\n"); - $dir = $newdir; - $needtoclose = 1; } - if (filetype($file) == "file") { - $md5 = md5_file($file); - $checksumconcat[] = $md5; - fputs($fp, ' '.$md5.''."\n"); - } -} -if ($needtoclose) { - fputs($fp, ' '."\n"); - $needtoclose = 0; -} -// Add the trigger file -$file = dirname(__FILE__).'/../../htdocs/core/triggers/interface_50_modBlockedlog_ActionsBlockedLog.class.php'; -$newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); -$newdir = str_replace(dirname(__FILE__).'/../../htdocs', '', dirname($file)); -if ($newdir != $dir) { - if ($needtoclose) { - fputs($fp, ' '."\n"); - $needtoclose = 0; - } - fputs($fp, ' '."\n"); - $dir = $newdir; - $needtoclose = 1; -} -if (filetype($file) == "file") { - $md5 = md5_file($file); - $checksumconcat[] = $md5; - fputs($fp, ' '.$md5.''."\n"); -} -if ($needtoclose) { - fputs($fp, ' '."\n"); - $needtoclose = 0; -} -// Add the interfaces.class.php file -$file = dirname(__FILE__).'/../../htdocs/core/class/interfaces.class.php'; -$newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); -$newdir = str_replace(dirname(__FILE__).'/../../htdocs', '', dirname($file)); -if ($newdir != $dir) { - if ($needtoclose) { - fputs($fp, ' '."\n"); - $needtoclose = 0; - } - fputs($fp, ' '."\n"); - $dir = $newdir; - //$needtoclose = 1; // close will be done in next filethat is in same dir -} -if (filetype($file) == "file") { - $md5 = md5_file($file); - $checksumconcat[] = $md5; - fputs($fp, ' '.$md5.''."\n"); -} -if ($needtoclose) { - fputs($fp, ' '."\n"); - $needtoclose = 0; -} -// Add the commontrigger.class.php file -$file = dirname(__FILE__).'/../../htdocs/core/class/commontrigger.class.php'; -$newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); -$newdir = str_replace(dirname(__FILE__).'/../../htdocs', '', dirname($file)); -if ($newdir != $dir) { - if ($needtoclose) { - fputs($fp, ' '."\n"); - $needtoclose = 0; - } - fputs($fp, ' '."\n"); - $dir = $newdir; - $needtoclose = 1; -} - -$needtoclose = 1; // This is the last file -if (filetype($file) == "file") { - $md5 = md5_file($file); - $checksumconcat[] = $md5; - fputs($fp, ' '.$md5.''."\n"); -} -if ($needtoclose) { - fputs($fp, ' '."\n"); - $needtoclose = 0; } diff --git a/htdocs/admin/mails_templates.php b/htdocs/admin/mails_templates.php index d3af47c0507..ca1e5795b28 100644 --- a/htdocs/admin/mails_templates.php +++ b/htdocs/admin/mails_templates.php @@ -13,6 +13,7 @@ * Copyright (C) 2016 Raphaël Doursenaud * Copyright (C) 2018-2024 Frédéric France * Copyright (C) 2024-2025 MDW + * Copyright (C) 2025 Vincent Maury * * 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 @@ -43,6 +44,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/cemailtemplate.class.php'; /** * @var Conf $conf @@ -236,6 +238,9 @@ if (isModEnabled("supplier_order") && ($user->hasRight('fournisseur', 'commande' if (isModEnabled("supplier_invoice") && ($user->hasRight('fournisseur', 'facture', 'lire') || $user->hasRight('supplier_invoice', 'read'))) { $elementList['invoice_supplier_send'] = img_picto('', 'bill', 'class="pictofixedwidth"').dol_escape_htmltag($langs->trans('MailToSendSupplierInvoice')); } +if (isModEnabled("supplier_invoice") && ($user->hasRight('fournisseur', 'facture', 'creer') || $user->hasRight("supplier_invoice", "write"))) { + $elementList['supplier_payment_send'] = img_picto('', 'bill', 'class="pictofixedwidth"').dol_escape_htmltag($langs->trans('SuppliersPayment')); +} if (isModEnabled('contract') && $user->hasRight('contrat', 'lire')) { $elementList['contract'] = img_picto('', 'contract', 'class="pictofixedwidth"').dol_escape_htmltag($langs->trans('MailToSendContract')); } @@ -279,7 +284,7 @@ $permissiontoadd = 1; $permissiontoedit = ($user->admin ? 1 : 0); $permissiontodelete = ($user->admin ? 1 : 0); if ($rowid > 0) { - $tmpmailtemplate = new ModelMail($db); + $tmpmailtemplate = new CEmailTemplate($db); $tmpmailtemplate->fetch($rowid); if ($tmpmailtemplate->fk_user == $user->id) { $permissiontoedit = 1; diff --git a/htdocs/admin/receiptprinter.php b/htdocs/admin/receiptprinter.php index b31e1b8e180..261336ac047 100644 --- a/htdocs/admin/receiptprinter.php +++ b/htdocs/admin/receiptprinter.php @@ -28,12 +28,6 @@ // Load Dolibarr environment require '../main.inc.php'; - -require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/receiptprinter.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/dolreceiptprinter.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -41,6 +35,10 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/dolreceiptprinter.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/receiptprinter.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/dolreceiptprinter.class.php'; // Load translation files required by the page $langs->loadLangs(array("admin", "receiptprinter")); @@ -54,6 +52,7 @@ $mode = GETPOST('mode', 'alpha'); $printername = GETPOST('printername', 'alpha'); $printerid = GETPOSTINT('printerid'); +$printertypeid = GETPOSTINT('printertypeid'); $parameter = GETPOST('parameter', 'alpha'); $template = GETPOST('template', 'alphanohtml'); @@ -93,13 +92,13 @@ if ($action == 'addprinter') { setEventMessages($langs->trans("PrinterNameEmpty"), null, 'errors'); } - if (empty($parameter)) { + if (empty($parameter) && $printertypeid > 1) { setEventMessages($langs->trans("PrinterParameterEmpty"), null, 'warnings'); } if (!$error) { $db->begin(); - $result = $printer->addPrinter($printername, GETPOSTINT('printertypeid'), GETPOSTINT('printerprofileid'), $parameter); + $result = $printer->addPrinter($printername, $printertypeid, GETPOSTINT('printerprofileid'), $parameter); if ($result > 0) { $error++; } @@ -109,7 +108,7 @@ if ($action == 'addprinter') { setEventMessages($langs->trans("PrinterAdded", $printername), null); } else { $db->rollback(); - dol_print_error($db); + setEventMessages($printer->error, $printer->errors, 'errors'); } } $action = ''; diff --git a/htdocs/admin/system/filecheck.php b/htdocs/admin/system/filecheck.php index ebc2deca917..dfb90bac16a 100644 --- a/htdocs/admin/system/filecheck.php +++ b/htdocs/admin/system/filecheck.php @@ -241,15 +241,22 @@ if (empty($error) && !empty($xml)) { $out .= ''.$langs->trans("CurrentValue").''; $out .= ''."\n"; + $i = 0; + if ($mode == 'unalterable') { + $i++; + $out .= ''; - $out .= ''."\n"; + $out .= ''.$i.''."\n"; $out .= ''.$langs->trans("Country").''."\n"; $out .= ''.$langs->trans("YourCountryCode").''."\n"; $out .= ''.$mysoc->country_code.''."\n"; $out .= "\n"; + + $i++; + $out .= ''; - $out .= ''."\n"; + $out .= ''.$i.''."\n"; $out .= ''.$langs->trans("StatusOfModule", $langs->transnoentitiesnoconv("BlockedLog")).''."\n"; $out .= ''.$langs->trans("Enabled").''."\n"; $out .= ''; @@ -257,10 +264,10 @@ if (empty($error) && !empty($xml)) { include_once DOL_DOCUMENT_ROOT.'/core/modules/modBlockedLog.class.php'; $objMod = new modBlockedLog($db); - $modulename = $objMod->getName(); + /*$modulename = $objMod->getName(); $moduledesc = $objMod->getDesc(); $moduleauthor = $objMod->getPublisher(); - $moduledir = strtolower(preg_replace('/^mod/i', '', get_class($objMod))); + $moduledir = strtolower(preg_replace('/^mod/i', '', get_class($objMod)));*/ $const_name = 'MAIN_MODULE_'.strtoupper(preg_replace('/^mod/i', '', get_class($objMod))); $htmltooltip = ''.$langs->trans("LastActivationDate").': '; @@ -294,7 +301,6 @@ if (empty($error) && !empty($xml)) { $out .= "\n"; } - $i = 0; foreach ($xml->dolibarr_constants[0]->constant as $constant) { // $constant is a simpleXMLElement $constname = (string) $constant['name']; $constvalue = (string) $constant; @@ -310,6 +316,7 @@ if (empty($error) && !empty($xml)) { $checksumconcat[$constname] = $valueforchecksum; $i++; + $out .= ''; $out .= ''.$i.''."\n"; $out .= ''.dol_escape_htmltag($constname).''."\n"; @@ -544,7 +551,7 @@ if (empty($error) && !empty($xml)) { $resultcode = 'ok'; $resultcomment = 'Success'; $outcurrentchecksum = ''.$checksumget.''; - $outcurrentchecksumtext.= img_picto('', 'tick').' '.$langs->trans($resultcomment).''; + $outcurrentchecksumtext.= img_picto('', 'tick').' '.$langs->trans($resultcomment).''; } } else { $resultcode = 'error'; @@ -610,7 +617,7 @@ if (empty($error) && !empty($xml)) { print '
'; print $outforlistoffiles; - print '
'; + print '

'; // Output detail diff --git a/htdocs/api/class/api_emailtemplates.class.php b/htdocs/api/class/api_emailtemplates.class.php new file mode 100644 index 00000000000..50a0bb4c3f5 --- /dev/null +++ b/htdocs/api/class/api_emailtemplates.class.php @@ -0,0 +1,641 @@ + + * + * 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 + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +use Luracast\Restler\RestException; + +require_once DOL_DOCUMENT_ROOT.'/api/class/api.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/cemailtemplate.class.php'; + +/** + * API for handling Object of table llx_c_email_templates + * + * @access protected + * @class DolibarrApiAccess {@requires user,external} + */ +class EmailTemplates extends DolibarrApi +{ + /** + * @var string[] Mandatory fields, checked when create and update object + */ + public static $FIELDS = array( + 'label', + 'topic', + 'type_template' + ); + + /** + * @var string[] Mandatory fields which needs to be an integer, checked when create and update object + */ + public static $INTFIELDS = array( + 'active', + 'private', + 'fk_user', + 'joinfiles', + 'position' + ); + + /** + * @var CEmailTemplate {@type CEmailTemplate} + */ + public $email_template; + + /** + * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management (so extrafields know the link to the parent table). + */ + public $table_element = 'c_email_templates'; + + /** + * Constructor of the class + */ + public function __construct() + { + global $db; + $this->db = $db; + $this->email_template = new CEmailTemplate($this->db); + } + + /** + * Delete an email template + * + * @param int $id email template ID + * @return array + * @phan-return array> + * @phpstan-return array> + * + * @url DELETE {id} + * + * @throws RestException 403 + * @throws RestException 404 + * @throws RestException 500 + */ + public function deleteById($id) + { + $allowaccess = $this->_checkAccessRights('lire'); + if (!$allowaccess) { + throw new RestException(403, 'denied read access to email templates'); + } + + $result = $this->email_template->apifetch($id, ''); + if (!$result || $id == 0) { + throw new RestException(404, 'Email Template with id '.$id.' not found'); + } + + if (!$this->email_template->delete(DolibarrApiAccess::$user)) { + throw new RestException(500, 'Error when delete email template : '.$this->email_template->error); + } + + return array( + 'success' => array( + 'code' => 200, + 'message' => 'email template deleted' + ) + ); + } + + /** + * Delete an email template + * + * @param string $label email template label + * @return array + * @phan-return array> + * @phpstan-return array> + * + * @url DELETE label/{label} + * + * @throws RestException 403 + * @throws RestException 404 + * @throws RestException 500 + */ + public function deleteByLAbel($label) + { + $allowaccess = $this->_checkAccessRights('lire'); + if (!$allowaccess) { + throw new RestException(403, 'denied read access to email templates'); + } + + $result = $this->email_template->apifetch(0, $label); + if (!$result) { + throw new RestException(404, "Email Template with label ".$label." not found"); + } + + if (!$this->email_template->delete(DolibarrApiAccess::$user)) { + throw new RestException(500, 'Error when delete email template : '.$this->email_template->error); + } + + return array( + 'success' => array( + 'code' => 200, + 'message' => 'email template deleted' + ) + ); + } + + /** + * Get properties of a email template by id + * + * Return an array with email template information + * + * @param int $id ID of email template + * @return Object Object with cleaned properties + * @phan-return CEmailTemplate + * @phpstan-return CEmailTemplate + * + * @url GET {id} + * + * @throws RestException 403 + * @throws RestException 404 + */ + public function getById($id) + { + return $this->_fetch($id, ''); + } + + /** + * Get properties of an email template by label + * + * Return an array with order information + * + * @param string $label Label of object + * @return Object Object with cleaned properties + * @phan-return CEmailTemplate + * @phpstan-return CEmailTemplate + * + * @url GET label/{label} + * + * @throws RestException 403 + * @throws RestException 404 + */ + public function getByLabel($label) + { + return $this->_fetch(0, $label); + } + + /** + * List email templates + * + * Get a list of email templates + * + * @param string $sortfield Sort field + * @param string $sortorder Sort order + * @param int $limit Limit for list + * @param int $page Page number + * @param string $fk_user User ids to filter email templates of (example '1' or '1,2,3') {@pattern /^[0-9,]*$/i} + * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(e.active:=:1) and (e.module:=:'adherent')" + * @param string $properties Restrict the data returned to these properties. Ignored if empty. Comma separated list of properties names + * @param bool $pagination_data If this parameter is set to true the response will include pagination data. Default value is false. Page starts from 0* + * @return array Array of order objects + * @phan-return CEmailTemplate[]|array{data:CEmailTemplate[],pagination:array{total:int,page:int,page_count:int,limit:int}} + * @phpstan-return CEmailTemplate[]|array{data:CEmailTemplate[],pagination:array{total:int,page:int,page_count:int,limit:int}} + * + * @url GET + * + * @throws RestException 404 Not found + * @throws RestException 503 Error + */ + public function index($sortfield = "e.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $fk_user = '', $sqlfilters = '', $properties = '', $pagination_data = false) + { + $allowaccess = $this->_checkAccessRights('lire'); + if (!$allowaccess) { + throw new RestException(403, 'denied read access to email templates'); + } + + $obj_ret = array(); + + $sql = "SELECT e.rowid"; + $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." AS e"; + $sql .= " WHERE e.entity IN (".getEntity($this->table_element).")"; + if (!$fk_user == '') { + $sql .= " AND e.fk_user = ".((int) $fk_user); + } + + // Add sql filters + if ($sqlfilters) { + $errormessage = ''; + $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage); + if ($errormessage) { + throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage); + } + } + + //this query will return total orders with the filters given + $sqlTotals = str_replace('SELECT e.rowid', 'SELECT count(e.rowid) as total', $sql); + + $sql .= $this->db->order($sortfield, $sortorder); + if ($limit) { + if ($page < 0) { + $page = 0; + } + $offset = $limit * $page; + + $sql .= $this->db->plimit($limit + 1, $offset); + } + + dol_syslog(get_class($this)."::index", LOG_DEBUG); + $result = $this->db->query($sql); + dol_syslog(get_class($this)."::pindex", LOG_DEBUG); + + if ($result) { + $num = $this->db->num_rows($result); + $min = min($num, ($limit <= 0 ? $num : $limit)); + $i = 0; + while ($i < $min) { + $obj = $this->db->fetch_object($result); + $email_template_static = new CEmailTemplate($this->db); + if ($email_template_static->apifetch($obj->rowid, '') > 0) { + $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($email_template_static), $properties); + } + $i++; + } + } else { + throw new RestException(503, 'Error when retrieve email template list : '.$this->db->lasterror()); + } + + //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit) + if ($pagination_data) { + $totalsResult = $this->db->query($sqlTotals); + $total = $this->db->fetch_object($totalsResult)->total; + + $tmp = $obj_ret; + $obj_ret = []; + + $obj_ret['data'] = $tmp; + $obj_ret['pagination'] = [ + 'total' => (int) $total, + 'page' => $page, //count starts from 0 + 'page_count' => ceil((int) $total / $limit), + 'limit' => $limit + ]; + } + + return $obj_ret; + } + + /** + * Create an email template + * + * Example: {"module":"adherent","type_template":"member","active": 1,"label":"(SendingEmailOnAutoSubscription)","fk_user":0,"joinfiles": "0", ... } + * Required: {"label":"myBestTemplate","topic":"myBestOffer","type_template":"propal_send"} + * + * @param array $request_data Request data + * @phan-param ?array $request_data + * @phpstan-param ?array $request_data + * + * @url POST + * + * @return int ID of email template + * + * @throws RestException 400 + * @throws RestException 403 + * @throws RestException 500 + */ + public function post($request_data = null) + { + $allowaccess = $this->_checkAccessRights('creer'); + if (!$allowaccess) { + throw new RestException(403, 'denied create access to email templates'); + } + + // Check mandatory fields + $result = $this->_validate($request_data); + + foreach ($request_data as $field => $value) { + if ($field === 'caller') { + // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller + $this->email_template->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); + continue; + } + if ($field == 'id') { + throw new RestException(400, 'Creating with id field is forbidden'); + } + + $this->email_template->$field = $this->_checkValForAPI($field, $value, $this->email_template); + } + + if ($this->email_template->create(DolibarrApiAccess::$user) < 0) { + throw new RestException(500, "Error creating email template", array_merge(array($this->email_template->error), $this->email_template->errors)); + } + + return ((int) $this->email_template->id); + } + + /** + * Update an email template + * + * Example: {"module":"adherent","type_template":"member","active": 1,"label":"(SendingEmailOnAutoSubscription)","fk_user":0,"joinfiles": "0", ... } + * Required: {"label":"myBestTemplate","topic":"myBestOffer","type_template":"propal_send"} + * + * @param int $id Id of order to update + * @param array $request_data Data + * @phan-param ?array $request_data + * @phpstan-param ?array $request_data + * + * @url PUT {id} + * + * @return Object Object with cleaned properties + * + * @throws RestException 403 + * @throws RestException 404 + * @throws RestException 500 + */ + public function putById($id, $request_data = null) + { + $allowaccess = $this->_checkAccessRights('creer'); + if (!$allowaccess) { + throw new RestException(403, 'denied update access to email templates'); + } + + $result = $this->email_template->apifetch($id, ''); + if (!$result || $id == 0) { + throw new RestException(404, 'email template with id='.$id.' not found'); + } + + foreach ($request_data as $field => $value) { + if ($field == 'id') { + continue; + } + if ($field === 'caller') { + // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller + $this->email_template->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); + continue; + } + + $this->email_template->$field = $this->_checkValForAPI($field, $value, $this->email_template); + } + + if ($this->email_template->update(DolibarrApiAccess::$user) > 0) { + return $this->_fetch($id, ''); + } else { + throw new RestException(500, $this->email_template->error); + } + } + + /** + * Update an email template + * + * Example: {"module":"adherent","type_template":"member","active": 1,"label":"(SendingEmailOnAutoSubscription)","fk_user":0,"joinfiles": "0", ... } + * Required: {"label":"myBestTemplate","topic":"myBestOffer","type_template":"propal_send"} + * + * @param string $label Label of order to update + * @param array $request_data Data + * @phan-param ?array $request_data + * @phpstan-param ?array $request_data + * + * @url PUT label/{label} + * + * @return Object Object with cleaned properties + * + * @throws RestException 403 + * @throws RestException 404 + * @throws RestException 500 + */ + public function putbyLabel($label, $request_data = null) + { + $allowaccess = $this->_checkAccessRights('creer'); + if (!$allowaccess) { + throw new RestException(403, 'denied update access to email templates'); + } + + $result = $this->email_template->apifetch(0, $label); + if (!$result) { + throw new RestException(404, 'email template not found'); + } + + $newlabel = $label; + foreach ($request_data as $field => $value) { + if ($field == 'id') { + continue; + } + if ($field == 'label') { + $newlabel = $this->_checkValForAPI($field, $value, $this->email_template); + } + if ($field === 'caller') { + // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller + $this->email_template->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); + continue; + } + + $this->email_template->$field = $this->_checkValForAPI($field, $value, $this->email_template); + } + + if ($this->email_template->update(DolibarrApiAccess::$user) > 0) { + return $this->_fetch(0, $newlabel); + } else { + throw new RestException(500, $this->email_template->error); + } + } + + /** + * Get properties of an email template + * + * Return an array with email templates + * + * @param int $id ID of email_template + * @param string $label Label of email_template + * @return Object Object with cleaned properties + * @phan-return CEmailTemplate + * @phpstan-return CEmailTemplate + * + * @throws RestException 400 + * @throws RestException 403 + * @throws RestException 404 + */ + private function _fetch($id, $label = '') + { + global $conf; + + $allowaccess = $this->_checkAccessRights('lire'); + if (!$allowaccess) { + throw new RestException(403, 'denied read access to email templates'); + } + + $result = $this->email_template->apifetch($id, $label); + if ($result > 0) { + return $this->_cleanObjectDatas($this->email_template); + } + if ($result == 0) { + if ($id) { + throw new RestException(404, 'Email template with id='.((string) $id).' not found in entity='.(int) $conf->entity); + } + if ($label) { + throw new RestException(404, 'Email template with label '.$label.' not found in entity='.(int) $conf->entity); + } + throw new RestException(404, 'Email Template not found'); + } else { + if (empty($this->email_template->error)) { + throw new RestException(400, 'Unknown error in your request'); + } else { + throw new RestException(400, 'Error: '.$this->email_template->error); + } + } + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Clean sensible object datas + * + * @param Object $object Object to clean + * @phan-param CEmailTemplate $object + * @phpstan-param CEmailTemplate $object + * + * @return Object Object with cleaned properties + * @phan-return CEmailTemplate + * @phpstan-return CEmailTemplate + */ + protected function _cleanObjectDatas($object) + { + // phpcs:enable + $object = parent::_cleanObjectDatas($object); + dol_syslog(get_class($this)."::_cleanObjectDatas", LOG_DEBUG); + + + unset($object->import_key); + unset($object->array_languages); + unset($object->contacts_ids); + unset($object->linkedObjectsIds); + unset($object->canvas); + unset($object->fk_project); + unset($object->contact_id); + unset($object->user); + unset($object->origin_type); + unset($object->origin_id); + unset($object->ref); + unset($object->ref_ext); + unset($object->statut); + unset($object->status); + unset($object->civility_code); + unset($object->country_id); + unset($object->country_code); + unset($object->state_id); + unset($object->region_id); + unset($object->barcode_type); + unset($object->barcode_type_coder); + unset($object->mode_reglement_id); + unset($object->cond_reglement_id); + unset($object->demand_reason_id); + unset($object->transport_mode_id); + unset($object->shipping_method_id); + unset($object->shipping_method); + unset($object->fk_multicurrency); + unset($object->multicurrency_code); + unset($object->multicurrency_tx); + unset($object->multicurrency_total_ht); + unset($object->multicurrency_total_tva); + unset($object->multicurrency_total_ttc); + unset($object->multicurrency_total_localtax1); + unset($object->multicurrency_total_localtax2); + unset($object->last_main_doc); + unset($object->fk_account); + unset($object->note_public); + unset($object->note_private); + unset($object->total_ht); + unset($object->total_tva); + unset($object->total_localtax1); + unset($object->total_localtax2); + unset($object->total_ttc); + unset($object->lines); + unset($object->actiontypecode); + unset($object->name); + unset($object->lastname); + unset($object->firstname); + unset($object->civility_id); + unset($object->user_author); + unset($object->user_creation); + unset($object->user_creation_id); + unset($object->user_valid); + unset($object->user_validation); + unset($object->user_validation_id); + unset($object->user_closing_id); + unset($object->user_modification); + unset($object->user_modification_id); + unset($object->fk_user_creat); + unset($object->fk_user_modif); + unset($object->totalpaid); + unset($object->product); + unset($object->cond_reglement_supplier_id); + unset($object->deposit_percent); + unset($object->retained_warranty_fk_cond_reglement); + unset($object->warehouse_id); + unset($object->target); + unset($object->array_options); + unset($object->extraparams); + unset($object->specimen); + unset($object->date_validation); + unset($object->date_modification); + unset($object->date_cloture); + unset($object->rowid); + + return $object; + } + + /** + * Validate fields before create or update object + * + * @param ?array $data Data to validate + * @return array Return array with validated mandatory fields and their value + * @phan-return array Return array with validated mandatory fields and their value + * + * @throws RestException 400 + */ + private function _validate($data) + { + $email_template = array(); + foreach (EmailTemplates::$FIELDS as $field) { + if (!isset($data[$field])) { + throw new RestException(400, $field." field missing"); + } + $email_template[$field] = $data[$field]; + } + return $email_template; + } + + /** + * function to check for access rights - should probably have 1. parameter which is read/write/delete/... + * Why a separate function? because we probably needs to check so many many different kinds of objects + * + * @param string $accesstype accesstype: read, write, delete, ... + * @return bool Return true if access is granted else false + * + * @throws RestException 403 + */ + private function _checkAccessRights($accesstype) + { + // what kind of access management do we need? + $allowaccess = false; + if (isModEnabled("societe") && DolibarrApiAccess::$user->hasRight('societe', $accesstype)) { + $allowaccess = true; + } + if (isModEnabled('member') && DolibarrApiAccess::$user->hasRight('adherent', $accesstype)) { + $allowaccess = true; + } + if (isModEnabled("propal") && DolibarrApiAccess::$user->hasRight('propal', $accesstype)) { + $allowaccess = true; + } + if (isModEnabled('order') && DolibarrApiAccess::$user->hasRight('commande', $accesstype)) { + $allowaccess = true; + } + if (isModEnabled('invoice') && DolibarrApiAccess::$user->hasRight('facture', $accesstype)) { + $allowaccess = true; + } + if ($allowaccess) { + return $allowaccess; + } else { + throw new RestException(403, 'denied access to email templates'); + } + } +} diff --git a/htdocs/blockedlog/admin/blockedlog.php b/htdocs/blockedlog/admin/blockedlog.php index c2f13847a0f..0616bfe0618 100644 --- a/htdocs/blockedlog/admin/blockedlog.php +++ b/htdocs/blockedlog/admin/blockedlog.php @@ -126,6 +126,7 @@ print $langs->trans("CompanyInitialKey").''; print $block_static->getSignature(); print ''; +/* if (getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY')) { // Example with a yes / no select print ''; @@ -142,6 +143,7 @@ if (getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY')) { print ''; } +*/ print ''; print ''.$langs->trans("BlockedLogDisableNotAllowedForCountry").''; diff --git a/htdocs/blockedlog/admin/blockedlog_list.php b/htdocs/blockedlog/admin/blockedlog_list.php index 1d4c831c91e..d23a2dc07b1 100644 --- a/htdocs/blockedlog/admin/blockedlog_list.php +++ b/htdocs/blockedlog/admin/blockedlog_list.php @@ -29,7 +29,6 @@ require '../../main.inc.php'; require_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php'; require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php'; -require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/authority.class.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/class/html.formother.class.php'; @@ -153,19 +152,7 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x' $search_array_options = array(); } -if ($action === 'downloadblockchain') { - $auth = new BlockedLogAuthority($db); - - $bc = $auth->getLocalBlockChain(); - - header('Content-Type: application/octet-stream'); - header("Content-Transfer-Encoding: Binary"); - header("Content-disposition: attachment; filename=\"".$auth->signature.".certif\""); - - echo $bc; - - exit; -} elseif (GETPOST('downloadcsv', 'alpha')) { +if (GETPOST('downloadcsv', 'alpha')) { $error = 0; $previoushash = ''; @@ -495,9 +482,9 @@ print $formother->select_month((string) GETPOSTINT('monthtoexport'), 'monthtoexp print ''; print ''; print ''; -if (getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY')) { +/*if (getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY')) { print ' | '.$langs->trans('DownloadBlockChain').''; -} +}*/ print '
'; print ''; @@ -568,11 +555,10 @@ $array = array("1" => "OnlyNonValid"); print $form->selectarray('search_showonlyerrors', $array, $search_showonlyerrors, 1, 0, 0, '', 1, 0, 0, 'ASC', 'search_status width100 onrightofpage', 1); print ''; -// Status note -//print ''; - -// Link to original ref into business software -print ''; +// Link to debug information object +if (getDolGlobalString('MAIN_FEATURES_LEVEL') > 0) { // If in experimental or develop mode, we add some debug information. It may help developers to find origin of bugs. + print ''; +} // Action column if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { @@ -599,7 +585,9 @@ print getTitleFieldOfList($langs->trans('Amount'), 0, $_SERVER["PHP_SELF"], '', print getTitleFieldOfList($langs->trans('DataOfArchivedEvent'), 0, $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, 'center ', 0, $langs->trans('DataOfArchivedEventHelp'), 1)."\n"; print getTitleFieldOfList($langs->trans('Fingerprint'), 0, $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, '')."\n"; print getTitleFieldOfList($form->textwithpicto($langs->trans('Status'), $langs->trans('DataOfArchivedEventHelp2')), 0, $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, 'center ')."\n"; -print getTitleFieldOfList('', 0, $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, '')."\n"; +if (getDolGlobalString('MAIN_FEATURES_LEVEL') > 0) { // If in experimental or develop mode, we add some debug information. It may help developers to find origin of bugs. + print getTitleFieldOfList('', 0, $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, '')."\n"; +} // Action column if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { print getTitleFieldOfList('', 0, $_SERVER["PHP_SELF"], '', '', $param, 'class="center"', $sortfield, $sortorder, '')."\n"; @@ -716,30 +704,28 @@ if (is_array($blocks)) { } else { print 'OK'; } - //print ''; // Note - //print ''; if (!$checkresult[$block->id] || ($loweridinerror && $block->id >= $loweridinerror)) { // If error if ($checkresult[$block->id]) { print $form->textwithpicto('', $langs->trans('OkCheckFingerprintValidityButChainIsKo')); - } else { - //print $form->textwithpicto('', $langs->trans('KoCheckFingerprintValidity')); } - } else { - //print $form->textwithpicto('', $langs->trans('DataOfArchivedEventHelp2')); } + /* if (getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY') && getDolGlobalString('BLOCKEDLOG_AUTHORITY_URL')) { print ' '.($block->certified ? img_picto($langs->trans('AddedByAuthority'), 'info') : img_picto($langs->trans('NotAddedByAuthorityYet'), 'info_black')); } + */ print ''; - // Link to source object - print ''; - print ''; // $object_link can be a ''; + // Link to debug information object + if (getDolGlobalString('MAIN_FEATURES_LEVEL') > 0) { // If in experimental or develop mode, we add some debug information. It may help developers to find origin of bugs. + print ''; + print ''; // $object_link can be a ''; + } // Action column if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { @@ -752,7 +738,11 @@ if (is_array($blocks)) { } if ($nbshown == 0) { - print ''.$langs->trans("NoRecordFound").''; + $colspan = 11; + if (getDolGlobalString('MAIN_FEATURES_LEVEL') > 0) { // If in experimental or develop mode, we add some debug information. It may help developers to find origin of bugs. + $colspan++; + } + print ''.$langs->trans("NoRecordFound").''; } } @@ -799,6 +789,7 @@ jQuery(document).ready(function () { '."\n"; +/* if (getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY') && getDolGlobalString('BLOCKEDLOG_AUTHORITY_URL')) { ?> - * Copyright (C) 2017 ATM Consulting - * Copyright (C) 2024 Frédéric France - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file htdocs/blockedlog/ajax/authority.php - * \ingroup blockedlog - * \brief authority - */ - - -// This script is called with a POST method. -// Directory to scan (full path) is inside POST['dir']. - -if (!defined('NOTOKENRENEWAL')) { - define('NOTOKENRENEWAL', 1); // Disables token renewal -} -if (!defined('NOREQUIREMENU')) { - define('NOREQUIREMENU', '1'); -} -if (!defined('NOREQUIREHTML')) { - define('NOREQUIREHTML', '1'); -} - -require '../../master.inc.php'; - -require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php'; -require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/authority.class.php'; -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Translate $langs - * @var User $user - */ - -$user = new User($db); -$user->fetch(1); //TODO conf user authority - - -/* - * View - */ - -top_httphead(); - -$auth = new BlockedLogAuthority($db); - -$signature = GETPOST('s'); -$newblock = GETPOST('b'); -$hash = GETPOST('h'); - -if ($auth->fetch(0, $signature) <= 0) { - $auth->signature = $signature; - $auth->create($user); -} - - -if (!empty($hash)) { - echo $auth->checkBlockchain($hash) ? 'hashisok' : 'hashisjunk'; -} elseif (!empty($newblock)) { - if ($auth->checkBlock($newblock)) { - $auth->addBlock($newblock); - $auth->update($user); - - echo 'blockadded'; - } else { - echo 'blockalreadyadded'; - } -} else { - echo 'idontunderstandwhatihavetodo'; -} diff --git a/htdocs/blockedlog/ajax/check_signature.php b/htdocs/blockedlog/ajax/check_signature.php deleted file mode 100644 index 6fea53096ea..00000000000 --- a/htdocs/blockedlog/ajax/check_signature.php +++ /dev/null @@ -1,91 +0,0 @@ - - * Copyright (C) 2017 ATM Consulting - * Copyright (C) 2024 MDW - * Copyright (C) 2024 Frédéric France - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file htdocs/blockedlog/ajax/check_signature.php - * \ingroup blockedlog - * \brief This page is not used yet. - */ - - -// This script is called with a POST method. -// Directory to scan (full path) is inside POST['dir']. - -if (!defined('NOTOKENRENEWAL')) { - define('NOTOKENRENEWAL', 1); // Disables token renewal -} -if (!defined('NOREQUIREMENU')) { - define('NOREQUIREMENU', '1'); -} -if (!defined('NOREQUIREHTML')) { - define('NOREQUIREHTML', '1'); -} - - -// Load Dolibarr environment -require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php'; -require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/authority.class.php'; - -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Translate $langs - * @var User $user - */ - -if (!getDolGlobalString('BLOCKEDLOG_AUTHORITY_URL')) { - exit('BLOCKEDLOG_AUTHORITY_URL not set'); -} - - -/* - * View - */ - -top_httphead(); - -$auth = new BlockedLogAuthority($db); -$auth->syncSignatureWithAuthority(); - -$block_static = new BlockedLog($db); - -$blocks = $block_static->getLog('just_certified', 0, 0, 'rowid', 'ASC'); - -$auth->signature = $block_static->getSignature(); - -if (is_array($blocks)) { - foreach ($blocks as &$b) { - $auth->blockchain .= $b->signature; - } -} - -$hash = $auth->getBlockchainHash(); - -// Call external authority -$url = getDolGlobalString('BLOCKEDLOG_AUTHORITY_URL') . '/blockedlog/ajax/authority.php?s='.urlencode($auth->signature).'&h='.urlencode($hash); - -$resarray = getURLContent($url, 'GET', '', 1, array(), array(), 2); -$res = $resarray['content']; - -//echo $url; -echo dol_escape_htmltag($res); diff --git a/htdocs/blockedlog/class/authority.class.php b/htdocs/blockedlog/class/authority.class.php deleted file mode 100644 index ca0089428d5..00000000000 --- a/htdocs/blockedlog/class/authority.class.php +++ /dev/null @@ -1,329 +0,0 @@ - - * Copyright (C) 2025 Frédéric France - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * Class to manage certif authority - */ -class BlockedLogAuthority -{ - /** - * DoliDB - * @var DoliDB - */ - public $db; - - /** - * Id of the authority - * @var int - */ - public $id; - - /** - * @var string Ref of the authority - */ - public $ref; - - /** - * Unique fingerprint of the blockchain to store - * @var string - */ - public $signature = ''; - - /** - * Entire fingerprints blockchain - * @var string - */ - public $blockchain = ''; - - /** - * timestamp - * @var int - */ - public $tms = 0; - - /** - * Error message - * @var string - */ - public $error; - - /** - * Constructor - * - * @param DoliDB $db Database handler - */ - public function __construct($db) - { - $this->db = $db; - } - - /** - * Get the blockchain - * - * @return string blockchain - */ - public function getLocalBlockChain() - { - $block_static = new BlockedLog($this->db); - - $this->signature = $block_static->getSignature(); - - $blocks = $block_static->getLog('all', 0, 0, 'rowid', 'ASC'); - - $this->blockchain = ''; - - if (is_array($blocks)) { - foreach ($blocks as &$b) { - $this->blockchain .= $b->signature; - } - } - - return $this->blockchain; - } - - /** - * Get hash of the block chain to check - * - * @return string hash md5 of blockchain - */ - public function getBlockchainHash() - { - return md5($this->signature.$this->blockchain); - } - - /** - * Get hash of the block chain to check - * - * @param string $hash hash md5 of blockchain to test - * @return boolean - */ - public function checkBlockchain($hash) - { - return ($hash === $this->getBlockchainHash()); - } - - /** - * Add a new block to the chain - * - * @param string $block new block to chain - * @return void - */ - public function addBlock($block) - { - $this->blockchain .= $block; - } - - /** - * hash already exist into chain ? - * - * @param string $block new block to chain - * @return boolean - */ - public function checkBlock($block) - { - if (strlen($block) != 64) { - return false; - } - - $blocks = str_split($this->blockchain, 64); - - if (!in_array($block, $blocks)) { - return true; - } else { - return false; - } - } - - - /** - * Get object from database - * - * @param int $id Id of object to load - * @param string $signature Signature of object to load - * @return int >0 if OK, <0 if KO, 0 if not found - */ - public function fetch($id, $signature = '') - { - global $langs; - - dol_syslog(get_class($this)."::fetch id=".((int) $id), LOG_DEBUG); - - if (empty($id) && empty($signature)) { - $this->error = 'BadParameter'; - return -1; - } - - $langs->load("blockedlog"); - - $sql = "SELECT b.rowid, b.signature, b.blockchain, b.tms"; - $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog_authority as b"; - - if ($id) { - $sql .= " WHERE b.rowid = ".((int) $id); - } elseif ($signature) { - $sql .= " WHERE b.signature = '".$this->db->escape($signature)."'"; - } - - $resql = $this->db->query($sql); - if ($resql) { - if ($this->db->num_rows($resql)) { - $obj = $this->db->fetch_object($resql); - - $this->id = $obj->rowid; - $this->ref = $obj->rowid; - - $this->signature = $obj->signature; - $this->blockchain = $obj->blockchain; - - $this->tms = $this->db->jdate($obj->tms); - - return 1; - } else { - $langs->load("errors"); - $this->error = $langs->trans("ErrorRecordNotFound"); - return 0; - } - } else { - $this->error = $this->db->error(); - return -1; - } - } - - /** - * Create authority in database. - * - * @param User $user Object user that create - * @return int Return integer <0 if KO, >0 if OK - */ - public function create($user) - { - global $conf, $langs, $hookmanager; - - $langs->load('blockedlog'); - - $error = 0; - - dol_syslog(get_class($this).'::create', LOG_DEBUG); - - $this->db->begin(); - - $sql = "INSERT INTO ".MAIN_DB_PREFIX."blockedlog_authority ("; - $sql .= " signature,"; - $sql .= " blockchain"; - $sql .= ") VALUES ("; - $sql .= "'".$this->db->escape($this->signature)."',"; - $sql .= "'".$this->db->escape($this->blockchain)."'"; - $sql .= ")"; - - $res = $this->db->query($sql); - if ($res) { - $id = $this->db->last_insert_id(MAIN_DB_PREFIX."blockedlog_authority"); - - if ($id > 0) { - $this->id = $id; - - $this->db->commit(); - - return $this->id; - } else { - $this->db->rollback(); - return -2; - } - } else { - $this->error = $this->db->error(); - $this->db->rollback(); - return -1; - } - } - - /** - * Create authority in database. - * - * @param User $user Object user that create - * @return int Return integer <0 if KO, >0 if OK - */ - public function update($user) - { - global $conf, $langs, $hookmanager; - - $langs->load('blockedlog'); - - $error = 0; - - dol_syslog(get_class($this).'::create', LOG_DEBUG); - - $this->db->begin(); - - $sql = "UPDATE ".MAIN_DB_PREFIX."blockedlog_authority SET "; - $sql .= " blockchain='".$this->db->escape($this->blockchain)."'"; - $sql .= " WHERE rowid=".((int) $this->id); - - $res = $this->db->query($sql); - if ($res) { - $this->db->commit(); - - return 1; - } else { - $this->error = $this->db->error(); - $this->db->rollback(); - return -1; - } - } - - /** - * For cron to sync to authority. - * - * @return int Return integer <0 if KO, >0 if OK - */ - public function syncSignatureWithAuthority() - { - global $conf, $langs; - - //TODO create cron task on activation - - if (!getDolGlobalString('BLOCKEDLOG_AUTHORITY_URL') || !getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY')) { - $this->error = $langs->trans('NoAuthorityURLDefined'); - return -2; - } - - require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php'; - - $block_static = new BlockedLog($this->db); - - $blocks = $block_static->getLog('not_certified', 0, 0, 'rowid', 'ASC'); - - $signature = $block_static->getSignature(); - - if (is_array($blocks)) { - foreach ($blocks as &$block) { - $url = getDolGlobalString('BLOCKEDLOG_AUTHORITY_URL') . '/blockedlog/ajax/authority.php?s='.$signature.'&b='.$block->signature; - - $res = getURLContent($url); - echo $block->signature.' '.$url.' '.$res['content'].'
'; - if ($res['content'] === 'blockalreadyadded' || $res['content'] === 'blockadded') { - $block->setCertified(); - } else { - $this->error = $langs->trans('ImpossibleToContactAuthority', $url); - return -1; - } - } - } - - return 1; - } -} diff --git a/htdocs/blockedlog/lib/blockedlog.lib.php b/htdocs/blockedlog/lib/blockedlog.lib.php index b1d7a157b15..eec09f22651 100644 --- a/htdocs/blockedlog/lib/blockedlog.lib.php +++ b/htdocs/blockedlog/lib/blockedlog.lib.php @@ -63,3 +63,35 @@ function blockedlogadmin_prepare_head() return $head; } + + + +/** + * Return if the version is a candidate version to get the LNE certification and if the prerequisites are OK. + * This function can be used to avoid to show the mandatory information "Certified LNE" on tickets when it is not true. + * + * @return boolean True or false + */ +function isALNECandidateVersion() +{ + global $mysoc; + + // Constant set by developer to force LNE restriction even if country is not France so we can test them on any dev instance. + if (defined('CERTIF_LNE') && (int) constant('CERTIF_LNE') === 2) { + return true; + } + if (preg_match('/\-/', DOL_VERSION)) { // This is not a stable version + return false; + } + if ($mysoc->country_code != 'FR') { + return false; + } + if (!defined('CERTIF_LNE') || (int) constant('CERTIF_LNE') === 0) { + return false; + } + if (!isModEnabled('blockedlog')) { + return false; + } + + return true; // all conditions are ok +} diff --git a/htdocs/compta/facture/list.php b/htdocs/compta/facture/list.php index 6413de08d75..a337ff90677 100644 --- a/htdocs/compta/facture/list.php +++ b/htdocs/compta/facture/list.php @@ -335,10 +335,19 @@ foreach ($object->fields as $key => $val) { // Extra fields include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_array_fields.tpl.php'; +// For POS context, we force some fields +if ($contextpage == 'poslist') { + $arrayfields["f.pos_source"]['checked'] = 1; + $arrayfields["f.date_lim_reglement"]['checked'] = 0; + $arrayfields["f.total_ttc"]['checked'] = 1; + $arrayfields["f.tms"]['checked'] = 1; + $arrayfields["p.ref"]['checked'] = 0; +} $object->fields = dol_sort_array($object->fields, 'position'); $arrayfields = dol_sort_array($arrayfields, 'position'); + // Check only if it's an internal user, external users are already filtered by $socid if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) { $search_sale = $user->id; diff --git a/htdocs/core/class/cemailtemplate.class.php b/htdocs/core/class/cemailtemplate.class.php new file mode 100644 index 00000000000..f40791085e4 --- /dev/null +++ b/htdocs/core/class/cemailtemplate.class.php @@ -0,0 +1,577 @@ + + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2010-2011 Juanjo Menent + * Copyright (C) 2015-2017 Marcos García + * Copyright (C) 2015-2017 Nicolas ZABOURI + * Copyright (C) 2018-2024 Frédéric France + * Copyright (C) 2022 Charlene Benke + * Copyright (C) 2023 Anthony Berton + * Copyright (C) 2024-2025 MDW + * Copyright (C) 2025 Jon Bendtsen + * + * 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 + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php'; + + +/** + * Object of table llx_c_email_templates + */ +class CEmailTemplate extends CommonObject +{ + const TRIGGER_PREFIX = 'EMAILTEMPLATE'; + /** + * @var string ID to identify managed object. + */ + public $element = 'email_template'; + + /** + * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management (so extrafields know the link to the parent table). + */ + public $table_element = 'c_email_templates'; + + + // BEGIN MODULEBUILDER PROPERTIES + /** + * @var array|string,position:int,notnull?:int,visible:int<-6,6>|string,alwayseditable?:int<0,1>,noteditable?:int<0,1>,default?:string,index?:int,foreignkey?:string,searchall?:int<0,1>,isameasure?:int<0,1>,css?:string,csslist?:string,help?:string,showoncombobox?:int<0,4>,disabled?:int<0,1>,arrayofkeyval?:array,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>,showonheader?:int<0,1>}> Array with all fields and their property. Do not use it as a static var. It may be modified by constructor. + */ + public $fields = array( + "rowid" => array("type" => "integer", "label" => "TechnicalID", 'enabled' => 1, 'position' => 10, 'notnull' => 1, 'visible' => -1,), + "module" => array("type" => "varchar(32)", "label" => "Module", 'enabled' => 1, 'position' => 20, 'notnull' => 0, 'visible' => -1,), + "type_template" => array("type" => "varchar(32)", "label" => "Typetemplate", 'enabled' => 1, 'position' => 25, 'notnull' => 0, 'visible' => -1,), + "lang" => array("type" => "varchar(6)", "label" => "Lang", 'enabled' => 1, 'position' => 30, 'notnull' => 0, 'visible' => -1,), + "private" => array("type" => "smallint(6)", "label" => "Private", 'enabled' => 1, 'position' => 35, 'notnull' => 1, 'visible' => -1,), + "fk_user" => array("type" => "integer:User:user/class/user.class.php", "label" => "Fkuser", 'enabled' => 1, 'position' => 40, 'notnull' => 0, 'visible' => -1, "css" => "maxwidth500 widthcentpercentminusxx", "csslist" => "tdoverflowmax150",), + "datec" => array("type" => "datetime", "label" => "DateCreation", 'enabled' => 1, 'position' => 45, 'notnull' => 0, 'visible' => -1,), + "tms" => array("type" => "timestamp", "label" => "DateModification", 'enabled' => 1, 'position' => 50, 'notnull' => 1, 'visible' => -1,), + "label" => array("type" => "varchar(255)", "label" => "Label", 'enabled' => 1, 'position' => 55, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1, "css" => "minwidth300", "cssview" => "wordbreak", "csslist" => "tdoverflowmax150",), + "position" => array("type" => "smallint(6)", "label" => "Position", 'enabled' => 1, 'position' => 60, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + "active" => array("type" => "integer", "label" => "Active", 'enabled' => 1, 'position' => 65, 'notnull' => 1, 'visible' => -1, 'alwayseditable' => 1,), + "topic" => array("type" => "text", "label" => "Topic", 'enabled' => 1, 'position' => 70, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + "content" => array("type" => "mediumtext", "label" => "Content", 'enabled' => 1, 'position' => 75, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + "content_lines" => array("type" => "text", "label" => "Contentlines", "enabled" => "getDolGlobalString('MAIN_EMAIL_TEMPLATES_FOR_OBJECT_LINES')", 'position' => 80, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + "enabled" => array("type" => "varchar(255)", "label" => "Enabled", 'enabled' => 1, 'position' => 85, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + "joinfiles" => array("type" => "varchar(255)", "label" => "Joinfiles", 'enabled' => 1, 'position' => 90, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + "email_from" => array("type" => "varchar(255)", "label" => "Emailfrom", 'enabled' => 1, 'position' => 95, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + "email_to" => array("type" => "varchar(255)", "label" => "Emailto", 'enabled' => 1, 'position' => 100, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + "email_tocc" => array("type" => "varchar(255)", "label" => "Emailtocc", 'enabled' => 1, 'position' => 105, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + "email_tobcc" => array("type" => "varchar(255)", "label" => "Emailtobcc", 'enabled' => 1, 'position' => 110, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + "defaultfortype" => array("type" => "smallint(6)", "label" => "Defaultfortype", 'enabled' => 1, 'position' => 115, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), + ); + /** + * @var int + */ + public $rowid; + /** + * @var string type of the template + */ + public $type_template; + /** + * @var int|string + */ + public $datec; + /** + * @var int + */ + public $tms; + /** + * @var int + */ + public $active; + /** + * @var string if 0 hidden from GUI, if 1 visible in GUI + */ + public $enabled; + /** + * @var int is the template a default or not + */ + public $defaultfortype; + + /** + * @var int ID + */ + public $id; + + /** + * @var string Model mail label + */ + public $label; + + /** + * @var int Owner of email template + */ + public $fk_user; + + /** + * @var int Is template private + */ + public $private; + + /** + * @var string Model mail topic + */ + public $topic; + + /** + * @var string Model mail content + */ + public $content; + /** + * @var string Model to use to generate the string with each lines + */ + public $content_lines; + + /** + * @var string language of the template + */ + public $lang; + /** + * @var int<0,1> + */ + public $joinfiles; + + /** + * @var string sender email address + */ + public $email_from; + + /** + * @var string recipient email address + */ + public $email_to; + + /** + * @var string Additional visible recipients + */ + public $email_tocc; + + /** + * @var string additional hidden recipients + */ + public $email_tobcc; + + /** + * @var string Module the template is dedicated for + */ + public $module; + + /** + * @var int Position of template in a combo list + */ + public $position; + // END MODULEBUILDER PROPERTIES + + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct(DoliDB $db) + { + global $langs; + + $this->db = $db; + $this->ismultientitymanaged = 1; + $this->isextrafieldmanaged = 1; + + // @phan-suppress-next-line PhanTypeMismatchProperty + if (!getDolGlobalInt('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid']) && !empty($this->fields['ref'])) { + $this->fields['rowid']['visible'] = 0; + } + if (!isModEnabled('multicompany') && isset($this->fields['entity'])) { + $this->fields['entity']['enabled'] = 0; + } + + // Example to show how to set values of fields definition dynamically + /*if ($user->hasRight('test', 'mailtemplate', 'read')) { + $this->fields['myfield']['visible'] = 1; + $this->fields['myfield']['noteditable'] = 0; + }*/ + + // Unset fields that are disabled + foreach ($this->fields as $key => $val) { + if (isset($val['enabled']) && empty($val['enabled'])) { + unset($this->fields[$key]); + } + } + + // Translate some data of arrayofkeyval + if (is_object($langs)) { + foreach ($this->fields as $key => $val) { + if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) { + foreach ($val['arrayofkeyval'] as $key2 => $val2) { + $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2); + } + } + } + } + } + + /** + * Create email template + * Required fields: label, type_template, topic + * + * @param User $user Object user that make creation + * @param int $notrigger Disable all triggers + * @return int Return integer <0 if KO, >0 if OK + */ + public function create($user, $notrigger = 0) + { + global $conf; + $error = 0; + + dol_syslog(get_class($this)."::create user=".$user->id); + + // Check parameters + if (!empty($this->label)) { // We check that label is not already used + $result = $this->isExistingObject($this->element, 0, $this->label); // Check label is not yet used + if ($result > 0) { + $this->error = 'ErrorLabelAlreadyExists'; + dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING); + $this->db->rollback(); + return -1; + } + } + + $now = dol_now(); + + $this->db->begin(); + + $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (entity,"; + $sql .= " module, type_template, lang, private, fk_user, datec, label,"; + $sql .= " position, defaultfortype, enabled, active, email_from, email_to,"; + $sql .= " email_tocc, email_tobcc, topic, joinfiles, content, content_lines)"; + $sql .= " VALUES ("; + $sql .= " ".((int) $conf->entity).","; + if (is_null($this->module)) { + $sql .= " NULL,"; + } else { + $sql .= " '".$this->db->escape($this->module)."',"; + } + $sql .= " '".$this->db->escape($this->type_template)."',"; + if (is_null($this->lang)) { + $sql .= " NULL,"; + } else { + $sql .= " '".$this->db->escape($this->lang)."',"; + } + $sql .= " ".((int) $this->private).","; + if (is_null($this->fk_user)) { + $sql .= " NULL,"; + } else { + $sql .= " '".((int) $this->fk_user)."',"; + } + if (is_null($this->datec)) { + $sql .= " NULL,"; + } else { + $sql .= " ".((int) $this->datec).","; + } + $sql .= " '".$this->db->escape($this->label)."',"; + $sql .= " ".((int) $this->position).", ".((int) $this->defaultfortype ).","; + if (is_null($this->enabled)) { + $sql .= " 1,"; + } else { + $sql .= " '".((int) $this->enabled)."',"; + } + if (is_null($this->active)) { + $sql .= " 1,"; + } else { + $sql .= " '".((int) $this->active)."',"; + } + if (is_null($this->email_from)) { + $sql .= " NULL,"; + } else { + $sql .= " '".$this->db->escape($this->email_from)."',"; + } + if (is_null($this->email_to)) { + $sql .= " NULL,"; + } else { + $sql .= " '".$this->db->escape($this->email_to)."',"; + } + if (is_null($this->email_tocc)) { + $sql .= " NULL,"; + } else { + $sql .= " '".$this->db->escape($this->email_tocc)."',"; + } + if (is_null($this->email_tobcc)) { + $sql .= " NULL,"; + } else { + $sql .= " '".$this->db->escape($this->email_tobcc)."',"; + } + $sql .= " '".$this->db->escape($this->topic)."',"; + $sql .= " ".((int) $this->joinfiles).","; + if (is_null($this->content)) { + $sql .= " NULL,"; + } else { + $sql .= " '".((string) $this->db->escape($this->content))."',"; + } + if (is_null($this->content_lines)) { + $sql .= " NULL"; + } else { + $sql .= " '".((string) $this->db->escape($this->content_lines))."'"; + } + $sql .= ")"; + + + dol_syslog(get_class($this)."::create", LOG_DEBUG); + $resql = $this->db->query($sql); + if ($resql) { + $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element); + + if (!$notrigger) { + // Call trigger + $result = $this->call_trigger(self::TRIGGER_PREFIX.'_CREATE', $user); + if ($result < 0) { + $error++; + } + // End call triggers + } + + if (!$error) { + $this->db->commit(); + return $this->id; + } else { + $this->db->rollback(); + return -1 * $error; + } + } else { + $this->error = $this->db->lasterror(); + $this->db->rollback(); + return -1; + } + } + + /** + * Update database with changed email template + * + * @param User $user User that modify + * @param int $notrigger 0=launch triggers after, 1=disable triggers + * @return int Return integer <0 if KO, >0 if OK + */ + public function update(User $user, $notrigger = 0) + { + $error = 0; + + // Update request + $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET"; + $sql .= " module=".($this->module ? "'".$this->db->escape($this->module)."', " : 'NULL, '); + $sql .= " type_template=".($this->type_template ? "'".$this->db->escape($this->type_template)."', " : 'NULL, '); + $sql .= " lang=".($this->lang ? "'".$this->db->escape($this->lang)."', " : 'NULL, '); + $sql .= " private=".((int) $this->private).","; + $sql .= " fk_user=".((int) $this->fk_user).","; + $sql .= " datec=".((int) $this->datec).","; + $sql .= " label=".($this->label ? "'".$this->db->escape($this->label)."', " : 'NULL, '); + $sql .= " position=".((int) $this->position).","; + $sql .= " defaultfortype=".((int) $this->defaultfortype).","; + $sql .= " enabled=".($this->enabled ? "'".$this->db->escape($this->enabled)."', " : 'NULL, '); + $sql .= " active=".((int) $this->active).","; + $sql .= " email_from=".($this->email_from ? "'".$this->db->escape($this->email_from)."', " : 'NULL, '); + $sql .= " email_to=".($this->email_to ? "'".$this->db->escape($this->email_to)."', " : 'NULL, '); + $sql .= " email_tocc=".($this->email_tocc ? "'".$this->db->escape($this->email_tocc)."', " : 'NULL, '); + $sql .= " email_tobcc=".($this->email_tobcc ? "'".$this->db->escape($this->email_tobcc)."', " : 'NULL, '); + $sql .= " topic=".($this->topic ? "'".$this->db->escape($this->topic)."', " : 'NULL, '); + $sql .= " joinfiles=".((int) $this->joinfiles).","; + $sql .= " content=".($this->content ? "'".$this->db->escape($this->content)."', " : 'NULL, '); + $sql .= " content_lines=".($this->content_lines ? "'".$this->db->escape($this->content_lines)."'" : 'NULL'); + $sql .= " WHERE rowid=".((int) $this->id); + + $this->db->begin(); + + dol_syslog(get_class($this)."::update", LOG_DEBUG); + $resql = $this->db->query($sql); + if (!$resql) { + $error++; + $this->errors[] = "Error ".$this->db->lasterror(); + } + + if (!$error && !$notrigger) { + // Call trigger + $result = $this->call_trigger(self::TRIGGER_PREFIX.'_MODIFY', $user); + if ($result < 0) { + $error++; + } + // End call triggers + } + + // Commit or rollback + if ($error) { + foreach ($this->errors as $errmsg) { + dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR); + $this->error .= ($this->error ? ', '.$errmsg : $errmsg); + } + $this->db->rollback(); + return -1 * $error; + } else { + $this->db->commit(); + return 1; + } + } + + /** + * Delete the email template + * + * @param User $user User object + * @param int $notrigger 1=Does not execute triggers, 0= execute triggers + * @return int Return integer <=0 if KO, >0 if OK + */ + public function delete($user, $notrigger = 0) + { + $error = 0; + + dol_syslog(get_class($this)."::delete ".$this->id, LOG_DEBUG); + + $this->db->begin(); + + if (!$notrigger) { + // Call trigger + $result = $this->call_trigger(self::TRIGGER_PREFIX.'_DELETE', $user); + if ($result < 0) { + $error++; + } + // End call triggers + } + + // Delete object link + if (!$error) { + $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id); + $res = $this->db->query($sql); + if (!$res) { + $error++; + $this->error = $this->db->lasterror(); + $this->errors[] = $this->error; + dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR); + } + } + + if (!$error) { + dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG); + $this->db->commit(); + return 1; + } else { + $this->db->rollback(); + return -1; + } + } + + /** + * Load object in memory from the database + * + * @param int $id Id object + * @param string $label Ref + * @param int $noextrafields 0=Default to load extrafields, 1=No extrafields + * @param int $nolines 0=Default to load extrafields, 1=No extrafields + * @return int Return integer <0 if KO, 0 if not found, >0 if OK + */ + public function fetch($id, $label = null, $noextrafields = 0, $nolines = 0) + { + // The table llx_c_email_templates has no field ref. The field ref was named "label" instead. So we change the call to fetchCommon. + //$result = $this->fetchCommon($id, $label, '', $noextrafields); + $result = $this->fetchCommon($id, '', " AND t.label = '".$this->db->escape($label)."'", $noextrafields); + + if ($result > 0 && !empty($this->table_element_line) && empty($nolines)) { + $this->fetchLines($noextrafields); + } + return $result; + } + + /** + * Get email template from database. + * + * @param int $id row Id of email template + * @param string $label label of email template + * @return int >0 if OK, <0 if KO, 0 if not found + */ + public function apifetch($id, $label = '') + { + // Check parameters + if (($id ==0 || empty($id)) && empty($label)) { + dol_syslog(get_class($this)."::apifetch id and label are empty", LOG_DEBUG); + $this->error = 'id='.$id.' and label are empty'; + return -1; + } + + $sql = "SELECT e.rowid, e.entity, e.module, e.type_template, e.lang,"; + $sql .= " e.private, e.fk_user, e.datec, e.tms, e.label, e.position,"; + $sql .= " e.defaultfortype, e.enabled, e.active, e.email_from, e.email_to,"; + $sql .= " e.email_tocc, e.email_tobcc, e.topic, e.joinfiles, e.content,"; + $sql .= " e.content_lines FROM ".$this->db->prefix().$this->table_element." as e"; + if ($id) { + $sql .= " WHERE e.rowid = ".((int) $id); + } else { + $sql .= " WHERE e.entity IN (".getEntity($this->table_element).")"; + if ($label) { + $sql .= " AND e.label = '".$this->db->escape($label)."'"; + } + } + + dol_syslog(get_class($this)."::apifetch", LOG_DEBUG); + $result = $this->db->query($sql); + if ($result) { + $obj = $this->db->fetch_object($result); + if ($obj) { + $this->id = (int) $obj->rowid; + $this->entity = (int) $obj->entity; + + $this->active = (int) $obj->active; + $this->content = (string) $obj->content; + $this->content_lines = (string) $obj->content_lines; + $this->datec = (int) $obj->datec; + $this->defaultfortype = (int) $obj->defaultfortype; + $this->email_from = (string) $obj->email_from; + $this->email_to = (string) $obj->email_to; + $this->email_tobcc = (string) $obj->email_tobcc; + $this->email_tocc = (string) $obj->email_tocc; + $this->enabled = (string) $obj->enabled; + $this->fk_user = (int) $obj->fk_user; + $this->joinfiles = (int) $obj->joinfiles; + $this->label = (string) $obj->label; + $this->lang = (string) $obj->lang; + $this->module = (string) $obj->module; + $this->position = (int) $obj->position; + $this->private = (int) $obj->private; + $this->tms = $obj->tms; + $this->topic = (string) $obj->topic; + $this->type_template = (string) $obj->type_template; + + // direct copy from facture.class.php + $this->date_creation = $this->db->jdate($obj->datec); + + return 1; + } else { + if ($id) { + $this->error = 'Email template with id '.((string) $id).' not found sql='.$sql; + } elseif ($label) { + $this->error = 'Email template with label '.$label.' not found sql='.$sql; + } + return 0; + } + } else { + $this->error = $this->db->error(); + return -1; + } + } +} + +/** + * Old class name for Object of table llx_c_email_templates + * I prefer the CEmailTemplate name as it better reflects the database + * + * @deprecated Use now class CEmailTemplate + */ +class ModelMail extends CEmailTemplate +{ + // just another name for compatibility +} diff --git a/htdocs/core/class/conf.class.php b/htdocs/core/class/conf.class.php index feead12ec7d..4e65c73f22d 100644 --- a/htdocs/core/class/conf.class.php +++ b/htdocs/core/class/conf.class.php @@ -75,11 +75,6 @@ class Conf extends stdClass */ public $multicompany; - //! To store module status of special module names - /** - * @var ?mixed - */ - public $expedition_bon; /** * @var ?mixed */ diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index b9b4fc39947..cec8b26ecae 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -27,9 +27,10 @@ /** * \file htdocs/core/class/html.formmail.class.php * \ingroup core - * \brief Fichier de la class permettant la generation du formulaire html d'envoi de mail unitaire + * \brief File of class to generate HTML form to send email of unitary email. */ require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/cemailtemplate.class.php'; // So the class ModelMail that was defined into this file in old version is still available when including this file /** @@ -1457,9 +1458,9 @@ class FormMail extends Form /** * Return Html section for the Topic of message * - * @param ModelMail $arraydefaultmessage Array with message template content - * @param string $helpforsubstitution Help string for substitution - * @return string Text for topic + * @param CEmailTemplate $arraydefaultmessage Array with message template content + * @param string $helpforsubstitution Help string for substitution + * @return string Text for topic */ public function getHtmlForTopic($arraydefaultmessage, $helpforsubstitution) { @@ -1722,7 +1723,7 @@ class FormMail extends Form * @param int $active 1=Only active template, 0=Only disabled, -1=All * @param string $label Label of template to get * @param int $defaultfortype 1=Only default templates, 0=Only not default, -1=All - * @return ModelMail|int<-1,-1> One instance of ModelMail or < 0 if error + * @return ModelMail|CEmailTemplate|int<-1,-1> One instance of CEmailTemplate or < 0 if error */ public function getEMailTemplate($dbs, $type_template, $user, $outputlangs, $id = 0, $active = 1, $label = '', $defaultfortype = -1) { @@ -1735,7 +1736,7 @@ class FormMail extends Form if ($type_template === 'societe') { $type_template = 'thirdparty'; } - $ret = new ModelMail($dbs); + $ret = new CEmailTemplate($dbs); $languagetosearch = (is_object($outputlangs) ? $outputlangs->defaultlang : ''); // Define $languagetosearchmain to fall back on main language (for example to get 'es_ES' for 'es_MX') @@ -1935,7 +1936,7 @@ class FormMail extends Form } } - $line = new ModelMail($db); + $line = new CEmailTemplate($db); $line->id = (int) $obj->rowid; $line->label = (string) $obj->label; $line->lang = $obj->lang; @@ -2134,217 +2135,3 @@ class FormMail extends Form return $tmparray; } } - - -require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; - -/** - * Object of table llx_c_email_templates - * - * TODO Move this class into a file cemailtemplate.class.php - */ -class ModelMail extends CommonObject -{ - /** - * @var string ID to identify managed object. - */ - public $element = 'email_template'; - - /** - * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management (so extrafields know the link to the parent table). - */ - public $table_element = 'c_email_templates'; - - - // BEGIN MODULEBUILDER PROPERTIES - /** - * @var array|string,position:int,notnull?:int,visible:int<-6,6>|string,alwayseditable?:int<0,1>,noteditable?:int<0,1>,default?:string,index?:int,foreignkey?:string,searchall?:int<0,1>,isameasure?:int<0,1>,css?:string,csslist?:string,help?:string,showoncombobox?:int<0,4>,disabled?:int<0,1>,arrayofkeyval?:array,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>,showonheader?:int<0,1>}> Array with all fields and their property. Do not use it as a static var. It may be modified by constructor. - */ - public $fields = array( - "rowid" => array("type" => "integer", "label" => "TechnicalID", 'enabled' => 1, 'position' => 10, 'notnull' => 1, 'visible' => -1,), - "module" => array("type" => "varchar(32)", "label" => "Module", 'enabled' => 1, 'position' => 20, 'notnull' => 0, 'visible' => -1,), - "type_template" => array("type" => "varchar(32)", "label" => "Typetemplate", 'enabled' => 1, 'position' => 25, 'notnull' => 0, 'visible' => -1,), - "lang" => array("type" => "varchar(6)", "label" => "Lang", 'enabled' => 1, 'position' => 30, 'notnull' => 0, 'visible' => -1,), - "private" => array("type" => "smallint(6)", "label" => "Private", 'enabled' => 1, 'position' => 35, 'notnull' => 1, 'visible' => -1,), - "fk_user" => array("type" => "integer:User:user/class/user.class.php", "label" => "Fkuser", 'enabled' => 1, 'position' => 40, 'notnull' => 0, 'visible' => -1, "css" => "maxwidth500 widthcentpercentminusxx", "csslist" => "tdoverflowmax150",), - "datec" => array("type" => "datetime", "label" => "DateCreation", 'enabled' => 1, 'position' => 45, 'notnull' => 0, 'visible' => -1,), - "tms" => array("type" => "timestamp", "label" => "DateModification", 'enabled' => 1, 'position' => 50, 'notnull' => 1, 'visible' => -1,), - "label" => array("type" => "varchar(255)", "label" => "Label", 'enabled' => 1, 'position' => 55, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1, "css" => "minwidth300", "cssview" => "wordbreak", "csslist" => "tdoverflowmax150",), - "position" => array("type" => "smallint(6)", "label" => "Position", 'enabled' => 1, 'position' => 60, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - "active" => array("type" => "integer", "label" => "Active", 'enabled' => 1, 'position' => 65, 'notnull' => 1, 'visible' => -1, 'alwayseditable' => 1,), - "topic" => array("type" => "text", "label" => "Topic", 'enabled' => 1, 'position' => 70, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - "content" => array("type" => "mediumtext", "label" => "Content", 'enabled' => 1, 'position' => 75, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - "content_lines" => array("type" => "text", "label" => "Contentlines", "enabled" => "getDolGlobalString('MAIN_EMAIL_TEMPLATES_FOR_OBJECT_LINES')", 'position' => 80, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - "enabled" => array("type" => "varchar(255)", "label" => "Enabled", 'enabled' => 1, 'position' => 85, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - "joinfiles" => array("type" => "varchar(255)", "label" => "Joinfiles", 'enabled' => 1, 'position' => 90, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - "email_from" => array("type" => "varchar(255)", "label" => "Emailfrom", 'enabled' => 1, 'position' => 95, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - "email_to" => array("type" => "varchar(255)", "label" => "Emailto", 'enabled' => 1, 'position' => 100, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - "email_tocc" => array("type" => "varchar(255)", "label" => "Emailtocc", 'enabled' => 1, 'position' => 105, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - "email_tobcc" => array("type" => "varchar(255)", "label" => "Emailtobcc", 'enabled' => 1, 'position' => 110, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - "defaultfortype" => array("type" => "smallint(6)", "label" => "Defaultfortype", 'enabled' => 1, 'position' => 115, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,), - ); - /** - * @var int - */ - public $rowid; - /** - * @var string - */ - public $type_template; - /** - * @var int|string - */ - public $datec; - /** - * @var int - */ - public $tms; - /** - * @var int - */ - public $active; - /** - * @var string - */ - public $enabled; - /** - * @var int - */ - public $defaultfortype; - - /** - * @var int ID - */ - public $id; - - /** - * @var string Model mail label - */ - public $label; - - /** - * @var int Owner of email template - */ - public $fk_user; - - /** - * @var int Is template private - */ - public $private; - - /** - * @var string Model mail topic - */ - public $topic; - - /** - * @var string Model mail content - */ - public $content; - /** - * @var string Model to use to generate the string with each lines - */ - public $content_lines; - - /** - * @var string - */ - public $lang; - /** - * @var int<0,1> - */ - public $joinfiles; - - /** - * @var string - */ - public $email_from; - /** - * @var string - */ - public $email_to; - /** - * @var string - */ - public $email_tocc; - /** - * @var string - */ - public $email_tobcc; - - /** - * @var string Module the template is dedicated for - */ - public $module; - - /** - * @var int Position of template in a combo list - */ - public $position; - // END MODULEBUILDER PROPERTIES - - - - /** - * Constructor - * - * @param DoliDB $db Database handler - */ - public function __construct(DoliDB $db) - { - global $langs; - - $this->db = $db; - $this->ismultientitymanaged = 0; - $this->isextrafieldmanaged = 1; - - // @phan-suppress-next-line PhanTypeMismatchProperty - if (!getDolGlobalInt('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid']) && !empty($this->fields['ref'])) { - $this->fields['rowid']['visible'] = 0; - } - if (!isModEnabled('multicompany') && isset($this->fields['entity'])) { - $this->fields['entity']['enabled'] = 0; - } - - // Example to show how to set values of fields definition dynamically - /*if ($user->hasRight('test', 'mailtemplate', 'read')) { - $this->fields['myfield']['visible'] = 1; - $this->fields['myfield']['noteditable'] = 0; - }*/ - - // Unset fields that are disabled - foreach ($this->fields as $key => $val) { - if (isset($val['enabled']) && empty($val['enabled'])) { - unset($this->fields[$key]); - } - } - - // Translate some data of arrayofkeyval - if (is_object($langs)) { - foreach ($this->fields as $key => $val) { - if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) { - foreach ($val['arrayofkeyval'] as $key2 => $val2) { - $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2); - } - } - } - } - } - - - /** - * Load object in memory from the database - * - * @param int $id Id object - * @param string $ref Ref - * @param int $noextrafields 0=Default to load extrafields, 1=No extrafields - * @return int Return integer <0 if KO, 0 if not found, >0 if OK - */ - public function fetch($id, $ref = null, $noextrafields = 0) - { - // The table llx_c_email_templates has no field ref. The field ref was named "label" instead. So we change the call to fetchCommon. - // $result = $this->fetchCommon($id, $ref, '', $noextrafields); - - return $this->fetchCommon($id, '', (empty($ref) ? '' : " AND t.label = '".$this->db->escape($ref)."'"), $noextrafields); - } -} diff --git a/htdocs/core/class/html.formsetup.class.php b/htdocs/core/class/html.formsetup.class.php index db4470485d9..79671d6a10c 100644 --- a/htdocs/core/class/html.formsetup.class.php +++ b/htdocs/core/class/html.formsetup.class.php @@ -911,10 +911,11 @@ class FormSetupItem } elseif ($this->type == 'yesno') { if (!empty($conf->use_javascript_ajax)) { $input = $this->fieldParams['input'] ?? array(); - $revertonoff = !empty($this->fieldParams['revertonoff']) ? 1 : 0; - $forcereload = !empty($this->fieldParams['forcereload']) ? 1 : 0; + $revertonoff = empty($this->fieldParams['revertonoff']) ? 0 : 1; + $forcereload = empty($this->fieldParams['forcereload']) ? 0 : 1; + $suffixarray = array('ifoff' => empty($this->fieldParams['alertifoff']) ? '' : '_red', 'ifon' => empty($this->fieldParams['alertifon']) ? '' : '_red'); - $out .= ajax_constantonoff($this->confKey, $input, $this->entity, $revertonoff, 0, $forcereload); + $out .= ajax_constantonoff($this->confKey, $input, $this->entity, $revertonoff, 0, $forcereload, 2, 0, 0, $suffixarray); } else { $out .= $this->form->selectyesno($this->confKey, $this->fieldValue, 1); } diff --git a/htdocs/core/class/ldap.class.php b/htdocs/core/class/ldap.class.php index a5ba2eb809c..869cbbfcea5 100644 --- a/htdocs/core/class/ldap.class.php +++ b/htdocs/core/class/ldap.class.php @@ -835,7 +835,6 @@ class Ldap /** * Build an LDAP message * - * @see dump_content renamed * @param string $dn DN entry key * @param array $info Attributes array * @return string Content of file @@ -1358,8 +1357,8 @@ class Ldap * Do not use for search of a given properties list because of upper-lower case conflict. * Only use for pages. * 'Fiche LDAP' shows readable fields by default. - * @see bind - * @see bindauth + * @see bind() + * @see bindauth() * * @param string $checkDn Search DN (Ex: ou=users,cn=my-domain,cn=com) * @param string $filter Search filter (ex: (sn=name_person) ) diff --git a/htdocs/core/lib/ajax.lib.php b/htdocs/core/lib/ajax.lib.php index b932a9a1433..fd3dfb0727e 100644 --- a/htdocs/core/lib/ajax.lib.php +++ b/htdocs/core/lib/ajax.lib.php @@ -662,24 +662,25 @@ function ajax_event($htmlname, $events) /** * On/off button for constant * - * @param string $code Name of constant - * @param array $input It's array of complementary actions to do if success ("disabled"|"enabled'|'set'|'del') => CSS element to switch, 'alert' => message to show, ... Example: array('disabled'=>array(0=>'cssid')) - * @param ?int $entity Entity. Current entity is used if null. - * @param int<0,1> $revertonoff 1 = Revert on/off - * @param int<0,1> $strict 0 = Default, 1=Only the complementary actions "disabled" and "enabled" (found into $input) are processed. Use only "disabled" with delConstant and "enabled" with setConstant. - * @param int $forcereload Force to reload page if we click/change value (this is supported only when there is no 'alert' option in input) - * @param int<0,2> $marginleftonlyshort 1 = Add a short left margin on picto, 2 = Add a larger left margin on picto, 0 = No left margin. - * @param int<0,1> $forcenoajax 1 = Force to use a ahref link instead of ajax code. - * @param int<0,1> $setzeroinsteadofdel 1 = Set constant to '0' instead of deleting it when $input is empty. - * @param string $suffix Suffix to use on the name of the switch picto when option is on. Example: '', '_red' - * @param string $mode Add parameter &mode= to the href link (Used for href link) - * @param string $morecss More CSS - * @param User|int $userconst If set, use the ajax On/Off for user or user ID $userconst - * @param string $showwarning String to show a warning when enabled the option - * @return string + * @param string $code Name of constant + * @param array $input It's array of complementary actions to do if success ("disabled"|"enabled'|'set'|'del') => CSS element to switch, 'alert' => message to show, ... Example: array('disabled'=>array(0=>'cssid')) + * @param ?int $entity Entity. Current entity is used if null. + * @param int<0,1> $revertonoff 1 = Revert on/off + * @param int<0,1> $strict 0 = Default, 1=Only the complementary actions "disabled" and "enabled" (found into $input) are processed. Use only "disabled" with delConstant and "enabled" with setConstant. + * @param int $forcereload Force to reload page if we click/change value (this is supported only when there is no 'alert' option in input) + * @param int<0,2> $marginleftonlyshort 1 = Add a short left margin on picto, 2 = Add a larger left margin on picto, 0 = No left margin. + * @param int<0,1> $forcenoajax 1 = Force to use a ahref link instead of ajax code. + * @param int<0,1> $setzeroinsteadofdel 1 = Set constant to '0' instead of deleting it when $input is empty. + * @param string|array $suffix Suffix to use on the name of the switch picto when option is on. Example: array('ifoff' => '_red', 'ifon' => '_green') + * @param string $mode Add parameter &mode= to the href link (Used for href link) + * @param string $morecss More CSS. Example: 'inline-block reposition' + * @param User|int $userconst If set, use the ajax On/Off for user or user ID $userconst + * @param string $showwarning String to show a warning when enabled the option + * @param int<0,1> $disabled If component must be disabled + * @return string The HTML component of button * @see ajax_object_onoff() to update the status of an object */ -function ajax_constantonoff($code, $input = array(), $entity = null, $revertonoff = 0, $strict = 0, $forcereload = 0, $marginleftonlyshort = 2, $forcenoajax = 0, $setzeroinsteadofdel = 0, $suffix = '', $mode = '', $morecss = 'inline-block', $userconst = 0, $showwarning = '') +function ajax_constantonoff($code, $input = array(), $entity = null, $revertonoff = 0, $strict = 0, $forcereload = 0, $marginleftonlyshort = 2, $forcenoajax = 0, $setzeroinsteadofdel = 0, $suffix = '', $mode = '', $morecss = 'inline-block', $userconst = 0, $showwarning = '', $disabled = 0) { global $conf, $langs, $user, $db; @@ -688,6 +689,8 @@ function ajax_constantonoff($code, $input = array(), $entity = null, $revertonof $input = array(); } + $out = ''; + if (empty($conf->use_javascript_ajax) || $forcenoajax) { if (!getDolGlobalString($code)) { $out = '
'.img_picto($langs->trans("Disabled"), 'off').''; @@ -704,61 +707,74 @@ function ajax_constantonoff($code, $input = array(), $entity = null, $revertonof $userconst->fetch($userconstid); } - $out = "\n".' - '."\n"; + '."\n"; + } if (!empty($userconst) && $userconst instanceof User) { $value = getDolUserString($code, '', $userconst); } else { $value = getDolGlobalString($code); } + + if (is_array($suffix)) { + $suffixon = $suffix['ifon']; + $suffixoff = $suffix['ifoff']; + } else { // old mode deprecated + $suffixon = (string) $suffix; + $suffixoff = ''; + } + $out .= ''; - $out .= ''.($revertonoff ? img_picto($langs->trans("Enabled"), 'switch_on', '', 0, 0, 0, '', '', $marginleftonlyshort) : img_picto($langs->trans("Disabled"), 'switch_off', '', 0, 0, 0, '', '', $marginleftonlyshort)).''; - $out .= ''.($revertonoff ? img_picto($langs->trans("Disabled"), 'switch_off'.$suffix, '', 0, 0, 0, '', '', $marginleftonlyshort) : img_picto($langs->trans("Enabled"), 'switch_on'.$suffix, '', 0, 0, 0, '', '', $marginleftonlyshort)).''; + $out .= ''.($revertonoff ? img_picto($langs->trans("Enabled"), 'switch_on'.$suffixoff, '', 0, 0, 0, '', '', $marginleftonlyshort) : img_picto($langs->trans("Disabled"), 'switch_off'.$suffixoff, '', 0, 0, 0, '', '', $marginleftonlyshort)).''; + $out .= ''.($revertonoff ? img_picto($langs->trans("Disabled"), 'switch_off'.$suffixon, '', 0, 0, 0, '', '', $marginleftonlyshort) : img_picto($langs->trans("Enabled"), 'switch_on'.$suffixon, '', 0, 0, 0, '', '', $marginleftonlyshort)).''; $out .= "\n"; } diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 770b9e8758e..e06cd2efe20 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -25,6 +25,7 @@ * Copyright (C) 2024 Lenin Rivas * Copyright (C) 2024 Josep Lluís Amador Teruel * Copyright (C) 2024 Benoît PASCAL + * Copyright (C) 2025 Vincent Maury * * 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 @@ -5563,6 +5564,8 @@ function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srco 'switch_on_grey', 'switch_on_red', 'switch_off', + 'switch_off_grey', + 'switch_off_red', 'uparrow', '1uparrow', '1downarrow', @@ -5620,6 +5623,8 @@ function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srco 'switch_on' => 'font-status4', 'switch_on_warning' => 'font-status4 warning', 'switch_on_red' => 'font-status8', + 'switch_off_warning' => 'font-status4 warning', + 'switch_off_red' => 'font-status8', 'holiday' => 'infobox-holiday', 'info' => 'opacityhigh', 'info_black' => 'font-status1', @@ -5896,6 +5901,9 @@ function getImgPictoConv($mode = 'fa') 'movement' => 'people-carry', 'sign-out' => 'sign-out-alt', 'switch_off' => 'toggle-off', + 'switch_off_grey' => 'toggle-off', + 'switch_off_warning' => 'toggle-off', + 'switch_off_red' => 'toggle-off', 'switch_on' => 'toggle-on', 'switch_on_grey' => 'toggle-on', 'switch_on_warning' => 'toggle-on', @@ -10309,6 +10317,27 @@ function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, /** @var FactureFournisseur $object */ $substitutionarray['__URL_SUPPLIER_INVOICE__'] = DOL_MAIN_URL_ROOT . "/fourn/facture/card.php?id=" . $object->id; } + if (is_object($object) && $object->element == 'payment_supplier') { + '@phan-var-force PaiementFourn $object'; + /** @var PaiementFourn $object */ + //print_r($object); + $liste_factures = []; + $total = 0; + + $sql = 'SELECT f.ref,f.multicurrency_code as f_mccode, pf.* + FROM '.MAIN_DB_PREFIX.'paiementfourn_facturefourn as pf + JOIN '.MAIN_DB_PREFIX.'facture_fourn as f ON pf.fk_facturefourn = f.rowid + WHERE pf.fk_paiementfourn = '.((int) $object->id); + + $resql = $db->query($sql); + if ($resql) { + while ($objp = $db->fetch_object($resql)) { + $liste_factures[] = ' - '.$outputlangs->trans('Invoice').' '. $objp->ref.' '.$outputlangs->trans('AmountPayed').' '.price($objp->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $objp->multicurrency_code); + } + } + $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_LIST__'] = implode("\n", $liste_factures);; + $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_TOTAL__'] = price($object->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $object->multicurrency_code ? $object->multicurrency_code : $conf->currency); + } if (is_object($object) && $object->element == 'shipping') { '@phan-var-force Expedition $object'; /** @var Expedition $object */ @@ -11295,10 +11324,10 @@ function dol_osencode($str) * * @param DoliDB $db Database handler * @param string|int $key Code (string) or Id (int) to get Id or Code - * @param string $tablename Table name without prefix + * @param string $tablename Table name without prefix. Example 'c_input_method' * @param string $fieldkey Field to search the key into * @param string $fieldid Field to get - * @param int $entityfilter Filter by entity + * @param int $entityfilter 1=Filter by entity * @param string $filters Filters to add. WARNING: string must be escaped for SQL and not coming from user input. * @param bool $useCache If true (default), cache will be queried and updated. * @return int<-1,max>|string ID of code if OK, 0 if key empty, -1 if KO diff --git a/htdocs/core/lib/functions2.lib.php b/htdocs/core/lib/functions2.lib.php index 8aa1965e5ad..a094248fbd0 100644 --- a/htdocs/core/lib/functions2.lib.php +++ b/htdocs/core/lib/functions2.lib.php @@ -2068,7 +2068,7 @@ function getSoapParams() * Return link url to an object * * @param int $objectid Id of record - * @param string $objecttype Type of object ('invoice', 'order', 'expedition_bon', 'user', 'myobject@mymodule', ...) + * @param string $objecttype Type of object ('invoice', 'order', 'expedition', 'user', 'myobject@mymodule', ...) * @param int $withpicto Picto to show * @param string $option More options * @return string URL of link to object id/type @@ -2120,7 +2120,7 @@ function dolGetElementUrl($objectid, $objecttype, $withpicto = 0, $option = '') $langs->load('sendings'); $classpath = 'expedition/class'; $myobject = 'expedition'; - $module = 'expedition_bon'; + $module = 'expedition'; } elseif ($objecttype == 'delivery') { $langs->load('sendings'); $classpath = 'delivery/class'; @@ -2651,7 +2651,7 @@ function getModuleDirForApiClass($moduleobject) if ($moduleobject == 'contracts') { $moduledirforclass = 'contrat'; - } elseif (in_array($moduleobject, array('admin', 'login', 'setup', 'access', 'status', 'tools', 'documents', 'objectlinks'))) { + } elseif (in_array($moduleobject, array('admin', 'login', 'setup', 'access', 'status', 'tools', 'documents', 'objectlinks', 'emailtemplates'))) { $moduledirforclass = 'api'; } elseif (in_array($moduleobject, ['contact', 'contacts', 'customer', 'thirdparty', 'thirdparties'])) { $moduledirforclass = 'societe'; diff --git a/htdocs/core/modules/modAccounting.class.php b/htdocs/core/modules/modAccounting.class.php index 4972958721b..6dc2c754176 100644 --- a/htdocs/core/modules/modAccounting.class.php +++ b/htdocs/core/modules/modAccounting.class.php @@ -1,10 +1,10 @@ - * Copyright (C) 2013-2021 Alexandre Spangaro - * Copyright (C) 2014 Ari Elbaz (elarifr) - * Copyright (C) 2014 Florian Henry - * Copyright (C) 2016-2017 Laurent Destailleur - * Copyright (C) 2017-2021 Open-DSI +/* Copyright (C) 2013-2014 Olivier Geffroy + * Copyright (C) 2013-2025 Alexandre Spangaro + * Copyright (C) 2014 Ari Elbaz (elarifr) + * Copyright (C) 2014 Florian Henry + * Copyright (C) 2016-2017 Laurent Destailleur + * Copyright (C) 2017-2021 Open-DSI * * 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 @@ -251,8 +251,8 @@ class modAccounting extends DolibarrModules $this->export_label[$r] = 'Chartofaccounts'; $this->export_icon[$r] = $this->picto; $this->export_permission[$r] = array(array("accounting", "chartofaccount")); - $this->export_fields_array[$r] = array('ac.rowid'=>'ChartofaccountsId', 'ac.pcg_version'=>'Chartofaccounts', 'aa.rowid'=>'ID', 'aa.account_number'=>"AccountAccounting", 'aa.label'=>"Label", 'aa2.account_number'=>"Accountparent", 'aa.pcg_type'=>"Pcgtype", 'aa.active'=>'Status'); - $this->export_TypeFields_array[$r] = array('ac.rowid'=>'List:accounting_system:pcg_version', 'ac.pcg_version'=>'Text', 'aa.rowid'=>'Numeric', 'aa.account_number'=>"Text", 'aa.label'=>"Text", 'aa2.account_number'=>"Text", 'aa.pcg_type'=>'Text', 'aa.active'=>'Status'); + $this->export_fields_array[$r] = array('ac.rowid'=>'ChartofaccountsId', 'ac.pcg_version'=>'Chartofaccounts', 'aa.rowid'=>'ID', 'aa.account_number'=>"AccountAccounting", 'aa.label'=>"Label", 'aa2.account_number'=>"Accountparent", 'aa.pcg_type'=>"Pcgtype", 'aa.centralized'=>'Centralized', 'aa.active'=>'Status'); + $this->export_TypeFields_array[$r] = array('ac.rowid'=>'List:accounting_system:pcg_version', 'ac.pcg_version'=>'Text', 'aa.rowid'=>'Numeric', 'aa.account_number'=>"Text", 'aa.label'=>"Text", 'aa2.account_number'=>"Text", 'aa.pcg_type'=>'Text', 'aa.centralized'=>'Status', 'aa.active'=>'Status'); $this->export_entities_array[$r] = array(); // We define here only fields that use another picto $this->export_sql_start[$r] = 'SELECT DISTINCT '; @@ -275,14 +275,14 @@ class modAccounting extends DolibarrModules $this->import_entities_array[$r] = array(); // We define here only fields that use another icon that the one defined into import_icon $this->import_tables_array[$r] = array('aa'=>MAIN_DB_PREFIX.'accounting_account'); $this->import_tables_creator_array[$r] = array('aa'=>'fk_user_author'); // Fields to store import user id - $this->import_fields_array[$r] = array('aa.fk_pcg_version'=>"Chartofaccounts*", 'aa.account_number'=>"AccountAccounting*", 'aa.label'=>"Label*", 'aa.account_parent'=>"Accountparent", "aa.fk_accounting_category"=>"AccountingCategory", "aa.pcg_type"=>"Pcgtype*", 'aa.active'=>'Status*', 'aa.datec'=>"DateCreation"); - $this->import_regex_array[$r] = array('aa.fk_pcg_version'=>'pcg_version@'.MAIN_DB_PREFIX.'accounting_system', 'aa.account_number'=>'^.{1,32}$', 'aa.label'=>'^.{1,255}$', 'aa.account_parent'=>'^.{0,32}$', 'aa.fk_accounting_category'=>'rowid@'.MAIN_DB_PREFIX.'c_accounting_category', 'aa.pcg_type'=>'^.{1,20}$', 'aa.active'=>'^0|1$', 'aa.datec'=>'^\d{4}-\d{2}-\d{2}$'); + $this->import_fields_array[$r] = array('aa.fk_pcg_version'=>"Chartofaccounts*", 'aa.account_number'=>"AccountAccounting*", 'aa.label'=>"Label*", 'aa.account_parent'=>"Accountparent", "aa.fk_accounting_category"=>"AccountingCategory", "aa.pcg_type"=>"Pcgtype*", 'aa.centralized'=>'Centralized*', 'aa.active'=>'Status*', 'aa.datec'=>"DateCreation"); + $this->import_regex_array[$r] = array('aa.fk_pcg_version'=>'pcg_version@'.MAIN_DB_PREFIX.'accounting_system', 'aa.account_number'=>'^.{1,32}$', 'aa.label'=>'^.{1,255}$', 'aa.account_parent'=>'^.{0,32}$', 'aa.fk_accounting_category'=>'rowid@'.MAIN_DB_PREFIX.'c_accounting_category', 'aa.pcg_type'=>'^.{1,20}$', 'aa.centralized'=>'^0|1$', 'aa.active'=>'^0|1$', 'aa.datec'=>'^\d{4}-\d{2}-\d{2}$'); $this->import_convertvalue_array[$r] = array( 'aa.account_number'=>array('rule'=>'accountingaccount'), 'aa.account_parent'=>array('rule'=>'fetchidfromref', 'classfile'=>'/accountancy/class/accountingaccount.class.php', 'class'=>'AccountingAccount', 'method'=>'fetch', 'element'=>'AccountingAccount'), 'aa.fk_accounting_category'=>array('rule'=>'fetchidfromcodeorlabel', 'classfile'=>'/accountancy/class/accountancycategory.class.php', 'class'=>'AccountancyCategory', 'method'=>'fetch', 'dict'=>'DictionaryAccountancyCategory'), ); - $this->import_examplevalues_array[$r] = array('aa.fk_pcg_version'=>"PCG99-ABREGE", 'aa.account_number'=>"707", 'aa.label'=>"Product sales", 'aa.account_parent'=>"ref:7 or id:1407", "aa.fk_accounting_category"=>"", "aa.pcg_type"=>"PROD", 'aa.active'=>'1', 'aa.datec'=>"2017-04-28"); + $this->import_examplevalues_array[$r] = array('aa.fk_pcg_version'=>"PCG25-DEV", 'aa.account_number'=>"707", 'aa.label'=>"Product sales", 'aa.account_parent'=>"ref:7 or id:1407", "aa.fk_accounting_category"=>"", "aa.pcg_type"=>"PROD", 'aa.centralized'=>'0', 'aa.active'=>'1', 'aa.datec'=>"2017-04-28"); $this->import_updatekeys_array[$r] = array('aa.fk_pcg_version'=>'Chartofaccounts', 'aa.account_number'=>'AccountAccounting'); // General ledger diff --git a/htdocs/core/modules/modReceiptPrinter.class.php b/htdocs/core/modules/modReceiptPrinter.class.php index 58515087a28..dff81ef3556 100644 --- a/htdocs/core/modules/modReceiptPrinter.class.php +++ b/htdocs/core/modules/modReceiptPrinter.class.php @@ -141,11 +141,9 @@ class modReceiptPrinter extends DolibarrModules // Clean before activation $this->remove($options); - // @TODO Move create/delete into sql file and insert into data file + // @TODO Move create/delete into sql data file $templateexample = '{dol_align_center}\r\n{dol_print_text}{dol_value_mysoc_name}\r\n{dol_print_text}{dol_value_mysoc_address}\r\n{dol_print_text}{dol_value_mysoc_zip}{dol_value_mysoc_town}\r\n{dol_line_feed}\r\n{dol_print_text}Facture {dol_value_object_ref}\r\n{dol_line_feed}\r\n{dol_align_left}\r\n{dol_print_object_lines}\r\n{dol_line_feed}\r\n{dol_print_object_tax}\r\n{dol_line_feed}\r\n{dol_print_object_total}\r\n{dol_line_feed}\r\n{dol_cut_paper_full}'; $sql = array( - "CREATE TABLE IF NOT EXISTS ".MAIN_DB_PREFIX."printer_receipt (rowid integer AUTO_INCREMENT PRIMARY KEY, name varchar(128), fk_type integer, fk_profile integer, parameter varchar(128), entity integer) ENGINE=innodb;", - "CREATE TABLE IF NOT EXISTS ".MAIN_DB_PREFIX."printer_receipt_template (rowid integer AUTO_INCREMENT PRIMARY KEY, name varchar(128), template text, entity integer) ENGINE=innodb;", "DELETE FROM ".MAIN_DB_PREFIX."printer_receipt_template WHERE name = '".$this->db->escape($langs->trans('Example'))."';", "INSERT INTO ".MAIN_DB_PREFIX."printer_receipt_template (name,template,entity) VALUES ('".$this->db->escape($langs->trans('Example'))."', '".$this->db->escape($templateexample)."', 1);", ); diff --git a/htdocs/filefunc.inc.php b/htdocs/filefunc.inc.php index 072abb35ae9..980a41103bc 100644 --- a/htdocs/filefunc.inc.php +++ b/htdocs/filefunc.inc.php @@ -32,22 +32,8 @@ * \brief File that include the conf.php file and commons lib like functions.lib.php */ -// TODO Move this 2 information into a file that will be included into the LNE -if (!defined('DOL_APPLICATION_TITLE')) { - define('DOL_APPLICATION_TITLE', 'Dolibarr'); -} -if (!defined('DOL_VERSION')) { - define('DOL_VERSION', '23.0.0-alpha'); // a.b.c-alpha, a.b.c-beta, a.b.c-rcX or a.b.c -} +require_once 'version.inc.php'; -// End of common declaration part -if (defined('DOL_INC_FOR_VERSION_ERROR')) { - return; -} - -if (!defined('CERTIF_LNE')) { - define('CERTIF_LNE', '0'); // Set to 1 if version was certified -} // Define syslog constants if (!defined('LOG_DEBUG')) { diff --git a/htdocs/fourn/paiement/card.php b/htdocs/fourn/paiement/card.php index 1f3ad902fab..e58a29bdb8c 100644 --- a/htdocs/fourn/paiement/card.php +++ b/htdocs/fourn/paiement/card.php @@ -5,6 +5,7 @@ * Copyright (C) 2014 Marcos García * Copyright (C) 2024-2025 MDW * Copyright (C) 2024-2025 Frédéric France + * Copyright (C) 2025 Vincent Maury * * 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 @@ -427,7 +428,7 @@ if ($result > 0) { } // Presend form - $modelmail = ''; //TODO: Add new 'payment receipt' model in email models + $modelmail = 'supplier_payment_send'; $defaulttopic = 'SendPaymentReceipt'; $diroutput = $conf->fournisseur->payment->dir_output; $autocopy = 'MAIN_MAIL_AUTOCOPY_SUPPLIER_INVOICE_TO'; diff --git a/htdocs/install/inc.php b/htdocs/install/inc.php index 3c6e648e57f..1d28feb7797 100644 --- a/htdocs/install/inc.php +++ b/htdocs/install/inc.php @@ -30,11 +30,7 @@ */ // Just to define version DOL_VERSION -if (!defined('DOL_INC_FOR_VERSION_ERROR')) { - define('DOL_INC_FOR_VERSION_ERROR', '1'); -} -require_once '../filefunc.inc.php'; - +require_once '../version.inc.php'; // Define DOL_DOCUMENT_ROOT used for install/upgrade process diff --git a/htdocs/install/mysql/data/llx_c_payment_term.sql b/htdocs/install/mysql/data/llx_c_payment_term.sql index 5a48e57a443..982da754d78 100644 --- a/htdocs/install/mysql/data/llx_c_payment_term.sql +++ b/htdocs/install/mysql/data/llx_c_payment_term.sql @@ -1,11 +1,12 @@ --- Copyright (C) 2001-2004 Rodolphe Quiedeville --- Copyright (C) 2003 Jean-Louis Bergamo --- Copyright (C) 2004-2009 Laurent Destailleur --- Copyright (C) 2004 Benoit Mortier --- Copyright (C) 2004 Guillaume Delecourt --- Copyright (C) 2005-2009 Regis Houssin --- Copyright (C) 2007 Patrick Raguin --- Copyright (C) 2012 Tommaso Basilici +-- Copyright (C) 2001-2004 Rodolphe Quiedeville +-- Copyright (C) 2003 Jean-Louis Bergamo +-- Copyright (C) 2004-2009 Laurent Destailleur +-- Copyright (C) 2004 Benoit Mortier +-- Copyright (C) 2004 Guillaume Delecourt +-- Copyright (C) 2005-2009 Regis Houssin +-- Copyright (C) 2007 Patrick Raguin +-- Copyright (C) 2012 Tommaso Basilici +-- Copyright (C) 2025 Alexandre Spangaro -- -- 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 @@ -26,18 +27,20 @@ -- Do not include comments at end of line, this file is parsed during install and string '--' are removed. -- -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (1 ,'RECEP', 1,1, 'Due upon receipt','Due upon receipt',0,1,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (2 ,'30D', 2,1, '30 days','Due in 30 days',0,30,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (3 ,'30DENDMONTH', 3,1, '30 days end of month','Due in 30 days, end of month',1,30,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (4 ,'60D', 4,1, '60 days','Due in 60 days, end of month',0,60,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (5 ,'60DENDMONTH', 5,1, '60 days end of month','Due in 60 days, end of month',1,60,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (6 ,'PT_ORDER', 6,1, 'Due on order','Due on order',0,1,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (7 ,'PT_DELIVERY', 7,1, 'Due on delivery','Due on delivery',0,1,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (8 ,'PT_5050', 8,1, '50 and 50','50% on order, 50% on delivery',0,1,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (1, 'RECEP', 1,1, 'Due upon receipt','Due upon receipt',0,1,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (2, '30D', 5,1, '30 days','Due in 30 days',0,30,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (3, '30DENDMONTH', 7,1, '30 days end of month','Due in 30 days, end of month',1,30,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (4, '60D', 20,1, '60 days','Due in 60 days',0,60,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (5, '60DENDMONTH', 25,0, '60 days end of month','Due in 60 days, end of month',1,60,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (6, 'PT_ORDER', 30,1, 'Due on order','Due on order',0,1,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (7, 'PT_DELIVERY', 35,1, 'Due on delivery','Due on delivery',0,1,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (8, 'PT_5050', 40,1, '50 and 50','50% on order, 50% on delivery',0,1,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (14,'45D', 10,1, '45 days','Due in 45 days',0,45,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (15,'45DENDMONTH', 15,1, '45 days end of month','Due in 45 days, end of month',1,45,NULL); -- Add additional payment terms often needed in Austria -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (9 ,'10D', 9,1, '10 days','Due in 10 days',0,10,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (10,'10DENDMONTH', 10,1, '10 days end of month','Due in 10 days, end of month',1,10,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (11,'14D', 11,1, '14 days','Due in 14 days',0,14,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (12,'14DENDMONTH', 12,1, '14 days end of month','Due in 14 days, end of month',1,14,NULL); -insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (13,'DEP30PCTDEL', 13,0, '__DEPOSIT_PERCENT__% deposit','__DEPOSIT_PERCENT__% deposit, remainder on delivery',0,1,'30'); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (9, '10D', 50,1, '10 days','Due in 10 days',0,10,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (10,'10DENDMONTH', 55,1, '10 days end of month','Due in 10 days, end of month',1,10,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (11,'14D', 60,1, '14 days','Due in 14 days',0,14,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (12,'14DENDMONTH', 65,1, '14 days end of month','Due in 14 days, end of month',1,14,NULL); +insert into llx_c_payment_term(rowid, code, sortorder, active, libelle, libelle_facture, type_cdr, nbjour, deposit_percent) values (13,'DEP30PCTDEL', 90,0, '__DEPOSIT_PERCENT__% deposit','__DEPOSIT_PERCENT__% deposit, remainder on delivery',0,1,'30'); diff --git a/htdocs/install/mysql/migration/22.0.0-23.0.0.sql b/htdocs/install/mysql/migration/22.0.0-23.0.0.sql index 44096d30aa6..d357501d02e 100644 --- a/htdocs/install/mysql/migration/22.0.0-23.0.0.sql +++ b/htdocs/install/mysql/migration/22.0.0-23.0.0.sql @@ -114,6 +114,7 @@ ALTER TABLE llx_accounting_analytic_distribution ADD CONSTRAINT fk_accounting_an ALTER TABLE llx_facture ADD COLUMN dispute_status integer DEFAULT 0 after payment_reference; ALTER TABLE llx_facture ADD COLUMN ip varchar(250); +ALTER TABLE llx_facture ADD COLUMN pos_print_counter integer DEFAULT 0; ALTER TABLE llx_commande ADD COLUMN ip varchar(250); ALTER TABLE llx_commande ADD COLUMN user_agent varchar(255); diff --git a/htdocs/install/mysql/tables/llx_blockedlog_authority.key.sql b/htdocs/install/mysql/tables/llx_blockedlog_authority.key.sql deleted file mode 100644 index 289b6dbde56..00000000000 --- a/htdocs/install/mysql/tables/llx_blockedlog_authority.key.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE llx_blockedlog_authority ADD INDEX signature (signature); diff --git a/htdocs/install/mysql/tables/llx_blockedlog_authority.sql b/htdocs/install/mysql/tables/llx_blockedlog_authority.sql deleted file mode 100644 index eb491f93af4..00000000000 --- a/htdocs/install/mysql/tables/llx_blockedlog_authority.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE llx_blockedlog_authority -( - rowid integer AUTO_INCREMENT PRIMARY KEY, - blockchain longtext NOT NULL, - signature varchar(100) NOT NULL, - tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -) ENGINE=innodb; diff --git a/htdocs/install/mysql/tables/llx_facture.sql b/htdocs/install/mysql/tables/llx_facture.sql index dabca7ae65e..e6c0a941fd8 100644 --- a/htdocs/install/mysql/tables/llx_facture.sql +++ b/htdocs/install/mysql/tables/llx_facture.sql @@ -67,7 +67,9 @@ create table llx_facture fk_user_closing integer, -- user closing module_source varchar(32), -- name of module when invoice generated by a dedicated module (POS, ecommerce...) - pos_source varchar(32), -- numero of POS terminal when order is generated by a POS module, IDsession@IDwebsite when order is generated for a website basket. + pos_source varchar(32), -- numero of POS terminal when order is generated by a POS module, IDsession@IDwebsite when invoice is generated for a website ecommerce. + pos_print_counter integer DEFAULT 0, -- counter used to track how many times the ticket was printed. + fk_fac_rec_source integer, -- facture rec source fk_facture_source integer, -- facture origin if credit notes or replacement invoice fk_projet integer DEFAULT NULL, -- project invoice is linked to diff --git a/htdocs/install/mysql/tables/llx_printing_receipt-receiptprinter.key.sql b/htdocs/install/mysql/tables/llx_printing_receipt-receiptprinter.key.sql new file mode 100644 index 00000000000..afc65391227 --- /dev/null +++ b/htdocs/install/mysql/tables/llx_printing_receipt-receiptprinter.key.sql @@ -0,0 +1,19 @@ +-- ============================================================================ +-- Copyright (C) 2025 Laurent Destailleur +-- +-- 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 +-- the Free Software Foundation; either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see . +-- +-- ============================================================================ + +ALTER TABLE llx_printer_receipt ADD UNIQUE INDEX uk_printer_receipt (name, entity); diff --git a/htdocs/install/mysql/tables/llx_printing_receipt-receiptprinter.sql b/htdocs/install/mysql/tables/llx_printing_receipt-receiptprinter.sql new file mode 100644 index 00000000000..815bd713c3d --- /dev/null +++ b/htdocs/install/mysql/tables/llx_printing_receipt-receiptprinter.sql @@ -0,0 +1,26 @@ +-- ============================================================================ +-- Copyright (C) 2025 Laurent Destailleur +-- +-- 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 +-- the Free Software Foundation; either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see . +-- +-- ============================================================================ + +CREATE TABLE llx_printer_receipt ( + rowid integer AUTO_INCREMENT PRIMARY KEY, + name varchar(128), + fk_type integer, + fk_profile integer, + parameter varchar(128), + entity integer +) ENGINE=innodb; diff --git a/htdocs/install/mysql/tables/llx_printing_receipt_template-receiptprinter.sql b/htdocs/install/mysql/tables/llx_printing_receipt_template-receiptprinter.sql new file mode 100644 index 00000000000..0ab59736384 --- /dev/null +++ b/htdocs/install/mysql/tables/llx_printing_receipt_template-receiptprinter.sql @@ -0,0 +1,24 @@ +-- ============================================================================ +-- Copyright (C) 2025 Laurent Destailleur +-- +-- 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 +-- the Free Software Foundation; either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see . +-- +-- ============================================================================ + +CREATE TABLE llx_printer_receipt_template ( + rowid integer AUTO_INCREMENT PRIMARY KEY, + name varchar(128), + template text, + entity integer +) ENGINE=innodb; diff --git a/htdocs/langs/en_US/cashdesk.lang b/htdocs/langs/en_US/cashdesk.lang index 869cd9e502d..0dff0896ec2 100644 --- a/htdocs/langs/en_US/cashdesk.lang +++ b/htdocs/langs/en_US/cashdesk.lang @@ -166,7 +166,8 @@ NoPreviousBillForCustomer=No previous bill for customer Sumup=Sumup SumupAffiliate=Sumup affiliate SumupAppId=Sumup AppId -NotAvailableForCountryWhenModuleIsOn=Not available for country %s when module %s is enabled +NotAvailableForCountryWhenModuleIsOn=Not available for country %s when module %s is enabled when using a certified version Printers=Printers +PrintOn=Print on printer %s SentToPrinter=Sent to printer FailedToSendToPrinter=Failed to send to printer diff --git a/htdocs/langs/en_US/other.lang b/htdocs/langs/en_US/other.lang index a513f6db7b4..05d4dc53dce 100644 --- a/htdocs/langs/en_US/other.lang +++ b/htdocs/langs/en_US/other.lang @@ -111,6 +111,8 @@ PredefinedMailContentSendSupplierProposal=__(Hello)__\n\nPlease find price reque PredefinedMailContentSendOrder=__(Hello)__\n\nPlease find order __REF__ attached\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ PredefinedMailContentSendSupplierOrder=__(Hello)__\n\nPlease find our order __REF__ attached\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ PredefinedMailContentSendSupplierInvoice=__(Hello)__\n\nPlease find invoice __REF__ attached\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ +PredefinedMailContentSendSupplierPayment=__(Hello)__\n\nPlease find on __DATE_YMD__ payment __REF__, corresponding to invoices : \n\n__SUPPLIER_PAYMENT_INVOICES_LIST__\n\nTotal : __SUPPLIER_PAYMENT_INVOICES_TOTAL__\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ +AmountPayed=amount payed PredefinedMailContentSendShipping=__(Hello)__\n\nPlease find shipping __REF__ attached\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ PredefinedMailContentSendReception=__(Hello)__\n\nPlease find reception __REF__ attached\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ PredefinedMailContentSendFichInter=__(Hello)__\n\nPlease find intervention __REF__ attached\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ diff --git a/htdocs/langs/en_US/receiptprinter.lang b/htdocs/langs/en_US/receiptprinter.lang index 993f6813f92..681be73b5a2 100644 --- a/htdocs/langs/en_US/receiptprinter.lang +++ b/htdocs/langs/en_US/receiptprinter.lang @@ -19,7 +19,7 @@ CONNECTOR_NETWORK_PRINT=Network Printer CONNECTOR_FILE_PRINT=Local Printer CONNECTOR_WINDOWS_PRINT=Local Windows Printer CONNECTOR_CUPS_PRINT=Cups Printer -CONNECTOR_DUMMY_HELP=Fake Printer for test, does nothing +CONNECTOR_DUMMY_HELP=Fake Printer for test, does nothing (except writing stream into file dolibarr_printer.txt) CONNECTOR_NETWORK_PRINT_HELP=10.x.x.x:9100 CONNECTOR_FILE_PRINT_HELP=/dev/usb/lp0, /dev/usb/lp1 CONNECTOR_WINDOWS_PRINT_HELP=LPT1, COM1, smb://FooUser:secret@computername/workgroup/RAW/Text Printer diff --git a/htdocs/langs/fr_FR/other.lang b/htdocs/langs/fr_FR/other.lang index 1fe716b152e..4f5535f6cbb 100644 --- a/htdocs/langs/fr_FR/other.lang +++ b/htdocs/langs/fr_FR/other.lang @@ -111,6 +111,8 @@ PredefinedMailContentSendSupplierProposal=__(Hello)__\n\nVeuillez trouver, ci-jo PredefinedMailContentSendOrder=__(Hello)__\n\nVeuillez trouver, ci-joint, la commande __REF__\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ PredefinedMailContentSendSupplierOrder=__(Hello)__\n\nVeuillez trouver, ci-joint, notre commande __REF__\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ PredefinedMailContentSendSupplierInvoice=__(Hello)__\n\nVeuillez trouver, ci-joint, la facture __REF__\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ +PredefinedMailContentSendSupplierPayment=__(Hello)__\n\nNous vous transmettons le __DATE_YMD__ le paiement __REF__, correspondant aux factures : \n\n__SUPPLIER_PAYMENT_INVOICES_LIST__\n\nTotal : __SUPPLIER_PAYMENT_INVOICES_TOTAL__\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ +AmountPayed=montant paye PredefinedMailContentSendShipping=__(Hello)__\n\nVeuillez trouver, ci-joint, le bon d'expédition __REF__\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ PredefinedMailContentSendReception=__(Bonjour)__\n\nVeuillez trouver ci-joint le reçu __REF__\n\n__(Cordialement)__\n\n__SENDEREMAIL_SIGNATURE__ PredefinedMailContentSendFichInter=__(Hello)__\n\nVeuillez trouver, ci-joint, la fiche intervention __REF__\n\n\n__(Sincerely)__\n\n__SENDEREMAIL_SIGNATURE__ diff --git a/htdocs/modulebuilder/template/admin/setup.php b/htdocs/modulebuilder/template/admin/setup.php index d6ac38cb35f..b9f7a5f501c 100644 --- a/htdocs/modulebuilder/template/admin/setup.php +++ b/htdocs/modulebuilder/template/admin/setup.php @@ -131,7 +131,7 @@ $item = $formSetup->newItem('MYMODULE_MYPARAM4'); $item->setAsThirdpartyType(); // Setup conf for a selection of a boolean -$formSetup->newItem('MYMODULE_MYPARAM5')->setAsYesNo(); +$formSetup->newItem('MYMODULE_MYPARAM5')->setAsYesNo(); // ->fieldParams['alertifoff'] = 1 or ->fieldParams['alertifon'] = 1; // Setup conf for a selection of an Email template of type thirdparty $formSetup->newItem('MYMODULE_MYPARAM6')->setAsEmailTemplate('thirdparty'); diff --git a/htdocs/product/stock/class/api_stockmovements.class.php b/htdocs/product/stock/class/api_stockmovements.class.php index cb40f8d6241..83d40737a2f 100644 --- a/htdocs/product/stock/class/api_stockmovements.class.php +++ b/htdocs/product/stock/class/api_stockmovements.class.php @@ -1,6 +1,7 @@ * Copyright (C) 2025 MDW + * Copyright (C) 2025 William Mead * * 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 @@ -85,19 +86,20 @@ class StockMovements extends DolibarrApi /** * Get a list of stock movement * - * @param string $sortfield Sort field - * @param string $sortorder Sort order - * @param int $limit Limit for list - * @param int $page Page number - * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.fk_product:=:1) and (t.date_creation:<:'20160101')" - * @param string $properties Restrict the data returned to these properties. Ignored if empty. Comma separated list of properties names - * @return array Array of warehouse objects + * @param string $sortfield Sort field + * @param string $sortorder Sort order + * @param int $limit Limit for list + * @param int $page Page number + * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.fk_product:=:1) and (t.date_creation:<:'20160101')" + * @param string $properties Restrict the data returned to these properties. Ignored if empty. Comma separated list of properties names + * @param bool $pagination_data If this parameter is set to true the response will include pagination data. Default value is false. Page starts from 0* + * @return array Array of warehouse objects * @phan-return MouvementStock[] * @phpstan-return MouvementStock[] * * @throws RestException */ - public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '') + public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '', $pagination_data = false) { $obj_ret = array(); @@ -119,6 +121,9 @@ class StockMovements extends DolibarrApi } } + // this query will return total stock movements with the filters given + $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql); + $sql .= $this->db->order($sortfield, $sortorder); if ($limit) { if ($page < 0) { @@ -146,6 +151,23 @@ class StockMovements extends DolibarrApi throw new RestException(503, 'Error when retrieve stock movement list : '.$this->db->lasterror()); } + // if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit) + if ($pagination_data) { + $totalsResult = $this->db->query($sqlTotals); + $total = $this->db->fetch_object($totalsResult)->total; + + $tmp = $obj_ret; + $obj_ret = []; + + $obj_ret['data'] = $tmp; + $obj_ret['pagination'] = [ + 'total' => (int) $total, + 'page' => $page, //count starts from 0 + 'page_count' => ceil((int) $total / $limit), + 'limit' => $limit + ]; + } + return $obj_ret; } diff --git a/htdocs/takepos/admin/receipt.php b/htdocs/takepos/admin/receipt.php index 354aa642bc6..caeb00bc03b 100644 --- a/htdocs/takepos/admin/receipt.php +++ b/htdocs/takepos/admin/receipt.php @@ -200,7 +200,7 @@ if (getDolGlobalString('TAKEPOS_PRINT_METHOD') == "takeposconnector" && filter_v print ''; print $langs->trans('PrintWithoutDetailsButton'); print ''; -print ajax_constantonoff('TAKEPOS_PRINT_WITHOUT_DETAILS', array(), $conf->entity, 0, 0, 1, 0); +print ajax_constantonoff('TAKEPOS_PRINT_WITHOUT_DETAILS', array(), $conf->entity, 0, 0, 1, 0, 0, 0, '', '', 'inline-block', 0, ''); print "\n"; if (getDolGlobalString('TAKEPOS_PRINT_WITHOUT_DETAILS')) { print ''; diff --git a/htdocs/takepos/admin/terminal.php b/htdocs/takepos/admin/terminal.php index b18800d8792..23235b266c0 100644 --- a/htdocs/takepos/admin/terminal.php +++ b/htdocs/takepos/admin/terminal.php @@ -437,11 +437,9 @@ if (getDolGlobalString('TAKEPOS_ADDON') == "terminal") { $customprinterallowed = true; $orderprinterallowed = (getDolGlobalString('TAKEPOS_BAR_RESTAURANT') && getDolGlobalInt('TAKEPOS_ORDER_PRINTERS')); $customprinttemplateallowed = true; -$arrayOfCountryWithPrintingOnBrowserMandatory = array('FR'); -if (in_array($mysoc->country_code, $arrayOfCountryWithPrintingOnBrowserMandatory) && isModEnabled('blockedlog')) { - //$customprinterallowed = false; // Custom printer are allowed but information in template are mandatory - $customprinttemplateallowed = false; - //$orderprinterallowed = false; +include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php'; +if (isALNECandidateVersion()) { // No need to show this option because it has no effect when isALNECandidateVersion is true. + $customprinttemplateallowed = false; // Custom printer may be allowed if mandatory information in template are guaranteed. For the moment, we prefer not allow this. } if (isModEnabled('receiptprinter')) { diff --git a/htdocs/takepos/index.php b/htdocs/takepos/index.php index faf1efec99b..02b4261cb90 100644 --- a/htdocs/takepos/index.php +++ b/htdocs/takepos/index.php @@ -1436,10 +1436,9 @@ if (getDolGlobalString('TAKEPOS_BAR_RESTAURANT')) { // Button to print receipt before payment $customprinterallowed = true; $customprinttemplateallowed = true; - $arrayOfCountryWithPrintingOnBrowserMandatory = array('FR'); - if (in_array($mysoc->country_code, $arrayOfCountryWithPrintingOnBrowserMandatory) && isModEnabled('blockedlog')) { - $customprinterallowed = true; - $customprinttemplateallowed = false; + include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php'; + if (isALNECandidateVersion()) { // No need to show this option because it has no effect when isALNECandidateVersion is true. + $customprinttemplateallowed = false; // Custom printer may be allowed if mandatory information in template are guaranteed. For the moment, we prefer not allow this. } if (getDolGlobalString('TAKEPOS_BAR_RESTAURANT')) { @@ -1451,7 +1450,8 @@ if (getDolGlobalString('TAKEPOS_BAR_RESTAURANT')) { } } elseif ($customprinterallowed && (isModEnabled('receiptprinter') && getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term) > 0) || getDolGlobalString('TAKEPOS_PRINT_METHOD') == "receiptprinter") { // @phpstan-ignore-line // Button Print Receipt on special printer - $menus[$r++] = array('title' => '
'.$langs->trans("PrintTicket").'
', 'action' => 'DolibarrTakeposPrinting(placeid);'); + $nameOfPrinter = dol_getIdFromCode($db, getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term), 'printer_receipt', 'rowid', 'name', 1); + $menus[$r++] = array('title' => '
'.$langs->trans("PrintTicket").'
', 'action' => 'DolibarrTakeposPrinting(placeid);'); } else { // Button Print Receipt on browser $menus[$r++] = array('title' => '
'.$langs->trans("PrintTicket").'
', 'action' => 'Print(placeid);'); diff --git a/htdocs/takepos/invoice.php b/htdocs/takepos/invoice.php index ff79d9a72a0..185b80139b8 100644 --- a/htdocs/takepos/invoice.php +++ b/htdocs/takepos/invoice.php @@ -1316,10 +1316,9 @@ if (empty($reshook)) { $customprinterallowed = true; $customprinttemplateallowed = true; - $arrayOfCountryWithPrintingOnBrowserMandatory = array('FR'); - if (in_array($mysoc->country_code, $arrayOfCountryWithPrintingOnBrowserMandatory) && isModEnabled('blockedlog')) { - $customprinterallowed = true; - $customprinttemplateallowed = false; + include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php'; + if (isALNECandidateVersion()) { // No need to show this option because it has no effect when isALNECandidateVersion is true. + $customprinttemplateallowed = false; // Custom printer may be allowed if mandatory information in template are guaranteed. For the moment, we prefer not allow this. } if (getDolGlobalInt('TAKEPOS_PRINT_INVOICE_DOC_INSTEAD_OF_RECEIPT')) { @@ -1333,7 +1332,8 @@ if (empty($reshook)) { } } elseif ($customprinterallowed && (isModEnabled('receiptprinter') && getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term) > 0) || getDolGlobalString('TAKEPOS_PRINT_METHOD') == "receiptprinter") { // @phpstan-ignore-line // If we set to use a specific receipt printer - $sectionwithinvoicelink .= ' '; + $nameOfPrinter = dol_getIdFromCode($db, getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term), 'printer_receipt', 'rowid', 'name', 1); + $sectionwithinvoicelink .= ' '; } else { $sectionwithinvoicelink .= ' '; if (getDolGlobalString('TAKEPOS_PRINT_WITHOUT_DETAILS')) { @@ -1348,7 +1348,7 @@ if (empty($reshook)) { } if ($remaintopay <= 0 && getDolGlobalString('TAKEPOS_AUTO_PRINT_TICKETS') && $action != "history") { - $sectionwithinvoicelink .= ''; + $sectionwithinvoicelink .= ''; } } } @@ -1555,6 +1555,9 @@ function TakeposConnector(id){ return true; } + // Call the ajax to execute the print. // With some external module another method may be called. function DolibarrTakeposPrinting(id) { @@ -1565,7 +1568,7 @@ function DolibarrTakeposPrinting(id) { data: { token: '' }, url: "" + id, success: function(){ - showPrintResultPopup('trans("SentToPrinter")); ?>', 2000); + showPrintResultPopup('trans("SentToPrinter").' '.$nameOfPrinter); ?>', 2000); }, error: function(){ showPrintResultPopup("trans("FailedToSendToPrinter")); ?>", 2000); diff --git a/htdocs/version.inc.php b/htdocs/version.inc.php new file mode 100644 index 00000000000..ee33497c213 --- /dev/null +++ b/htdocs/version.inc.php @@ -0,0 +1,49 @@ + + * Copyright (C) 2003 Xavier Dutoit + * Copyright (C) 2004-2025 Laurent Destailleur + * Copyright (C) 2004 Sebastien Di Cintio + * Copyright (C) 2004 Benoit Mortier + * Copyright (C) 2005-2011 Regis Houssin + * Copyright (C) 2005 Simon Tosser + * Copyright (C) 2006 Andre Cianfarani + * Copyright (C) 2010 Juanjo Menent + * Copyright (C) 2015 Bahfir Abbes + * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024 Frédéric France + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/version.inc.php + * \ingroup core + * \brief File that include the conf.php file and commons lib like functions.lib.php + */ + +if (!defined('DOL_APPLICATION_TITLE')) { + define('DOL_APPLICATION_TITLE', 'Dolibarr'); +} +if (!defined('DOL_VERSION')) { + define('DOL_VERSION', '23.0.0-alpha'); // a.b.c-alpha, a.b.c-beta, a.b.c-rcX or a.b.c +} + +// End of common declaration part +if (defined('DOL_INC_FOR_VERSION_ERROR')) { + return; +} + +if (!defined('CERTIF_LNE')) { + define('CERTIF_LNE', '2'); // Set to 1 if the beta version is a candidate for certification or if the stable version has been certified. Use 2 for debug to force LNE feature. +} diff --git a/scripts/cron/cron_run_jobs.php b/scripts/cron/cron_run_jobs.php index bafcf3a4dab..e97501a6880 100755 --- a/scripts/cron/cron_run_jobs.php +++ b/scripts/cron/cron_run_jobs.php @@ -75,24 +75,24 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/functionscli.lib.php'; require_once DOL_DOCUMENT_ROOT."/cron/class/cronjob.class.php"; require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; +// Global variables +$version = DOL_VERSION; +$error = 0; + // Check parameters if (!isset($argv[1]) || !$argv[1]) { - usageCron($path, $script_file); + usageCron($path, $script_file, $version); exit(1); } $key = $argv[1]; if (!isset($argv[2]) || !$argv[2]) { - usageCron($path, $script_file); + usageCron($path, $script_file, $version); exit(1); } $userlogin = $argv[2]; -// Global variables -$version = DOL_VERSION; -$error = 0; - $hookmanager->initHooks(array('cli')); @@ -351,20 +351,26 @@ exit(0); /** * script cron usageCron * - * @param string $path Path - * @param string $script_file Filename + * @param string $path Path + * @param string $script_file Filename + * @param string $version Version * @return void */ -function usageCron($path, $script_file) +function usageCron($path, $script_file, $version) { + $now = dol_now(); + + print "***** ".$script_file." (".$version.") pid=".dol_getmypid()." - ".dol_print_date($now, 'dayhourrfc', 'gmt')." - ".gethostname()." *****\n"; print "Usage: ".$script_file." securitykey userlogin|'firstadmin' [cronjobid] [--force]\n"; + print "\n"; print "The script return 0 when everything worked successfully.\n"; print "\n"; - print "On Linux system, you can have cron jobs ran automatically by adding an entry into cron.\n"; - print "For example, to run pending tasks each day at 3:30, you can add this line:\n"; + print "On Linux system, you can have cron jobs ran automatically by adding an entry into cron file.\n"; + print "For example, to run this script each day at 3:30, you can add this line:\n"; print "30 3 * * * ".$path.$script_file." securitykey userlogin > ".DOL_DATA_ROOT."/".$script_file.".log\n"; - print "For example, to run pending tasks every 5mn, you can add this line:\n"; + print "For example, to run this script every 5mn, you can add this line:\n"; print "*/5 * * * * ".$path.$script_file." securitykey userlogin > ".DOL_DATA_ROOT."/".$script_file.".log\n"; print "\n"; print "The option --force allow to bypass the check on date of execution so job will be executed even if date is not yet reached.\n"; + print "\n"; } diff --git a/test/hurl/api/emailtemplates/10_emailtemplates.hurl b/test/hurl/api/emailtemplates/10_emailtemplates.hurl new file mode 100644 index 00000000000..c1fa92cb23c --- /dev/null +++ b/test/hurl/api/emailtemplates/10_emailtemplates.hurl @@ -0,0 +1,98 @@ +# GET emailtemplates +GET http://{{hostnport}}/api/index.php/emailtemplates +HTTP 200 + +# GET sorted emailtemplates +GET http://{{hostnport}}/api/index.php/emailtemplates?sortfield=e.rowid&sortorder=ASC +HTTP 200 + +# GET sorted emailtemplates +GET http://{{hostnport}}/api/index.php/emailtemplates?sortfield=e.rowid&sortorder=DSC +HTTP 200 + +# GET with limit and page +GET http://{{hostnport}}/api/index.php/emailtemplates?limit=100&page=1 +HTTP 200 + +# GET with fk_user +GET http://{{hostnport}}/api/index.php/emailtemplates?fk_user=0 +HTTP 200 + +# GET with properties=id%2Cstatus +GET http://{{hostnport}}/api/index.php/emailtemplates?properties=id%2Cstatus +HTTP 200 + +# GET with pagination_data=false +GET http://{{hostnport}}/api/index.php/emailtemplates?pagination_data=false +HTTP 200 + +# GET with pagination_data=true +GET http://{{hostnport}}/api/index.php/emailtemplates?pagination_data=true +HTTP 200 +[Asserts] +jsonpath "$.data" exists +jsonpath "$.pagination.total" >= 0 +jsonpath "$.pagination.page" >= 0 +jsonpath "$.pagination.page_count" >= 0 +jsonpath "$.pagination.limit" == 100 + +# GET mailing with ID 0 - which should not exist +GET http://{{hostnport}}/api/index.php/emailtemplates/0 +HTTP 400 +{"error":{"code":400,"message":"Bad Request: Error: id=0 and label are empty"}} + +# POST {} +POST http://{{hostnport}}/api/index.php/emailtemplates +{} +HTTP 400 +{"error":{"code":400,"message":"Bad Request: label field missing"}} + +# POST topic required +POST http://{{hostnport}}/api/index.php/emailtemplates +{ "id" : 42, "label" : "automatic test using 10_emailtemplates.hurl"} +HTTP 400 +{"error":{"code":400,"message":"Bad Request: topic field missing"}} + +# POST type_template field required +POST http://{{hostnport}}/api/index.php/emailtemplates +{ "id" : 42, "label" : "automatic test using 10_emailtemplates.hurl", "topic" : "automatic test using 10_emailtemplates.hurl" } +HTTP 400 +{"error":{"code":400,"message":"Bad Request: type_template field missing"}} + +# POST No id in post request data +POST http://{{hostnport}}/api/index.php/emailtemplates +{ "id" : 42, "label" : "automatic test using 10_emailtemplates.hurl", "topic" : "automatic test using 10_emailtemplates.hurl", "type_template" : "all" } +HTTP 400 +{"error":{"code":400,"message":"Bad Request: Creating with id field is forbidden"}} + +# DELETE +DELETE http://{{hostnport}}/api/index.php/emailtemplates/ +HTTP 405 + +# DELETE mailing with ID 0 - which should not exist +DELETE http://{{hostnport}}/api/index.php/emailtemplates/0 +HTTP 404 +{"error":{"code":404,"message":"Not Found: Email Template with id 0 not found"}} + +# PUT +PUT http://{{hostnport}}/api/index.php/emailtemplates/ +{} +HTTP 405 + +# PUT +PUT http://{{hostnport}}/api/index.php/emailtemplates/0 +{} +HTTP 404 +{"error":{"code":404,"message":"Not Found: email template with id=0 not found"}} + + +# GET emailtemplates DOLAPIENTITY: 1 +GET http://{{hostnport}}/api/index.php/emailtemplates +DOLAPIENTITY: 1 +HTTP 200 + +# GET emailtemplates DOLAPIENTITY: 2 +GET http://{{hostnport}}/api/index.php/emailtemplates +DOLAPIENTITY: 2 +HTTP 401 +{"error":{"code":401,"message":"Unauthorized: Error user not valid (not found with api key or bad status or bad validity dates) (conf->entity=2)"}} diff --git a/test/hurl/gui/admin/10_mails_templates.hurl b/test/hurl/gui/admin/10_mails_templates.hurl new file mode 100644 index 00000000000..d24ba9e349d --- /dev/null +++ b/test/hurl/gui/admin/10_mails_templates.hurl @@ -0,0 +1,19 @@ +GET http://{{hostnport}}/admin/mails_templates.php +HTTP 200 +[Asserts] +body not contains "Enter login details" +body contains "" +body contains "Emails setup" +body contains "Email templates" +body contains "Attach file" +body contains "Code" +body contains "Default" +body contains "Language" +body contains "Module/Application" +body contains "Owner" +body contains "Position" +body contains "Private" +body contains "Status" +body contains "Subject" +body contains "Type of template" +body contains "NewEMailTemplate" diff --git a/test/hurl/run.sh b/test/hurl/run.sh index db96888494a..d4045574a44 100755 --- a/test/hurl/run.sh +++ b/test/hurl/run.sh @@ -1,4 +1,6 @@ #!/bin/bash +# Script to run unit tests on API with hurl +# if [[ -z ${DOLIHOST+x} ]]; then DOLIHOST="localhost" @@ -16,6 +18,12 @@ if [[ "" != "${DOLISUBURL}" ]]; then hostnport="${hostnport}/${DOLISUBURL}" fi +echo "----- Run hurl test on APIs ---" +if ! command -v hurl &> /dev/null; then + echo The command hurl must be available. + exit 1 +fi + echo "First we run tests that do not require authentication" find api/ gui/ public/ -type f -iname '00*.hurl' -exec hurl --variable "hostnport=${hostnport}" --test "{}" + || exit 1