]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Entity/PdfModel.php
b2456b1231cb4aacbf857f282702b3c5f0fb19d2
[galette.git] / galette / lib / Galette / Entity / PdfModel.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * PDF Model
7 *
8 * PHP version 5
9 *
10 * Copyright © 2013-2014 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
26 *
27 * @category Entity
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2013-2014 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @version SVN: $Id$
34 * @link http://galette.tuxfamily.org
35 * @since Available since 0.7.5dev - 2013-02-19
36 */
37
38 namespace Galette\Entity;
39
40 use Galette\Core;
41 use Galette\Core\Db;
42 use Galette\Repository\PdfModels;
43 use Analog\Analog;
44 use Laminas\Db\Sql\Expression;
45
46 /**
47 * PDF Model
48 *
49 * @category Entity
50 * @name PdfModel
51 * @package Galette
52 * @author Johan Cwiklinski <johan@x-tnd.be>
53 * @copyright 2013-2014 The Galette Team
54 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
55 * @link http://galette.tuxfamily.org
56 * @since Available since 0.7.5dev - 2013-02-19
57 */
58
59 abstract class PdfModel
60 {
61 public const TABLE = 'pdfmodels';
62 public const PK = 'model_id';
63
64 public const MAIN_MODEL = 1;
65 public const INVOICE_MODEL = 2;
66 public const RECEIPT_MODEL = 3;
67 public const ADHESION_FORM_MODEL = 4;
68
69 private $zdb;
70
71 private $id;
72 private $name;
73 private $type;
74 private $header;
75 private $footer;
76 private $title;
77 private $subtitle;
78 private $body;
79 private $styles;
80 private $parent;
81
82 private $patterns;
83 private $replaces;
84
85 /**
86 * Main constructor
87 *
88 * @param Db $zdb Database instance
89 * @param Preferences $preferences Galette preferences
90 * @param int $type Model type
91 * @param mixed $args Arguments
92 */
93 public function __construct(Db $zdb, $preferences, $type, $args = null)
94 {
95 global $container;
96 $router = $container->get('router');
97
98 if (!$zdb) {
99 throw new \RuntimeException(
100 get_class($this) . ' Database instance required!'
101 );
102 }
103
104 $this->zdb = $zdb;
105 $this->type = $type;
106
107 if (is_int($args)) {
108 $this->load($args, $preferences);
109 } elseif ($args !== null && is_object($args)) {
110 $this->loadFromRs($args, $preferences);
111 }
112
113 $this->patterns = array(
114 'asso_name' => '/{ASSO_NAME}/',
115 'asso_slogan' => '/{ASSO_SLOGAN}/',
116 'asso_address' => '/{ASSO_ADDRESS}/',
117 'asso_address_multi' => '/{ASSO_ADDRESS_MULTI}/',
118 'asso_website' => '/{ASSO_WEBSITE}/',
119 'asso_logo' => '/{ASSO_LOGO}/',
120 'date_now' => '/{DATE_NOW}/'
121 );
122
123 $address = $preferences->getPostalAddress();
124 $address_multi = preg_replace("/\n/", "<br>", $address);
125
126 $website = '';
127 if ($preferences->pref_website != '') {
128 $website = '<a href="' . $preferences->pref_website . '">' .
129 $preferences->pref_website . '</a>';
130 }
131
132 $logo = new Core\Logo();
133 $logo_elt = '<img' .
134 ' src="' . $preferences->getURL() . $router->pathFor('logo') . '"' .
135 ' width="' . $logo->getOptimalWidth() . '"' .
136 ' height="' . $logo->getOptimalHeight() . '"' .
137 '/>';
138
139 $this->replaces = array(
140 'asso_name' => $preferences->pref_nom,
141 'asso_slogan' => $preferences->pref_slogan,
142 'asso_address' => $address,
143 'asso_address_multi' => $address_multi,
144 'asso_website' => $website,
145 'asso_logo' => $logo_elt,
146 'date_now' => date(_T('Y-m-d'))
147 );
148 }
149
150 /**
151 * Load a Model from its identifier
152 *
153 * @param int $id Identifier
154 * @param Preferences $preferences Galette preferences
155 * @param boolean $init Init data if required model is missing
156 *
157 * @return void
158 */
159 protected function load($id, $preferences, $init = true)
160 {
161 try {
162 $select = $this->zdb->select(self::TABLE);
163 $select->limit(1)
164 ->where(self::PK . ' = ' . $id);
165
166 $results = $this->zdb->execute($select);
167
168 $count = $results->count();
169 if ($count === 0) {
170 if ($init === true) {
171 $models = new PdfModels($this->zdb, $preferences, $this->login);
172 $models->installInit();
173 $this->load($id, $preferences, false);
174 } else {
175 throw new \RuntimeException('Model not found!');
176 }
177 } else {
178 $this->loadFromRs($results->current(), $preferences);
179 }
180 } catch (\Exception $e) {
181 Analog::log(
182 'An error occurred loading model #' . $id . "Message:\n" .
183 $e->getMessage(),
184 Analog::ERROR
185 );
186 }
187 }
188
189 /**
190 * Load model from a db ResultSet
191 *
192 * @param ResultSet $rs ResultSet
193 * @param Preferences $preferences Galette preferences
194 *
195 * @return void
196 */
197 protected function loadFromRs($rs, $preferences)
198 {
199 $pk = self::PK;
200 $this->id = (int)$rs->$pk;
201
202 $callback = function ($matches) {
203 return _T($matches[1]);
204 };
205 $this->name = preg_replace_callback(
206 '/_T\("([^\"]+)"\)/',
207 $callback,
208 $rs->model_name
209 );
210
211 $this->title = $rs->model_title;
212 $this->subtitle = $rs->model_subtitle;
213 $this->header = $rs->model_header;
214 $this->footer = $rs->model_footer;
215 $this->body = $rs->model_body;
216 $this->styles .= $rs->model_styles;
217
218 if ($this->id > self::MAIN_MODEL) {
219 //FIXME: for now, parent will always be a PdfMain
220 $this->parent = new PdfMain(
221 $this->zdb,
222 $preferences,
223 (int)$rs->model_parent
224 );
225 }
226
227 //let's check if some values should be retrieved from parent model
228 /*if ( $this->id > self::MAIN_MODEL ) {
229 //some infos are missing, load parent
230 if ( trim($this->header) === ''
231 || trim($this->footer) === ''
232 ) {
233 if ( trim($this->header) === '' ) {
234 $this->header = $parent->header;
235 }
236 if ( trim($this->header) === '' ) {
237 $this->footer = $parent->footer;
238 }
239 }
240 }*/
241 }
242
243 /**
244 * Store model in database
245 *
246 * @return boolean
247 */
248 public function store()
249 {
250 $title = $this->title;
251 if (trim($title === '')) {
252 $title = new Expression('NULL');
253 }
254
255 $subtitle = $this->subtitle;
256 if (trim($subtitle === '')) {
257 $subtitle = new Expression('NULL');
258 }
259
260 $data = array(
261 'model_header' => $this->header,
262 'model_footer' => $this->footer,
263 'model_type' => $this->type,
264 'model_title' => $title,
265 'model_subtitle' => $subtitle,
266 'model_body' => $this->body,
267 'model_styles' => $this->styles
268 );
269
270 try {
271 if ($this->id !== null) {
272 $update = $this->zdb->update(self::TABLE);
273 $update->set($data)->where(
274 self::PK . '=' . $this->id
275 );
276 $this->zdb->execute($update);
277 } else {
278 $data['model_name'] = $this->name;
279 $insert = $this->zdb->insert(self::TABLE);
280 $insert->values($data);
281 $add = $this->zdb->execute($insert);
282 if (!$add->count() > 0) {
283 Analog::log('Not stored!', Analog::ERROR);
284 return false;
285 }
286 }
287 return true;
288 } catch (\Exception $e) {
289 Analog::log(
290 'An error occurred storing model: ' . $e->getMessage() .
291 "\n" . print_r($data, true),
292 Analog::ERROR
293 );
294 return false;
295 }
296 }
297
298 /**
299 * Get object class for specified type
300 *
301 * @param int $type Type
302 *
303 * @return Class
304 */
305 public static function getTypeClass($type)
306 {
307 $class = null;
308 switch ($type) {
309 case self::INVOICE_MODEL:
310 $class = 'PdfInvoice';
311 break;
312 case self::RECEIPT_MODEL:
313 $class = 'PdfReceipt';
314 break;
315 case self::ADHESION_FORM_MODEL:
316 $class = 'PdfAdhesionFormModel';
317 break;
318 default:
319 $class = 'PdfMain';
320 break;
321 }
322 $class = 'Galette\\Entity\\' . $class;
323 return $class;
324 }
325
326 /**
327 * Check lenght
328 *
329 * @param string $value The value
330 * @param int $chars Lenght
331 * @param string $field Field name
332 * @param boolean $empty Can value be empty
333 *
334 * @return void
335 */
336 protected function checkChars($value, $chars, $field, $empty = false)
337 {
338 if ($value !== null && trim($value) !== '') {
339 if (mb_strlen($value) > $chars) {
340 throw new \LengthException(
341 str_replace(
342 array('%field', '%chars'),
343 array($field, $chars),
344 _T("%field should be less than %chars characters long.")
345 )
346 );
347 }
348 } else {
349 if ($empty === false) {
350 throw new \UnexpectedValueException(
351 str_replace(
352 '%field',
353 $field,
354 _T("%field should not be empty!")
355 )
356 );
357 }
358 }
359 }
360
361 /**
362 * Extract patterns
363 *
364 * @return array
365 */
366 public function extractDynamicPatterns()
367 {
368
369 $patterns = array();
370 $parts = array('header', 'footer', 'title', 'subtitle', 'body');
371 foreach ($parts as $part) {
372 $content = $this->$part;
373
374 $matches = array();
375 preg_match_all(
376 '/{((LABEL|INPUT)?_DYNFIELD_[0-9]+_ADH)}/',
377 $content,
378 $matches
379 );
380 $patterns = array_merge($patterns, $matches[1]);
381
382 Analog::log("dynamic patterns found in $part: " . join(",", $matches[1]), Analog::DEBUG);
383 }
384
385 return $patterns;
386 }
387
388 /**
389 * Set patterns
390 *
391 * @param array $patterns Patterns to add
392 *
393 * @return void
394 */
395 public function setPatterns($patterns)
396 {
397 $this->patterns = array_merge(
398 $this->patterns,
399 $patterns
400 );
401 }
402
403 /**
404 * Set replacements
405 *
406 * @param array $replaces Replacements to add
407 *
408 * @return void
409 */
410 public function setReplacements($replaces)
411 {
412 $this->replaces = array_merge(
413 $this->replaces,
414 $replaces
415 );
416 }
417
418 /**
419 * Getter
420 *
421 * @param string $name Property name
422 *
423 * @return mixed
424 */
425 public function __get($name)
426 {
427 global $lang;
428
429 switch ($name) {
430 case 'name':
431 case 'id':
432 case 'header':
433 case 'footer':
434 case 'body':
435 case 'title':
436 case 'subtitle':
437 case 'type':
438 case 'styles':
439 return $this->$name;
440 break;
441 case 'hstyles':
442 $value = null;
443
444 //get header and footer from parent if not defined in current model
445 if (
446 $this->id > self::MAIN_MODEL
447 && $this->parent !== null
448 ) {
449 $value = $this->parent->styles;
450 }
451
452 $value .= $this->styles;
453 return $value;
454 break;
455 case 'hheader':
456 case 'hfooter':
457 case 'htitle':
458 case 'hsubtitle':
459 case 'hbody':
460 $pname = substr($name, 1);
461 $prop_value = $this->$pname;
462
463 //get header and footer from parent if not defined in current model
464 if (
465 $this->id > self::MAIN_MODEL
466 && trim($prop_value) === ''
467 && $this->parent !== null
468 && ($pname === 'footer'
469 || $pname === 'header')
470 ) {
471 $prop_value = $this->parent->$pname;
472 }
473
474 //handle translations
475 $callback = function ($matches) {
476 return _T($matches[1]);
477 };
478 $value = preg_replace_callback(
479 '/_T\("([^\"]+)"\)/',
480 $callback,
481 $prop_value
482 );
483
484 //handle replacements
485 $value = preg_replace(
486 $this->patterns,
487 $this->replaces,
488 $value
489 );
490
491 //handle translations with replacements
492 $repl_callback = function ($matches) {
493 return str_replace(
494 $matches[1],
495 $matches[2],
496 $matches[3]
497 );
498 };
499 $value = preg_replace_callback(
500 '/str_replace\(\'([^,]+)\', ?\'([^,]+)\', ?\'(.*)\'\)/',
501 $repl_callback,
502 $value
503 );
504
505 return $value;
506 break;
507 default:
508 Analog::log(
509 'Unable to get PdfModel property ' . $name,
510 Analog::WARNING
511 );
512 break;
513 }
514 }
515
516 /**
517 * Setter
518 *
519 * @param string $name Property name
520 * @param mixed $value Property value
521 *
522 * @return void
523 */
524 public function __set($name, $value)
525 {
526 switch ($name) {
527 case 'type':
528 if (
529 $value === self::MAIN_MODEL
530 || $value === self::INVOICE_MODEL
531 || $value === self::RECEIPT_MODEL
532 || $value === self::ADHESION_FORM_MODEL
533 ) {
534 $this->$name = $value;
535 } else {
536 throw new \UnexpectedValueException(
537 str_replace(
538 '%type',
539 $value,
540 _T("Unknown type %type!")
541 )
542 );
543 }
544 break;
545 case 'name':
546 try {
547 $this->checkChars($value, 50, _T("Name"));
548 $this->$name = $value;
549 } catch (\Exception $e) {
550 throw $e;
551 }
552 break;
553 case 'title':
554 case 'subtitle':
555 if ($name == 'title') {
556 $field = _T("Title");
557 } else {
558 $field = _T("Subtitle");
559 }
560 try {
561 $this->checkChars($value, 100, $field, true);
562 $this->$name = $value;
563 } catch (\Exception $e) {
564 throw $e;
565 }
566 break;
567 case 'header':
568 case 'footer':
569 case 'body':
570 if ($value == null || trim($value) == '') {
571 if (get_class($this) === 'PdfMain' && $name !== 'body') {
572 throw new \UnexpectedValueException(
573 _T("header and footer should not be empty!")
574 );
575 } elseif (get_class($this) !== 'PdfMain' && $name === 'body') {
576 throw new \UnexpectedValueException(
577 _T("body should not be empty!")
578 );
579 }
580 }
581
582 if (function_exists('tidy_parse_string')) {
583 //if tidy extension is present, we use it to clean a bit
584 /*$tidy_config = array(
585 'clean' => true,
586 'show-body-only' => true,
587 'join-styles' => false,
588 'join-classes' => false,
589 'wrap' => 0,
590 );
591 $tidy = tidy_parse_string($value, $tidy_config, 'UTF8');
592 $tidy->cleanRepair();
593 $this->$name = tidy_get_output($tidy);*/
594 $this->$name = $value;
595 } else {
596 //if it is not... Well, let's serve the text as it.
597 $this->$name = $value;
598 }
599 break;
600 case 'styles':
601 $this->styles = $value;
602 break;
603 default:
604 Analog::log(
605 'Unable to set PdfModel property ' . $name,
606 Analog::WARNING
607 );
608 break;
609 }
610 }
611 }