Passed
Branch develop (21edb4)
by
unknown
41:33
created

doc_generic_invoice_odt   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 468
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 274
dl 0
loc 468
rs 6.96
c 1
b 0
f 0
wmc 53

3 Methods

Rating   Name   Duplication   Size   Complexity  
B info() 0 84 8
A __construct() 0 37 2
F write_file() 0 297 43

How to fix   Complexity   

Complex Class

Complex classes like doc_generic_invoice_odt often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use doc_generic_invoice_odt, and based on these observations, apply Extract Interface, too.

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