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