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