4 * Copyright © 2003-2024 The Galette Team
6 * This file is part of Galette (https://galette.eu).
8 * Galette is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Galette is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
22 namespace Galette\Core
;
24 use Galette\Entity\PaymentType
;
25 use Galette\Entity\Social
;
26 use Galette\Features\Replacements
;
27 use Galette\Features\Socials
;
28 use PHPMailer\PHPMailer\PHPMailer
;
31 use Galette\Entity\Adherent
;
32 use Galette\Entity\Status
;
33 use Galette\IO\PdfMembersCards
;
34 use Galette\Repository\Members
;
37 * Preferences for galette
39 * @author Johan Cwiklinski <johan@x-tnd.be>
41 * @property string $pref_admin_login Super admin login
42 * @property string $pref_admin_pass Super admin password
43 * @property string $pref_nom Association name
44 * @property string $pref_slogan Association slogan
45 * @property string $pref_adresse Address
46 * @property string $pref_adresse2 Address continuation
47 * @property string $pref_cp Association zipcode
48 * @property string $pref_ville Association
49 * @property string $pref_region Region
50 * @property string $pref_pays Country
51 * @property integer $pref_postal_adress Postal adress to use, one of self::POSTAL_ADDRESS*
52 * @property integer $pref_postal_staff_member Staff member ID from which retrieve postal address
53 * @property boolean $pref_disable_members_socials
54 * @property string $pref_lang Default instance language
55 * @property integer $pref_numrows Default number of rows in lists
56 * @property integer $pref_log History, one of self::LOG_*
57 * @property integer $pref_statut Default status for new members
58 * @property string $pref_email_nom
59 * @property string $pref_email
60 * @property string $pref_email_newadh
61 * @property boolean $pref_bool_mailadh
62 * @property boolean $pref_bool_mailowner
63 * @property boolean $pref_editor_enabled
64 * @property integer $pref_mail_method Mail method, see GaletteMail::METHOD_*
65 * @property string $pref_mail_smtp
66 * @property string $pref_mail_smtp_host
67 * @property boolean $pref_mail_smtp_auth
68 * @property boolean $pref_mail_smtp_secure
69 * @property integer $pref_mail_smtp_port
70 * @property string $pref_mail_smtp_user
71 * @property string $pref_mail_smtp_password
72 * @property integer $pref_membership_ext
73 * @property string $pref_beg_membership
74 * @property integer $pref_membership_offermonths
75 * @property string $pref_email_reply_to
76 * @property string $pref_website
77 * @property integer $pref_etiq_marges_v
78 * @property integer $pref_etiq_marges_h
79 * @property integer $pref_etiq_hspace
80 * @property integer $pref_etiq_vspace
81 * @property integer $pref_etiq_hsize
82 * @property integer $pref_etiq_vsize
83 * @property integer $pref_etiq_cols
84 * @property integer $pref_etiq_rows
85 * @property integer $pref_etiq_corps
86 * @property boolean $pref_etiq_border
87 * @property boolean $pref_force_picture_ratio
88 * @property string $pref_member_picture_ratio
89 * @property string $pref_card_abrev
90 * @property string $pref_card_strip
91 * @property string $pref_card_tcol
92 * @property string $pref_card_scol
93 * @property string $pref_card_bcol
94 * @property string $pref_card_hcol
95 * @property string $pref_bool_display_title
96 * @property integer $pref_card_address
97 * @property string $pref_card_year
98 * @property integer $pref_card_marges_v
99 * @property integer $pref_card_marges_h
100 * @property integer $pref_card_vspace
101 * @property integer $pref_card_hspace
102 * @property string $pref_card_self
103 * @property string $pref_theme Prefered theme
104 * @property boolean $pref_bool_publicpages
105 * @property integer $pref_publicpages_visibility
106 * @property boolean $pref_bool_selfsubscribe
107 * @property string $pref_member_form_grid
108 * @property string $pref_mail_sign
109 * @property string $pref_new_contrib_script
110 * @property boolean $pref_bool_wrap_mails
111 * @property string $pref_rss_url
112 * @property boolean $pref_show_id
113 * @property string $pref_adhesion_form
114 * @property boolean $pref_mail_allow_unsecure
115 * @property string $pref_instance_uuid
116 * @property string $pref_registration_uuid
117 * @property string $pref_telemetry_date
118 * @property string $pref_registration_date
119 * @property string $pref_footer
120 * @property integer $pref_filter_account
121 * @property string $pref_galette_url
122 * @property integer $pref_redirect_on_create
123 * @property integer $pref_password_length
124 * @property boolean $pref_password_blacklist
125 * @property integer $pref_password_strength
126 * @property integer $pref_default_paymenttype
127 * @property boolean $pref_bool_create_member
128 * @property boolean $pref_bool_groupsmanagers_create_member
129 * @property boolean $pref_bool_groupsmanagers_edit_member
130 * @property boolean $pref_bool_groupsmanagers_edit_groups
131 * @property boolean $pref_bool_groupsmanagers_mailings
132 * @property boolean $pref_bool_groupsmanagers_exports
133 * @property-read array $vpref_email_newadh list of mail senders
134 * @property-read array $vpref_email list of mail senders
135 * @property boolean $pref_noindex
142 protected Preferences
$preferences; //redefined from Replacements feature - avoid circular dependency
143 /** @var array<string, bool|int|string> */
144 private array $prefs;
145 /** @var array<string> */
146 private array $errors = [];
148 public const TABLE
= 'preferences';
149 public const PK
= 'nom_pref';
151 /** Postal address will be the one given in the preferences */
152 public const POSTAL_ADDRESS_FROM_PREFS
= 0;
153 /** Postal address will be the one of the selected staff member */
154 public const POSTAL_ADDRESS_FROM_STAFF
= 1;
156 /** Public pages stuff */
157 /** Public pages are publically visibles */
158 public const PUBLIC_PAGES_VISIBILITY_PUBLIC
= 0;
159 /** Public pages are visibles for up to date members only */
160 public const PUBLIC_PAGES_VISIBILITY_RESTRICTED
= 1;
161 /** Public pages are visibles for admin and staff members only */
162 public const PUBLIC_PAGES_VISIBILITY_PRIVATE
= 2;
164 public const LOG_DISABLED
= 0;
165 public const LOG_ENABLED
= 1;
167 /** No password strength */
168 public const PWD_NONE
= 0;
169 /** Weak password strength */
170 public const PWD_WEAK
= 1;
171 /** Medium password strength */
172 public const PWD_MEDIUM
= 2;
173 /** Strong password strength */
174 public const PWD_STRONG
= 3;
175 /** Very strong password strength */
176 public const PWD_VERY_STRONG
= 4;
178 /** @var array<string> */
179 private static array $fields = array(
184 /** @var array<string, bool|int|string> */
185 private static array $defaults = array(
186 'pref_admin_login' => 'admin',
187 'pref_admin_pass' => 'admin',
188 'pref_nom' => 'Galette',
190 'pref_adresse' => '-',
191 'pref_adresse2' => '',
196 'pref_postal_adress' => self
::POSTAL_ADDRESS_FROM_PREFS
,
197 'pref_postal_staff_member' => '',
198 'pref_disable_members_socials' => false,
199 'pref_lang' => I18n
::DEFAULT_LANG
,
200 'pref_numrows' => 30,
201 'pref_log' => self
::LOG_ENABLED
,
202 'pref_statut' => Status
::DEFAULT_STATUS
,
203 /* Preferences for emails */
204 'pref_email_nom' => 'Galette',
205 'pref_email' => 'mail@domain.com',
206 'pref_email_newadh' => 'mail@domain.com',
207 'pref_bool_mailadh' => false,
208 'pref_bool_mailowner' => false,
209 'pref_editor_enabled' => false,
210 'pref_mail_method' => GaletteMail
::METHOD_DISABLED
,
211 'pref_mail_smtp' => '',
212 'pref_mail_smtp_host' => '',
213 'pref_mail_smtp_auth' => false,
214 'pref_mail_smtp_secure' => false,
215 'pref_mail_smtp_port' => '',
216 'pref_mail_smtp_user' => '',
217 'pref_mail_smtp_password' => '',
218 'pref_membership_ext' => 12,
219 'pref_beg_membership' => '',
220 'pref_membership_offermonths' => 0,
221 'pref_email_reply_to' => '',
222 'pref_website' => '',
223 /* Preferences for labels */
224 'pref_etiq_marges_v' => 10,
225 'pref_etiq_marges_h' => 10,
226 'pref_etiq_hspace' => 10,
227 'pref_etiq_vspace' => 5,
228 'pref_etiq_hsize' => 90,
229 'pref_etiq_vsize' => 35,
230 'pref_etiq_cols' => 2,
231 'pref_etiq_rows' => 7,
232 'pref_etiq_corps' => 12,
233 'pref_etiq_border' => true,
234 /* Preferences for members cards */
235 'pref_force_picture_ratio' => false,
236 'pref_member_picture_ratio' => 'square_ratio',
237 'pref_card_abrev' => 'GALETTE',
238 'pref_card_strip' => 'Gestion d\'Adherents en Ligne Extrêmement Tarabiscotée',
239 'pref_card_tcol' => '#FFFFFF',
240 'pref_card_scol' => '#8C2453',
241 'pref_card_bcol' => '#53248C',
242 'pref_card_hcol' => '#248C53',
243 'pref_bool_display_title' => false,
244 'pref_card_address' => 1,
245 'pref_card_year' => '',
246 'pref_card_marges_v' => 15,
247 'pref_card_marges_h' => 20,
248 'pref_card_vspace' => 5,
249 'pref_card_hspace' => 10,
250 'pref_card_self' => 1,
251 'pref_theme' => 'default',
252 'pref_bool_publicpages' => true,
253 'pref_publicpages_visibility' => self
::PUBLIC_PAGES_VISIBILITY_RESTRICTED
,
254 'pref_mail_sign' => "{ASSO_NAME}\r\n\r\n{ASSO_WEBSITE}",
255 /* Preferences for member/subscribe form */
256 'pref_bool_selfsubscribe' => true,
257 'pref_member_form_grid' => 'one',
258 /* New contribution script */
259 'pref_new_contrib_script' => '',
260 'pref_bool_wrap_mails' => true,
261 'pref_rss_url' => 'https://galette.eu/dc/index.php/feed/atom',
262 'pref_show_id' => false,
263 'pref_adhesion_form' => '\Galette\IO\PdfAdhesionForm',
264 'pref_mail_allow_unsecure' => false,
265 'pref_instance_uuid' => '',
266 'pref_registration_uuid' => '',
267 'pref_telemetry_date' => '',
268 'pref_registration_date' => '',
270 'pref_filter_account' => Members
::ALL_ACCOUNTS
,
271 'pref_galette_url' => '',
272 'pref_redirect_on_create' => Adherent
::AFTER_ADD_DEFAULT
,
273 /* Security related */
274 'pref_password_length' => 6,
275 'pref_password_blacklist' => false,
276 'pref_password_strength' => self
::PWD_NONE
,
277 'pref_default_paymenttype' => PaymentType
::CHECK
,
278 'pref_bool_create_member' => false,
279 'pref_bool_groupsmanagers_create_member' => false,
280 'pref_bool_groupsmanagers_edit_member' => false,
281 'pref_bool_groupsmanagers_edit_groups' => false,
282 'pref_bool_groupsmanagers_mailings' => false,
283 'pref_bool_groupsmanagers_exports' => true,
284 'pref_noindex' => false
288 private array $socials;
290 // flagging required fields
291 /** @var array<string> */
292 private array $required = array(
298 'pref_etiq_marges_v',
299 'pref_etiq_marges_h',
307 'pref_card_marges_v',
308 'pref_card_marges_h',
314 * Default constructor
316 * @param Db $zdb Db instance
317 * @param boolean $load Automatically load preferences on load
321 public function __construct(Db
$zdb, bool $load = true)
326 $this->checkUpdate();
331 * Check if all fields referenced in the default array does exists,
336 private function checkUpdate(): bool
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
);
345 $this->prefs
[$k] = $v;
347 'The field `' . $k . '` does not exists, Galette will attempt to create it.',
357 if ($proceed !== false) {
359 $insert = $this->zdb
->insert(self
::TABLE
);
362 'nom_pref' => ':nom_pref',
363 'val_pref' => ':val_pref'
366 $stmt = $this->zdb
->sql
->prepareStatementForSqlObject($insert);
368 foreach ($params as $p) {
371 'nom_pref' => $p['nom_pref'],
372 'val_pref' => $p['val_pref']
376 } catch (Throwable
$e) {
378 'Unable to add missing preferences.' . $e->getMessage(),
385 'Missing preferences were successfully stored into database.',
394 * Load current preferences from database.
398 public function load(): bool
400 $this->prefs
= array();
403 $result = $this->zdb
->selectAll(self
::TABLE
);
404 foreach ($result as $pref) {
405 $this->prefs
[$pref->nom_pref
] = $pref->val_pref
;
407 $this->socials
= Social
::getListForMember(null);
409 } catch (Throwable
$e) {
411 'Preferences cannot be loaded. Galette should not work without ' .
412 'preferences. Exiting.',
420 * Set default preferences at install time
422 * @param string $lang language selected at install screen
423 * @param string $adm_login admin login entered at install time
424 * @param string $adm_pass admin password entered at install time
429 public function installInit(string $lang, string $adm_login, string $adm_pass): bool
432 //first, we drop all values
433 $delete = $this->zdb
->delete(self
::TABLE
);
434 $this->zdb
->execute($delete);
436 //we then replace default values with the ones user has selected
437 $values = self
::$defaults;
438 $values['pref_lang'] = $lang;
439 $values['pref_admin_login'] = $adm_login;
440 $values['pref_admin_pass'] = $adm_pass;
441 $values['pref_card_year'] = date('Y');
443 $insert = $this->zdb
->insert(self
::TABLE
);
446 'nom_pref' => ':nom_pref',
447 'val_pref' => ':val_pref'
450 $stmt = $this->zdb
->sql
->prepareStatementForSqlObject($insert);
452 foreach ($values as $k => $v) {
462 'Default preferences were successfully stored into database.',
466 } catch (Throwable
$e) {
468 'Unable to initialize default preferences.' . $e->getMessage(),
476 * Returns all preferences keys
478 * @return array<string>
480 public function getFieldsNames(): array
482 return array_keys($this->prefs
);
488 * @param array<string, mixed> $values Values
489 * @param Login $login Logged in user
493 public function check(array $values, Login
$login): bool
495 $insert_values = array();
496 if ($login->isSuperAdmin() && !Galette
::isDemo()) {
497 $this->required
[] = 'pref_admin_login';
501 foreach ($this->getFieldsNames() as $fieldname) {
502 if (isset($values[$fieldname])) {
503 $value = trim($values[$fieldname]);
508 $insert_values[$fieldname] = $value;
511 //cleanup fields for demo
512 if (Galette
::isDemo()) {
514 $insert_values['pref_admin_login'],
515 $insert_values['pref_admin_pass'],
516 $insert_values['pref_mail_method']
523 && isset($insert_values['pref_mail_method'])
525 if ($insert_values['pref_mail_method'] > GaletteMail
::METHOD_DISABLED
) {
527 !isset($insert_values['pref_email_nom'])
528 ||
$insert_values['pref_email_nom'] == ''
530 $this->errors
[] = _T("- You must indicate a sender name for emails!");
533 !isset($insert_values['pref_email'])
534 ||
$insert_values['pref_email'] == ''
536 $this->errors
[] = _T("- You must indicate an email address Galette should use to send emails!");
538 if ($insert_values['pref_mail_method'] == GaletteMail
::METHOD_SMTP
) {
540 !isset($insert_values['pref_mail_smtp_host'])
541 ||
$insert_values['pref_mail_smtp_host'] == ''
543 $this->errors
[] = _T("- You must indicate the SMTP server you want to use!");
547 $insert_values['pref_mail_method'] == GaletteMail
::METHOD_GMAIL
548 ||
($insert_values['pref_mail_method'] == GaletteMail
::METHOD_SMTP
549 && $insert_values['pref_mail_smtp_auth'])
552 !isset($insert_values['pref_mail_smtp_user'])
553 ||
trim($insert_values['pref_mail_smtp_user']) == ''
555 $this->errors
[] = _T("- You must provide a login for SMTP authentication.");
558 !isset($insert_values['pref_mail_smtp_password'])
559 ||
($insert_values['pref_mail_smtp_password']) == ''
561 $this->errors
[] = _T("- You must provide a password for SMTP authentication.");
568 isset($insert_values['pref_beg_membership'])
569 && $insert_values['pref_beg_membership'] != ''
570 && isset($insert_values['pref_membership_ext'])
571 && $insert_values['pref_membership_ext'] != ''
573 $this->errors
[] = _T("- Default membership extention and beginning of membership are mutually exclusive.");
577 isset($insert_values['pref_membership_offermonths'])
578 && (int)$insert_values['pref_membership_offermonths'] > 0
579 && isset($insert_values['pref_membership_ext'])
580 && $insert_values['pref_membership_ext'] != ''
582 $this->errors
[] = _T("- Offering months is only compatible with beginning of membership.");
585 // missing required fields?
586 foreach ($this->required
as $val) {
587 if (!isset($values[$val]) ||
isset($values[$val]) && trim($values[$val]) == '') {
588 $this->errors
[] = str_replace(
591 _T("- Mandatory field %field empty.")
596 if (!Galette
::isDemo() && isset($values['pref_admin_pass_check'])) {
597 // Check passwords. Hash will be done into the Preferences class
598 if (strcmp($insert_values['pref_admin_pass'], $values['pref_admin_pass_check']) != 0) {
599 $this->errors
[] = _T("Passwords mismatch");
604 if (isset($insert_values['pref_postal_adress'])) {
605 $value = $insert_values['pref_postal_adress'];
606 if ($value == Preferences
::POSTAL_ADDRESS_FROM_PREFS
) {
607 if (isset($insert_values['pref_postal_staff_member'])) {
608 unset($insert_values['pref_postal_staff_member']);
610 } elseif ($value == Preferences
::POSTAL_ADDRESS_FROM_STAFF
) {
612 $this->errors
[] = _T("You have to select a staff member");
617 // update preferences
618 foreach ($insert_values as $champ => $valeur) {
620 $login->isSuperAdmin()
621 ||
$champ != 'pref_admin_pass' && $champ != 'pref_admin_login'
624 ($champ == "pref_admin_pass" && $_POST['pref_admin_pass'] != '')
625 ||
($champ != "pref_admin_pass")
627 $this->$champ = $valeur;
632 $this->checkSocials($values);
634 return 0 === count($this->errors
);
638 * Validate value of a field
640 * @param string $fieldname Field name
641 * @param mixed $value Value to be set
645 public function validateValue(string $fieldname, $value)
649 switch ($fieldname) {
651 case 'pref_email_newadh':
652 case 'pref_email_reply_to':
653 //check emails validity
654 //may be a comma separated list of valid emails identifiers:
655 //"The Name <mail@domain.com>,The Other <other@mail.com>" expect for reply_to.
657 if (trim($value) != '') {
658 if ($fieldname == 'pref_email_newadh') {
659 $addresses = explode(',', $value);
661 $addresses = [$value];
664 foreach ($addresses as $address) {
665 if (!GaletteMail
::isValidEmail($address)) {
666 $msg = str_replace('%s', $address, _T("Invalid E-Mail address: %s"));
667 Analog
::log($msg, Analog
::WARNING
);
668 $this->errors
[] = $msg;
672 case 'pref_admin_login':
673 if (Galette
::isDemo()) {
675 'Trying to set superadmin login while in DEMO.',
679 if (strlen($value) < 4) {
680 $this->errors
[] = _T("- The username must be composed of at least 4 characters!");
682 //check if login is already taken
683 if ($login->loginExists($value)) {
684 $this->errors
[] = _T("- This username is already used by another member !");
690 case 'pref_etiq_marges_h':
691 case 'pref_etiq_marges_v':
692 case 'pref_etiq_hspace':
693 case 'pref_etiq_vspace':
694 case 'pref_etiq_hsize':
695 case 'pref_etiq_vsize':
696 case 'pref_etiq_cols':
697 case 'pref_etiq_rows':
698 case 'pref_etiq_corps':
699 case 'pref_card_marges_v':
700 case 'pref_card_marges_h':
701 case 'pref_card_hspace':
702 case 'pref_card_vspace':
703 if (!is_numeric($value) ||
$value < 0) {
704 $this->errors
[] = _T("- The numbers and measures have to be integers!");
707 case 'pref_card_tcol':
708 case 'pref_card_scol':
709 case 'pref_card_bcol':
710 case 'pref_card_hcol':
712 if (!preg_match("/^(#)?([0-9A-F]{6})$/i", $value, $matches)) {
713 // Set strip background colors to black or white (for tcol)
714 $value = ($fieldname == 'pref_card_tcol' ?
'#FFFFFF' : '#000000');
716 $value = '#' . $matches[2];
719 case 'pref_admin_pass':
720 if (Galette
::isDemo()) {
722 'Trying to set superadmin pass while in DEMO.',
726 $pwcheck = new \Galette\Util\
Password($this);
727 $pwcheck->addPersonalInformation([$this->pref_admin_login
]);
728 if (!$pwcheck->isValid($value)) {
729 $this->errors
= array_merge(
731 $pwcheck->getErrors()
736 case 'pref_membership_ext':
737 if (!is_numeric($value) ||
$value < 0) {
738 $this->errors
[] = _T("- Invalid number of months of membership extension.");
741 case 'pref_beg_membership':
742 $beg_membership = explode("/", $value);
743 if (count($beg_membership) != 2) {
744 $this->errors
[] = _T("- Invalid format of beginning of membership.");
747 if (!checkdate((int)$beg_membership[1], (int)$beg_membership[0], $now['year'])) {
748 $this->errors
[] = _T("- Invalid date for beginning of membership.");
752 case 'pref_membership_offermonths':
753 if (!is_numeric($value) ||
$value < 0) {
754 $this->errors
[] = _T("- Invalid number of offered months.");
757 case 'pref_card_year':
758 if ($value !== 'DEADLINE' && !preg_match('/^(?:\d{4}|\d{2})(\D?)(?:\d{4}|\d{2})$/', $value)) {
759 $this->errors
[] = _T("- Invalid year for cards.");
763 $value = $this->cleanHtmlValue($value);
766 if (!isValidWebUrl($value)) {
767 $this->errors
[] = _T("- Invalid website URL.");
776 * Will store all preferences in the database
780 public function store(): bool
783 $this->zdb
->connection
->beginTransaction();
784 $update = $this->zdb
->update(self
::TABLE
);
787 'val_pref' => ':val_pref'
789 )->where
->equalTo('nom_pref', ':nom_pref');
791 $stmt = $this->zdb
->sql
->prepareStatementForSqlObject($update);
793 foreach (self
::$defaults as $k => $v) {
796 && in_array($k, ['pref_admin_pass', 'pref_admin_login', 'pref_mail_method'])
800 Analog
::log('Storing ' . $k, Analog
::DEBUG
);
802 $value = $this->prefs
[$k];
803 //do not store pdf_adhesion_form, it's designed to be overriden by plugin
804 if ($k === 'pref_adhesion_form') {
805 if (trim($v) == '') {
806 //Reset to default, should not be empty
807 $v = self
::$defaults['pref_adhesion_form'];
814 'val_pref' => $value,
819 $this->zdb
->connection
->commit();
821 'Preferences were successfully stored into database.',
825 $this->storeSocials(null);
828 } catch (Throwable
$e) {
829 $this->zdb
->connection
->rollBack();
833 $messages[] = $e->getMessage();
834 } while ($e = $e->getPrevious());
837 'Unable to store preferences | ' . print_r($messages, true),
845 * Returns postal address
847 * @return string postal address
849 public function getPostalAddress(): string
860 $replacements = null;
862 if ($this->prefs
['pref_postal_adress'] == self
::POSTAL_ADDRESS_FROM_PREFS
) {
863 $_address = $this->prefs
['pref_adresse'];
864 if ($this->prefs
['pref_adresse2'] && $this->prefs
['pref_adresse2'] != '') {
865 $_address .= "\n" . $this->prefs
['pref_adresse2'];
867 $replacements = array(
868 $this->prefs
['pref_nom'],
871 $this->prefs
['pref_cp'],
872 $this->prefs
['pref_ville'],
873 $this->prefs
['pref_pays']
876 //get selected staff member address
877 $adh = new Adherent($this->zdb
, (int)$this->prefs
['pref_postal_staff_member']);
878 $_complement = preg_replace(
879 array('/%name/', '/%status/'),
880 array($this->prefs
['pref_nom'], $adh->sstatus
),
881 _T("%name association's %status")
883 $_address = $adh->address
;
885 $replacements = array(
886 $adh->sfullname
. "\n",
895 /*FIXME: i18n fails :/ */
899 _T("%name\n%complement\n%address\n%zip %town - %country")
904 "%name%complement%address\n%zip %town - %country"
910 * Are public pages visible?
912 * @param Authentication $login Authentication instance
916 public function showPublicPages(Authentication
$login): bool
918 if ($this->prefs
['pref_bool_publicpages']) {
919 //if public pages are actives, let's check if we
920 //display them for curent call
921 switch ($this->prefs
['pref_publicpages_visibility']) {
922 case self
::PUBLIC_PAGES_VISIBILITY_PUBLIC
:
923 //pages are publically visibles
925 case self
::PUBLIC_PAGES_VISIBILITY_RESTRICTED
:
926 //pages should be displayed only for up-to-date members
936 case self
::PUBLIC_PAGES_VISIBILITY_PRIVATE
:
937 //pages should be displayed only for staff and admins
938 if ($login->isAdmin() ||
$login->isStaff()) {
944 //should never be there
953 * Global getter method
955 * @param string $name name of the property we want to retrieve
957 * @return mixed the called property
959 public function __get(string $name)
961 $forbidden = array('defaults');
962 $virtuals = array('vpref_email', 'vpref_email_newadh');
964 if (!in_array($name, $forbidden) && isset($this->prefs
[$name])) {
967 && $name == 'pref_mail_method'
969 return GaletteMail
::METHOD_DISABLED
;
970 } elseif ($name == 'pref_footer') {
971 return $this->cleanHtmlValue($this->prefs
[$name]);
973 if ($name == 'pref_adhesion_form' && $this->prefs
[$name] == '') {
974 $this->prefs
[$name] = self
::$defaults['pref_adhesion_form'];
976 $value = $this->prefs
[$name];
977 if (TYPE_DB
=== \Galette\Core\Db
::PGSQL
) {
978 if ($value === 'f') {
983 if (in_array($name, ['vpref_email', 'pref_email_newadh'])) {
984 $values = explode(',', $value);
985 $value = $values[0]; //take first as default
990 } elseif (in_array($name, $virtuals)) {
991 $virtual = str_replace('vpref_', 'pref_', $name);
992 return explode(',', $this->prefs
[$virtual]);
993 } elseif ($name === 'socials') {
994 return $this->socials
;
997 'Preference `' . $name . '` is not set or is forbidden',
1005 * Global isset method
1006 * Required for twig to access properties via __get
1008 * @param string $name name of the property we want to retrieve
1012 public function __isset(string $name): bool
1014 $forbidden = array('defaults');
1015 $virtuals = array('vpref_email', 'vpref_email_newadh');
1017 if (!in_array($name, $forbidden) && isset($this->prefs
[$name])) {
1019 } elseif (in_array($name, $virtuals)) {
1021 } elseif ($name === 'socials') {
1025 'Preference `' . $name . '` is not set or is forbidden',
1033 * Get default preferences
1035 * @return array<string, mixed>
1037 public function getDefaults(): array
1039 return self
::$defaults;
1043 * Global setter method
1045 * @param string $name name of the property we want to assign a value to
1046 * @param mixed $value a relevant value for the property
1050 public function __set(string $name, $value): void
1052 //does this pref exists ?
1053 if (!array_key_exists($name, self
::$defaults)) {
1055 'Trying to set a preference value which does not seem to exist ('
1063 $name == 'pref_email'
1064 ||
$name == 'pref_email_newadh'
1065 ||
$name == 'pref_email_reply_to'
1067 if (Galette
::isDemo()) {
1069 'Trying to set pref_email while in DEMO.',
1076 // now, check validity
1078 $value = $this->validateValue($name, $value);
1081 //some values need to be changed (eg. passwords)
1082 if ($name == 'pref_admin_pass') {
1083 $value = password_hash($value, PASSWORD_BCRYPT
);
1086 //okay, let's update value
1087 $this->prefs
[$name] = $value;
1091 * Get instance URL from configuration (if set) or guessed if not
1095 public function getURL(): string
1098 if (isset($this->prefs
['pref_galette_url']) && !empty($this->prefs
['pref_galette_url'])) {
1099 $url = $this->prefs
['pref_galette_url'];
1101 $url = $this->getDefaultURL();
1107 * Get default URL (when not set by user in preferences)
1111 public function getDefaultURL(): string
1113 if (defined('GALETTE_CRON')) {
1114 if (defined('GALETTE_URI')) {
1117 throw new \
RuntimeException(_T('Please define constant "GALETTE_URI" with the path to your instance.'));
1121 $scheme = (isset($_SERVER['HTTPS']) ?
'https' : 'http');
1122 $uri = $scheme . '://' . $_SERVER['HTTP_HOST'];
1127 * Get last telemetry date
1131 public function getTelemetryDate(): string
1133 $rawdate = $this->prefs
['pref_telemetry_date'];
1135 $date = new \
DateTime($rawdate);
1136 return $date->format(_T('Y-m-d H:i:s'));
1143 * Get last telemetry registration date
1145 * @return string|null
1147 public function getRegistrationDate(): ?
string
1149 $rawdate = $this->prefs
['pref_registration_date'];
1151 $date = new \
DateTime($rawdate);
1152 return $date->format(_T('Y-m-d H:i:s'));
1159 * Check member cards sizes
1160 * Always a A4/portrait
1162 * @return array<string>
1164 public function checkCardsSizes(): array
1166 $warning_detected = [];
1170 $size = $this->pref_card_marges_h
* 2;
1172 $size +
= PdfMembersCards
::getWidth() * PdfMembersCards
::getCols();
1174 $size +
= $this->pref_card_hspace
* (PdfMembersCards
::getCols() - 1);
1176 $warning_detected[] = _T('Current cards configuration may exceed page width!');
1181 $size = $this->pref_card_marges_v
* 2;
1183 $size +
= PdfMembersCards
::getHeight() * PdfMembersCards
::getRows();
1185 $size +
= $this->pref_card_vspace
* (PdfMembersCards
::getRows() - 1);
1187 $warning_detected[] = _T('Current cards configuration may exceed page height!');
1190 return $warning_detected;
1196 * @return array<string>
1198 public function getErrors(): array
1200 return $this->errors
;
1204 * Build legend array
1206 * @return array<string, array<string, string>>
1208 public function getLegend(): array
1213 'title' => _T('Main information'),
1214 'patterns' => $this->getMainPatterns()
1217 $s_patterns = $this->getSignaturePatterns(false);
1218 if (count($s_patterns)) {
1219 $legend['socials'] = [
1220 'title' => _T('Social networks'),
1221 'patterns' => $this->getSignaturePatterns(false)
1229 * Get email signature
1231 * @param PHPMailer $mail PHPMailer instance
1235 public function getMailSignature(PHPMailer
$mail): string
1237 global $routeparser;
1239 $signature = $this->pref_mail_sign
;
1241 if (trim($signature) == '') {
1245 $this->setPreferences($this)->setRouteparser($routeparser);
1247 $this->getMainPatterns() +
$this->getSignaturePatterns()
1252 ->setSocialReplacements();
1254 $signature = $this->proceedReplacements($signature);
1256 return "\r\n-- \r\n" . $signature;
1260 * Get patterns for mail signature
1262 * @param boolean $legacy Whether to load legacy patterns
1264 * @return array<string, array<string, string>>
1266 protected function getSignaturePatterns(bool $legacy = true): array
1269 $social = new Social($this->zdb
);
1271 $types = $this->getCoreRegisteredTypes() +
$social->getSystemTypes(false);
1273 foreach ($types as $type) {
1274 $s_patterns['asso_social_' . strtolower($type)] = [
1275 'title' => $social->getSystemType($type),
1276 'pattern' => '/{ASSO_SOCIAL_' . strtoupper($type) . '}/'
1280 if ($legacy === true) {
1281 $main = $this->getMainPatterns();
1282 $s_patterns['_asso_name'] = [
1283 'title' => $main['asso_name']['title'],
1284 'pattern' => '/{NAME}/'
1287 $s_patterns['_asso_website'] = [
1288 'title' => $main['asso_website']['title'],
1289 'pattern' => '/{WEBSITE}/'
1292 foreach ([Social
::FACEBOOK
, Social
::TWITTER
, Social
::LINKEDIN
, Social
::VIADEO
] as $legacy_type) {
1293 $s_patterns['_asso_social_' . $legacy_type] = [
1294 'title' => $s_patterns['asso_social_' . $legacy_type]['title'],
1295 'pattern' => '/{' . strtoupper($legacy_type) . '}/'
1304 * Set emails replacements
1308 public function setSocialReplacements(): self
1312 $done_replacements = $this->getReplacements();
1313 $replacements['_asso_name'] = $done_replacements['asso_name'];
1314 $replacements['asso_website'] = $this->pref_website
;
1315 $replacements['_asso_website'] = $replacements['asso_website'];
1317 $social = new Social($this->zdb
);
1318 $types = $this->getCoreRegisteredTypes() +
$social->getSystemTypes(false);
1320 foreach ($types as $type) {
1321 $replace_value = null;
1322 $socials = Social
::getListForMember(null, $type);
1323 if (count($socials)) {
1324 $replace_value = '';
1325 foreach ($socials as $social) {
1326 if ($replace_value != '') {
1327 $replace_value .= ', ';
1329 $replace_value .= $social->url
;
1332 $replacements['asso_social_' . strtolower($type)] = $replace_value;
1336 foreach ([Social
::FACEBOOK
, Social
::TWITTER
, Social
::LINKEDIN
, Social
::VIADEO
] as $legacy_type) {
1337 $replacements['_asso_social_' . $legacy_type] = $replacements['asso_social_' . $legacy_type];
1340 $this->setReplacements($replacements);
1348 * @param string $value Value to clean
1352 public function cleanHtmlValue(string $value): string
1354 $config = \HTMLPurifier_Config
::createDefault();
1355 $cache_dir = rtrim(GALETTE_CACHE_DIR
, DIRECTORY_SEPARATOR
) . DIRECTORY_SEPARATOR
. 'htmlpurifier';
1356 if (!file_exists($cache_dir)) {
1357 mkdir($cache_dir, 0755, true);
1359 $config->set('Cache.SerializerPath', $cache_dir);
1360 $purifier = new \
HTMLPurifier($config);
1361 return $purifier->purify($value);