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