]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Entity/PdfModel.php
Switch to phpstan level 4
[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-2023 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-2023 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 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7.5dev - 2013-02-19
35 */
36
37 namespace Galette\Entity;
38
39 use ArrayObject;
40 use Slim\Routing\RouteParser;
41 use Throwable;
42 use Galette\Core\Db;
43 use Galette\Core\Preferences;
44 use Galette\Features\Replacements;
45 use Galette\Repository\PdfModels;
46 use Analog\Analog;
47 use Laminas\Db\Sql\Expression;
48
49 /**
50 * PDF Model
51 *
52 * @category Entity
53 * @name PdfModel
54 * @package Galette
55 * @author Johan Cwiklinski <johan@x-tnd.be>
56 * @copyright 2013-2023 The Galette Team
57 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
58 * @link http://galette.tuxfamily.org
59 * @since Available since 0.7.5dev - 2013-02-19
60 *
61 * @property integer $id
62 * @property string $name
63 * @property integer $type
64 * @property string $header
65 * @property-read string $hheader
66 * @property string $footer
67 * @property-read string $hfooter
68 * @property string $title
69 * @property-read string $htitle
70 * @property string $subtitle
71 * @property-read string $hsubtitle
72 * @property string $body
73 * @property-read string $hbody
74 * @property string $styles
75 * @property PdfMain $parent
76 */
77
78 abstract class PdfModel
79 {
80 use Replacements;
81
82 public const TABLE = 'pdfmodels';
83 public const PK = 'model_id';
84
85 public const MAIN_MODEL = 1;
86 public const INVOICE_MODEL = 2;
87 public const RECEIPT_MODEL = 3;
88 public const ADHESION_FORM_MODEL = 4;
89
90 private $id;
91 private $name;
92 private $type;
93 private $header;
94 private $footer;
95 private $title;
96 private $subtitle;
97 private $body;
98 private $styles;
99 private $parent;
100
101 /**
102 * Main constructor
103 *
104 * @param Db $zdb Database instance
105 * @param Preferences $preferences Galette preferences
106 * @param int $type Model type
107 * @param mixed $args Arguments
108 */
109 public function __construct(Db $zdb, Preferences $preferences, $type, $args = null)
110 {
111 global $container, $login;
112 $this->routeparser = $container->get(RouteParser::class);
113 $this->preferences = $preferences;
114 $this
115 ->setDb($zdb)
116 ->setLogin($login);
117 $this->type = $type;
118
119 if (is_int($args)) {
120 $this->load($args);
121 } elseif ($args !== null && is_object($args)) {
122 $this->loadFromRs($args);
123 } else {
124 $this->load($type);
125 }
126
127 $this->setPatterns($this->getMainPatterns());
128 $this->setMain();
129 }
130
131 /**
132 * Load a Model from its identifier
133 *
134 * @param int $id Identifier
135 * @param boolean $init Init data if required model is missing
136 *
137 * @return void
138 */
139 protected function load($id, $init = true)
140 {
141 global $login;
142
143 try {
144 $select = $this->zdb->select(self::TABLE);
145 $select->limit(1)
146 ->where([self::PK => $id]);
147
148 $results = $this->zdb->execute($select);
149
150 $count = $results->count();
151 if ($count === 0) {
152 if ($init === true) {
153 $models = new PdfModels($this->zdb, $this->preferences, $login);
154 $models->installInit();
155 $this->load($id, false);
156 } else {
157 throw new \RuntimeException('Model not found!');
158 }
159 } else {
160 /** @var ArrayObject $result */
161 $result = $results->current();
162 $this->loadFromRs($result);
163 }
164 } catch (Throwable $e) {
165 Analog::log(
166 'An error occurred loading model #' . $id . "Message:\n" .
167 $e->getMessage(),
168 Analog::ERROR
169 );
170 throw $e;
171 }
172 }
173
174 /**
175 * Load model from a db ResultSet
176 *
177 * @param ArrayObject $rs ResultSet
178 *
179 * @return void
180 */
181 protected function loadFromRs(ArrayObject $rs)
182 {
183 $pk = self::PK;
184 $this->id = (int)$rs->$pk;
185
186 $callback = function ($matches) {
187 return _T($matches[1]);
188 };
189 $this->name = preg_replace_callback(
190 '/_T\("([^\"]+)"\)/',
191 $callback,
192 $rs->model_name
193 );
194
195 $this->title = $rs->model_title;
196 $this->subtitle = $rs->model_subtitle;
197 $this->header = $rs->model_header;
198 $this->footer = $rs->model_footer;
199 $this->body = $rs->model_body;
200 $this->styles .= $rs->model_styles;
201
202 if ($this->id > self::MAIN_MODEL) {
203 //FIXME: for now, parent will always be a PdfMain
204 $this->parent = new PdfMain(
205 $this->zdb,
206 $this->preferences,
207 (int)$rs->model_parent
208 );
209 }
210 }
211
212 /**
213 * Store model in database
214 *
215 * @return boolean
216 */
217 public function store()
218 {
219 $title = $this->title;
220 //@phpstan-ignore-next-line
221 if ($title === null || trim($title) === '') {
222 $title = new Expression('NULL');
223 }
224
225 $subtitle = $this->subtitle;
226 //@phpstan-ignore-next-line
227 if ($subtitle === null || trim($subtitle) === '') {
228 $subtitle = new Expression('NULL');
229 }
230
231 $data = array(
232 'model_header' => $this->header,
233 'model_footer' => $this->footer,
234 'model_type' => $this->type,
235 'model_title' => $title,
236 'model_subtitle' => $subtitle,
237 'model_body' => $this->body,
238 'model_styles' => $this->styles
239 );
240
241 try {
242 if ($this->id !== null) {
243 $update = $this->zdb->update(self::TABLE);
244 $update->set($data)->where(
245 [self::PK => $this->id]
246 );
247 $this->zdb->execute($update);
248 } else {
249 $data['model_name'] = $this->name;
250 $insert = $this->zdb->insert(self::TABLE);
251 $insert->values($data);
252 $add = $this->zdb->execute($insert);
253 if (!($add->count() > 0)) {
254 Analog::log('Not stored!', Analog::ERROR);
255 return false;
256 }
257 }
258 return true;
259 } catch (Throwable $e) {
260 Analog::log(
261 'An error occurred storing model: ' . $e->getMessage() .
262 "\n" . print_r($data, true),
263 Analog::ERROR
264 );
265 throw $e;
266 }
267 }
268
269 /**
270 * Get object class for specified type
271 *
272 * @param int $type Type
273 *
274 * @return string
275 */
276 public static function getTypeClass(int $type)
277 {
278 $class = null;
279 switch ($type) {
280 case self::INVOICE_MODEL:
281 $class = 'PdfInvoice';
282 break;
283 case self::RECEIPT_MODEL:
284 $class = 'PdfReceipt';
285 break;
286 case self::ADHESION_FORM_MODEL:
287 $class = 'PdfAdhesionFormModel';
288 break;
289 default:
290 $class = 'PdfMain';
291 break;
292 }
293 $class = 'Galette\\Entity\\' . $class;
294 return $class;
295 }
296
297 /**
298 * Check length
299 *
300 * @param string $value The value
301 * @param int $chars Length
302 * @param string $field Field name
303 * @param boolean $empty Can value be empty
304 *
305 * @return void
306 */
307 protected function checkChars($value, $chars, $field, $empty = false)
308 {
309 if ($value !== null && trim($value) !== '') {
310 if (mb_strlen($value) > $chars) {
311 throw new \LengthException(
312 str_replace(
313 array('%field', '%chars'),
314 array($field, $chars),
315 _T("%field should be less than %chars characters long.")
316 )
317 );
318 }
319 } else {
320 if ($empty === false) {
321 throw new \UnexpectedValueException(
322 str_replace(
323 '%field',
324 $field,
325 _T("%field should not be empty!")
326 )
327 );
328 }
329 }
330 }
331
332 /**
333 * Getter
334 *
335 * @param string $name Property name
336 *
337 * @return mixed
338 */
339 public function __get($name)
340 {
341 global $lang;
342
343 switch ($name) {
344 case 'id':
345 return (int)$this->$name;
346 case 'name':
347 case 'header':
348 case 'footer':
349 case 'body':
350 case 'title':
351 case 'subtitle':
352 case 'type':
353 case 'styles':
354 case 'patterns':
355 case 'replaces':
356 return $this->$name ?? '';
357 case 'hstyles':
358 $value = null;
359
360 //get header and footer from parent if not defined in current model
361 if (
362 $this->id > self::MAIN_MODEL
363 && $this->parent !== null
364 ) {
365 $value = $this->parent->styles;
366 }
367
368 $value .= $this->styles;
369 return $value;
370 case 'hheader':
371 case 'hfooter':
372 case 'htitle':
373 case 'hsubtitle':
374 case 'hbody':
375 $pname = substr($name, 1);
376 $prop_value = $this->$pname ?? '';
377
378 //get header and footer from parent if not defined in current model
379 if (
380 $this->id > self::MAIN_MODEL
381 && $this->parent !== null
382 && ($pname === 'footer'
383 || $pname === 'header')
384 && trim($prop_value) === ''
385 ) {
386 $prop_value = $this->parent->$pname;
387 }
388
389 $value = $this->proceedReplacements($prop_value);
390 return $value;
391 default:
392 Analog::log(
393 'Unable to get PdfModel property ' . $name,
394 Analog::WARNING
395 );
396 break;
397 }
398 }
399
400 /**
401 * Isset
402 * Required for twig to access properties via __get
403 *
404 * @param string $name Property name
405 *
406 * @return bool
407 */
408 public function __isset($name)
409 {
410 switch ($name) {
411 case 'id':
412 case 'name':
413 case 'header':
414 case 'footer':
415 case 'body':
416 case 'title':
417 case 'subtitle':
418 case 'type':
419 case 'styles':
420 case 'patterns':
421 case 'replaces':
422 case 'hstyles':
423 case 'hheader':
424 case 'hfooter':
425 case 'htitle':
426 case 'hsubtitle':
427 case 'hbody':
428 return true;
429 }
430
431 return false;
432 }
433
434 /**
435 * Setter
436 *
437 * @param string $name Property name
438 * @param mixed $value Property value
439 *
440 * @return void
441 */
442 public function __set($name, $value)
443 {
444 switch ($name) {
445 case 'type':
446 if (
447 $value === self::MAIN_MODEL
448 || $value === self::INVOICE_MODEL
449 || $value === self::RECEIPT_MODEL
450 || $value === self::ADHESION_FORM_MODEL
451 ) {
452 $this->$name = $value;
453 } else {
454 throw new \UnexpectedValueException(
455 str_replace(
456 '%type',
457 $value,
458 _T("Unknown type %type!")
459 )
460 );
461 }
462 break;
463 case 'name':
464 try {
465 $this->checkChars($value, 50, _T("Name"));
466 $this->$name = $value;
467 } catch (Throwable $e) {
468 throw $e;
469 }
470 break;
471 case 'title':
472 case 'subtitle':
473 if ($name == 'title') {
474 $field = _T("Title");
475 } else {
476 $field = _T("Subtitle");
477 }
478 try {
479 $this->checkChars($value, 100, $field, true);
480 $this->$name = $value;
481 } catch (Throwable $e) {
482 throw $e;
483 }
484 break;
485 case 'header':
486 case 'footer':
487 case 'body':
488 if ($value === null || trim($value) === '') {
489 if ($name !== 'body' && get_class($this) === PdfMain::class) {
490 throw new \UnexpectedValueException(
491 _T("header and footer should not be empty!")
492 );
493 } elseif ($name === 'body' && get_class($this) !== PdfMain::class) {
494 throw new \UnexpectedValueException(
495 _T("body should not be empty!")
496 );
497 }
498 }
499
500 $this->$name = $value;
501 break;
502 case 'styles':
503 $this->styles = $value;
504 break;
505 default:
506 Analog::log(
507 'Unable to set PdfModel property ' . $name,
508 Analog::WARNING
509 );
510 break;
511 }
512 }
513 }