]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Entity/PdfModel.php
Some phpstan lvl 4 checks
[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 if ($title === null || trim($title) === '') {
221 $title = new Expression('NULL');
222 }
223
224 $subtitle = $this->subtitle;
225 if ($subtitle === null || trim($subtitle) === '') {
226 $subtitle = new Expression('NULL');
227 }
228
229 $data = array(
230 'model_header' => $this->header,
231 'model_footer' => $this->footer,
232 'model_type' => $this->type,
233 'model_title' => $title,
234 'model_subtitle' => $subtitle,
235 'model_body' => $this->body,
236 'model_styles' => $this->styles
237 );
238
239 try {
240 if ($this->id !== null) {
241 $update = $this->zdb->update(self::TABLE);
242 $update->set($data)->where(
243 [self::PK => $this->id]
244 );
245 $this->zdb->execute($update);
246 } else {
247 $data['model_name'] = $this->name;
248 $insert = $this->zdb->insert(self::TABLE);
249 $insert->values($data);
250 $add = $this->zdb->execute($insert);
251 if (!($add->count() > 0)) {
252 Analog::log('Not stored!', Analog::ERROR);
253 return false;
254 }
255 }
256 return true;
257 } catch (Throwable $e) {
258 Analog::log(
259 'An error occurred storing model: ' . $e->getMessage() .
260 "\n" . print_r($data, true),
261 Analog::ERROR
262 );
263 throw $e;
264 }
265 }
266
267 /**
268 * Get object class for specified type
269 *
270 * @param int $type Type
271 *
272 * @return string
273 */
274 public static function getTypeClass(int $type)
275 {
276 $class = null;
277 switch ($type) {
278 case self::INVOICE_MODEL:
279 $class = 'PdfInvoice';
280 break;
281 case self::RECEIPT_MODEL:
282 $class = 'PdfReceipt';
283 break;
284 case self::ADHESION_FORM_MODEL:
285 $class = 'PdfAdhesionFormModel';
286 break;
287 default:
288 $class = 'PdfMain';
289 break;
290 }
291 $class = 'Galette\\Entity\\' . $class;
292 return $class;
293 }
294
295 /**
296 * Check length
297 *
298 * @param string $value The value
299 * @param int $chars Length
300 * @param string $field Field name
301 * @param boolean $empty Can value be empty
302 *
303 * @return void
304 */
305 protected function checkChars($value, $chars, $field, $empty = false)
306 {
307 if ($value !== null && trim($value) !== '') {
308 if (mb_strlen($value) > $chars) {
309 throw new \LengthException(
310 str_replace(
311 array('%field', '%chars'),
312 array($field, $chars),
313 _T("%field should be less than %chars characters long.")
314 )
315 );
316 }
317 } else {
318 if ($empty === false) {
319 throw new \UnexpectedValueException(
320 str_replace(
321 '%field',
322 $field,
323 _T("%field should not be empty!")
324 )
325 );
326 }
327 }
328 }
329
330 /**
331 * Getter
332 *
333 * @param string $name Property name
334 *
335 * @return mixed
336 */
337 public function __get($name)
338 {
339 global $lang;
340
341 switch ($name) {
342 case 'id':
343 return (int)$this->$name;
344 case 'name':
345 case 'header':
346 case 'footer':
347 case 'body':
348 case 'title':
349 case 'subtitle':
350 case 'type':
351 case 'styles':
352 case 'patterns':
353 case 'replaces':
354 return $this->$name ?? '';
355 case 'hstyles':
356 $value = null;
357
358 //get header and footer from parent if not defined in current model
359 if (
360 $this->id > self::MAIN_MODEL
361 && $this->parent !== null
362 ) {
363 $value = $this->parent->styles;
364 }
365
366 $value .= $this->styles;
367 return $value;
368 case 'hheader':
369 case 'hfooter':
370 case 'htitle':
371 case 'hsubtitle':
372 case 'hbody':
373 $pname = substr($name, 1);
374 $prop_value = $this->$pname ?? '';
375
376 //get header and footer from parent if not defined in current model
377 if (
378 $this->id > self::MAIN_MODEL
379 && $this->parent !== null
380 && ($pname === 'footer'
381 || $pname === 'header')
382 && trim($prop_value) === ''
383 ) {
384 $prop_value = $this->parent->$pname;
385 }
386
387 $value = $this->proceedReplacements($prop_value);
388 return $value;
389 default:
390 Analog::log(
391 'Unable to get PdfModel property ' . $name,
392 Analog::WARNING
393 );
394 break;
395 }
396 }
397
398 /**
399 * Isset
400 * Required for twig to access properties via __get
401 *
402 * @param string $name Property name
403 *
404 * @return bool
405 */
406 public function __isset($name)
407 {
408 switch ($name) {
409 case 'id':
410 case 'name':
411 case 'header':
412 case 'footer':
413 case 'body':
414 case 'title':
415 case 'subtitle':
416 case 'type':
417 case 'styles':
418 case 'patterns':
419 case 'replaces':
420 case 'hstyles':
421 case 'hheader':
422 case 'hfooter':
423 case 'htitle':
424 case 'hsubtitle':
425 case 'hbody':
426 return true;
427 }
428
429 return false;
430 }
431
432 /**
433 * Setter
434 *
435 * @param string $name Property name
436 * @param mixed $value Property value
437 *
438 * @return void
439 */
440 public function __set($name, $value)
441 {
442 switch ($name) {
443 case 'type':
444 if (
445 $value === self::MAIN_MODEL
446 || $value === self::INVOICE_MODEL
447 || $value === self::RECEIPT_MODEL
448 || $value === self::ADHESION_FORM_MODEL
449 ) {
450 $this->$name = $value;
451 } else {
452 throw new \UnexpectedValueException(
453 str_replace(
454 '%type',
455 $value,
456 _T("Unknown type %type!")
457 )
458 );
459 }
460 break;
461 case 'name':
462 try {
463 $this->checkChars($value, 50, _T("Name"));
464 $this->$name = $value;
465 } catch (Throwable $e) {
466 throw $e;
467 }
468 break;
469 case 'title':
470 case 'subtitle':
471 if ($name == 'title') {
472 $field = _T("Title");
473 } else {
474 $field = _T("Subtitle");
475 }
476 try {
477 $this->checkChars($value, 100, $field, true);
478 $this->$name = $value;
479 } catch (Throwable $e) {
480 throw $e;
481 }
482 break;
483 case 'header':
484 case 'footer':
485 case 'body':
486 if ($value === null || trim($value) === '') {
487 if ($name !== 'body' && get_class($this) === PdfMain::class) {
488 throw new \UnexpectedValueException(
489 _T("header and footer should not be empty!")
490 );
491 } elseif ($name === 'body' && get_class($this) !== PdfMain::class) {
492 throw new \UnexpectedValueException(
493 _T("body should not be empty!")
494 );
495 }
496 }
497
498 $this->$name = $value;
499 break;
500 case 'styles':
501 $this->styles = $value;
502 break;
503 default:
504 Analog::log(
505 'Unable to set PdfModel property ' . $name,
506 Analog::WARNING
507 );
508 break;
509 }
510 }
511 }