3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
10 * Copyright © 2020-2021 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
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.
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.
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/>.
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2020-2021 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.eu
37 namespace Galette\Features
;
40 use Galette\Core\Login
;
41 use Galette\Core\Logo
;
42 use Galette\Core\Preferences
;
43 use Galette\DynamicFields\Choice
;
44 use Galette\DynamicFields\Separator
;
45 use Galette\Entity\Adherent
;
46 use Galette\Entity\Contribution
;
47 use Galette\Entity\PdfModel
;
48 use Galette\Entity\Texts
;
49 use Galette\Repository\DynamicFieldsSet
;
50 use Galette\DynamicFields\DynamicField
;
56 * Replacements feature
61 * @author Johan Cwiklinski <johan@x-tnd.be>
62 * @copyright 2020-2021 The Galette Team
63 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
64 * @link http://galette.eu
70 private $patterns = [];
71 private $replaces = [];
72 private $dynamic_patterns = [];
87 * @Inject("preferences")
90 protected $preferences;
98 * Get dynamic patterns
100 * @param string $form_name Dynamic form name
101 * @param boolean $legacy Whether to load legacy patterns
105 public function getDynamicPatterns(string $form_name, bool $legacy = true): array
107 $fields = new DynamicFieldsSet($this->zdb
, $this->login
);
108 $dynamic_fields = $fields->getList($form_name);
110 $dynamic_patterns = [];
111 foreach ($dynamic_fields as $dynamic_field) {
112 //no pattern for separators
113 if ($dynamic_field instanceof Separator
) {
116 $key = strtoupper('DYNFIELD_' . $dynamic_field->getId() . '_' . $form_name);
121 if (!($this instanceof Texts
) && ($legacy === true ||
$dynamic_field instanceof Choice
)) {
122 $capabilities[] = 'INPUT';
124 foreach ($capabilities as $capability) {
125 $skey = sprintf('%s_%s', $capability, $key);
126 switch ($capability) {
128 $title = _T('Label for dynamic field "%s"');
131 $title = _T('Form entry for dynamic field "%s"');
136 $title = _T('Value for dynamic field "%s"');
139 $dynamic_patterns[strtolower($skey)] = [
142 $dynamic_field->getName()
144 'pattern' => sprintf('/{%s}/', $skey)
149 $this->dynamic_patterns
[$form_name] = $dynamic_patterns;
150 return $this->dynamic_patterns
[$form_name];
156 * @param array $patterns Patterns to add
160 protected function setPatterns(array $patterns): self
163 foreach ($patterns as $key => $info) {
164 if (is_array($info)) {
165 $toset[$key] = $info['pattern'];
167 $toset[$key] = $info;
171 $this->patterns
= array_merge(
182 * @param array $replaces Replacements to add
186 public function setReplacements(array $replaces): void
188 $this->replaces
= array_merge(
199 protected function getMainPatterns(): array
203 'title' => _T('Your organisation name'),
204 'pattern' => '/{ASSO_NAME}/'
207 'title' => _T('Your organisation slogan'),
208 'pattern' => '/{ASSO_SLOGAN}/'
211 'title' => _T('Your organisation address'),
212 'pattern' => '/{ASSO_ADDRESS}/',
214 'asso_address_multi' => [
215 'title' => sprintf('%s (%s)', _T('Your organisation address'), _T('with break lines')),
216 'pattern' => '/{ASSO_ADDRESS_MULTI}/',
219 'title' => _T('Your organisation website'),
220 'pattern' => '/{ASSO_WEBSITE}/',
223 'title' => _T('Your organisation logo'),
224 'pattern' => '/{ASSO_LOGO}/',
227 'title' => _T('Current date (Y-m-d)'),
228 'pattern' => '/{DATE_NOW}/'
231 'title' => _T("Galette's login URI"),
232 'pattern' => '/{LOGIN_URI}/'
238 * Get patterns for a member
240 * @param boolean $legacy Whether to load legacy patterns
244 protected function getMemberPatterns(bool $legacy = true): array
246 $dynamic_patterns = $this->getDynamicPatterns('adh', $legacy);
249 'title' => _('Title'),
250 'pattern' => '/{TITLE_ADH}/',
253 'title' => _T("Member's ID"),
254 'pattern' => '/{ID_ADH}/',
257 'title' => _T("Member number"),
258 'pattern' => '/{NUM_ADH}/',
261 'title' => _T("Name"),
262 'pattern' => '/{NAME_ADH}/',
265 'title' => _T('Last name'),
266 'pattern' => '/{LAST_NAME_ADH}/',
268 'adh_first_name' => [
269 'title' => _T('First name'),
270 'pattern' => '/{FIRST_NAME_ADH}/',
273 'title' => _T('Nickname'),
274 'pattern' => '/{NICKNAME_ADH}/',
277 'title' => _T('Gender'),
278 'pattern' => '/{GENDER_ADH}/',
280 'adh_birth_date' => [
281 'title' => _T('Birth date'),
282 'pattern' => '/{ADH_BIRTH_DATE}/',
284 'adh_birth_place' => [
285 'title' => _T('Birth place'),
286 'pattern' => '/{ADH_BIRTH_PLACE}/',
288 'adh_profession' => [
289 'title' => _T('Profession'),
290 'pattern' => '/{PROFESSION_ADH}/',
293 'title' => _T("Company name"),
294 'pattern' => '/{COMPANY_ADH}/',
297 'title' => _T("Address"),
298 'pattern' => '/{ADDRESS_ADH}/',
301 'title' => _T("Zipcode"),
302 'pattern' => '/{ZIP_ADH}/',
305 'title' => _T("Town"),
306 'pattern' => '/{TOWN_ADH}/',
309 'title' => _T('Country'),
310 'pattern' => '/{COUNTRY_ADH}/',
313 'title' => _T('Phone'),
314 'pattern' => '/{PHONE_ADH}/',
317 'title' => _T('GSM'),
318 'pattern' => '/{MOBILE_ADH}/',
321 'title' => _T('Email'),
322 'pattern' => '/{EMAIL_ADH}/',
325 'title' => _T('Login'),
326 'pattern' => '/{LOGIN_ADH}/',
328 'adh_main_group' => [
329 'title' => _T("Member's main group"),
330 'pattern' => '/{GROUP_ADH}/',
333 'title' => _T("Member's groups (as list)"),
334 'pattern' => '/{GROUPS_ADH}/'
337 'title' => _T('Member state of dues'),
338 'pattern' => '/{ADH_DUES}/'
340 'days_remaining' => [
341 'title' => _T('Membership remaining days'),
342 'pattern' => '/{DAYS_REMAINING}/',
345 'title' => _T('Membership expired since'),
346 'pattern' => '/{DAYS_EXPIRED}/',
350 if ($legacy === true) {
353 'title' => _T("Company name"),
354 'pattern' => '/{COMPANY_NAME_ADH}/',
356 '_adh_last_name' => [
357 'title' => _T('Last name'),
358 'pattern' => '/{LASTNAME_ADH}/',
360 '_adh_first_name' => [
361 'title' => _T('First name'),
362 'pattern' => '/{FIRSTNAME_ADH}/',
365 'title' => _T('Login'),
366 'pattern' => '/{LOGIN}/',
369 'title' => _T('Email'),
370 'pattern' => '/{MAIL_ADH}/',
375 return $m_patterns +
$dynamic_patterns;
379 * Get patterns for a contribution
381 * @param boolean $legacy Whether to load legacy patterns
385 protected function getContributionPatterns($legacy = true): array
387 $dynamic_patterns = $this->getDynamicPatterns('contrib', $legacy);
391 'title' => _T('Contribution label'),
392 'pattern' => '/{CONTRIB_LABEL}/',
394 'contrib_amount' => [
395 'title' => _T('Amount'),
396 'pattern' => '/{CONTRIB_AMOUNT}/',
398 'contrib_amount_letters' => [
399 'title' => _T('Amount (in letters)'),
400 'pattern' => '/{CONTRIB_AMOUNT_LETTERS}/',
403 'title' => _T('Full date'),
404 'pattern' => '/{CONTRIB_DATE}/',
407 'title' => _T('Contribution year'),
408 'pattern' => '/{CONTRIB_YEAR}/',
410 'contrib_comment' => [
411 'title' => _T('Comment'),
412 'pattern' => '/{CONTRIB_COMMENT}/',
415 'title' => _T('Begin date'),
416 'pattern' => '/{CONTRIB_BEGIN_DATE}/',
419 'title' => _T('End date'),
420 'pattern' => '/{CONTRIB_END_DATE}/',
423 'title' => _T('Contribution id'),
424 'pattern' => '/{CONTRIB_ID}/',
426 'contrib_payment' => [
427 'title' => _T('Payment type'),
428 'pattern' => '/{CONTRIB_PAYMENT_TYPE}/'
431 'title' => _T('Contribution information'),
432 'pattern' => '/{CONTRIB_INFO}/'
436 if ($legacy === true) {
437 foreach ($c_patterns as $key => $pattern) {
439 $pattern['pattern'] = str_replace(
444 $c_patterns[$nkey] = $pattern;
447 $c_patterns['__contrib_label'] = [
448 'title' => $c_patterns['contrib_label']['title'],
449 'pattern' => '/{CONTRIB_TYPE}/'
453 //handle DEADLINE alias
454 $c_patterns['deadline'] = [
455 'title' => $c_patterns['contrib_edate']['title'],
456 'pattern' => '/{DEADLINE}/'
459 return $c_patterns +
$dynamic_patterns;
463 * Set main replacements
467 public function setMain(): self
469 $address = $this->preferences
->getPostalAddress();
470 $address_multi = preg_replace("/\n/", "<br>", $address);
473 if ($this->preferences
->pref_website
!== '') {
474 $website = '<a href="' . $this->preferences
->pref_website
. '">' .
475 $this->preferences
->pref_website
. '</a>';
480 ' src="' . $this->preferences
->getURL() . $this->router
->pathFor('logo') . '"' .
481 ' width="' . $logo->getOptimalWidth() . '"' .
482 ' height="' . $logo->getOptimalHeight() . '"' .
485 $this->setReplacements(
487 'asso_name' => $this->preferences
->pref_nom
,
488 'asso_slogan' => $this->preferences
->pref_slogan
,
489 'asso_address' => $address,
490 'asso_address_multi' => $address_multi,
491 'asso_website' => $website,
492 'asso_logo' => $logo_elt,
493 'date_now' => date(_T('Y-m-d')),
494 'login_uri' => $this->preferences
->getURL() . $this->router
->pathFor('login'),
502 * Set contribution and proceed related replacements
506 public function setNoContribution(): self
511 'contrib_label' => null,
512 'contrib_amount' => null,
513 'contrib_amount_letters' => null,
514 'contrib_date' => null,
515 'contrib_year' => null,
516 'contrib_comment' => null,
517 'contrib_bdate' => null,
518 'contrib_edate' => null,
519 'contrib_id' => null,
520 'contrib_payment' => null,
521 'contrib_info' => null
524 foreach ($c_replacements as $key => $replacement) {
526 $c_replacements[$nkey] = $replacement;
528 $c_replacements['__contrib_label'] = $c_replacements['contrib_label'];
530 //handle DEADLINE alias
531 $c_replacements['deadline'] = null;
533 $this->setReplacements($c_replacements);
535 /** the list of all dynamic fields */
536 $fields = new DynamicFieldsSet($this->zdb
, $login);
537 $dynamic_fields = $fields->getList('contrib');
538 $this->setDynamicFields('contrib', $dynamic_fields, null);
544 * Set contribution and proceed related replacements
546 * @param Contribution $contrib Contribution
550 public function setContribution(Contribution
$contrib): self
552 global $login, $i18n;
554 $formatter = new NumberFormatter($i18n->getID(), NumberFormatter
::SPELLOUT
);
557 'contrib_label' => $contrib->type
->libelle
,
558 'contrib_amount' => $contrib->amount
,
559 'contrib_amount_letters' => $formatter->format($contrib->amount
),
560 'contrib_date' => $contrib->date
,
561 'contrib_year' => $contrib->raw_date
->format('Y'),
562 'contrib_comment' => $contrib->info
,
563 'contrib_bdate' => $contrib->begin_date
,
564 'contrib_edate' => $contrib->end_date
,
565 'contrib_id' => $contrib->id
,
566 'contrib_payment' => $contrib->spayment_type
,
567 'contrib_info' => $contrib->info
570 foreach ($c_replacements as $key => $replacement) {
572 $c_replacements[$nkey] = $replacement;
574 $c_replacements['__contrib_label'] = $c_replacements['contrib_label'];
576 //handle DEADLINE alias
577 $c_replacements['deadline'] = $c_replacements['contrib_edate'];
579 $this->setReplacements($c_replacements);
581 /** the list of all dynamic fields */
582 $fields = new DynamicFieldsSet($this->zdb
, $login);
583 $dynamic_fields = $fields->getList('contrib');
584 $this->setDynamicFields('contrib', $dynamic_fields, $contrib);
590 * Set member and proceed related replacements
592 * @param Adherent $member Member
596 public function setMember(Adherent
$member): self
600 $address = $member->getAddress();
601 if ($member->getAddressContinuation() !== '') {
602 $address .= '<br/>' . $member->getAddressContinuation();
605 if ($member->isMan()) {
607 } elseif ($member->isWoman()) {
608 $gender = _T("Woman");
610 $gender = _T("Unspecified");
613 $member_groups = $member->groups
;
614 $main_group = _T("None");
615 $group_list = _T("None");
616 if (is_array($member_groups) && count($member_groups) > 0) {
617 $main_group = $member_groups[0]->getName();
618 $group_list = '<ul>';
619 foreach ($member_groups as $group) {
620 $group_list .= '<li>' . $group->getName() . '</li>';
622 $group_list .= '</ul>';
625 $this->setReplacements(
627 'adh_title' => $member->stitle
,
628 'adh_id' => $member->id
,
629 'adh_num' => $member->number
,
630 'adh_name' => $member->sfullname
,
631 'adh_last_name' => $member->name
,
632 'adh_first_name' => $member->surname
,
633 'adh_nickname' => $member->nickname
,
634 'adh_gender' => $gender,
635 'adh_birth_date' => $member->birthdate
,
636 'adh_birth_place' => $member->birth_place
,
637 'adh_profession' => $member->job
,
638 'adh_company' => $member->company_name
,
639 'adh_address' => $address,
640 'adh_zip' => $member->getZipcode(),
641 'adh_town' => $member->getTown(),
642 'adh_country' => $member->getCountry(),
643 'adh_phone' => $member->phone
,
644 'adh_mobile' => $member->gsm
,
645 //always take current member email, to be sure.
646 'adh_email' => $member->email
,
647 'adh_login' => $member->login
,
648 'adh_main_group' => $main_group,
649 'adh_groups' => $group_list,
650 'adh_dues' => $member->getDues(),
651 'days_remaining' => $member->days_remaining
,
652 'days_expired' => ($member->days_remaining
* -1),
653 //Handle COMPANY_NAME_ADH... https://bugs.galette.eu/issues/1530
654 '_adh_company' => $member->company_name
,
655 //Handle old names for variables ... https://bugs.galette.eu/issues/1393
656 '_adh_last_name' => $member->name
,
657 '_adh_first_name' => $member->surname
,
658 '_adh_login' => $member->login
,
659 '_adh_email' => $member->email
663 /** the list of all dynamic fields */
664 $fields = new DynamicFieldsSet($this->zdb
, $login);
665 $dynamic_fields = $fields->getList('adh');
666 $this->setDynamicFields('adh', $dynamic_fields, $member);
672 * Set dynamic fields and proceed related replacements
674 * @param string $form_name Form name
675 * @param array $dynamic_fields Dynamic fields
676 * @param mixed $object Related object (Adherent, Contribution, ...)
680 public function setDynamicFields(string $form_name, array $dynamic_fields, $object): self
682 $uform_name = strtoupper($form_name);
684 $dynamic_patterns = $this->getDynamicPatterns($form_name);
685 foreach ($dynamic_patterns as $dynamic_pattern) {
686 $pattern = trim($dynamic_pattern['pattern'], '/');
687 $key = strtolower(rtrim(ltrim($pattern, '{'), '}'));
690 if (preg_match("/^{LABEL_DYNFIELD_([0-9]+)_$uform_name}$/", $pattern, $match)) {
691 /** dynamic field label */
692 $field_id = $match[1];
693 $value = $dynamic_fields[$field_id]->getName();
695 if (preg_match("/^{(INPUT_|VALUE_)?DYNFIELD_([0-9]+)_$uform_name}$/", $pattern, $match)) {
696 /** dynamic field value */
697 $capacity = trim($match[1], '_');
698 $field_id = $match[2];
699 $field_name = $dynamic_fields[$field_id]->getName();
700 $field_type = $dynamic_fields[$field_id]->getType();
702 if ($object !== null) {
703 $all_values = $object->getDynamicFields()->getValues($field_id);
704 foreach ($all_values as $field_value) {
705 $field_values[$field_value['field_val']] = $field_value['text_val'] ??
$field_value['field_val'];
711 switch ($field_type) {
712 case DynamicField
::CHOICE
:
713 $choice_values = $dynamic_fields[$field_id]->getValues();
714 if ($capacity == 'INPUT') {
715 foreach ($choice_values as $choice_idx => $choice_value) {
716 $value .= '<input type="radio" class="box" name="' . $field_name . '" value="' . $field_id . '"';
717 if (isset($field_values[$choice_idx])) {
718 $value .= ' checked="checked"';
720 $value .= ' disabled="disabled">' . $choice_value . ' ';
723 foreach ($field_values as $field_value) {
724 $value .= $field_value;
728 case DynamicField
::BOOLEAN
:
729 foreach ($field_values as $field_value) {
730 $value .= ($field_value ?
_T("Yes") : _T("No"));
733 case DynamicField
::FILE
:
735 foreach ($field_values as $field_value) {
736 if (empty($field_value)) {
739 $spattern = (($this instanceof Texts
) ?
741 '<a href="%1$s%2$s">%3$s</a>'
745 $this->preferences
->getURL(),
746 $this->router
->pathFor(
752 'name' => $field_value
759 case DynamicField
::TEXT
:
760 case DynamicField
::LINE
:
761 case DynamicField
::DATE
:
762 $value .= implode('<br/>', $field_values);
767 $this->setReplacements(array($key => $value));
768 Analog
::log("adding dynamic replacement $key => $value", Analog
::DEBUG
);
779 public function getLegend(): array
784 'title' => _T('Main information'),
785 'patterns' => $this->getMainPatterns()
788 $legend['member'] = [
789 'title' => _T('Member information'),
790 'patterns' => $this->getMemberPatterns(false)
797 * Get configured replacements
801 public function getReplacements(): array
803 return $this->replaces
;
809 * @param Db $db Db instance
813 public function setDb(Db
$db): self
820 * Set Login dependency
822 * @param Login $login Login instance
826 public function setLogin(Login
$login): self
828 $this->login
= $login;
833 * Set Preferences dependency
835 * @param Preferences $preferences Preferences instance
839 public function setPreferences(Preferences
$preferences): self
841 $this->preferences
= $preferences;
846 * Set Router dependency
848 * @param Router $router Router instance
852 public function setRouter(Router
$router): self
854 $this->router
= $router;
859 * Proceed replacement on given entry
861 * @param string $source Source string
865 protected function proceedReplacements(string $source): string
869 //handle translations
870 $callback = static function ($matches) {
871 return _T($matches[1]);
873 $replaced = preg_replace_callback(
874 '/_T\("([^\"]+)"\)/',
880 ksort($this->patterns
, SORT_NATURAL
);
881 ksort($this->replaces
, SORT_NATURAL
);
883 if (array_keys($this->patterns
) !== array_keys($this->replaces
)) {
884 throw new \
RuntimeException('Patterns and replacements does not match!');
887 //handle replacements
888 $replaced = preg_replace(
894 //handle translations with replacements
895 $repl_callback = static function ($matches) {
902 $replaced = preg_replace_callback(
903 '/str_replace\(\'([^,]+)\', ?\'([^,]+)\', ?\'(.*)\'\)/',
916 public function getPatterns(): array
918 return $this->patterns
;