1<?php 2/* Copyright (C) 2010-2012 Laurent Destailleur <eldy@users.sourceforge.net> 3 * Copyright (C) 2012 Juanjo Menent <jmenent@2byte.es> 4 * Copyright (C) 2018 Frédéric France <frederic.france@netlogic.fr> 5* 6* This program is free software; you can redistribute it and/or modify 7* it under the terms of the GNU General Public License as published by 8* the Free Software Foundation; either version 3 of the License, or 9* (at your option) any later version. 10* 11* This program is distributed in the hope that it will be useful, 12* but WITHOUT ANY WARRANTY; without even the implied warranty of 13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14* GNU General Public License for more details. 15* 16* You should have received a copy of the GNU General Public License 17* along with this program. If not, see <https://www.gnu.org/licenses/>. 18* or see https://www.gnu.org/ 19*/ 20 21/** 22 * \file htdocs/core/modules/member/doc/doc_generic_member_odt.modules.php 23 * \ingroup societe 24 * \brief File of class to build ODT documents for members 25 */ 26 27require_once DOL_DOCUMENT_ROOT.'/core/modules/member/modules_member.class.php'; 28require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; 29require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; 30require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 31require_once DOL_DOCUMENT_ROOT.'/core/lib/doc.lib.php'; 32 33 34/** 35 * Class to build documents using ODF templates generator 36 */ 37class doc_generic_member_odt extends ModelePDFMember 38{ 39 /** 40 * @var Societe Issuer 41 */ 42 public $emetteur; 43 44 /** 45 * @var array Minimum version of PHP required by module. 46 * e.g.: PHP ≥ 5.6 = array(5, 6) 47 */ 48 public $phpmin = array(5, 6); 49 50 /** 51 * Dolibarr version of the loaded document 52 * @var string 53 */ 54 public $version = 'dolibarr'; 55 56 57 /** 58 * Constructor 59 * 60 * @param DoliDB $db Database handler 61 */ 62 public function __construct($db) 63 { 64 global $conf, $langs, $mysoc; 65 66 // Load translation files required by the page 67 $langs->loadLangs(array("main", "companies")); 68 69 $this->db = $db; 70 $this->name = "ODT templates"; 71 $this->description = $langs->trans("DocumentModelOdt"); 72 $this->scandir = 'MEMBER_ADDON_PDF_ODT_PATH'; // Name of constant that is used to save list of directories to scan 73 74 // Page size for A4 format 75 $this->type = 'odt'; 76 $this->page_largeur = 0; 77 $this->page_hauteur = 0; 78 $this->format = array($this->page_largeur, $this->page_hauteur); 79 $this->marge_gauche = 0; 80 $this->marge_droite = 0; 81 $this->marge_haute = 0; 82 $this->marge_basse = 0; 83 84 $this->option_logo = 1; // Affiche logo 85 $this->option_tva = 0; // Gere option tva MEMBER_TVAOPTION 86 $this->option_modereg = 0; // Affiche mode reglement 87 $this->option_condreg = 0; // Affiche conditions reglement 88 $this->option_codeproduitservice = 0; // Affiche code produit-service 89 $this->option_multilang = 1; // Dispo en plusieurs langues 90 $this->option_escompte = 0; // Affiche si il y a eu escompte 91 $this->option_credit_note = 0; // Support credit notes 92 $this->option_freetext = 1; // Support add of a personalised text 93 $this->option_draft_watermark = 0; // Support add of a watermark on drafts 94 95 // Recupere emetteur 96 $this->emetteur = $mysoc; 97 if (!$this->emetteur->country_code) $this->emetteur->country_code = substr($langs->defaultlang, -2); // By default if not defined 98 } 99 100 101 /** 102 * Return description of a module 103 * 104 * @param Translate $langs Lang object to use for output 105 * @return string Description 106 */ 107 public function info($langs) 108 { 109 global $conf, $langs; 110 111 // Load translation files required by the page 112 $langs->loadLangs(array('companies', 'errors')); 113 114 $form = new Form($this->db); 115 116 $texte = $this->description.".<br>\n"; 117 $texte .= '<form action="'.$_SERVER["PHP_SELF"].'" method="POST" enctype="multipart/form-data">'; 118 $texte .= '<input type="hidden" name="token" value="'.newToken().'">'; 119 $texte .= '<input type="hidden" name="action" value="setModuleOptions">'; 120 $texte .= '<input type="hidden" name="param1" value="MEMBER_ADDON_PDF_ODT_PATH">'; 121 $texte .= '<table class="nobordernopadding" width="100%">'; 122 123 // List of directories area 124 $texte .= '<tr><td>'; 125 $texttitle = $langs->trans("ListOfDirectories"); 126 $listofdir = explode(',', preg_replace('/[\r\n]+/', ',', trim($conf->global->MEMBER_ADDON_PDF_ODT_PATH))); 127 $listoffiles = array(); 128 foreach ($listofdir as $key=>$tmpdir) 129 { 130 $tmpdir = trim($tmpdir); 131 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir); 132 if (!$tmpdir) { 133 unset($listofdir[$key]); continue; 134 } 135 if (!is_dir($tmpdir)) $texttitle .= img_warning($langs->trans("ErrorDirNotFound", $tmpdir), 0); 136 else { 137 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.(ods|odt)'); 138 if (count($tmpfiles)) $listoffiles = array_merge($listoffiles, $tmpfiles); 139 } 140 } 141 $texthelp = $langs->trans("ListOfDirectoriesForModelGenODT"); 142 // Add list of substitution keys 143 $texthelp .= '<br>'.$langs->trans("FollowingSubstitutionKeysCanBeUsed").'<br>'; 144 $texthelp .= $langs->transnoentitiesnoconv("FullListOnOnlineDocumentation"); // This contains an url, we don't modify it 145 146 $texte .= $form->textwithpicto($texttitle, $texthelp, 1, 'help', '', 1); 147 $texte .= '<div><div style="display: inline-block; min-width: 100px; vertical-align: middle;">'; 148 $texte .= '<textarea class="flat" cols="60" name="value1">'; 149 $texte .= $conf->global->MEMBER_ADDON_PDF_ODT_PATH; 150 $texte .= '</textarea>'; 151 $texte .= '</div><div style="display: inline-block; vertical-align: middle;">'; 152 $texte .= '<input type="submit" class="button" value="'.$langs->trans("Modify").'" name="Button">'; 153 $texte .= '<br></div></div>'; 154 155 // Scan directories 156 if (count($listofdir)) 157 { 158 $texte .= $langs->trans("NumberOfModelFilesFound").': <b>'.count($listoffiles).'</b>'; 159 160 $texte .= '<div id="div_'.get_class($this).'" class="hiddenx">'; 161 // Show list of found files 162 foreach ($listoffiles as $file) { 163 $texte .= '- '.$file['name'].' <a href="'.DOL_URL_ROOT.'/document.php?modulepart=doctemplates&file=members/'.urlencode(basename($file['name'])).'">'.img_picto('', 'listlight').'</a><br>'; 164 } 165 $texte .= '</div>'; 166 } 167 // Add input to upload a new template file. 168 $texte .= '<div>'.$langs->trans("UploadNewTemplate").' <input type="file" name="uploadfile">'; 169 $texte .= '<input type="hidden" value="MEMBER_ADDON_PDF_ODT_PATH" name="keyforuploaddir">'; 170 $texte .= '<input type="submit" class="button" value="'.dol_escape_htmltag($langs->trans("Upload")).'" name="upload">'; 171 $texte .= '</div>'; 172 $texte .= '</td>'; 173 174 $texte .= '<td rowspan="2" class="tdtop hideonsmartphone">'; 175 $texte .= $langs->trans("ExampleOfDirectoriesForModelGen"); 176 $texte .= '</td>'; 177 $texte .= '</tr>'; 178 179 $texte .= '</table>'; 180 $texte .= '</form>'; 181 182 return $texte; 183 } 184 185 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 186 /** 187 * Function to build a document on disk using the generic odt module. 188 * 189 * @param Adherent $object Object source to build document 190 * @param Translate $outputlangs Lang output object 191 * @param string $srctemplatepath Full path of source filename for generator using a template file 192 * @param string $mode Tell if doc module is called for 'member', ... 193 * @param int $nooutput 1=Generate only file on disk and do not return it on response 194 * @return int 1 if OK, <=0 if KO 195 */ 196 public function write_file($object, $outputlangs, $srctemplatepath, $mode = 'member', $nooutput = 0) 197 { 198 // phpcs:enable 199 global $user, $langs, $conf, $mysoc, $hookmanager; 200 201 if (empty($srctemplatepath)) { 202 dol_syslog("doc_generic_odt::write_file parameter srctemplatepath empty", LOG_WARNING); 203 return -1; 204 } 205 206 // Add odtgeneration hook 207 if (!is_object($hookmanager)) 208 { 209 include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; 210 $hookmanager = new HookManager($this->db); 211 } 212 $hookmanager->initHooks(array('odtgeneration')); 213 global $action; 214 215 if (!is_object($outputlangs)) $outputlangs = $langs; 216 $sav_charset_output = $outputlangs->charset_output; 217 $outputlangs->charset_output = 'UTF-8'; 218 219 // Load translation files required by the page 220 $outputlangs->loadLangs(array("main", "companies", "bills", "dict")); 221 222 if ($conf->adherent->dir_output) { 223 // If $object is id instead of object 224 if (!is_object($object)) { 225 $id = $object; 226 $object = new User($this->db); 227 $result = $object->fetch($id); 228 if ($result < 0) { 229 dol_print_error($this->db, $object->error); 230 return -1; 231 } 232 } 233 234 $object->fetch_thirdparty(); 235 236 $dir = $conf->adherent->dir_output; 237 $objectref = dol_sanitizeFileName($object->ref); 238 if (!preg_match('/specimen/i', $objectref)) $dir .= "/".$objectref; 239 $file = $dir."/".$objectref.".odt"; 240 241 if (!file_exists($dir)) { 242 if (dol_mkdir($dir) < 0) { 243 $this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir); 244 return -1; 245 } 246 } 247 248 if (file_exists($dir)) { 249 //print "srctemplatepath=".$srctemplatepath; // Src filename 250 $newfile = basename($srctemplatepath); 251 $newfiletmp = preg_replace('/\.od(t|s)/i', '', $newfile); 252 $newfiletmp = preg_replace('/template_/i', '', $newfiletmp); 253 $newfiletmp = preg_replace('/modele_/i', '', $newfiletmp); 254 255 $newfiletmp = $objectref.'_'.$newfiletmp; 256 257 // Get extension (ods or odt) 258 $newfileformat = substr($newfile, strrpos($newfile, '.') + 1); 259 if (!empty($conf->global->MAIN_DOC_USE_TIMING)) { 260 $format = $conf->global->MAIN_DOC_USE_TIMING; 261 if ($format == '1') $format = '%Y%m%d%H%M%S'; 262 $filename = $newfiletmp.'-'.dol_print_date(dol_now(), $format).'.'.$newfileformat; 263 } else { 264 $filename = $newfiletmp.'.'.$newfileformat; 265 } 266 $file = $dir.'/'.$filename; 267 //print "newdir=".$dir; 268 //print "newfile=".$newfile; 269 //print "file=".$file; 270 //print "conf->adherent->dir_temp=".$conf->adherent->dir_temp; 271 272 dol_mkdir($conf->adherent->dir_temp); 273 274 275 // If CUSTOMER contact defined on member, we use it 276 $usecontact = false; 277 $arrayidcontact = $object->getIdContact('external', 'CUSTOMER'); 278 if (count($arrayidcontact) > 0) { 279 $usecontact = true; 280 $result = $object->fetch_contact($arrayidcontact[0]); 281 } 282 283 // Recipient name 284 if (!empty($usecontact)) { 285 if ($usecontact && ($object->contact->fk_soc != $object->thirdparty->id && (!isset($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT) || !empty($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT)))) { 286 $socobject = $object->contact; 287 } else { 288 $socobject = $object->thirdparty; 289 // if we have a CUSTOMER contact and we dont use it as recipient we store the contact object for later use 290 $contactobject = $object->contact; 291 } 292 } else { 293 $socobject = $object->thirdparty; 294 } 295 296 // Open and load template 297 require_once ODTPHP_PATH.'odf.php'; 298 try { 299 $odfHandler = new odf( 300 $srctemplatepath, 301 array( 302 'PATH_TO_TMP' => $conf->adherent->dir_temp, 303 'ZIP_PROXY' => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy. 304 'DELIMITER_LEFT' => '{', 305 'DELIMITER_RIGHT' => '}' 306 ) 307 ); 308 } catch (Exception $e) 309 { 310 $this->error = $e->getMessage(); 311 dol_syslog($e->getMessage(), LOG_WARNING); 312 return -1; 313 } 314 315 // Make substitutions into odt 316 $array_member = $this->getSubstitutionarrayMember($object, $outputlangs); 317 $array_soc = $this->get_substitutionarray_mysoc($mysoc, $outputlangs); 318 $array_thirdparty = $this->get_substitutionarray_thirdparty($socobject, $outputlangs); 319 $array_other = $this->get_substitutionarray_other($outputlangs); 320 // retrieve contact information for use in object as contact_xxx tags 321 $array_thirdparty_contact = array(); 322 if ($usecontact && is_object($contactobject)) $array_thirdparty_contact = $this->get_substitutionarray_contact($contactobject, $outputlangs, 'contact'); 323 324 $tmparray = array_merge($array_member, $array_soc, $array_thirdparty, $array_other, $array_thirdparty_contact); 325 complete_substitutions_array($tmparray, $outputlangs, $object); 326 // Call the ODTSubstitution hook 327 $parameters = array( 328 'file'=>$file, 329 'object'=>$object, 330 'outputlangs'=>$outputlangs, 331 'substitutionarray'=>&$tmparray 332 ); 333 $reshook = $hookmanager->executeHooks('ODTSubstitution', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks 334 foreach ($tmparray as $key=>$value) { 335 try { 336 if (preg_match('/logo$/', $key)) { 337 // Image 338 if (file_exists($value)) $odfHandler->setImage($key, $value); 339 else $odfHandler->setVars($key, 'ErrorFileNotFound', true, 'UTF-8'); 340 } else { 341 // Text 342 $odfHandler->setVars($key, $value, true, 'UTF-8'); 343 } 344 } catch (OdfException $e) { 345 dol_syslog($e->getMessage(), LOG_WARNING); 346 } 347 } 348 349 // Replace labels translated 350 $tmparray = $outputlangs->get_translations_for_substitutions(); 351 foreach ($tmparray as $key=>$value) { 352 try { 353 $odfHandler->setVars($key, $value, true, 'UTF-8'); 354 } catch (OdfException $e) { 355 dol_syslog($e->getMessage(), LOG_WARNING); 356 } 357 } 358 359 // Call the beforeODTSave hook 360 $parameters = array('odfHandler'=>&$odfHandler, 'file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs); 361 $reshook = $hookmanager->executeHooks('beforeODTSave', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks 362 363 // Write new file 364 if (!empty($conf->global->MAIN_ODT_AS_PDF)) { 365 try { 366 $odfHandler->exportAsAttachedPDF($file); 367 } catch (Exception $e) { 368 $this->error = $e->getMessage(); 369 dol_syslog($e->getMessage(), LOG_WARNING); 370 return -1; 371 } 372 } else { 373 try { 374 $odfHandler->saveToDisk($file); 375 } catch (Exception $e) { 376 $this->error = $e->getMessage(); 377 dol_syslog($e->getMessage(), LOG_WARNING); 378 return -1; 379 } 380 } 381 382 $reshook = $hookmanager->executeHooks('afterODTCreation', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks 383 384 if (!empty($conf->global->MAIN_UMASK)) 385 @chmod($file, octdec($conf->global->MAIN_UMASK)); 386 387 $odfHandler = null; // Destroy object 388 389 $this->result = array('fullpath'=>$file); 390 391 return 1; // Success 392 } else { 393 $this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir); 394 return -1; 395 } 396 } 397 398 return -1; 399 } 400 401 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 402 /** 403 * get substitution array for object 404 * 405 * @param Adherent $object member 406 * @param Translate $outputlangs translation object 407 * @param string $array_key key for array 408 * @return array array of substitutions 409 */ 410 public function get_substitutionarray_object($object, $outputlangs, $array_key = 'object') 411 { 412 // phpcs:enable 413 $array_other = array(); 414 foreach ($object as $key => $value) { 415 if (!is_array($value) && !is_object($value)) { 416 $array_other[$array_key.'_'.$key] = $value; 417 } 418 } 419 return $array_other; 420 } 421} 422