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