From 5b24dd4a4e156cf74623cde0ae956e3b5522bcd7 Mon Sep 17 00:00:00 2001 From: David Beniamine Date: Mon, 13 May 2024 14:39:33 +0200 Subject: [PATCH 1/4] Fix generate odt model as default --- htdocs/don/class/don.class.php | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/htdocs/don/class/don.class.php b/htdocs/don/class/don.class.php index 483a53465ee27..c91469d1b9bc1 100644 --- a/htdocs/don/class/don.class.php +++ b/htdocs/don/class/don.class.php @@ -1083,6 +1083,45 @@ public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hided $classname = $modele; $obj = new $classname($this->db); + // If generator is ODT, we must have srctemplatepath defined, if not we set it. + if ($obj->type == 'odt' && empty($srctemplatepath)) { + $varfortemplatedir = $obj->scandir; + if ($varfortemplatedir && !empty($conf->global->$varfortemplatedir)) { + $dirtoscan = $conf->global->$varfortemplatedir; + + $listoffiles = array(); + + // Now we add first model found in directories scanned + $listofdir = explode(',', $dirtoscan); + foreach ($listofdir as $key => $tmpdir) { + $tmpdir = trim($tmpdir); + $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir); + if (!$tmpdir) { + unset($listofdir[$key]); + continue; + } + if (is_dir($tmpdir)) { + $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0); + if (count($tmpfiles)) { + $listoffiles = array_merge($listoffiles, $tmpfiles); + } + } + } + + if (count($listoffiles)) { + foreach ($listoffiles as $record) { + $srctemplatepath = $record['fullname']; + break; + } + } + } + + if (empty($srctemplatepath)) { + $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined'; + return -1; + } + } + // We save charset_output to restore it because write_file can change it if needed for // output format that does not support UTF8. $sav_charset_output = $outputlangs->charset_output; From 8ecf819ad78185415a0f7c01d80d79728c522093 Mon Sep 17 00:00:00 2001 From: David Beniamine Date: Mon, 13 May 2024 16:13:12 +0200 Subject: [PATCH 2/4] Fix creating index --- htdocs/don/class/don.class.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/htdocs/don/class/don.class.php b/htdocs/don/class/don.class.php index c91469d1b9bc1..0cb5fa8ba5a4c 100644 --- a/htdocs/don/class/don.class.php +++ b/htdocs/don/class/don.class.php @@ -1131,6 +1131,23 @@ public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hided // we delete preview files require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; dol_delete_preview($object); + if (!empty($obj->result['fullpath'])) { + $destfull = $obj->result['fullpath']; + + // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set) + $update_main_doc_field = 0; + if (!empty($obj->update_main_doc_field)) { + $update_main_doc_field = 1; + } + + $this->indexFile($destfull, $update_main_doc_field); + } else { + dol_syslog('Method ->write_file was called on object '.get_class($obj).' and return a success but the return array ->result["fullpath"] was not set.', LOG_WARNING); + } + + // Success in building document. We build meta file. + dol_meta_create($this); + return 1; } else { $outputlangs->charset_output = $sav_charset_output; From f88c29f540bfb0a086c45ce9a3ddb3ff007351b8 Mon Sep 17 00:00:00 2001 From: David Beniamine Date: Fri, 31 May 2024 15:40:03 +0200 Subject: [PATCH 3/4] Add numering model on don --- htdocs/core/modules/dons/mod_don_terre.php | 233 +++++++++++++++++++++ htdocs/don/admin/donation.php | 134 ++++++++++++ htdocs/don/class/don.class.php | 100 ++++++++- htdocs/don/list.php | 4 +- 4 files changed, 466 insertions(+), 5 deletions(-) create mode 100644 htdocs/core/modules/dons/mod_don_terre.php diff --git a/htdocs/core/modules/dons/mod_don_terre.php b/htdocs/core/modules/dons/mod_don_terre.php new file mode 100644 index 0000000000000..8700e79d9f647 --- /dev/null +++ b/htdocs/core/modules/dons/mod_don_terre.php @@ -0,0 +1,233 @@ + + * Copyright (C) 2005-2015 Regis Houssin + * + * 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 . + * or see https://www.gnu.org/ + */ + +/** + * \file htdocs/core/modules/don/mod_don_terre.php + * \ingroup don + * \brief File containing class for numbering module Terre + */ +require_once DOL_DOCUMENT_ROOT.'/core/modules/dons/modules_don.php'; + +/** + * \class mod_don_terre + * \brief Class of numbering module Terre for dons + */ +class mod_don_terre extends ModeleNumRefDons +{ + /** + * Dolibarr version of the loaded document 'development', 'experimental', 'dolibarr' + * @var string + */ + public $version = 'dolibarr'; + + /** + * Prefix for dons + * @var string + */ + public $prefixdon = 'DON'; + + /** + * Prefix for replacement dons + * @var string + */ + public $prefixreplacement = 'FA'; + + /** + * Prefix for credit note + * @var string + */ + public $prefixcreditnote = 'AV'; + + /** + * Prefix for deposit + * @var string + */ + public $prefixdeposit = 'AC'; + + /** + * @var string Error code (or message) + */ + public $error = ''; + + + /** + * Constructor + */ + public function __construct() + { + global $conf, $mysoc; + + } + + /** + * Returns the description of the numbering model + * + * @return string Texte descripif + */ + public function info() + { + global $langs; + $langs->load("bills"); + return $langs->trans('TerreNumRefModelDesc1', $this->prefixdon, $this->prefixcreditnote, $this->prefixdeposit); + } + + /** + * Return an example of numbering + * + * @return string Example + */ + public function getExample() + { + return $this->prefixdon."0501-0001"; + } + + /** + * Checks if the numbers already in the database do not + * cause conflicts that would prevent this numbering working. + * + * @return boolean false if conflict, true if ok + */ + public function canBeActivated() + { + global $langs, $conf, $db; + + $langs->load("bills"); + + // Check don num + $fayymm = ''; + $max = ''; + + $posindice = strlen($this->prefixdon) + 6; + $sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".$posindice.") AS SIGNED)) as max"; // This is standard SQL + $sql .= " FROM ".MAIN_DB_PREFIX."don"; + $sql .= " WHERE ref LIKE '".$db->escape($this->prefixdon)."____-%'"; + $sql .= " AND entity = ".$conf->entity; + + $resql = $db->query($sql); + if ($resql) { + $row = $db->fetch_row($resql); + if ($row) { + $fayymm = substr($row[0], 0, 6); + $max = $row[0]; + } + } + if ($fayymm && !preg_match('/'.$this->prefixdon.'[0-9][0-9][0-9][0-9]/i', $fayymm)) { + $langs->load("errors"); + $this->error = $langs->trans('ErrorNumRefModel', $max); + return false; + } + + return true; + } + + /** + * Return next value not used or last value used. + * Note to increase perf of this numbering engine, you can create a calculated column and modify request to use this field instead for select: + * ALTER TABLE llx_don ADD COLUMN calculated_numrefonly INTEGER AS (CASE SUBSTRING(ref FROM 1 FOR 2) WHEN 'FA' THEN CAST(SUBSTRING(ref FROM 10) AS SIGNED) ELSE 0 END) PERSISTENT; + * ALTER TABLE llx_don ADD INDEX calculated_numrefonly_idx (calculated_numrefonly); + * + * @param Societe $objsoc Object third party + * @param Don $don Object don + * @param string $mode 'next' for next value or 'last' for last value + * @return string Next ref value or last ref if $mode is 'last', <= 0 if KO + */ + public function getNextValue($objsoc, $don, $mode = 'next') + { + global $db; + + dol_syslog(get_class($this)."::getNextValue mode=".$mode, LOG_DEBUG); + + $prefix = $this->prefixdon; + + // First we get the max value + $posindice = strlen($prefix) + 6; + $sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".$posindice.") AS SIGNED)) as max"; // This is standard SQL + $sql .= " FROM ".MAIN_DB_PREFIX."don"; + $sql .= " WHERE ref LIKE '".$db->escape($prefix)."____-%'"; + $sql .= " AND entity IN (".getEntity('donnumber', 1, $don).")"; + + $resql = $db->query($sql); + if ($resql) { + $obj = $db->fetch_object($resql); + if ($obj) { + $max = intval($obj->max); + } else { + $max = 0; + } + } else { + return -1; + } + + if ($mode == 'last') { + if ($max >= (pow(10, 4) - 1)) { + $num = $max; // If counter > 9999, we do not format on 4 chars, we take number as it is + } else { + $num = sprintf("%04s", $max); + } + + $ref = ''; + $sql = "SELECT ref as ref"; + $sql .= " FROM ".MAIN_DB_PREFIX."don"; + $sql .= " WHERE ref LIKE '".$db->escape($prefix)."____-".$num."'"; + $sql .= " AND entity IN (".getEntity('donnumber', 1, $don).")"; + $sql .= " ORDER BY ref DESC"; + + $resql = $db->query($sql); + if ($resql) { + $obj = $db->fetch_object($resql); + if ($obj) { + $ref = $obj->ref; + } + } else { + dol_print_error($db); + } + + return $ref; + } elseif ($mode == 'next') { + $date = $don->date; // This is don date (not creation date) + $yymm = strftime("%y%m", $date); + + if ($max >= (pow(10, 4) - 1)) { + $num = $max + 1; // If counter > 9999, we do not format on 4 chars, we take number as it is + } else { + $num = sprintf("%04s", $max + 1); + } + + dol_syslog(get_class($this)."::getNextValue return ".$prefix.$yymm."-".$num); + return $prefix.$yymm."-".$num; + } else { + dol_print_error('', 'Bad parameter for getNextValue'); + } + + return 0; + } + + /** + * Return next free value + * + * @param Societe $objsoc Object third party + * @param string $objforref Object for number to search + * @param string $mode 'next' for next value or 'last' for last value + * @return string Next free value + */ + public function getNumRef($objsoc, $objforref, $mode = 'next') + { + return $this->getNextValue($objsoc, $objforref, $mode); + } +} diff --git a/htdocs/don/admin/donation.php b/htdocs/don/admin/donation.php index 89f03fde21181..0e810ed2d0e44 100644 --- a/htdocs/don/admin/donation.php +++ b/htdocs/don/admin/donation.php @@ -102,6 +102,11 @@ dolibarr_del_const($db, 'DON_ADDON_MODEL', $conf->entity); } } +} elseif ($action == 'setmod') { + // TODO Verifier si module numerotation choisi peut etre active + // par appel methode canBeActivated + + dolibarr_set_const($db, "DON_ADDON", $value, 'chaine', 0, '', $conf->entity); } // Options @@ -298,6 +303,135 @@ print '
'; +/* + * Numbering module + */ + +print load_fiche_titre($langs->trans("BillsNumberingModule"), '', ''); + +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''."\n"; + +clearstatcache(); + +foreach ($dirmodels as $reldir) { + $dir = dol_buildpath($reldir."core/modules/dons/"); + if (is_dir($dir)) { + $handle = opendir($dir); + if (is_resource($handle)) { + while (($file = readdir($handle)) !== false) { + if (!is_dir($dir.$file) || (substr($file, 0, 1) <> '.' && substr($file, 0, 3) <> 'CVS')) { + $filebis = $file; + $classname = preg_replace('/\.php$/', '', $file); + // For compatibility + if (!is_file($dir.$filebis)) { + $filebis = $file."/".$file.".modules.php"; + $classname = "mod_don_".$file; + } + // Check if there is a filter on country + preg_match('/\-(.*)_(.*)$/', $classname, $reg); + if (!empty($reg[2]) && $reg[2] != strtoupper($mysoc->country_code)) { + continue; + } + + $classname = preg_replace('/\-.*$/', '', $classname); + if (!class_exists($classname) && is_readable($dir.$filebis) && (preg_match('/mod_/', $filebis) || preg_match('/mod_/', $classname)) && substr($filebis, dol_strlen($filebis) - 3, 3) == 'php') { + // Charging the numbering class + require_once $dir.$filebis; + + $module = new $classname($db); + + // Show modules according to features level + if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) { + continue; + } + if ($module->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) { + continue; + } + + if ($module->isEnabled()) { + print ''; + + // Show example of numbering module + print ''."\n"; + + print ''; + + $don = new Don($db); + $don->initAsSpecimen(); + + // Example for standard invoice + $htmltooltip = ''; + $htmltooltip .= ''.$langs->trans("Version").': '.$module->getVersion().'
'; + $don->type = 0; + $nextval = $module->getNextValue($mysoc, $don); + if ("$nextval" != $langs->trans("NotAvailable")) { // Keep " on nextval + $htmltooltip .= $langs->trans("NextValueForInvoices").': '; + if ($nextval) { + if (preg_match('/^Error/', $nextval) || $nextval == 'NotConfigured') { + $nextval = $langs->trans($nextval); + } + $htmltooltip .= $nextval.'
'; + } else { + $htmltooltip .= $langs->trans($module->error).'
'; + } + } + print ''; + + print "\n"; + } + } + } + } + closedir($handle); + } + } +} + +print '
'.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Example").''.$langs->trans("Status").''.$langs->trans("ShortInfo").'
'; + echo preg_replace('/\-.*$/', '', preg_replace('/mod_don_/', '', preg_replace('/\.php$/', '', $file))); + print "\n"; + + print $module->info(); + + print ''; + $tmp = $module->getExample(); + if (preg_match('/^Error/', $tmp)) { + $langs->load("errors"); + print '
'.$langs->trans($tmp).'
'; + } elseif ($tmp == 'NotConfigured') { + print ''.$langs->trans($tmp).''; + } else { + print $tmp; + } + print '
'; + //print "> ".$conf->global->DON_ADDON." - ".$file; + if ($conf->global->DON_ADDON == $file || $conf->global->DON_ADDON.'.php' == $file) { + print img_picto($langs->trans("Activated"), 'switch_on'); + } else { + print ''.img_picto($langs->trans("Disabled"), 'switch_off').''; + } + print ''; + print $form->textwithpicto('', $htmltooltip, 1, 0); + + if ($conf->global->DON_ADDON.'.php' == $file) { // If module is the one used, we show existing errors + if (!empty($module->error)) { + dol_htmloutput_mesg($module->error, '', 'error', 1); + } + } + + print '
'; +print '
'; + + + /* * Params */ diff --git a/htdocs/don/class/don.class.php b/htdocs/don/class/don.class.php index 0cb5fa8ba5a4c..67d0c4ff0a9c0 100644 --- a/htdocs/don/class/don.class.php +++ b/htdocs/don/class/don.class.php @@ -638,6 +638,7 @@ public function delete($user, $notrigger = 0) */ public function fetch($id, $ref = '') { + global $conf; $sql = "SELECT d.rowid, d.datec, d.date_valid, d.tms as datem, d.datedon,"; $sql .= " d.fk_soc as socid, d.firstname, d.lastname, d.societe, d.amount, d.fk_statut as status, d.address, d.zip, d.town, "; $sql .= " d.fk_country, d.public, d.amount, d.fk_payment, d.paid, d.note_private, d.note_public, d.email, d.phone, "; @@ -663,7 +664,7 @@ public function fetch($id, $ref = '') $obj = $this->db->fetch_object($resql); $this->id = $obj->rowid; - $this->ref = $obj->rowid; + $this->ref = $obj->ref != null ? $obj->ref : $obj->rowid; $this->date_creation = $this->db->jdate($obj->datec); $this->datec = $this->db->jdate($obj->datec); $this->date_validation = $this->db->jdate($obj->date_valid); @@ -739,8 +740,10 @@ public function valid_promesse($id, $userid, $notrigger = 0) $error = 0; $this->db->begin(); + $num = $this->getNextNumRef($this->thirdparty); + $this->ref = dol_sanitizeFileName($num); - $sql = "UPDATE ".MAIN_DB_PREFIX."don SET fk_statut = 1, fk_user_valid = ".((int) $userid)." WHERE rowid = ".((int) $id)." AND fk_statut = 0"; + $sql = "UPDATE ".MAIN_DB_PREFIX."don SET fk_statut = 1, fk_user_valid = ".((int) $userid).", ref = '". $this->ref ."' WHERE rowid = ".((int) $id)." AND fk_statut = 0"; $resql = $this->db->query($sql); if ($resql) { @@ -907,6 +910,94 @@ public function load_state_board() } } + /** + * Return next reference of customer invoice not already used (or last reference) + * according to numbering module defined into constant FACTURE_ADDON + * + * @param Societe $soc object company + * @param string $mode 'next' for next value or 'last' for last value + * @return string free ref or last ref + */ + public function getNextNumRef($soc, $mode = 'next') + { + global $conf, $langs; + + $langs->load('don'); + + $moduleName = 'don'; + $moduleSourceName = 'Don'; + $addonConstName = 'DON_ADDON'; + + // Clean parameters (if not defined or using deprecated value) + if ($conf->global->DON_ADDON == 'terre') { + $conf->DON_ADDON = 'mod_facture_terre'; + } + + $addon = $conf->global->DON_ADDON; + + if (!empty($addon)) { + dol_syslog("Call getNextNumRef with ".$addonConstName." = ".$conf->global->FACTURE_ADDON.", thirdparty=".$soc->name.", type=".$soc->typent_code.", mode=".$mode, LOG_DEBUG); + + $mybool = false; + + $file = $addon.'.php'; + $classname = $addon; + + + // Include file with class + $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); + foreach ($dirmodels as $reldir) { + $dir = dol_buildpath($reldir.'core/modules/'.$moduleName.'s/'); + + // Load file with numbering class (if found) + if (is_file($dir.$file) && is_readable($dir.$file)) { + $mybool |= include_once $dir.$file; + } + } + + // For compatibility + if (!$mybool) { + $file = $addon.'/'.$addon.'.modules.php'; + $classname = 'mod_'.$moduleName.'_'.$addon; + $classname = preg_replace('/\-.*$/', '', $classname); + // Include file with class + foreach ($conf->file->dol_document_root as $dirroot) { + $dir = $dirroot.'/core/modules/'.$moduleName.'s/'; + + // Load file with numbering class (if found) + if (is_file($dir.$file) && is_readable($dir.$file)) { + $mybool |= include_once $dir.$file; + } + } + } + + if (!$mybool) { + dol_print_error('', 'Failed to include file '.$file); + return ''; + } + + $obj = new $classname(); + + $numref = $obj->getNextValue($soc, $this, $mode); + + + /** + * $numref can be empty in case we ask for the last value because if there is no invoice created with the + * set up mask. + */ + if ($mode != 'last' && !$numref) { + $this->error = $obj->error; + return ''; + } + + return $numref; + } else { + $langs->load('errors'); + print $langs->trans('Error').' '.$langs->trans('ErrorModuleSetupNotComplete', $langs->transnoentitiesnoconv($moduleSourceName)); + return ''; + } + } + /** * Return clicable name (with picto eventually) * @@ -929,7 +1020,10 @@ public function getNomUrl($withpicto = 0, $notooltip = 0, $moretitle = '', $save if (isset($this->status)) { $label .= ' '.$this->getLibStatut(5); } - if (!empty($this->id)) { + if (!empty($this->ref)) { + $label .= '
'.$langs->trans('Ref').': '.$this->ref; + $label .= '
'.$langs->trans('Date').': '.dol_print_date($this->date, 'day'); + } elseif (!empty($this->id)) { $label .= '
'.$langs->trans('Ref').': '.$this->id; $label .= '
'.$langs->trans('Date').': '.dol_print_date($this->date, 'day'); } diff --git a/htdocs/don/list.php b/htdocs/don/list.php index 12fd413f0de78..f43181e14890f 100644 --- a/htdocs/don/list.php +++ b/htdocs/don/list.php @@ -129,7 +129,7 @@ // Build and execute select // -------------------------------------------------------------------- $sql = "SELECT d.rowid, d.datedon, d.fk_soc as socid, d.firstname, d.lastname, d.societe,"; -$sql .= " d.amount, d.fk_statut as status,"; +$sql .= " d.amount, d.fk_statut as status, d.ref as don_ref, "; $sql .= " p.rowid as pid, p.ref, p.title, p.public"; $sqlfields = $sql; // $sql fields to remove for count total @@ -455,7 +455,7 @@ print ''; } $donationstatic->id = $obj->rowid; - $donationstatic->ref = $obj->rowid; + $donationstatic->ref = $objp->don_ref ? $objp->don_ref : $objp->rowid; $donationstatic->lastname = $obj->lastname; $donationstatic->firstname = $obj->firstname; print "".$donationstatic->getNomUrl(1).""; From 21834715e2817f78e4e8012c2c6d86ad0a53a985 Mon Sep 17 00:00:00 2001 From: David Beniamine Date: Fri, 31 May 2024 16:22:36 +0200 Subject: [PATCH 4/4] Fix pathes --- .../dons/doc/doc_generic_don_odt.modules.php | 541 ++++++++++++++++++ 1 file changed, 541 insertions(+) create mode 100644 htdocs/core/modules/dons/doc/doc_generic_don_odt.modules.php diff --git a/htdocs/core/modules/dons/doc/doc_generic_don_odt.modules.php b/htdocs/core/modules/dons/doc/doc_generic_don_odt.modules.php new file mode 100644 index 0000000000000..38a33c6f7914f --- /dev/null +++ b/htdocs/core/modules/dons/doc/doc_generic_don_odt.modules.php @@ -0,0 +1,541 @@ + + * Copyright (C) 2012 Regis Houssin + * Copyright (C) 2014 Marcos García + * Copyright (C) 2016 Charlie Benke + * Copyright (C) 2018-2019 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 . + * or see https://www.gnu.org/ + */ + +/** + * \file htdocs/core/modules/don/doc/doc_generic_don_odt.modules.php + * \ingroup societe + * \brief File of class to build ODT documents for third parties + */ + +require_once DOL_DOCUMENT_ROOT.'/core/modules/dons/modules_don.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/doc.lib.php'; + + +/** + * Class to build documents using ODF templates generator + */ +class doc_generic_don_odt extends ModeleDon +{ + /** + * Issuer + * @var Societe Object that emits + */ + public $emetteur; + + /** + * @var array Minimum version of PHP required by module. + * e.g.: PHP ≥ 7.0 = array(7, 0) + */ + public $phpmin = array(7, 0); + + /** + * Dolibarr version of the loaded document + * @var string + */ + public $version = 'dolibarr'; + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + global $conf, $langs, $mysoc; + + // Load translation files required by the page + $langs->loadLangs(array("main", "companies")); + + $this->db = $db; + $this->name = "ODT/ODS templates"; + $this->description = $langs->trans("DocumentModelOdt"); + $this->scandir = 'DON_ADDON_PDF_ODT_PATH'; // Name of constant that is used to save list of directories to scan + + // Page size for A4 format + $this->type = 'odt'; + $this->page_largeur = 0; + $this->page_hauteur = 0; + $this->format = array($this->page_largeur, $this->page_hauteur); + $this->marge_gauche = 0; + $this->marge_droite = 0; + $this->marge_haute = 0; + $this->marge_basse = 0; + + $this->option_logo = 1; // Display logo + $this->option_tva = 0; // Manage the vat option FACTURE_TVAOPTION + $this->option_modereg = 0; // Display payment mode + $this->option_condreg = 0; // Display payment terms + $this->option_multilang = 1; // Available in several languages + $this->option_escompte = 0; // Displays if there has been a discount + $this->option_credit_note = 0; // Support credit notes + $this->option_freetext = 1; // Support add of a personalised text + $this->option_draft_watermark = 0; // Support add of a watermark on drafts + + // Recupere emetteur + $this->emetteur = $mysoc; + if (!$this->emetteur->country_code) { + $this->emetteur->country_code = substr($langs->defaultlang, -2); // Par defaut, si n'etait pas defini + } + } + + + /** + * Return description of a module + * + * @param Translate $langs Lang object to use for output + * @return string Description + */ + public function info($langs) + { + global $conf, $langs; + + // Load translation files required by the page + $langs->loadLangs(array("errors", "companies")); + + $form = new Form($this->db); + + $texte = $this->description.".
\n"; + $texte .= '
'; + $texte .= ''; + $texte .= ''; + $texte .= ''; + $texte .= ''; + $texte .= ''; + + // List of directories area + $texte .= ''; + + $texte .= ''; + $texte .= ''; + + $texte .= '
'; + $texttitle = $langs->trans("ListOfDirectories"); + $listofdir = explode(',', preg_replace('/[\r\n]+/', ',', trim($conf->global->DON_ADDON_PDF_ODT_PATH))); + $listoffiles = array(); + foreach ($listofdir as $key => $tmpdir) { + $tmpdir = trim($tmpdir); + $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir); + if (!$tmpdir) { + unset($listofdir[$key]); + continue; + } + if (!is_dir($tmpdir)) { + $texttitle .= img_warning($langs->trans("ErrorDirNotFound", $tmpdir), 0); + } else { + $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.(ods|odt)'); + if (count($tmpfiles)) { + $listoffiles = array_merge($listoffiles, $tmpfiles); + } + } + } + $texthelp = $langs->trans("ListOfDirectoriesForModelGenODT"); + // Add list of substitution keys + $texthelp .= '
'.$langs->trans("FollowingSubstitutionKeysCanBeUsed").'
'; + $texthelp .= $langs->transnoentitiesnoconv("FullListOnOnlineDocumentation"); // This contains an url, we don't modify it + + $texte .= $form->textwithpicto($texttitle, $texthelp, 1, 'help', '', 1); + $texte .= '
'; + $texte .= ''; + $texte .= '
'; + $texte .= ''; + $texte .= '
'; + + // Scan directories + $nbofiles = count($listoffiles); + if (!empty($conf->global->DON_ADDON_PDF_ODT_PATH)) { + $texte .= $langs->trans("NumberOfModelFilesFound").': '; + //$texte.=$nbofiles?'':''; + $texte .= count($listoffiles); + //$texte.=$nbofiles?'':''; + $texte .= ''; + } + if ($nbofiles) { + $texte .= '
'; + // Show list of found files + foreach ($listoffiles as $file) { + $texte .= '- '.$file['name'].' '.img_picto('', 'listlight').''; + $texte .= '   '.img_picto('', 'delete').''; + $texte .= '
'; + } + $texte .= '
'; + } + // Add input to upload a new template file. + $texte .= '
'.$langs->trans("UploadNewTemplate"); + $maxfilesizearray = getMaxFileSizeArray(); + $maxmin = $maxfilesizearray['maxmin']; + if ($maxmin > 0) { + $texte .= ''; // MAX_FILE_SIZE must precede the field type=file + } + $texte .= ' '; + $texte .= ''; + $texte .= ''; + $texte .= '
'; + $texte .= '
'; + $texte .= ''; + $texte .= $langs->trans("ExampleOfDirectoriesForModelGen"); + $texte .= ''; + $texte .= '
'; + $texte .= '
'; + + return $texte; + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps + /** + * Function to build a document on disk using the generic odt module. + * + * @param Don $object Object source to build document + * @param Translate $outputlangs Lang output object + * @param string $srctemplatepath Full path of source filename for generator using a template file + * @param int $hidedetails Do not show line details + * @param int $hidedesc Do not show desc + * @param int $hideref Do not show ref + * @return int 1 if OK, <=0 if KO + */ + public function write_file($object, $outputlangs, $srctemplatepath, $hidedetails = 0, $hidedesc = 0, $hideref = 0) + { + // phpcs:enable + global $user, $langs, $conf, $mysoc, $hookmanager; + + if (empty($srctemplatepath)) { + dol_syslog("doc_generic_odt::write_file parameter srctemplatepath empty", LOG_WARNING); + return -1; + } + + // Add odtgeneration hook + if (!is_object($hookmanager)) { + include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; + $hookmanager = new HookManager($this->db); + } + $hookmanager->initHooks(array('odtgeneration')); + global $action; + + if (!is_object($outputlangs)) { + $outputlangs = $langs; + } + $sav_charset_output = $outputlangs->charset_output; + $outputlangs->charset_output = 'UTF-8'; + + // Load translation files required by the page + $outputlangs->loadLangs(array("main", "dict", "companies", "bills")); + + if ($conf->don->dir_output) { + // If $object is id instead of object + if (!is_object($object)) { + $id = $object; + $object = new Don($this->db); + $result = $object->fetch($id); + if ($result < 0) { + dol_print_error($this->db, $object->error); + return -1; + } + } + + $object->fetch_thirdparty(); + + $dir = $conf->don->dir_output; + $objectref = dol_sanitizeFileName($object->ref); + $objectid = dol_sanitizeFileName($object->id); + if (!preg_match('/specimen/i', $objectref)) { + $dir .= "/".$objectid; + } + $file = $dir."/".$objectid.".odt"; + + if (!file_exists($dir)) { + if (dol_mkdir($dir) < 0) { + $this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir); + return -1; + } + } + + if (file_exists($dir)) { + //print "srctemplatepath=".$srctemplatepath; // Src filename + $newfile = basename($srctemplatepath); + $newfiletmp = preg_replace('/\.od(t|s)/i', '', $newfile); + $newfiletmp = preg_replace('/template_/i', '', $newfiletmp); + $newfiletmp = preg_replace('/modele_/i', '', $newfiletmp); + + $newfiletmp = $objectref . '_' . $newfiletmp; + + // Get extension (ods or odt) + $newfileformat = substr($newfile, strrpos($newfile, '.') + 1); + if (!empty($conf->global->MAIN_DOC_USE_TIMING)) { + $format = $conf->global->MAIN_DOC_USE_TIMING; + if ($format == '1') { + $format = '%Y%m%d%H%M%S'; + } + $filename = $newfiletmp . '-' . dol_print_date(dol_now(), $format) . '.' . $newfileformat; + } else { + $filename = $newfiletmp . '.' . $newfileformat; + } + $file = $dir . '/' . $filename; + //$file=$dir.'/'.$newfiletmp.'.'.dol_print_date(dol_now(),'%Y%m%d%H%M%S').'.odt'; + //print "newdir=".$dir; + //print "newfile=".$newfile; + //print "file=".$file; + //print "conf->societe->dir_temp=".$conf->societe->dir_temp; + + dol_mkdir($conf->don->dir_temp); + if (!is_writable($conf->don->dir_temp)) { + $this->error = $langs->transnoentities("ErrorFailedToWriteInTempDirectory", $conf->don->dir_temp); + dol_syslog('Error in write_file: ' . $this->error, LOG_ERR); + return -1; + } + + // If BILLING contact defined on invoice, we use it + $usecontact = false; + $arrayidcontact = $object->getIdContact('external', 'BILLING'); + if (count($arrayidcontact) > 0) { + $usecontact = true; + $result = $object->fetch_contact($arrayidcontact[0]); + } + + // Recipient name + $contactobject = null; + if (!empty($usecontact)) { + // We can use the company of contact instead of thirdparty company + if ($object->contact->socid != $object->thirdparty->id && (!isset($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT) || !empty($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT))) { + $object->contact->fetch_thirdparty(); + $socobject = $object->contact->thirdparty; + $contactobject = $object->contact; + } else { + $socobject = $object->thirdparty; + // if we have a BILLING contact and we dont use it as thirdparty recipient we store the contact object for later use + $contactobject = $object->contact; + } + } else { + $socobject = $object->thirdparty; + } + + // Fetch info for linked propal + $object->fetchObjectLinked('', '', '', ''); + //print_r($object->linkedObjects['propal']); exit; + + if (isset($object->linkedObjects['propal'][0])) { + $propal_object = $object->linkedObjects['propal'][0]; + } else { + $propal_object = null; + } + + // Make substitution + $substitutionarray = array( + '__FROM_NAME__' => $this->emetteur->name, + '__FROM_EMAIL__' => $this->emetteur->email, + '__TOTAL_TTC__' => $object->total_ttc, + '__TOTAL_HT__' => $object->total_ht, + '__TOTAL_VAT__' => $object->total_tva + ); + complete_substitutions_array($substitutionarray, $langs, $object); + // Call the ODTSubstitution hook + $parameters = array('file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs, 'substitutionarray'=>&$substitutionarray); + $reshook = $hookmanager->executeHooks('ODTSubstitution', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks + + // Line of free text + $newfreetext = ''; + $paramfreetext = 'DON_FREE_TEXT'; + if (!empty($conf->global->$paramfreetext)) { + $newfreetext = make_substitutions($conf->global->$paramfreetext, $substitutionarray); + } + + // Open and load template + require_once ODTPHP_PATH.'odf.php'; + try { + $odfHandler = new odf( + $srctemplatepath, + array( + 'PATH_TO_TMP' => $conf->don->dir_temp, + 'ZIP_PROXY' => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy. + 'DELIMITER_LEFT' => '{', + 'DELIMITER_RIGHT' => '}' + ) + ); + } catch (Exception $e) { + $this->error = $e->getMessage(); + dol_syslog($e->getMessage(), LOG_INFO); + return -1; + } + // After construction $odfHandler->contentXml contains content and + // [!-- BEGIN row.lines --]*[!-- END row.lines --] has been replaced by + // [!-- BEGIN lines --]*[!-- END lines --] + //print html_entity_decode($odfHandler->__toString()); + //print exit; + + + // Make substitutions into odt of freetext + try { + $odfHandler->setVars('free_text', $newfreetext, true, 'UTF-8'); + } catch (OdfException $e) { + dol_syslog($e->getMessage(), LOG_INFO); + } + + // Define substitution array + $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $object); // Set tags __...__ + $array_object_from_properties = $this->get_substitutionarray_each_var_object($object, $outputlangs); + $array_objet = $this->get_substitutionarray_object($object, $outputlangs); // Set tags object_... + $array_user = $this->get_substitutionarray_user($user, $outputlangs); // Set tags myuser_... + $array_soc = $this->get_substitutionarray_mysoc($mysoc, $outputlangs); + $array_thirdparty = $this->get_substitutionarray_thirdparty($socobject, $outputlangs); + $array_propal = is_object($propal_object) ? $this->get_substitutionarray_object($propal_object, $outputlangs, 'propal') : array(); + $array_other = $this->get_substitutionarray_other($outputlangs); + // retrieve contact information for use in object as contact_xxx tags + $array_thirdparty_contact = array(); + if ($usecontact && is_object($contactobject)) { + $array_thirdparty_contact = $this->get_substitutionarray_contact($contactobject, $outputlangs, 'contact'); + } + + $tmparray = array_merge($substitutionarray, $array_object_from_properties, $array_user, $array_soc, $array_thirdparty, $array_objet, $array_propal, $array_other, $array_thirdparty_contact); + complete_substitutions_array($tmparray, $outputlangs, $object); + + // Complete also with substitution from keys directly found into template + // TODO Search all tags {object_...:xxxx} into template then loop on this found tags to analyze them and the the corresponding + // property of object and use the xxxx to know how to format it. + // Before that, we hard code this substitution as if we have found them into the template. + $tmparray['object_PREVIOUS_MONTH'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m'); + $tmparray['object_MONTH'] = dol_print_date($this->date, '%m'); + $tmparray['object_NEXT_MONTH'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m'); + $tmparray['object_PREVIOUS_MONTH_TEXT'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B'); + $tmparray['object_MONTH_TEXT'] = dol_print_date($this->date, '%B'); + $tmparray['object_NEXT_MONTH_TEXT'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B'); + $tmparray['object_PREVIOUS_YEAR'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y'); + $tmparray['object_YEAR'] = dol_print_date($this->date, '%Y'); + $tmparray['object_NEXT_YEAR'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y'); + + // Call the ODTSubstitution hook + $parameters = array('odfHandler'=>&$odfHandler, 'file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs, 'substitutionarray'=>&$tmparray); + $reshook = $hookmanager->executeHooks('ODTSubstitution', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks + + //var_dump($tmparray); exit; + foreach ($tmparray as $key => $value) { + try { + if (preg_match('/logo$/', $key)) { // Image + //var_dump($value);exit; + if (file_exists($value)) { + $odfHandler->setImage($key, $value); + } else { + $odfHandler->setVars($key, 'ErrorFileNotFound', true, 'UTF-8'); + } + } else { + // Text + $odfHandler->setVars($key, $value, true, 'UTF-8'); + } + } catch (OdfException $e) { + dol_syslog($e->getMessage(), LOG_INFO); + } + } + // Replace tags of lines + try { + $foundtagforlines = 1; + try { + $listlines = $odfHandler->setSegment('lines'); + } catch (OdfException $e) { + // We may arrive here if tags for lines not present into template + $foundtagforlines = 0; + dol_syslog($e->getMessage(), LOG_INFO); + } + if ($foundtagforlines) { + $linenumber = 0; + foreach ($object->lines as $line) { + $linenumber++; + $tmparray = $this->get_substitutionarray_lines($line, $outputlangs, $linenumber); + complete_substitutions_array($tmparray, $outputlangs, $object, $line, "completesubstitutionarray_lines"); + // Call the ODTSubstitutionLine hook + $parameters = array('odfHandler'=>&$odfHandler, 'file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs, 'substitutionarray'=>&$tmparray, 'line'=>$line); + $reshook = $hookmanager->executeHooks('ODTSubstitutionLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks + foreach ($tmparray as $key => $val) { + try { + $listlines->setVars($key, $val, true, 'UTF-8'); + } catch (OdfException $e) { + dol_syslog($e->getMessage(), LOG_INFO); + } catch (SegmentException $e) { + dol_syslog($e->getMessage(), LOG_INFO); + } + } + $listlines->merge(); + } + $odfHandler->mergeSegment($listlines); + } + } catch (OdfException $e) { + $this->error = $e->getMessage(); + dol_syslog($this->error, LOG_WARNING); + return -1; + } + + // Replace labels translated + $tmparray = $outputlangs->get_translations_for_substitutions(); + foreach ($tmparray as $key => $value) { + try { + $odfHandler->setVars($key, $value, true, 'UTF-8'); + } catch (OdfException $e) { + dol_syslog($e->getMessage(), LOG_INFO); + } + } + + // Call the beforeODTSave hook + $parameters = array('odfHandler'=>&$odfHandler, 'file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs, 'substitutionarray'=>&$tmparray); + $reshook = $hookmanager->executeHooks('beforeODTSave', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks + + // Write new file + if (!empty($conf->global->MAIN_ODT_AS_PDF)) { + try { + $odfHandler->exportAsAttachedPDF($file); + } catch (Exception $e) { + $this->error = $e->getMessage(); + dol_syslog($e->getMessage(), LOG_INFO); + return -1; + } + } else { + try { + $odfHandler->saveToDisk($file); + } catch (Exception $e) { + $this->error = $e->getMessage(); + dol_syslog($e->getMessage(), LOG_INFO); + return -1; + } + } + $parameters = array('odfHandler'=>&$odfHandler, 'file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs, 'substitutionarray'=>&$tmparray); + $reshook = $hookmanager->executeHooks('afterODTCreation', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks + + if (!empty($conf->global->MAIN_UMASK)) { + @chmod($file, octdec($conf->global->MAIN_UMASK)); + } + + $odfHandler = null; // Destroy object + + $this->result = array('fullpath'=>$file); + + return 1; // Success + } else { + $this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir); + return -1; + } + } + + return -1; + } +}