File: //proc/self/cwd/guiaweb/htdocs/core/modules/societe/doc/doc_generic_odt.modules.php
<?php
/* Copyright (C) 2010-2011 Laurent Destailleur <ely@users.sourceforge.net>
* Copyright (C) 2016 Charlie Benke <charlie@patas-monkey.com>
* Copyright (C) 2018-2024 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
*
* 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 <https://www.gnu.org/licenses/>.
* or see https://www.gnu.org/
*/
/**
* \file htdocs/core/modules/societe/doc/doc_generic_odt.modules.php
* \ingroup societe
* \brief File of class to build ODT documents for third parties
*/
require_once DOL_DOCUMENT_ROOT.'/core/modules/societe/modules_societe.class.php';
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/company.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_odt extends ModeleThirdPartyDoc
{
/**
* @var string Version, possible values are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated' or a version string like 'x.y.z'''|'development'|'dolibarr'|'experimental' Dolibarr version of the loaded document
*/
public $version = 'dolibarr';
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
global $langs, $mysoc;
// Load translation files required by the page
$langs->loadLangs(array("main", "companies"));
$this->db = $db;
$this->name = "ODT templates";
$this->description = $langs->trans("DocumentModelOdt");
$this->scandir = 'COMPANY_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_freetext = 0; // Support add of a personalised text
$this->option_draft_watermark = 0; // Support add of a watermark on drafts
if ($mysoc === null) {
dol_syslog(get_class($this).'::__construct() Global $mysoc should not be null.'. getCallerInfoString(), LOG_ERR);
return;
}
// Retrieves issuer
$this->emetteur = $mysoc;
if (!$this->emetteur->country_code) {
$this->emetteur->country_code = substr($langs->defaultlang, -2); // By default, if was not defined
}
}
/**
* Return description of a module
*
* @param Translate $langs Object language
* @return string Description
*/
public function info($langs)
{
global $langs;
// Load traductions files required by page
$langs->loadLangs(array("companies", "errors"));
$form = new Form($this->db);
$texte = $this->description.".<br>\n";
$texte .= '<!-- form for option of ODT templates -->';
$texte .= '<form action="'.$_SERVER["PHP_SELF"].'" method="POST" enctype="multipart/form-data">';
$texte .= '<input type="hidden" name="token" value="'.newToken().'">';
$texte .= '<input type="hidden" name="page_y" value="">';
$texte .= '<input type="hidden" name="action" value="setModuleOptions">';
$texte .= '<input type="hidden" name="param1" value="COMPANY_ADDON_PDF_ODT_PATH">';
$texte .= '<table class="nobordernopadding centpercent">';
// List of directories area
$texte .= '<tr><td>';
$texttitle = $langs->trans("ListOfDirectories");
$listofdir = explode(',', preg_replace('/[\r\n]+/', ',', trim(getDolGlobalString('COMPANY_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), '');
} else {
$tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0, 1); // Disable hook for the moment
if (count($tmpfiles)) {
$listoffiles = array_merge($listoffiles, $tmpfiles);
}
}
}
$texthelp = $langs->trans("ListOfDirectoriesForModelGenODT");
$texthelp .= '<br><br><span class="opacitymedium">'.$langs->trans("ExampleOfDirectoriesForModelGen").'</span>';
// Add list of substitution keys
$texthelp .= '<br>'.$langs->trans("FollowingSubstitutionKeysCanBeUsed").'<br>';
$texthelp .= $langs->transnoentitiesnoconv("FullListOnOnlineDocumentation"); // This contains an url, we don't modify it
if (!getDolGlobalString('MAIN_NO_MULTIDIR_FOR_ODT')) {
$texte .= $form->textwithpicto($texttitle, $texthelp, 1, 'help', '', 1, 3, $this->name);
$texte .= '<div><div style="display: inline-block; min-width: 100px; vertical-align: middle;">';
//$texte .= '<table><tr><td>';
$texte .= '<textarea class="flat textareafordir" spellcheck="false" cols="60" name="value1">';
$texte .= getDolGlobalString('COMPANY_ADDON_PDF_ODT_PATH');
$texte .= '</textarea>';
//$texte .= '</td>';
//$texte .= '<td class="center"> ';
$texte .= '</div><div style="display: inline-block; vertical-align: middle;">';
$texte .= '<input type="submit" class="button button-edit reposition smallpaddingimp" name="modify" value="'.dol_escape_htmltag($langs->trans("Modify")).'">';
//$texte .= '</td>';
//$texte .= '</tr>';
//$texte .= '</table>';
$texte .= '</div></div>';
} else {
$texte .= '<br>';
$texte .= '<input type="hidden" name="value1" value="COMPANY_ADDON_PDF_ODT_PATH">';
}
// Scan directories
$nbofiles = count($listoffiles);
if (getDolGlobalString('COMPANY_ADDON_PDF_ODT_PATH')) {
$texte .= $langs->trans("NumberOfModelFilesFound").': <b>';
//$texte.=$nbofiles?'<a id="a_'.get_class($this).'" href="#">':'';
$texte .= $nbofiles;
//$texte.=$nbofiles?'</a>':'';
$texte .= '</b>';
}
if ($nbofiles) {
$texte .= '<div id="div_'.get_class($this).'" class="hiddenx">';
// Show list of found files
foreach ($listoffiles as $file) {
$texte .= '- '.$file['name'].' <a class="reposition" href="'.DOL_URL_ROOT.'/document.php?modulepart=doctemplates&file=thirdparties/'.urlencode(basename($file['name'])).'">'.img_picto('', 'listlight').'</a>';
$texte .= ' <a class="reposition" href="'.$_SERVER["PHP_SELF"].'?modulepart=doctemplates&keyforuploaddir=COMPANY_ADDON_PDF_ODT_PATH&action=deletefile&token='.newToken().'&file='.urlencode(basename($file['name'])).'">'.img_picto('', 'delete').'</a>';
$texte .= '<br>';
}
$texte .= '</div>';
}
// Add input to upload a new template file.
$texte .= '<div>'.$langs->trans("UploadNewTemplate");
$maxfilesizearray = getMaxFileSizeArray();
$maxmin = $maxfilesizearray['maxmin'];
if ($maxmin > 0) {
$texte .= '<input type="hidden" name="MAX_FILE_SIZE" value="'.($maxmin * 1024).'">'; // MAX_FILE_SIZE must precede the field type=file
}
$texte .= ' <input type="file" name="uploadfile">';
$texte .= '<input type="hidden" value="COMPANY_ADDON_PDF_ODT_PATH" name="keyforuploaddir">';
$texte .= '<input type="submit" class="button smallpaddingimp reposition" value="'.dol_escape_htmltag($langs->trans("Upload")).'" name="upload">';
$texte .= '</div>';
$texte .= '</td>';
$texte .= '</tr>';
$texte .= '</table>';
$texte .= '</form>';
return $texte;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Function to build a document on disk using the generic odt module.
*
* @param Societe $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<0,1> $hidedetails Do not show line details
* @param int<0,1> $hidedesc Do not show desc
* @param int<0,1> $hideref Do not show ref
* @return int<-1,1> 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;
global $action;
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", "projects"));
if ($conf->societe->multidir_output[$object->entity]) {
$dir = $conf->societe->multidir_output[$object->entity];
$objectref = dol_sanitizeFileName((string) $object->id);
if (!preg_match('/specimen/i', $objectref)) {
$dir .= "/".$objectref;
}
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(s|t)/i', '', $newfile);
$newfiletmp = preg_replace('/template_/i', '', $newfiletmp);
$newfiletmp = preg_replace('/modele_/i', '', $newfiletmp);
// Get extension (ods or odt)
$newfileformat = substr($newfile, strrpos($newfile, '.') + 1);
if (getDolGlobalString('MAIN_DOC_USE_OBJECT_THIRDPARTY_NAME')) {
$newfiletmp = dol_sanitizeFileName(dol_string_nospecial($object->name)) . '-' . $newfiletmp;
$newfiletmp = preg_replace('/__+/', '_', $newfiletmp); // Replace repeated _ into one _ (to avoid string with substitution syntax)
}
if (getDolGlobalString('MAIN_DOC_USE_TIMING')) {
$format = getDolGlobalString('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;
$object->builddoc_filename = $filename; // For triggers
$object->context['builddoc_filename'] = $filename; // For triggers
//print "newfileformat=".$newfileformat;
//print "newdir=".$dir;
//print "newfile=".$newfile;
//print "file=".$file;
//print "conf->societe->dir_temp=".$conf->societe->dir_temp;
//exit;
dol_mkdir($conf->societe->multidir_temp[$object->entity]);
if (!is_writable($conf->societe->multidir_temp[$object->entity])) {
$this->error = $langs->transnoentities("ErrorFailedToWriteInTempDirectory", $conf->societe->multidir_temp[$object->entity]);
dol_syslog('Error in write_file: ' . $this->error, LOG_ERR);
return -1;
}
// Open and load template
require_once ODTPHP_PATH.'odf.php';
try {
$odfHandler = new Odf(
$srctemplatepath,
array(
'PATH_TO_TMP' => $conf->societe->multidir_temp[$object->entity],
'ZIP_PROXY' => getDolGlobalString('MAIN_ODF_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;
}
//print $odfHandler->__toString()."\n";
// Replace tags of lines for contacts
$contact_arrray = array();
$sql = "SELECT p.rowid";
$sql .= " FROM ".MAIN_DB_PREFIX."socpeople as p";
$sql .= " WHERE p.fk_soc = ".((int) $object->id);
$result = $this->db->query($sql);
$num = $this->db->num_rows($result);
if ($num) {
require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
$i = 0;
$contactstatic = new Contact($this->db);
while ($i < $num) {
$obj = $this->db->fetch_object($result);
$contact_arrray[$i] = $obj->rowid;
$i++;
}
}
if ((is_array($contact_arrray) && count($contact_arrray) > 0)) {
$foundtagforlines = 1;
try {
$listlines = $odfHandler->setSegment('companycontacts');
} catch (OdfExceptionSegmentNotFound $e) {
// We may arrive here if tags for lines not present into template
$foundtagforlines = 0;
dol_syslog($e->getMessage(), LOG_INFO);
}
if ($foundtagforlines && $contactstatic !== null) {
foreach ($contact_arrray as $array_key => $contact_id) {
$res_contact = $contactstatic->fetch($contact_id);
if ((int) $res_contact > 0) {
$tmparray = $this->get_substitutionarray_contact($contactstatic, $outputlangs, 'contact');
foreach ($tmparray as $key => $val) {
try {
$listlines->setVars($key, $val, true, 'UTF-8');
} catch (SegmentException $e) {
dol_syslog($e->getMessage(), LOG_INFO);
}
}
$listlines->merge();
} else {
$this->error = $contactstatic->error;
dol_syslog($this->error, LOG_WARNING);
}
}
try {
$odfHandler->mergeSegment($listlines);
} catch (OdfException $e) {
$this->error = $e->getMessage();
dol_syslog($this->error, LOG_WARNING);
//return -1;
}
}
}
// Make substitutions into odt
$array_user = $this->get_substitutionarray_user($user, $outputlangs);
$array_soc = $this->get_substitutionarray_mysoc($mysoc, $outputlangs);
$array_thirdparty = $this->get_substitutionarray_thirdparty($object, $outputlangs);
$array_other = $this->get_substitutionarray_other($outputlangs);
$tmparray = array_merge($array_user, $array_soc, $array_thirdparty, $array_other);
complete_substitutions_array($tmparray, $outputlangs, $object);
// 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
// retrieve the constant to apply a ratio for image size or set the ratio to 1
if (getDolGlobalString('MAIN_DOC_ODT_IMAGE_RATIO')) {
$ratio = floatval(getDolGlobalString('MAIN_DOC_ODT_IMAGE_RATIO'));
} else {
$ratio = 1;
}
// Replace variables into document
foreach ($tmparray as $key => $value) {
try {
if (preg_match('/logo$/', $key)) { // Image
if (file_exists($value)) {
$odfHandler->setImage($key, $value, $ratio);
} else {
$odfHandler->setVars($key, 'ErrorFileNotFound', true, 'UTF-8');
}
} else { // Text
$odfHandler->setVars($key, $value, true, 'UTF-8');
}
} catch (OdfException $e) {
// setVars failed, probably because key not found
dol_syslog($e->getMessage(), LOG_INFO);
}
}
// 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 (getDolGlobalString('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->creator = $user->getFullName($outputlangs);
$odfHandler->title = $object->builddoc_filename;
$odfHandler->subject = $object->builddoc_filename;
if (getDolGlobalString('ODT_ADD_DOLIBARR_ID')) {
$odfHandler->userdefined['dol_id'] = $object->id;
$odfHandler->userdefined['dol_element'] = $object->element;
}
$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
dolChmod($file);
$odfHandler = null; // Destroy object
$this->result = array('fullpath' => $file);
return 1; // Success
} else {
$this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir);
return -1;
}
}
$this->error = 'UnknownError';
return -1;
}
}