]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Core/Preferences.php
03c4f17c93c8352878672a92f1bec045d18b8583
[galette.git] / galette / lib / Galette / Core / Preferences.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Preferences handling
7 *
8 * PHP version 5
9 *
10 * Copyright © 2007-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 Core
28 * @package Galette
29 * @author Johan Cwiklinski <johan@x-tnd.be>
30 * @copyright 2007-2023 The Galette Team
31 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
32 * @link http://galette.tuxfamily.org
33 * @since Available since 0.7dev - 2007-10-14
34 */
35
36 namespace Galette\Core;
37
38 use Galette\Entity\PaymentType;
39 use Galette\Entity\Social;
40 use Galette\Features\Replacements;
41 use Galette\Features\Socials;
42 use PHPMailer\PHPMailer\PHPMailer;
43 use Throwable;
44 use Analog\Analog;
45 use Galette\Entity\Adherent;
46 use Galette\Entity\Status;
47 use Galette\IO\PdfMembersCards;
48 use Galette\Repository\Members;
49
50 /**
51 * Preferences for galette
52 *
53 * @category Core
54 * @name Preferences
55 * @package Galette
56 * @author Johan Cwiklinski <johan@x-tnd.be>
57 * @copyright 2007-2023 The Galette Team
58 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
59 * @link http://galette.tuxfamily.org
60 * @since Available since 0.7dev - 2007-10-14
61 *
62 * @property string $pref_admin_login Super admin login
63 * @property string $pref_admin_pass Super admin password
64 * @property string $pref_nom Association name
65 * @property string $pref_slogan Association slogan
66 * @property string $pref_adresse Address
67 * @property string $pref_adresse2 Address continuation
68 * @property string $pref_cp Association zipcode
69 * @property string $pref_ville Association town
70 * @property string $pref_pays Country
71 * @property integer $pref_postal_adress Postal adress to use, one of self::POSTAL_ADDRESS*
72 * @property integer $pref_postal_staff_member Staff member ID from which retrieve postal address
73 * @property string $pref_lang Default instance language
74 * @property integer $pref_numrows Default number of rows in lists
75 * @property integer $pref_log History, one of self::LOG_*
76 * @property integer $pref_statut Default status for new members
77 * @property string $pref_email_nom
78 * @property string $pref_email
79 * @property string $pref_email_newadh
80 * @property boolean $pref_bool_mailadh
81 * @property boolean $pref_bool_mailowner
82 * @property boolean $pref_editor_enabled
83 * @property integer $pref_mail_method Mail method, see GaletteMail::METHOD_*
84 * @property string $pref_mail_smtp
85 * @property string $pref_mail_smtp_host
86 * @property boolean $pref_mail_smtp_auth
87 * @property boolean $pref_mail_smtp_secure
88 * @property integer $pref_mail_smtp_port
89 * @property string $pref_mail_smtp_user
90 * @property string $pref_mail_smtp_password
91 * @property integer $pref_membership_ext
92 * @property string $pref_beg_membership
93 * @property integer $pref_membership_offermonths
94 * @property string $pref_email_reply_to
95 * @property string $pref_website
96 * @property integer $pref_etiq_marges_v
97 * @property string $pref_etiq_marges_h
98 * @property string $pref_etiq_hspace
99 * @property string $pref_etiq_vspace
100 * @property string $pref_etiq_hsize
101 * @property string $pref_etiq_vsize
102 * @property string $pref_etiq_cols
103 * @property string $pref_etiq_rows
104 * @property string $pref_etiq_corps
105 * @property boolean $pref_etiq_border
106 * @property boolean $pref_force_picture_ratio
107 * @property string $pref_member_picture_ratio
108 * @property string $pref_card_abrev
109 * @property string $pref_card_strip
110 * @property string $pref_card_tcol
111 * @property string $pref_card_scol
112 * @property string $pref_card_bcol
113 * @property string $pref_card_hcol
114 * @property string $pref_bool_display_title
115 * @property integer $pref_card_address
116 * @property string $pref_card_year
117 * @property integer $pref_card_marges_v
118 * @property integer $pref_card_marges_h
119 * @property integer $pref_card_vspace
120 * @property integer $pref_card_hspace
121 * @property string $pref_card_self
122 * @property string $pref_theme Prefered theme
123 * @property boolean $pref_bool_publicpages
124 * @property integer $pref_publicpages_visibility
125 * @property boolean $pref_bool_selfsubscribe
126 * @property string $pref_mail_sign
127 * @property string $pref_new_contrib_script
128 * @property boolean $pref_bool_wrap_mails
129 * @property string $pref_rss_url
130 * @property boolean $pref_show_id
131 * @property string $pref_adhesion_form
132 * @property boolean $pref_mail_allow_unsecure
133 * @property string $pref_instance_uuid
134 * @property string $pref_registration_uuid
135 * @property string $pref_telemetry_date
136 * @property string $pref_registration_date
137 * @property string $pref_footer
138 * @property integer $pref_filter_account
139 * @property string $pref_galette_url
140 * @property integer $pref_redirect_on_create
141 * @property integer $pref_password_length
142 * @property boolean $pref_password_blacklist
143 * @property integer $pref_password_strength
144 * @property integer $pref_default_paymenttype
145 * @property boolean $pref_bool_create_member
146 * @property boolean $pref_bool_groupsmanagers_create_member
147 * @property boolean $pref_bool_groupsmanagers_edit_member
148 * @property boolean $pref_bool_groupsmanagers_edit_groups
149 * @property boolean $pref_bool_groupsmanagers_mailings
150 * @property boolean $pref_bool_groupsmanagers_exports
151 * @property-read array $vpref_email_newadh list of mail senders
152 * @property-read array $vpref_email list of mail senders
153 */
154 class Preferences
155 {
156 use Replacements;
157 use Socials;
158
159 protected $preferences; //redefined from Replacements feature - avoid circular dependency
160 private $prefs;
161 private $errors = [];
162
163 public const TABLE = 'preferences';
164 public const PK = 'nom_pref';
165
166 /** Postal address will be the one given in the preferences */
167 public const POSTAL_ADDRESS_FROM_PREFS = 0;
168 /** Postal address will be the one of the selected staff member */
169 public const POSTAL_ADDRESS_FROM_STAFF = 1;
170
171 /** Public pages stuff */
172 /** Public pages are publically visibles */
173 public const PUBLIC_PAGES_VISIBILITY_PUBLIC = 0;
174 /** Public pages are visibles for up to date members only */
175 public const PUBLIC_PAGES_VISIBILITY_RESTRICTED = 1;
176 /** Public pages are visibles for admin and staff members only */
177 public const PUBLIC_PAGES_VISIBILITY_PRIVATE = 2;
178
179 public const LOG_DISABLED = 0;
180 public const LOG_ENABLED = 1;
181
182 /** No password strength */
183 public const PWD_NONE = 0;
184 /** Weak password strength */
185 public const PWD_WEAK = 1;
186 /** Medium password strength */
187 public const PWD_MEDIUM = 2;
188 /** Strong password strength */
189 public const PWD_STRONG = 3;
190 /** Very strong password strength */
191 public const PWD_VERY_STRONG = 4;
192
193 private static $fields = array(
194 'nom_pref',
195 'val_pref'
196 );
197
198 private static $defaults = array(
199 'pref_admin_login' => 'admin',
200 'pref_admin_pass' => 'admin',
201 'pref_nom' => 'Galette',
202 'pref_slogan' => '',
203 'pref_adresse' => '-',
204 'pref_adresse2' => '',
205 'pref_cp' => '',
206 'pref_ville' => '',
207 'pref_pays' => '',
208 'pref_postal_adress' => self::POSTAL_ADDRESS_FROM_PREFS,
209 'pref_postal_staff_member' => '',
210 'pref_lang' => I18n::DEFAULT_LANG,
211 'pref_numrows' => 30,
212 'pref_log' => self::LOG_ENABLED,
213 'pref_statut' => Status::DEFAULT_STATUS,
214 /* Preferences for emails */
215 'pref_email_nom' => 'Galette',
216 'pref_email' => 'mail@domain.com',
217 'pref_email_newadh' => 'mail@domain.com',
218 'pref_bool_mailadh' => false,
219 'pref_bool_mailowner' => false,
220 'pref_editor_enabled' => false,
221 'pref_mail_method' => GaletteMail::METHOD_DISABLED,
222 'pref_mail_smtp' => '',
223 'pref_mail_smtp_host' => '',
224 'pref_mail_smtp_auth' => false,
225 'pref_mail_smtp_secure' => false,
226 'pref_mail_smtp_port' => '',
227 'pref_mail_smtp_user' => '',
228 'pref_mail_smtp_password' => '',
229 'pref_membership_ext' => 12,
230 'pref_beg_membership' => '',
231 'pref_membership_offermonths' => 0,
232 'pref_email_reply_to' => '',
233 'pref_website' => '',
234 /* Preferences for labels */
235 'pref_etiq_marges_v' => 10,
236 'pref_etiq_marges_h' => 10,
237 'pref_etiq_hspace' => 10,
238 'pref_etiq_vspace' => 5,
239 'pref_etiq_hsize' => 90,
240 'pref_etiq_vsize' => 35,
241 'pref_etiq_cols' => 2,
242 'pref_etiq_rows' => 7,
243 'pref_etiq_corps' => 12,
244 'pref_etiq_border' => true,
245 /* Preferences for members cards */
246 'pref_force_picture_ratio' => false,
247 'pref_member_picture_ratio' => 'square_ratio',
248 'pref_card_abrev' => 'GALETTE',
249 'pref_card_strip' => 'Gestion d\'Adherents en Ligne Extrêmement Tarabiscotée',
250 'pref_card_tcol' => '#FFFFFF',
251 'pref_card_scol' => '#8C2453',
252 'pref_card_bcol' => '#53248C',
253 'pref_card_hcol' => '#248C53',
254 'pref_bool_display_title' => false,
255 'pref_card_address' => 1,
256 'pref_card_year' => '',
257 'pref_card_marges_v' => 15,
258 'pref_card_marges_h' => 20,
259 'pref_card_vspace' => 5,
260 'pref_card_hspace' => 10,
261 'pref_card_self' => 1,
262 'pref_theme' => 'default',
263 'pref_bool_publicpages' => true,
264 'pref_publicpages_visibility' => self::PUBLIC_PAGES_VISIBILITY_RESTRICTED,
265 'pref_bool_selfsubscribe' => true,
266 'pref_mail_sign' => "{ASSO_NAME}\r\n\r\n{ASSO_WEBSITE}",
267 /* New contribution script */
268 'pref_new_contrib_script' => '',
269 'pref_bool_wrap_mails' => true,
270 'pref_rss_url' => 'http://galette.eu/dc/index.php/feed/atom',
271 'pref_show_id' => false,
272 'pref_adhesion_form' => '\Galette\IO\PdfAdhesionForm',
273 'pref_mail_allow_unsecure' => false,
274 'pref_instance_uuid' => '',
275 'pref_registration_uuid' => '',
276 'pref_telemetry_date' => '',
277 'pref_registration_date' => '',
278 'pref_footer' => '',
279 'pref_filter_account' => Members::ALL_ACCOUNTS,
280 'pref_galette_url' => '',
281 'pref_redirect_on_create' => Adherent::AFTER_ADD_DEFAULT,
282 /* Security related */
283 'pref_password_length' => 6,
284 'pref_password_blacklist' => false,
285 'pref_password_strength' => self::PWD_NONE,
286 'pref_default_paymenttype' => PaymentType::CHECK,
287 'pref_bool_create_member' => false,
288 'pref_bool_groupsmanagers_create_member' => false,
289 'pref_bool_groupsmanagers_edit_member' => false,
290 'pref_bool_groupsmanagers_edit_groups' => false,
291 'pref_bool_groupsmanagers_mailings' => false,
292 'pref_bool_groupsmanagers_exports' => true
293 );
294
295 /** @var Social[] */
296 private $socials;
297
298 // flagging required fields
299 private $required = array(
300 'pref_nom',
301 'pref_lang',
302 'pref_numrows',
303 'pref_log',
304 'pref_statut',
305 'pref_etiq_marges_v',
306 'pref_etiq_marges_h',
307 'pref_etiq_hspace',
308 'pref_etiq_vspace',
309 'pref_etiq_hsize',
310 'pref_etiq_vsize',
311 'pref_etiq_cols',
312 'pref_etiq_rows',
313 'pref_etiq_corps',
314 'pref_card_marges_v',
315 'pref_card_marges_h',
316 'pref_card_hspace',
317 'pref_card_vspace'
318 );
319
320 /**
321 * Default constructor
322 *
323 * @param Db $zdb Db instance
324 * @param boolean $load Automatically load preferences on load
325 *
326 * @return void
327 */
328 public function __construct(Db $zdb, $load = true)
329 {
330 $this->zdb = $zdb;
331 if ($load) {
332 $this->load();
333 $this->checkUpdate();
334 }
335 }
336
337 /**
338 * Check if all fields referenced in the default array does exists,
339 * create them if not
340 *
341 * @return boolean
342 */
343 private function checkUpdate()
344 {
345 $proceed = false;
346 $params = array();
347 foreach (self::$defaults as $k => $v) {
348 if (!isset($this->prefs[$k])) {
349 if ($k == 'pref_admin_pass' && $v == 'admin') {
350 $v = password_hash($v, PASSWORD_BCRYPT);
351 }
352 $this->prefs[$k] = $v;
353 Analog::log(
354 'The field `' . $k . '` does not exists, Galette will attempt to create it.',
355 Analog::INFO
356 );
357 $proceed = true;
358 $params[] = array(
359 'nom_pref' => $k,
360 'val_pref' => $v
361 );
362 }
363 }
364 if ($proceed !== false) {
365 try {
366 $insert = $this->zdb->insert(self::TABLE);
367 $insert->values(
368 array(
369 'nom_pref' => ':nom_pref',
370 'val_pref' => ':val_pref'
371 )
372 );
373 $stmt = $this->zdb->sql->prepareStatementForSqlObject($insert);
374
375 foreach ($params as $p) {
376 $stmt->execute(
377 array(
378 'nom_pref' => $p['nom_pref'],
379 'val_pref' => $p['val_pref']
380 )
381 );
382 }
383 } catch (Throwable $e) {
384 Analog::log(
385 'Unable to add missing preferences.' . $e->getMessage(),
386 Analog::WARNING
387 );
388 return false;
389 }
390
391 Analog::log(
392 'Missing preferences were successfully stored into database.',
393 Analog::INFO
394 );
395 }
396
397 return true;
398 }
399
400 /**
401 * Load current preferences from database.
402 *
403 * @return boolean
404 */
405 public function load()
406 {
407 $this->prefs = array();
408
409 try {
410 $result = $this->zdb->selectAll(self::TABLE);
411 foreach ($result as $pref) {
412 $this->prefs[$pref->nom_pref] = $pref->val_pref;
413 }
414 $this->socials = Social::getListForMember(null);
415 return true;
416 } catch (Throwable $e) {
417 Analog::log(
418 'Preferences cannot be loaded. Galette should not work without ' .
419 'preferences. Exiting.',
420 Analog::URGENT
421 );
422 return false;
423 }
424 }
425
426 /**
427 * Set default preferences at install time
428 *
429 * @param string $lang language selected at install screen
430 * @param string $adm_login admin login entered at install time
431 * @param string $adm_pass admin password entered at install time
432 *
433 * @return boolean|\Exception
434 */
435 public function installInit($lang, $adm_login, $adm_pass)
436 {
437 try {
438 //first, we drop all values
439 $delete = $this->zdb->delete(self::TABLE);
440 $this->zdb->execute($delete);
441
442 //we then replace default values with the ones user has selected
443 $values = self::$defaults;
444 $values['pref_lang'] = $lang;
445 $values['pref_admin_login'] = $adm_login;
446 $values['pref_admin_pass'] = $adm_pass;
447 $values['pref_card_year'] = date('Y');
448
449 $insert = $this->zdb->insert(self::TABLE);
450 $insert->values(
451 array(
452 'nom_pref' => ':nom_pref',
453 'val_pref' => ':val_pref'
454 )
455 );
456 $stmt = $this->zdb->sql->prepareStatementForSqlObject($insert);
457
458 foreach ($values as $k => $v) {
459 $stmt->execute(
460 array(
461 'nom_pref' => $k,
462 'val_pref' => $v
463 )
464 );
465 }
466
467 Analog::log(
468 'Default preferences were successfully stored into database.',
469 Analog::INFO
470 );
471 return true;
472 } catch (Throwable $e) {
473 Analog::log(
474 'Unable to initialize default preferences.' . $e->getMessage(),
475 Analog::WARNING
476 );
477 throw $e;
478 }
479 }
480
481 /**
482 * Returns all preferences keys
483 *
484 * @return array
485 */
486 public function getFieldsNames()
487 {
488 return array_keys($this->prefs);
489 }
490
491 /**
492 * Check values
493 *
494 * @param array $values Values
495 * @param Login $login Logged in user
496 *
497 * @return boolean
498 */
499 public function check(array $values, Login $login)
500 {
501 $insert_values = array();
502 if ($login->isSuperAdmin() && !Galette::isDemo()) {
503 $this->required[] = 'pref_admin_login';
504 }
505
506 // obtain fields
507 foreach ($this->getFieldsNames() as $fieldname) {
508 if (isset($values[$fieldname])) {
509 $value = trim($values[$fieldname]);
510 } else {
511 $value = "";
512 }
513
514 $insert_values[$fieldname] = $value;
515 }
516
517 //cleanup fields for demo
518 if (Galette::isDemo()) {
519 unset(
520 $insert_values['pref_admin_login'],
521 $insert_values['pref_admin_pass'],
522 $insert_values['pref_mail_method']
523 );
524 }
525
526 // missing relations
527 if (
528 !Galette::isDemo()
529 && isset($insert_values['pref_mail_method'])
530 ) {
531 if ($insert_values['pref_mail_method'] > GaletteMail::METHOD_DISABLED) {
532 if (
533 !isset($insert_values['pref_email_nom'])
534 || $insert_values['pref_email_nom'] == ''
535 ) {
536 $this->errors[] = _T("- You must indicate a sender name for emails!");
537 }
538 if (
539 !isset($insert_values['pref_email'])
540 || $insert_values['pref_email'] == ''
541 ) {
542 $this->errors[] = _T("- You must indicate an email address Galette should use to send emails!");
543 }
544 if ($insert_values['pref_mail_method'] == GaletteMail::METHOD_SMTP) {
545 if (
546 !isset($insert_values['pref_mail_smtp_host'])
547 || $insert_values['pref_mail_smtp_host'] == ''
548 ) {
549 $this->errors[] = _T("- You must indicate the SMTP server you want to use!");
550 }
551 }
552 if (
553 $insert_values['pref_mail_method'] == GaletteMail::METHOD_GMAIL
554 || ($insert_values['pref_mail_method'] == GaletteMail::METHOD_SMTP
555 && $insert_values['pref_mail_smtp_auth'])
556 ) {
557 if (
558 !isset($insert_values['pref_mail_smtp_user'])
559 || trim($insert_values['pref_mail_smtp_user']) == ''
560 ) {
561 $this->errors[] = _T("- You must provide a login for SMTP authentication.");
562 }
563 if (
564 !isset($insert_values['pref_mail_smtp_password'])
565 || ($insert_values['pref_mail_smtp_password']) == ''
566 ) {
567 $this->errors[] = _T("- You must provide a password for SMTP authentication.");
568 }
569 }
570 }
571 }
572
573 if (
574 isset($insert_values['pref_beg_membership'])
575 && $insert_values['pref_beg_membership'] != ''
576 && isset($insert_values['pref_membership_ext'])
577 && $insert_values['pref_membership_ext'] != ''
578 ) {
579 $this->errors[] = _T("- Default membership extention and beginning of membership are mutually exclusive.");
580 }
581
582 if (
583 isset($insert_values['pref_membership_offermonths'])
584 && (int)$insert_values['pref_membership_offermonths'] > 0
585 && isset($insert_values['pref_membership_ext'])
586 && $insert_values['pref_membership_ext'] != ''
587 ) {
588 $this->errors[] = _T("- Offering months is only compatible with beginning of membership.");
589 }
590
591 // missing required fields?
592 foreach ($this->required as $val) {
593 if (!isset($values[$val]) || isset($values[$val]) && trim($values[$val]) == '') {
594 $this->errors[] = str_replace(
595 '%field',
596 $val,
597 _T("- Mandatory field %field empty.")
598 );
599 }
600 }
601
602 if (!Galette::isDemo() && isset($values['pref_admin_pass_check'])) {
603 // Check passwords. Hash will be done into the Preferences class
604 if (strcmp($insert_values['pref_admin_pass'], $values['pref_admin_pass_check']) != 0) {
605 $this->errors[] = _T("Passwords mismatch");
606 }
607 }
608
609 //postal address
610 if (isset($insert_values['pref_postal_adress'])) {
611 $value = $insert_values['pref_postal_adress'];
612 if ($value == Preferences::POSTAL_ADDRESS_FROM_PREFS) {
613 if (isset($insert_values['pref_postal_staff_member'])) {
614 unset($insert_values['pref_postal_staff_member']);
615 }
616 } elseif ($value == Preferences::POSTAL_ADDRESS_FROM_STAFF) {
617 if ($value < 1) {
618 $this->errors[] = _T("You have to select a staff member");
619 }
620 }
621 }
622
623 // update preferences
624 foreach ($insert_values as $champ => $valeur) {
625 if (
626 $login->isSuperAdmin()
627 || $champ != 'pref_admin_pass' && $champ != 'pref_admin_login'
628 ) {
629 if (
630 ($champ == "pref_admin_pass" && $_POST['pref_admin_pass'] != '')
631 || ($champ != "pref_admin_pass")
632 ) {
633 $this->$champ = $valeur;
634 }
635 }
636 }
637
638 $this->checkSocials($values);
639
640 return 0 === count($this->errors);
641 }
642
643 /**
644 * Validate value of a field
645 *
646 * @param string $fieldname Field name
647 * @param mixed $value Value to be set
648 *
649 * @return mixed
650 */
651 public function validateValue($fieldname, $value)
652 {
653 global $login;
654
655 switch ($fieldname) {
656 case 'pref_email':
657 case 'pref_email_newadh':
658 case 'pref_email_reply_to':
659 //check emails validity
660 //may be a comma separated list of valid emails identifiers:
661 //"The Name <mail@domain.com>,The Other <other@mail.com>" expect for reply_to.
662 $addresses = [];
663 if (trim($value) != '') {
664 if ($fieldname == 'pref_email_newadh') {
665 $addresses = explode(',', $value);
666 } else {
667 $addresses = [$value];
668 }
669 }
670 foreach ($addresses as $address) {
671 if (!GaletteMail::isValidEmail($address)) {
672 $msg = str_replace('%s', $address, _T("Invalid E-Mail address: %s"));
673 Analog::log($msg, Analog::WARNING);
674 $this->errors[] = $msg;
675 }
676 }
677 break;
678 case 'pref_admin_login':
679 if (Galette::isDemo()) {
680 Analog::log(
681 'Trying to set superadmin login while in DEMO.',
682 Analog::WARNING
683 );
684 } else {
685 if (strlen($value) < 4) {
686 $this->errors[] = _T("- The username must be composed of at least 4 characters!");
687 } else {
688 //check if login is already taken
689 if ($login->loginExists($value)) {
690 $this->errors[] = _T("- This username is already used by another member !");
691 }
692 }
693 }
694 break;
695 case 'pref_numrows':
696 case 'pref_etiq_marges_h':
697 case 'pref_etiq_marges_v':
698 case 'pref_etiq_hspace':
699 case 'pref_etiq_vspace':
700 case 'pref_etiq_hsize':
701 case 'pref_etiq_vsize':
702 case 'pref_etiq_cols':
703 case 'pref_etiq_rows':
704 case 'pref_etiq_corps':
705 case 'pref_card_marges_v':
706 case 'pref_card_marges_h':
707 case 'pref_card_hspace':
708 case 'pref_card_vspace':
709 if (!is_numeric($value) || $value < 0) {
710 $this->errors[] = _T("- The numbers and measures have to be integers!");
711 }
712 break;
713 case 'pref_card_tcol':
714 case 'pref_card_scol':
715 case 'pref_card_bcol':
716 case 'pref_card_hcol':
717 $matches = [];
718 if (!preg_match("/^(#)?([0-9A-F]{6})$/i", $value, $matches)) {
719 // Set strip background colors to black or white (for tcol)
720 $value = ($fieldname == 'pref_card_tcol' ? '#FFFFFF' : '#000000');
721 } else {
722 $value = '#' . $matches[2];
723 }
724 break;
725 case 'pref_admin_pass':
726 if (Galette::isDemo()) {
727 Analog::log(
728 'Trying to set superadmin pass while in DEMO.',
729 Analog::WARNING
730 );
731 } else {
732 $pwcheck = new \Galette\Util\Password($this);
733 $pwcheck->addPersonalInformation(['pref_admin_login' => $this->pref_admin_login]);
734 if (!$pwcheck->isValid($value)) {
735 $this->errors = array_merge(
736 $this->errors,
737 $pwcheck->getErrors()
738 );
739 }
740 }
741 break;
742 case 'pref_membership_ext':
743 if (!is_numeric($value) || $value < 0) {
744 $this->errors[] = _T("- Invalid number of months of membership extension.");
745 }
746 break;
747 case 'pref_beg_membership':
748 $beg_membership = explode("/", $value);
749 if (count($beg_membership) != 2) {
750 $this->errors[] = _T("- Invalid format of beginning of membership.");
751 } else {
752 $now = getdate();
753 if (!checkdate($beg_membership[1], $beg_membership[0], $now['year'])) {
754 $this->errors[] = _T("- Invalid date for beginning of membership.");
755 }
756 }
757 break;
758 case 'pref_membership_offermonths':
759 if (!is_numeric($value) || $value < 0) {
760 $this->errors[] = _T("- Invalid number of offered months.");
761 }
762 break;
763 case 'pref_card_year':
764 if ($value !== 'DEADLINE' && !preg_match('/^(?:\d{4}|\d{2})(\D?)(?:\d{4}|\d{2})$/', $value)) {
765 $this->errors[] = _T("- Invalid year for cards.");
766 }
767 break;
768 case 'pref_footer':
769 $value = $this->cleanHtmlValue($value);
770 break;
771 }
772
773 return $value;
774 }
775
776 /**
777 * Will store all preferences in the database
778 *
779 * @return boolean
780 */
781 public function store()
782 {
783 try {
784 $this->zdb->connection->beginTransaction();
785 $update = $this->zdb->update(self::TABLE);
786 $update->set(
787 array(
788 'val_pref' => ':val_pref'
789 )
790 )->where->equalTo('nom_pref', ':nom_pref');
791
792 $stmt = $this->zdb->sql->prepareStatementForSqlObject($update);
793
794 foreach (self::$defaults as $k => $v) {
795 if (
796 Galette::isDemo()
797 && in_array($k, ['pref_admin_pass', 'pref_admin_login', 'pref_mail_method'])
798 ) {
799 continue;
800 }
801 Analog::log('Storing ' . $k, Analog::DEBUG);
802
803 $value = $this->prefs[$k];
804 //do not store pdf_adhesion_form, it's designed to be overriden by plugin
805 if ($k === 'pref_adhesion_form') {
806 if (trim($v) == '') {
807 //Reset to default, should not be empty
808 $v = self::$defaults['pref_adhesion_form'];
809 }
810 $value = $v;
811 }
812
813 $stmt->execute(
814 array(
815 'val_pref' => $value,
816 'nom_pref' => $k
817 )
818 );
819 }
820 $this->zdb->connection->commit();
821 Analog::log(
822 'Preferences were successfully stored into database.',
823 Analog::INFO
824 );
825
826 $this->storeSocials(null);
827
828 return true;
829 } catch (Throwable $e) {
830 $this->zdb->connection->rollBack();
831
832 $messages = array();
833 do {
834 $messages[] = $e->getMessage();
835 } while ($e = $e->getPrevious());
836
837 Analog::log(
838 'Unable to store preferences | ' . print_r($messages, true),
839 Analog::WARNING
840 );
841 return false;
842 }
843 }
844
845 /**
846 * Returns postal address
847 *
848 * @return string postal address
849 */
850 public function getPostalAddress()
851 {
852 $regs = array(
853 '/%name/',
854 '/%complement/',
855 '/%address/',
856 '/%zip/',
857 '/%town/',
858 '/%country/',
859 );
860
861 $replacements = null;
862
863 if ($this->prefs['pref_postal_adress'] == self::POSTAL_ADDRESS_FROM_PREFS) {
864 $_address = $this->prefs['pref_adresse'];
865 if ($this->prefs['pref_adresse2'] && $this->prefs['pref_adresse2'] != '') {
866 $_address .= "\n" . $this->prefs['pref_adresse2'];
867 }
868 $replacements = array(
869 $this->prefs['pref_nom'],
870 "\n",
871 $_address,
872 $this->prefs['pref_cp'],
873 $this->prefs['pref_ville'],
874 $this->prefs['pref_pays']
875 );
876 } else {
877 //get selected staff member address
878 $adh = new Adherent($this->zdb, (int)$this->prefs['pref_postal_staff_member']);
879 $_complement = preg_replace(
880 array('/%name/', '/%status/'),
881 array($this->prefs['pref_nom'], $adh->sstatus),
882 _T("%name association's %status")
883 ) . "\n";
884 $_address = $adh->address;
885
886 $replacements = array(
887 $adh->sfullname . "\n",
888 $_complement,
889 $_address,
890 $adh->zipcode,
891 $adh->town,
892 $adh->country
893 );
894 }
895
896 /*FIXME: i18n fails :/ */
897 /*$r = preg_replace(
898 $regs,
899 $replacements,
900 _T("%name\n%complement\n%address\n%zip %town - %country")
901 );*/
902 $r = preg_replace(
903 $regs,
904 $replacements,
905 "%name%complement%address\n%zip %town - %country"
906 );
907 return $r;
908 }
909
910 /**
911 * Are public pages visibles?
912 *
913 * @param Authentication $login Authenticaqtion instance
914 *
915 * @return boolean
916 */
917 public function showPublicPages(Authentication $login)
918 {
919 if ($this->prefs['pref_bool_publicpages']) {
920 //if public pages are actives, let's check if we
921 //display them for curent call
922 switch ($this->prefs['pref_publicpages_visibility']) {
923 case self::PUBLIC_PAGES_VISIBILITY_PUBLIC:
924 //pages are publically visibles
925 return true;
926 case self::PUBLIC_PAGES_VISIBILITY_RESTRICTED:
927 //pages should be displayed only for up-to-date members
928 if (
929 $login->isUp2Date()
930 || $login->isAdmin()
931 || $login->isStaff()
932 ) {
933 return true;
934 } else {
935 return false;
936 }
937 case self::PUBLIC_PAGES_VISIBILITY_PRIVATE:
938 //pages should be displayed only for staff and admins
939 if ($login->isAdmin() || $login->isStaff()) {
940 return true;
941 } else {
942 return false;
943 }
944 default:
945 //should never be there
946 return false;
947 }
948 } else {
949 return false;
950 }
951 }
952
953 /**
954 * Global getter method
955 *
956 * @param string $name name of the property we want to retrieve
957 *
958 * @return mixed the called property
959 */
960 public function __get($name)
961 {
962 $forbidden = array('defaults');
963 $virtuals = array('vpref_email', 'vpref_email_newadh');
964
965 if (!in_array($name, $forbidden) && isset($this->prefs[$name])) {
966 if (
967 Galette::isDemo()
968 && $name == 'pref_mail_method'
969 ) {
970 return GaletteMail::METHOD_DISABLED;
971 } elseif ($name == 'pref_footer') {
972 return $this->cleanHtmlValue($this->prefs[$name]);
973 } else {
974 if ($name == 'pref_adhesion_form' && $this->prefs[$name] == '') {
975 $this->prefs[$name] = self::$defaults['pref_adhesion_form'];
976 }
977 $value = $this->prefs[$name];
978 if (TYPE_DB === \Galette\Core\Db::PGSQL) {
979 if ($value === 'f') {
980 $value = false;
981 }
982 }
983
984 if (in_array($name, ['vpref_email', 'pref_email_newadh'])) {
985 $values = explode(',', $value);
986 $value = $values[0]; //take first as default
987 }
988
989 return $value;
990 }
991 } elseif (in_array($name, $virtuals)) {
992 $virtual = str_replace('vpref_', 'pref_', $name);
993 return explode(',', $this->prefs[$virtual]);
994 } elseif ($name === 'socials') {
995 return $this->socials;
996 } else {
997 Analog::log(
998 'Preference `' . $name . '` is not set or is forbidden',
999 Analog::INFO
1000 );
1001 return false;
1002 }
1003 }
1004
1005 /**
1006 * Global isset method
1007 * Required for twig to access properties via __get
1008 *
1009 * @param string $name name of the property we want to retrieve
1010 *
1011 * @return bool
1012 */
1013 public function __isset($name)
1014 {
1015 $forbidden = array('defaults');
1016 $virtuals = array('vpref_email', 'vpref_email_newadh');
1017
1018 if (!in_array($name, $forbidden) && isset($this->prefs[$name])) {
1019 return true;
1020 } elseif (in_array($name, $virtuals)) {
1021 return true;
1022 } elseif ($name === 'socials') {
1023 return true;
1024 } else {
1025 Analog::log(
1026 'Preference `' . $name . '` is not set or is forbidden',
1027 Analog::INFO
1028 );
1029 return false;
1030 }
1031 }
1032
1033 /**
1034 * Get default preferences
1035 *
1036 * @return array
1037 */
1038 public function getDefaults()
1039 {
1040 return self::$defaults;
1041 }
1042
1043 /**
1044 * Global setter method
1045 *
1046 * @param string $name name of the property we want to assign a value to
1047 * @param mixed $value a relevant value for the property
1048 *
1049 * @return void
1050 */
1051 public function __set($name, $value)
1052 {
1053 //does this pref exists ?
1054 if (!array_key_exists($name, self::$defaults)) {
1055 Analog::log(
1056 'Trying to set a preference value which does not seem to exist ('
1057 . $name . ')',
1058 Analog::WARNING
1059 );
1060 return;
1061 }
1062
1063 if (
1064 $name == 'pref_email'
1065 || $name == 'pref_email_newadh'
1066 || $name == 'pref_email_reply_to'
1067 ) {
1068 if (Galette::isDemo()) {
1069 Analog::log(
1070 'Trying to set pref_email while in DEMO.',
1071 Analog::WARNING
1072 );
1073 return;
1074 }
1075 }
1076
1077 // now, check validity
1078 if ($value != '') {
1079 $value = $this->validateValue($name, $value);
1080 }
1081
1082 //some values need to be changed (eg. passwords)
1083 if ($name == 'pref_admin_pass') {
1084 $value = password_hash($value, PASSWORD_BCRYPT);
1085 }
1086
1087 //okay, let's update value
1088 $this->prefs[$name] = $value;
1089 }
1090
1091 /**
1092 * Get instance URL from configuration (if set) or guessed if not
1093 *
1094 * @return string
1095 */
1096 public function getURL()
1097 {
1098 $url = null;
1099 if (isset($this->prefs['pref_galette_url']) && !empty($this->prefs['pref_galette_url'])) {
1100 $url = $this->prefs['pref_galette_url'];
1101 } else {
1102 $url = $this->getDefaultURL();
1103 }
1104 return $url;
1105 }
1106
1107 /**
1108 * Get default URL (when not set by user in preferences)
1109 *
1110 * @return string
1111 */
1112 public function getDefaultURL()
1113 {
1114 if (defined('GALETTE_CRON')) {
1115 if (defined('GALETTE_URI')) {
1116 return GALETTE_URI;
1117 } else {
1118 throw new \RuntimeException(_T('Please define constant "GALETTE_URI" with the path to your instance.'));
1119 }
1120 }
1121
1122 $scheme = (isset($_SERVER['HTTPS']) ? 'https' : 'http');
1123 $uri = $scheme . '://' . $_SERVER['HTTP_HOST'];
1124 return $uri;
1125 }
1126
1127 /**
1128 * Get last telemetry date
1129 *
1130 * @return string
1131 */
1132 public function getTelemetryDate(): string
1133 {
1134 $rawdate = $this->prefs['pref_telemetry_date'];
1135 if ($rawdate) {
1136 $date = new \DateTime($rawdate);
1137 return $date->format(_T('Y-m-d H:i:s'));
1138 } else {
1139 return _T('Never');
1140 }
1141 }
1142
1143 /**
1144 * Get last telemetry date
1145 *
1146 * @return string|null
1147 */
1148 public function getRegistrationDate()
1149 {
1150 $rawdate = $this->prefs['pref_registration_date'];
1151 if ($rawdate) {
1152 $date = new \DateTime($rawdate);
1153 return $date->format(_T('Y-m-d H:i:s'));
1154 }
1155
1156 return null;
1157 }
1158
1159 /**
1160 * Check member cards sizes
1161 * Always a A4/portrait
1162 *
1163 * @return array
1164 */
1165 public function checkCardsSizes()
1166 {
1167 $warning_detected = [];
1168 //check page width
1169 $max = 210;
1170 //margins
1171 $size = $this->pref_card_marges_h * 2;
1172 //cards
1173 $size += PdfMembersCards::getWidth() * PdfMembersCards::getCols();
1174 //spacing
1175 $size += $this->pref_card_hspace * (PdfMembersCards::getCols() - 1);
1176 if ($size > $max) {
1177 $warning_detected[] = _T('Current cards configuration may exceed page width!');
1178 }
1179
1180 $max = 297;
1181 //margins
1182 $size = $this->pref_card_marges_v * 2;
1183 //cards
1184 $size += PdfMembersCards::getHeight() * PdfMembersCards::getRows();
1185 //spacing
1186 $size += $this->pref_card_vspace * (PdfMembersCards::getRows() - 1);
1187 if ($size > $max) {
1188 $warning_detected[] = _T('Current cards configuration may exceed page height!');
1189 }
1190
1191 return $warning_detected;
1192 }
1193
1194 /**
1195 * Get errors
1196 *
1197 * @return array
1198 */
1199 public function getErrors()
1200 {
1201 return $this->errors;
1202 }
1203
1204 /**
1205 * Build legend array
1206 *
1207 * @return array
1208 */
1209 public function getLegend(): array
1210 {
1211 $legend = [];
1212
1213 $legend['main'] = [
1214 'title' => _T('Main information'),
1215 'patterns' => $this->getMainPatterns()
1216 ];
1217
1218 $s_patterns = $this->getSignaturePatterns(false);
1219 if (count($s_patterns)) {
1220 $legend['socials'] = [
1221 'title' => _T('Social networks'),
1222 'patterns' => $this->getSignaturePatterns(false)
1223 ];
1224 }
1225
1226 return $legend;
1227 }
1228
1229 /**
1230 * Get email signature
1231 *
1232 * @param PHPMailer $mail PHPMailer instance
1233 *
1234 * @return string
1235 */
1236 public function getMailSignature(PHPMailer $mail): string
1237 {
1238 global $routeparser;
1239
1240 $signature = $this->pref_mail_sign;
1241
1242 if (trim($signature) == '') {
1243 return '';
1244 }
1245
1246 $this->setPreferences($this)->setRouteparser($routeparser);
1247 $this->setPatterns(
1248 $this->getMainPatterns() + $this->getSignaturePatterns()
1249 );
1250 $this
1251 ->setMail($mail)
1252 ->setMain()
1253 ->setSocialReplacements();
1254
1255 $signature = $this->proceedReplacements($signature);
1256
1257 return "\r\n-- \r\n" . $signature;
1258 }
1259
1260 /**
1261 * Get patterns for mail signature
1262 *
1263 * @param boolean $legacy Whether to load legacy patterns
1264 *
1265 * @return array
1266 */
1267 protected function getSignaturePatterns($legacy = true): array
1268 {
1269 $s_patterns = [];
1270 $social = new Social($this->zdb);
1271
1272 $types = $this->getCoreRegisteredTypes() + $social->getSystemTypes(false);
1273
1274 foreach ($types as $type) {
1275 $s_patterns['asso_social_' . strtolower($type)] = [
1276 'title' => $social->getSystemType($type),
1277 'pattern' => '/{ASSO_SOCIAL_' . strtoupper($type) . '}/'
1278 ];
1279 }
1280
1281 if ($legacy === true) {
1282 $main = $this->getMainPatterns();
1283 $s_patterns['_asso_name'] = [
1284 'title' => $main['asso_name']['title'],
1285 'pattern' => '/{NAME}/'
1286 ];
1287
1288 $s_patterns['_asso_website'] = [
1289 'title' => $main['asso_website']['title'],
1290 'pattern' => '/{WEBSITE}/'
1291 ];
1292
1293 foreach ([Social::FACEBOOK, Social::TWITTER, Social::LINKEDIN, Social::VIADEO] as $legacy_type) {
1294 $s_patterns['_asso_social_' . $legacy_type] = [
1295 'title' => $s_patterns['asso_social_' . $legacy_type]['title'],
1296 'pattern' => '/{' . strtoupper($legacy_type) . '}/'
1297 ];
1298 }
1299 }
1300
1301 return $s_patterns;
1302 }
1303
1304 /**
1305 * Set emails replacements
1306 *
1307 * @return $this
1308 */
1309 public function setSocialReplacements(): self
1310 {
1311 $replacements = [];
1312
1313 $done_replacements = $this->getReplacements();
1314 $replacements['_asso_name'] = $done_replacements['asso_name'];
1315 $replacements['asso_website'] = $this->pref_website;
1316 $replacements['_asso_website'] = $replacements['asso_website'];
1317
1318 $social = new Social($this->zdb);
1319 $types = $this->getCoreRegisteredTypes() + $social->getSystemTypes(false);
1320
1321 foreach ($types as $type) {
1322 $replace_value = null;
1323 $socials = Social::getListForMember(null, $type);
1324 if (count($socials)) {
1325 $replace_value = '';
1326 foreach ($socials as $social) {
1327 if ($replace_value != '') {
1328 $replace_value .= ', ';
1329 }
1330 $replace_value .= $social->url;
1331 }
1332 }
1333 $replacements['asso_social_' . strtolower($type)] = $replace_value;
1334 }
1335
1336
1337 foreach ([Social::FACEBOOK, Social::TWITTER, Social::LINKEDIN, Social::VIADEO] as $legacy_type) {
1338 $replacements['_asso_social_' . $legacy_type] = $replacements['asso_social_' . $legacy_type];
1339 }
1340
1341 $this->setReplacements($replacements);
1342
1343 return $this;
1344 }
1345
1346 /**
1347 * Purify HTML value
1348 *
1349 * @param string $value Value to clean
1350 *
1351 * @return string
1352 */
1353 public function cleanHtmlValue(string $value): string
1354 {
1355 $config = \HTMLPurifier_Config::createDefault();
1356 $config->set('Cache.SerializerPath', GALETTE_CACHE_DIR);
1357 $purifier = new \HTMLPurifier($config);
1358 return $purifier->purify($value);
1359 }
1360 }