]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Entity/Adherent.php
Catch Throwables instead of Exceptions
[galette.git] / galette / lib / Galette / Entity / Adherent.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Member class for galette
7 *
8 * PHP version 5
9 *
10 * Copyright © 2009-2014 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
26 *
27 * @category Entity
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2009-2014 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7dev - 2009-06-02
35 */
36
37 namespace Galette\Entity;
38
39 use Throwable;
40 use Analog\Analog;
41 use Laminas\Db\Sql\Expression;
42 use Galette\Core\Db;
43 use Galette\Core\Picture;
44 use Galette\Core\GaletteMail;
45 use Galette\Core\Password;
46 use Galette\Core\Preferences;
47 use Galette\Core\History;
48 use Galette\Repository\Groups;
49 use Galette\Repository\Members;
50
51 /**
52 * Member class for galette
53 *
54 * @category Entity
55 * @name Adherent
56 * @package Galette
57 * @author Johan Cwiklinski <johan@x-tnd.be>
58 * @copyright 2009-2014 The Galette Team
59 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
60 * @link http://galette.tuxfamily.org
61 * @since Available since 0.7dev - 02-06-2009
62 */
63 class Adherent
64 {
65 use DynamicsTrait;
66
67 public const TABLE = 'adherents';
68 public const PK = 'id_adh';
69
70 public const NC = 0;
71 public const MAN = 1;
72 public const WOMAN = 2;
73
74 public const AFTER_ADD_DEFAULT = 0;
75 public const AFTER_ADD_TRANS = 1;
76 public const AFTER_ADD_NEW = 2;
77 public const AFTER_ADD_SHOW = 3;
78 public const AFTER_ADD_LIST = 4;
79 public const AFTER_ADD_HOME = 5;
80
81 private $_id;
82 //Identity
83 private $_title;
84 private $_company_name;
85 private $_name;
86 private $_surname;
87 private $_nickname;
88 private $_birthdate;
89 private $_birth_place;
90 private $_gender;
91 private $_job;
92 private $_language;
93 private $_active;
94 private $_status;
95 //Contact information
96 private $_address;
97 private $_address_continuation; /** TODO: remove */
98 private $_zipcode;
99 private $_town;
100 private $_country;
101 private $_phone;
102 private $_gsm;
103 private $_email;
104 private $_website;
105 private $_msn; /** TODO: remove */
106 private $_icq; /** TODO: remove */
107 private $_jabber; /** TODO: remove */
108 private $_gnupgid; /** TODO: remove */
109 private $_fingerprint; /** TODO: remove */
110 //Galette relative information
111 private $_appears_in_list;
112 private $_admin;
113 private $_staff;
114 private $_due_free;
115 private $_login;
116 private $_password;
117 private $_creation_date;
118 private $_modification_date;
119 private $_due_date;
120 private $_others_infos;
121 private $_others_infos_admin;
122 private $_picture;
123 private $_oldness;
124 private $_days_remaining;
125 private $_groups;
126 private $_managed_groups;
127 private $_parent;
128 private $_children;
129 private $_duplicate = false;
130
131 private $_row_classes;
132
133 private $_self_adh = false;
134 private $_deps = array(
135 'picture' => true,
136 'groups' => true,
137 'dues' => true,
138 'parent' => false,
139 'children' => false,
140 'dynamics' => false
141 );
142
143 private $zdb;
144 private $preferences;
145 private $fields;
146 private $history;
147
148 private $parent_fields = [
149 'adresse_adh',
150 'adresse2_adh',
151 'cp_adh',
152 'ville_adh',
153 'email_adh'
154 ];
155
156 private $errors = [];
157
158 private $sendmail = false;
159
160 /**
161 * Default constructor
162 *
163 * @param Db $zdb Database instance
164 * @param mixed $args Either a ResultSet row, its id or its
165 * login or its email for to load s specific
166 * member, or null to just instanciate object
167 * @param false|array $deps Dependencies configuration, see Adherent::$_deps
168 */
169 public function __construct(Db $zdb, $args = null, $deps = null)
170 {
171 global $i18n;
172
173 $this->zdb = $zdb;
174
175 if ($deps !== null) {
176 if (is_array($deps)) {
177 $this->_deps = array_merge(
178 $this->_deps,
179 $deps
180 );
181 } elseif ($deps === false) {
182 //no dependencies
183 $this->_deps = array_fill_keys(
184 array_keys($this->_deps),
185 false
186 );
187 } else {
188 Analog::log(
189 '$deps shoud be an array, ' . gettype($deps) . ' given!',
190 Analog::WARNING
191 );
192 }
193 }
194
195 if ($args == null || is_int($args)) {
196 if (is_int($args) && $args > 0) {
197 $this->load($args);
198 } else {
199 $this->_active = true;
200 $this->_language = $i18n->getID();
201 $this->_creation_date = date("Y-m-d");
202 $this->_status = $this->getDefaultStatus();
203 $this->_title = null;
204 $this->_gender = self::NC;
205 $gp = new Password($this->zdb);
206 $this->_password = $gp->makeRandomPassword();
207 $this->_picture = new Picture();
208 $this->_admin = false;
209 $this->_staff = false;
210 $this->_due_free = false;
211 $this->_appears_in_list = false;
212 $this->_parent = null;
213
214 if ($this->_deps['dynamics'] === true) {
215 $this->loadDynamicFields();
216 }
217 }
218 } elseif (is_object($args)) {
219 $this->loadFromRS($args);
220 } elseif (is_string($args)) {
221 $this->loadFromLoginOrMail($args);
222 }
223 }
224
225 /**
226 * Loads a member from its id
227 *
228 * @param int $id the identifiant for the member to load
229 *
230 * @return bool true if query succeed, false otherwise
231 */
232 public function load($id)
233 {
234 try {
235 $select = $this->zdb->select(self::TABLE, 'a');
236
237 $select->join(
238 array('b' => PREFIX_DB . Status::TABLE),
239 'a.' . Status::PK . '=b.' . Status::PK,
240 array('priorite_statut')
241 )->where(array(self::PK => $id));
242
243 $results = $this->zdb->execute($select);
244
245 if ($results->count() === 0) {
246 return false;
247 }
248
249 $this->loadFromRS($results->current());
250 return true;
251 } catch (Throwable $e) {
252 Analog::log(
253 'Cannot load member form id `' . $id . '` | ' . $e->getMessage(),
254 Analog::WARNING
255 );
256 return false;
257 }
258 }
259
260 /**
261 * Loads a member from its login
262 *
263 * @param string $login login for the member to load
264 *
265 * @return bool true if query succeed, false otherwise
266 */
267 public function loadFromLoginOrMail($login)
268 {
269 try {
270 $select = $this->zdb->select(self::TABLE);
271 if (GaletteMail::isValidEmail($login)) {
272 //we got a valid email address, use it
273 $select->where(array('email_adh' => $login));
274 } else {
275 ///we did not get an email address, consider using login
276 $select->where(array('login_adh' => $login));
277 }
278
279 $results = $this->zdb->execute($select);
280 $result = $results->current();
281 if ($result) {
282 $this->loadFromRS($result);
283 }
284 } catch (Throwable $e) {
285 Analog::log(
286 'Cannot load member form login `' . $login . '` | ' .
287 $e->getMessage(),
288 Analog::WARNING
289 );
290 return false;
291 }
292 }
293
294 /**
295 * Populate object from a resultset row
296 *
297 * @param ResultSet $r the resultset row
298 *
299 * @return void
300 */
301 private function loadFromRS($r)
302 {
303 $this->_self_adh = false;
304 $this->_id = $r->id_adh;
305 //Identity
306 if ($r->titre_adh !== null) {
307 $this->_title = new Title((int)$r->titre_adh);
308 }
309 $this->_company_name = $r->societe_adh;
310 $this->_name = $r->nom_adh;
311 $this->_surname = $r->prenom_adh;
312 $this->_nickname = $r->pseudo_adh;
313 if ($r->ddn_adh != '1901-01-01') {
314 $this->_birthdate = $r->ddn_adh;
315 }
316 $this->_birth_place = $r->lieu_naissance;
317 $this->_gender = (int)$r->sexe_adh;
318 $this->_job = $r->prof_adh;
319 $this->_language = $r->pref_lang;
320 $this->_active = ($r->activite_adh == 1) ? true : false;
321 $this->_status = (int)$r->id_statut;
322 //Contact information
323 $this->_address = $r->adresse_adh;
324 /** TODO: remove and merge with address */
325 $this->_address_continuation = $r->adresse2_adh;
326 $this->_zipcode = $r->cp_adh;
327 $this->_town = $r->ville_adh;
328 $this->_country = $r->pays_adh;
329 $this->_phone = $r->tel_adh;
330 $this->_gsm = $r->gsm_adh;
331 $this->_email = $r->email_adh;
332 $this->_website = $r->url_adh;
333 /** TODO: remove */
334 $this->_msn = $r->msn_adh;
335 /** TODO: remove */
336 $this->_icq = $r->icq_adh;
337 /** TODO: remove */
338 $this->_jabber = $r->jabber_adh;
339 /** TODO: remove */
340 $this->_gnupgid = $r->gpgid;
341 /** TODO: remove */
342 $this->_fingerprint = $r->fingerprint;
343 //Galette relative information
344 $this->_appears_in_list = ($r->bool_display_info == 1) ? true : false;
345 $this->_admin = ($r->bool_admin_adh == 1) ? true : false;
346 if (
347 isset($r->priorite_statut)
348 && $r->priorite_statut < Members::NON_STAFF_MEMBERS
349 ) {
350 $this->_staff = true;
351 }
352 $this->_due_free = ($r->bool_exempt_adh == 1) ? true : false;
353 $this->_login = $r->login_adh;
354 $this->_password = $r->mdp_adh;
355 $this->_creation_date = $r->date_crea_adh;
356 if ($r->date_modif_adh != '1901-01-01') {
357 $this->_modification_date = $r->date_modif_adh;
358 } else {
359 $this->_modification_date = $this->_creation_date;
360 }
361 $this->_due_date = $r->date_echeance;
362 $this->_others_infos = $r->info_public_adh;
363 $this->_others_infos_admin = $r->info_adh;
364
365 if ($r->parent_id !== null) {
366 $this->_parent = (int)$r->parent_id;
367 if ($this->_deps['parent'] === true) {
368 $this->loadParent($r->parent_id);
369 }
370 }
371
372 if ($this->_deps['children'] === true) {
373 $this->loadChildren();
374 }
375
376 if ($this->_deps['picture'] === true) {
377 $this->_picture = new Picture($this->_id);
378 }
379
380 if ($this->_deps['groups'] === true) {
381 $this->loadGroups();
382 }
383
384 if ($this->_deps['dues'] === true) {
385 $this->checkDues();
386 }
387
388 if ($this->_deps['dynamics'] === true) {
389 $this->loadDynamicFields();
390 }
391 }
392
393 /**
394 * Load member parent
395 *
396 * @return void
397 */
398 private function loadParent()
399 {
400 if (!$this->_parent instanceof Adherent) {
401 $deps = array_fill_keys(array_keys($this->_deps), false);
402 $this->_parent = new Adherent($this->zdb, (int)$this->_parent, $deps);
403 }
404 }
405
406 /**
407 * Load member children
408 *
409 * @return void
410 */
411 private function loadChildren()
412 {
413 $this->_children = array();
414 try {
415 $id = self::PK;
416 $select = $this->zdb->select(self::TABLE);
417 $select->columns(
418 array($id)
419 )->where(
420 'parent_id = ' . $this->_id
421 );
422
423 $results = $this->zdb->execute($select);
424
425 if ($results->count() > 0) {
426 foreach ($results as $row) {
427 $deps = $this->_deps;
428 $deps['children'] = false;
429 $deps['parent'] = false;
430 $this->_children[] = new Adherent($this->zdb, (int)$row->$id, $deps);
431 }
432 }
433 } catch (Throwable $e) {
434 Analog::log(
435 'Cannot load children for member #' . $this->_id . ' | ' .
436 $e->getMessage(),
437 Analog::WARNING
438 );
439 return false;
440 }
441 }
442
443 /**
444 * Load member groups
445 *
446 * @return void
447 */
448 public function loadGroups()
449 {
450 $this->_groups = Groups::loadGroups($this->_id);
451 $this->_managed_groups = Groups::loadManagedGroups($this->_id);
452 }
453
454 /**
455 * Retrieve status from preferences
456 *
457 * @return pref_statut
458 *
459 */
460 private function getDefaultStatus()
461 {
462 global $preferences;
463 if ($preferences->pref_statut != '') {
464 return $preferences->pref_statut;
465 } else {
466 Analog::log(
467 'Unable to get pref_statut; is it defined in preferences?',
468 Analog::ERROR
469 );
470 return Status::DEFAULT_STATUS;
471 }
472 }
473
474 /**
475 * Check for dues status
476 *
477 * @return void
478 */
479 private function checkDues()
480 {
481 //how many days since our beloved member has been created
482 $date_now = new \DateTime();
483 $this->_oldness = $date_now->diff(
484 new \DateTime($this->_creation_date)
485 )->days;
486
487 if ($this->isDueFree()) {
488 //no fee required, we don't care about dates
489 $this->_row_classes .= ' cotis-exempt';
490 } else {
491 //ok, fee is required. Let's check the dates
492 if ($this->_due_date == '') {
493 $this->_row_classes .= ' cotis-never';
494 } else {
495 $date_end = new \DateTime($this->_due_date);
496 $date_diff = $date_now->diff($date_end);
497 $this->_days_remaining = ($date_diff->invert == 1)
498 ? $date_diff->days * -1
499 : $date_diff->days;
500
501 if ($this->_days_remaining == 0) {
502 $this->_row_classes .= ' cotis-lastday';
503 } elseif ($this->_days_remaining < 0) {
504 //check if member is still active
505 $this->_row_classes .= $this->isActive() ? ' cotis-late' : ' cotis-old';
506 } elseif ($this->_days_remaining < 30) {
507 $this->_row_classes .= ' cotis-soon';
508 } else {
509 $this->_row_classes .= ' cotis-ok';
510 }
511 }
512 }
513 }
514
515 /**
516 * Is member admin?
517 *
518 * @return bool
519 */
520 public function isAdmin()
521 {
522 return $this->_admin;
523 }
524
525 /**
526 * Is user member of staff?
527 *
528 * @return bool
529 */
530 public function isStaff()
531 {
532 return $this->_staff;
533 }
534
535 /**
536 * Is member freed of dues?
537 *
538 * @return bool
539 */
540 public function isDueFree()
541 {
542 return $this->_due_free;
543 }
544
545 /**
546 * Is member in specified group?
547 *
548 * @param string $group_name Group name
549 *
550 * @return boolean
551 */
552 public function isGroupMember($group_name)
553 {
554 if (is_array($this->_groups)) {
555 foreach ($this->_groups as $g) {
556 if ($g->getName() == $group_name) {
557 return true;
558 break;
559 }
560 }
561 } else {
562 Analog::log(
563 'Calling ' . __METHOD__ . ' without groups loaded!',
564 Analog::ERROR
565 );
566 return false;
567 }
568 }
569
570 /**
571 * Is member manager of specified group?
572 *
573 * @param string $group_name Group name
574 *
575 * @return boolean
576 */
577 public function isGroupManager($group_name)
578 {
579 if (is_array($this->_managed_groups)) {
580 foreach ($this->_managed_groups as $mg) {
581 if ($mg->getName() == $group_name) {
582 return true;
583 break;
584 }
585 }
586 } else {
587 Analog::log(
588 'Calling ' . __METHOD__ . ' without groups loaded!',
589 Analog::ERROR
590 );
591 return false;
592 }
593 }
594
595 /**
596 * Does current member represents a company?
597 *
598 * @return boolean
599 */
600 public function isCompany()
601 {
602 return trim($this->_company_name) != '';
603 }
604
605 /**
606 * Is current member a man?
607 *
608 * @return boolean
609 */
610 public function isMan()
611 {
612 return (int)$this->_gender === self::MAN;
613 }
614
615 /**
616 * Is current member a woman?
617 *
618 * @return boolean
619 */
620 public function isWoman()
621 {
622 return (int)$this->_gender === self::WOMAN;
623 }
624
625
626 /**
627 * Can member appears in public members list?
628 *
629 * @return bool
630 */
631 public function appearsInMembersList()
632 {
633 return $this->_appears_in_list;
634 }
635
636 /**
637 * Is member active?
638 *
639 * @return bool
640 */
641 public function isActive()
642 {
643 return $this->_active;
644 }
645
646 /**
647 * Does member have uploaded a picture?
648 *
649 * @return bool
650 */
651 public function hasPicture()
652 {
653 return $this->_picture->hasPicture();
654 }
655
656 /**
657 * Does member have a parent?
658 *
659 * @return bool
660 */
661 public function hasParent()
662 {
663 return !empty($this->_parent);
664 }
665
666 /**
667 * Does member have children?
668 *
669 * @return bool
670 */
671 public function hasChildren()
672 {
673 if ($this->_children === null) {
674 if ($this->id) {
675 Analog::log(
676 'Children has not been loaded!',
677 Analog::WARNING
678 );
679 }
680 return false;
681 } else {
682 return count($this->_children) > 0;
683 }
684 }
685
686 /**
687 * Get row class related to current fee status
688 *
689 * @param boolean $public we want the class for public pages
690 *
691 * @return string the class to apply
692 */
693 public function getRowClass($public = false)
694 {
695 $strclass = ($this->isActive()) ? 'active' : 'inactive';
696 if ($public === false) {
697 $strclass .= $this->_row_classes;
698 }
699 return $strclass;
700 }
701
702 /**
703 * Get current member due status
704 *
705 * @return string i18n string representing state of due
706 */
707 public function getDues()
708 {
709 $ret = '';
710 if ($this->isDueFree()) {
711 $ret = _T("Freed of dues");
712 } elseif ($this->_due_date == '') {
713 $patterns = array('/%days/', '/%date/');
714 $cdate = new \DateTime($this->_creation_date);
715 $replace = array(
716 $this->_oldness,
717 $cdate->format(__("Y-m-d"))
718 );
719 if ($this->_active) {
720 $ret = preg_replace(
721 $patterns,
722 $replace,
723 _T("Never contributed: Registered %days days ago (since %date)")
724 );
725 } else {
726 $ret = _T("Never contributed");
727 }
728 } elseif ($this->_days_remaining == 0) {
729 $ret = _T("Last day!");
730 } elseif ($this->_days_remaining < 0) {
731 $patterns = array('/%days/', '/%date/');
732 $ddate = new \DateTime($this->_due_date);
733 $replace = array(
734 $this->_days_remaining * -1,
735 $ddate->format(__("Y-m-d"))
736 );
737 if ($this->_active) {
738 $ret = preg_replace(
739 $patterns,
740 $replace,
741 _T("Late of %days days (since %date)")
742 );
743 } else {
744 $ret = _T("No longer member");
745 }
746 } else {
747 $patterns = array('/%days/', '/%date/');
748 $ddate = new \DateTime($this->_due_date);
749 $replace = array(
750 $this->_days_remaining,
751 $ddate->format(__("Y-m-d"))
752 );
753 $ret = preg_replace(
754 $patterns,
755 $replace,
756 _T("%days days remaining (ending on %date)")
757 );
758 }
759 return $ret;
760 }
761
762 /**
763 * Retrieve Full name and surname for the specified member id
764 *
765 * @param Db $zdb Database instance
766 * @param integer $id Member id
767 * @param boolean $wid Add member id
768 * @param boolean $wnick Add member nickname
769 *
770 * @return string formatted Name and Surname
771 */
772 public static function getSName($zdb, $id, $wid = false, $wnick = false)
773 {
774 try {
775 $select = $zdb->select(self::TABLE);
776 $select->where(self::PK . ' = ' . $id);
777
778 $results = $zdb->execute($select);
779 $row = $results->current();
780 return self::getNameWithCase(
781 $row->nom_adh,
782 $row->prenom_adh,
783 false,
784 ($wid === true ? $row->id_adh : false),
785 ($wnick === true ? $row->pseudo_adh : false)
786 );
787 } catch (Throwable $e) {
788 Analog::log(
789 'Cannot get formatted name for member form id `' . $id . '` | ' .
790 $e->getMessage(),
791 Analog::WARNING
792 );
793 return false;
794 }
795 }
796
797 /**
798 * Get member name with correct case
799 *
800 * @param string $name Member name
801 * @param string $surname Mmeber surname
802 * @param false|Title $title Member title to show or false
803 * @param false|integer $id Member id to display or false
804 * @param false|string $nick Member nickname to display or false
805 *
806 * @return string
807 */
808 public static function getNameWithCase($name, $surname, $title = false, $id = false, $nick = false)
809 {
810 $str = '';
811
812 if ($title !== false && $title instanceof Title) {
813 $str .= $title->tshort . ' ';
814 }
815
816 $str .= mb_strtoupper($name, 'UTF-8') . ' ' .
817 ucwords(mb_strtolower($surname, 'UTF-8'), " \t\r\n\f\v-_|");
818
819 if ($id !== false || $nick !== false) {
820 $str .= ' (';
821 }
822 if ($nick !== false) {
823 $str .= $nick;
824 }
825 if ($id !== false) {
826 if ($nick !== false && !empty($nick)) {
827 $str .= ', ';
828 }
829 $str .= $id;
830 }
831 if ($id !== false || $nick !== false) {
832 $str .= ')';
833 }
834 return $str;
835 }
836
837 /**
838 * Change password for a given user
839 *
840 * @param Db $zdb Database instance
841 * @param string $id_adh Member identifier
842 * @param string $pass New password
843 *
844 * @return boolean
845 */
846 public static function updatePassword(Db $zdb, $id_adh, $pass)
847 {
848 try {
849 $cpass = password_hash($pass, PASSWORD_BCRYPT);
850
851 $update = $zdb->update(self::TABLE);
852 $update->set(
853 array('mdp_adh' => $cpass)
854 )->where(self::PK . ' = ' . $id_adh);
855 $zdb->execute($update);
856 Analog::log(
857 'Password for `' . $id_adh . '` has been updated.',
858 Analog::DEBUG
859 );
860 return true;
861 } catch (Throwable $e) {
862 Analog::log(
863 'An error occurred while updating password for `' . $id_adh .
864 '` | ' . $e->getMessage(),
865 Analog::ERROR
866 );
867 return false;
868 }
869 }
870
871 /**
872 * Get field label
873 *
874 * @param string $field Field name
875 *
876 * @return string
877 */
878 private function getFieldLabel($field)
879 {
880 $label = $this->fields[$field]['label'];
881 //remove trailing ':' and then nbsp (for french at least)
882 $label = trim(trim($label, ':'), '&nbsp;');
883 return $label;
884 }
885
886 /**
887 * Retrieve fields from database
888 *
889 * @param Db $zdb Database instance
890 *
891 * @return array
892 */
893 public static function getDbFields(Db $zdb)
894 {
895 $columns = $zdb->getColumns(self::TABLE);
896 $fields = array();
897 foreach ($columns as $col) {
898 $fields[] = $col->getName();
899 }
900 return $fields;
901 }
902
903 /**
904 * Mark as self membership
905 *
906 * @return void
907 */
908 public function setSelfMembership()
909 {
910 $this->_self_adh = true;
911 }
912
913 /**
914 * Is member up to date?
915 *
916 * @return boolean
917 */
918 public function isUp2Date()
919 {
920 if ($this->_deps['dues']) {
921 if ($this->isDueFree()) {
922 //member is due free, he's up to date.
923 return true;
924 } else {
925 //let's check from end date, if present
926 if ($this->_due_date == null) {
927 return false;
928 } else {
929 $ech = new \DateTime($this->_due_date);
930 $now = new \DateTime();
931 $now->setTime(0, 0, 0);
932 return $ech >= $now;
933 }
934 }
935 } else {
936 throw new \RuntimeException(
937 'Cannot check if member is up to date, dues deps is disabled!'
938 );
939 }
940 }
941
942 /**
943 * Set dependencies
944 *
945 * @param Preferences $preferences Preferences instance
946 * @param array $fields Members fields configuration
947 * @param History $history History instance
948 *
949 * @return void
950 */
951 public function setDependencies(
952 Preferences $preferences,
953 array $fields,
954 History $history
955 ) {
956 $this->preferences = $preferences;
957 $this->fields = $fields;
958 $this->history = $history;
959 }
960
961 /**
962 * Check posted values validity
963 *
964 * @param array $values All values to check, basically the $_POST array
965 * after sending the form
966 * @param array $required Array of required fields
967 * @param array $disabled Array of disabled fields
968 *
969 * @return true|array
970 */
971 public function check($values, $required, $disabled)
972 {
973 $this->errors = array();
974
975 $fields = self::getDbFields($this->zdb);
976
977 //reset company name if needeed
978 if (!isset($values['is_company'])) {
979 unset($values['is_company']);
980 $values['societe_adh'] = '';
981 }
982
983 //no parent if checkbox was unchecked
984 if (
985 !isset($values['attach'])
986 && empty($this->_id)
987 && isset($values['parent_id'])
988 ) {
989 unset($values['parent_id']);
990 }
991
992 if (isset($values['duplicate'])) {
993 //if we're duplicating, keep a trace (if an error occurs)
994 $this->_duplicate = true;
995 }
996
997 foreach ($fields as $key) {
998 //first of all, let's sanitize values
999 $key = strtolower($key);
1000 $prop = '_' . $this->fields[$key]['propname'];
1001
1002 if (isset($values[$key])) {
1003 $value = $values[$key];
1004 if ($value !== true && $value !== false) {
1005 $value = trim($value);
1006 }
1007 } elseif ($this->_id == '' || $this->_id == null) {
1008 switch ($key) {
1009 case 'bool_admin_adh':
1010 case 'bool_exempt_adh':
1011 case 'bool_display_info':
1012 $value = 0;
1013 break;
1014 case 'activite_adh':
1015 //values that are setted at object instanciation
1016 $value = true;
1017 break;
1018 case 'date_crea_adh':
1019 case 'sexe_adh':
1020 case 'titre_adh':
1021 case 'id_statut':
1022 case 'pref_lang':
1023 case 'parent_id':
1024 //values that are setted at object instanciation
1025 $value = $this->$prop;
1026 break;
1027 default:
1028 $value = '';
1029 }
1030 } else {
1031 //keep stored value on update
1032 if ($prop != '_password' || isset($values['mdp_adh']) && isset($values['mdp_adh2'])) {
1033 $value = $this->$prop;
1034 } else {
1035 $value = null;
1036 }
1037 }
1038
1039 // if the field is enabled, check it
1040 if (!isset($disabled[$key])) {
1041 // fill up the adherent structure
1042 if ($value !== null && $value !== true && $value !== false && !is_object($value)) {
1043 $value = stripslashes($value);
1044 }
1045 $this->$prop = $value;
1046
1047 // now, check validity
1048 if ($value !== null && $value != '') {
1049 $this->validate($key, $value, $values);
1050 } elseif (
1051 ($key == 'login_adh' && !isset($required['login_adh']))
1052 || ($key == 'mdp_adh' && !isset($required['mdp_adh']))
1053 && !isset($this->_id)
1054 ) {
1055 $p = new Password($this->zdb);
1056 $this->$prop = $p->makeRandomPassword(15);
1057 }
1058 }
1059 }
1060
1061 // missing required fields?
1062 foreach ($required as $key => $val) {
1063 $prop = '_' . $this->fields[$key]['propname'];
1064
1065 if (!isset($disabled[$key])) {
1066 $mandatory_missing = false;
1067 if (!isset($this->$prop) || $this->$prop == '') {
1068 $mandatory_missing = true;
1069 } elseif ($key === 'titre_adh' && $this->$prop == '-1') {
1070 $mandatory_missing = true;
1071 }
1072
1073 if ($mandatory_missing === true) {
1074 $this->errors[] = str_replace(
1075 '%field',
1076 '<a href="#' . $key . '">' . $this->getFieldLabel($key) . '</a>',
1077 _T("- Mandatory field %field empty.")
1078 );
1079 }
1080 }
1081 }
1082
1083 //attach to/detach from parent
1084 if (isset($values['detach_parent'])) {
1085 $this->_parent = null;
1086 }
1087
1088 $this->dynamicsCheck($values, $required, $disabled);
1089
1090 if (count($this->errors) > 0) {
1091 Analog::log(
1092 'Some errors has been throwed attempting to edit/store a member' . "\n" .
1093 print_r($this->errors, true),
1094 Analog::ERROR
1095 );
1096 return $this->errors;
1097 } else {
1098 $this->checkDues();
1099
1100 Analog::log(
1101 'Member checked successfully.',
1102 Analog::DEBUG
1103 );
1104 return true;
1105 }
1106 }
1107
1108 /**
1109 * Validate data for given key
1110 * Set valid data in current object, also resets errors list
1111 *
1112 * @param string $field Field name
1113 * @param mixed $value Value we want to set
1114 * @param array $values All values, for some references
1115 *
1116 * @return void
1117 */
1118 public function validate($field, $value, $values)
1119 {
1120 global $preferences;
1121
1122 $prop = '_' . $this->fields[$field]['propname'];
1123
1124 if ($value === null || (is_string($value) && trim($value) == '')) {
1125 //empty values are OK
1126 $this->$prop = $value;
1127 return;
1128 }
1129
1130 switch ($field) {
1131 // dates
1132 case 'date_crea_adh':
1133 case 'date_modif_adh_':
1134 case 'ddn_adh':
1135 case 'date_echeance':
1136 try {
1137 $d = \DateTime::createFromFormat(__("Y-m-d"), $value);
1138 if ($d === false) {
1139 //try with non localized date
1140 $d = \DateTime::createFromFormat("Y-m-d", $value);
1141 if ($d === false) {
1142 throw new \Exception('Incorrect format');
1143 }
1144 }
1145
1146 if ($field === 'ddn_adh') {
1147 $now = new \DateTime();
1148 $now->setTime(0, 0, 0);
1149 $d->setTime(0, 0, 0);
1150
1151 $diff = $now->diff($d);
1152 $days = (int)$diff->format('%R%a');
1153 if ($days >= 0) {
1154 $this->errors[] = _T('- Birthdate must be set in the past!');
1155 }
1156
1157 $years = (int)$diff->format('%R%Y');
1158 if ($years <= -200) {
1159 $this->errors[] = str_replace(
1160 '%years',
1161 $years * -1,
1162 _T('- Members must be less than 200 years old (currently %years)!')
1163 );
1164 }
1165 }
1166 $this->$prop = $d->format('Y-m-d');
1167 } catch (Throwable $e) {
1168 Analog::log(
1169 'Wrong date format. field: ' . $field .
1170 ', value: ' . $value . ', expected fmt: ' .
1171 __("Y-m-d") . ' | ' . $e->getMessage(),
1172 Analog::INFO
1173 );
1174 $this->errors[] = str_replace(
1175 array(
1176 '%date_format',
1177 '%field'
1178 ),
1179 array(
1180 __("Y-m-d"),
1181 $this->getFieldLabel($field)
1182 ),
1183 _T("- Wrong date format (%date_format) for %field!")
1184 );
1185 }
1186 break;
1187 case 'titre_adh':
1188 if ($value !== null && $value !== '') {
1189 if ($value == '-1') {
1190 $this->$prop = null;
1191 } elseif (!$value instanceof Title) {
1192 $this->$prop = new Title((int)$value);
1193 }
1194 } else {
1195 $this->$prop = null;
1196 }
1197 break;
1198 case 'email_adh':
1199 case 'msn_adh':
1200 if (!GaletteMail::isValidEmail($value)) {
1201 $this->errors[] = _T("- Non-valid E-Mail address!") .
1202 ' (' . $this->getFieldLabel($field) . ')';
1203 }
1204 if ($field == 'email_adh') {
1205 try {
1206 $select = $this->zdb->select(self::TABLE);
1207 $select->columns(
1208 array(self::PK)
1209 )->where(array('email_adh' => $value));
1210 if ($this->_id != '' && $this->_id != null) {
1211 $select->where(
1212 self::PK . ' != ' . $this->_id
1213 );
1214 }
1215
1216 $results = $this->zdb->execute($select);
1217 if ($results->count() !== 0) {
1218 $this->errors[] = _T("- This E-Mail address is already used by another member!");
1219 }
1220 } catch (Throwable $e) {
1221 Analog::log(
1222 'An error occurred checking member email unicity.',
1223 Analog::ERROR
1224 );
1225 $this->errors[] = _T("An error has occurred while looking if login already exists.");
1226 }
1227 }
1228 break;
1229 case 'url_adh':
1230 if ($value == 'http://') {
1231 $this->$prop = '';
1232 } elseif (!isValidWebUrl($value)) {
1233 $this->errors[] = _T("- Non-valid Website address! Maybe you've skipped the http://?");
1234 }
1235 break;
1236 case 'login_adh':
1237 /** FIXME: add a preference for login lenght */
1238 if (strlen($value) < 2) {
1239 $this->errors[] = str_replace(
1240 '%i',
1241 2,
1242 _T("- The username must be composed of at least %i characters!")
1243 );
1244 } else {
1245 //check if login does not contain the @ character
1246 if (strpos($value, '@') != false) {
1247 $this->errors[] = _T("- The username cannot contain the @ character");
1248 } else {
1249 //check if login is already taken
1250 try {
1251 $select = $this->zdb->select(self::TABLE);
1252 $select->columns(
1253 array(self::PK)
1254 )->where(array('login_adh' => $value));
1255 if ($this->_id != '' && $this->_id != null) {
1256 $select->where(
1257 self::PK . ' != ' . $this->_id
1258 );
1259 }
1260
1261 $results = $this->zdb->execute($select);
1262 if (
1263 $results->count() !== 0
1264 || $value == $preferences->pref_admin_login
1265 ) {
1266 $this->errors[] = _T("- This username is already in use, please choose another one!");
1267 }
1268 } catch (Throwable $e) {
1269 Analog::log(
1270 'An error occurred checking member login unicity.',
1271 Analog::ERROR
1272 );
1273 $this->errors[] = _T("An error has occurred while looking if login already exists.");
1274 }
1275 }
1276 }
1277 break;
1278 case 'mdp_adh':
1279 if (
1280 $this->_self_adh !== true
1281 && (!isset($values['mdp_adh2'])
1282 || $values['mdp_adh2'] != $value)
1283 ) {
1284 $this->errors[] = _T("- The passwords don't match!");
1285 } elseif (
1286 $this->_self_adh === true
1287 && !crypt($value, $values['mdp_crypt']) == $values['mdp_crypt']
1288 ) {
1289 $this->errors[] = _T("Password misrepeated: ");
1290 } else {
1291 $pinfos = password_get_info($value);
1292 //check if value is already a hash
1293 if ($pinfos['algo'] == 0) {
1294 $this->$prop = password_hash(
1295 $value,
1296 PASSWORD_BCRYPT
1297 );
1298
1299 $pwcheck = new \Galette\Util\Password($preferences);
1300 $pwcheck->setAdherent($this);
1301 if (!$pwcheck->isValid($value)) {
1302 $this->errors = array_merge(
1303 $this->errors,
1304 $pwcheck->getErrors()
1305 );
1306 }
1307 }
1308 }
1309 break;
1310 case 'id_statut':
1311 try {
1312 $this->$prop = (int)$value;
1313 //check if status exists
1314 $select = $this->zdb->select(Status::TABLE);
1315 $select->where(Status::PK . '= ' . $value);
1316
1317 $results = $this->zdb->execute($select);
1318 $result = $results->current();
1319 if (!$result) {
1320 $this->errors[] = str_replace(
1321 '%id',
1322 $value,
1323 _T("Status #%id does not exists in database.")
1324 );
1325 break;
1326 }
1327 } catch (Throwable $e) {
1328 Analog::log(
1329 'An error occurred checking status existance: ' . $e->getMessage(),
1330 Analog::ERROR
1331 );
1332 $this->errors[] = _T("An error has occurred while looking if status does exists.");
1333 }
1334 break;
1335 case 'sexe_adh':
1336 if (in_array($value, [self::NC, self::MAN, self::WOMAN])) {
1337 $this->$prop = (int)$value;
1338 } else {
1339 $this->errors[] = _T("Gender %gender does not exists!");
1340 }
1341 break;
1342 case 'parent_id':
1343 $this->$prop = ($value instanceof Adherent) ? (int)$value->id : (int)$value;
1344 $this->loadParent();
1345 break;
1346 }
1347 }
1348
1349 /**
1350 * Store the member
1351 *
1352 * @return boolean
1353 */
1354 public function store()
1355 {
1356 global $hist, $emitter;
1357 $event = null;
1358
1359 try {
1360 $values = array();
1361 $fields = self::getDbFields($this->zdb);
1362
1363 foreach ($fields as $field) {
1364 if (
1365 $field !== 'date_modif_adh'
1366 || !isset($this->_id)
1367 || $this->_id == ''
1368 ) {
1369 $prop = '_' . $this->fields[$field]['propname'];
1370 if (
1371 ($field === 'bool_admin_adh'
1372 || $field === 'bool_exempt_adh'
1373 || $field === 'bool_display_info'
1374 || $field === 'activite_adh')
1375 && $this->$prop === false
1376 ) {
1377 //Handle booleans for postgres ; bugs #18899 and #19354
1378 $values[$field] = $this->zdb->isPostgres() ? 'false' : 0;
1379 } elseif ($field === 'parent_id') {
1380 //handle parents
1381 if ($this->_parent === null) {
1382 $values['parent_id'] = new Expression('NULL');
1383 } elseif ($this->parent instanceof Adherent) {
1384 $values['parent_id'] = $this->_parent->id;
1385 } else {
1386 $values['parent_id'] = $this->_parent;
1387 }
1388 } else {
1389 $values[$field] = $this->$prop;
1390 }
1391 }
1392 }
1393
1394 //an empty value will cause date to be set to 1901-01-01, a null
1395 //will result in 0000-00-00. We want a database NULL value here.
1396 if (!$this->_birthdate) {
1397 $values['ddn_adh'] = new Expression('NULL');
1398 }
1399 if (!$this->_due_date) {
1400 $values['date_echeance'] = new Expression('NULL');
1401 }
1402
1403 if ($this->_title instanceof Title) {
1404 $values['titre_adh'] = $this->_title->id;
1405 } else {
1406 $values['titre_adh'] = new Expression('NULL');
1407 }
1408
1409 if (!$this->_parent) {
1410 $values['parent_id'] = new Expression('NULL');
1411 }
1412
1413 //fields that cannot be null
1414 $notnull = [
1415 '_surname' => 'prenom_adh',
1416 '_nickname' => 'pseudo_adh',
1417 '_address' => 'adresse_adh',
1418 '_zipcode' => 'cp_adh',
1419 '_town' => 'ville_adh'
1420 ];
1421 foreach ($notnull as $prop => $field) {
1422 if ($this->$prop === null) {
1423 $values[$field] = '';
1424 }
1425 }
1426
1427 $success = false;
1428 if (!isset($this->_id) || $this->_id == '') {
1429 //we're inserting a new member
1430 unset($values[self::PK]);
1431 //set modification date
1432 $this->_modification_date = date('Y-m-d');
1433 $values['date_modif_adh'] = $this->_modification_date;
1434
1435 $insert = $this->zdb->insert(self::TABLE);
1436 $insert->values($values);
1437 $add = $this->zdb->execute($insert);
1438 if ($add->count() > 0) {
1439 if ($this->zdb->isPostgres()) {
1440 $this->_id = $this->zdb->driver->getLastGeneratedValue(
1441 PREFIX_DB . 'adherents_id_seq'
1442 );
1443 } else {
1444 $this->_id = $this->zdb->driver->getLastGeneratedValue();
1445 }
1446 $this->_picture = new Picture($this->_id);
1447 // logging
1448 if ($this->_self_adh) {
1449 $hist->add(
1450 _T("Self_subscription as a member: ") .
1451 $this->getNameWithCase($this->_name, $this->_surname),
1452 $this->sname
1453 );
1454 } else {
1455 $hist->add(
1456 _T("Member card added"),
1457 $this->sname
1458 );
1459 }
1460 $success = true;
1461
1462 $event = 'member.add';
1463 } else {
1464 $hist->add(_T("Fail to add new member."));
1465 throw new \Exception(
1466 'An error occurred inserting new member!'
1467 );
1468 }
1469 } else {
1470 //we're editing an existing member
1471 if (!$this->isDueFree()) {
1472 // deadline
1473 $due_date = Contribution::getDueDate($this->zdb, $this->_id);
1474 if ($due_date) {
1475 $values['date_echeance'] = $due_date;
1476 }
1477 }
1478
1479 if (!$this->_password) {
1480 unset($values['mdp_adh']);
1481 }
1482
1483 $update = $this->zdb->update(self::TABLE);
1484 $update->set($values);
1485 $update->where(
1486 self::PK . '=' . $this->_id
1487 );
1488
1489 $edit = $this->zdb->execute($update);
1490
1491 //edit == 0 does not mean there were an error, but that there
1492 //were nothing to change
1493 if ($edit->count() > 0) {
1494 $this->updateModificationDate();
1495 $hist->add(
1496 _T("Member card updated"),
1497 $this->sname
1498 );
1499 }
1500 $success = true;
1501
1502 $event = 'member.edit';
1503 }
1504
1505 //dynamic fields
1506 if ($success) {
1507 $success = $this->dynamicsStore();
1508 }
1509
1510 //send event at the end of process, once all has been stored
1511 if ($event !== null) {
1512 $emitter->emit($event, $this);
1513 }
1514 return $success;
1515 } catch (Throwable $e) {
1516 Analog::log(
1517 'Something went wrong :\'( | ' . $e->getMessage() . "\n" .
1518 $e->getTraceAsString(),
1519 Analog::ERROR
1520 );
1521 return false;
1522 }
1523 }
1524
1525 /**
1526 * Update member modification date
1527 *
1528 * @return void
1529 */
1530 private function updateModificationDate()
1531 {
1532 try {
1533 $modif_date = date('Y-m-d');
1534 $update = $this->zdb->update(self::TABLE);
1535 $update->set(
1536 array('date_modif_adh' => $modif_date)
1537 )->where(self::PK . '=' . $this->_id);
1538
1539 $edit = $this->zdb->execute($update);
1540 $this->_modification_date = $modif_date;
1541 } catch (Throwable $e) {
1542 Analog::log(
1543 'Something went wrong updating modif date :\'( | ' .
1544 $e->getMessage() . "\n" . $e->getTraceAsString(),
1545 Analog::ERROR
1546 );
1547 }
1548 }
1549
1550 /**
1551 * Global getter method
1552 *
1553 * @param string $name name of the property we want to retrive
1554 *
1555 * @return false|object the called property
1556 */
1557 public function __get($name)
1558 {
1559 $forbidden = array(
1560 'admin', 'staff', 'due_free', 'appears_in_list', 'active',
1561 'row_classes'
1562 );
1563
1564 $virtuals = array(
1565 'sadmin', 'sstaff', 'sdue_free', 'sappears_in_list', 'sactive',
1566 'stitle', 'sstatus', 'sfullname', 'sname', 'rowclass', 'saddress',
1567 'rbirthdate', 'sgender', 'contribstatus'
1568 );
1569
1570 if (in_array($name, $forbidden)) {
1571 switch ($name) {
1572 case 'admin':
1573 return $this->isAdmin();
1574 break;
1575 case 'staff':
1576 return $this->isStaff();
1577 break;
1578 case 'due_free':
1579 return $this->isDueFree();
1580 break;
1581 case 'appears_in_list':
1582 return $this->appearsInMembersList();
1583 break;
1584 case 'active':
1585 return $this->isActive();
1586 break;
1587 default:
1588 throw new \RuntimeException("Call to __get for '$name' is forbidden!");
1589 }
1590 } else {
1591 if (in_array($name, $virtuals)) {
1592 if (substr($name, 0, 1) !== '_') {
1593 $real = '_' . substr($name, 1);
1594 } else {
1595 $real = $name;
1596 }
1597 switch ($name) {
1598 case 'sadmin':
1599 case 'sdue_free':
1600 case 'sappears_in_list':
1601 case 'sstaff':
1602 return (($this->$real) ? _T("Yes") : _T("No"));
1603 break;
1604 case 'sactive':
1605 return (($this->$real) ? _T("Active") : _T("Inactive"));
1606 break;
1607 case 'stitle':
1608 if (isset($this->_title)) {
1609 return $this->_title->tshort;
1610 } else {
1611 return null;
1612 }
1613 break;
1614 case 'sstatus':
1615 $status = new Status($this->zdb);
1616 return $status->getLabel($this->_status);
1617 break;
1618 case 'sfullname':
1619 return $this->getNameWithCase(
1620 $this->_name,
1621 $this->_surname,
1622 (isset($this->_title) ? $this->title : false)
1623 );
1624 break;
1625 case 'saddress':
1626 $address = $this->_address;
1627 if ($this->_address_continuation !== '' && $this->_address_continuation !== null) {
1628 $address .= "\n" . $this->_address_continuation;
1629 }
1630 return $address;
1631 break;
1632 case 'sname':
1633 return $this->getNameWithCase($this->_name, $this->_surname);
1634 break;
1635 case 'rbirthdate':
1636 return $this->_birthdate;
1637 break;
1638 case 'sgender':
1639 switch ($this->gender) {
1640 case self::MAN:
1641 return _T('Man');
1642 case self::WOMAN:
1643 return _T('Woman');
1644 default:
1645 return __('Unspecified');
1646 }
1647 break;
1648 case 'contribstatus':
1649 return $this->getDues();
1650 break;
1651 }
1652 } else {
1653 if (substr($name, 0, 1) !== '_') {
1654 $rname = '_' . $name;
1655 } else {
1656 $rname = $name;
1657 }
1658
1659 switch ($name) {
1660 case 'id':
1661 case 'id_statut':
1662 if ($this->$rname !== null) {
1663 return (int)$this->$rname;
1664 } else {
1665 return null;
1666 }
1667 break;
1668 case 'birthdate':
1669 case 'creation_date':
1670 case 'modification_date':
1671 case 'due_date':
1672 if ($this->$rname != '') {
1673 try {
1674 $d = new \DateTime($this->$rname);
1675 return $d->format(__("Y-m-d"));
1676 } catch (Throwable $e) {
1677 //oops, we've got a bad date :/
1678 Analog::log(
1679 'Bad date (' . $this->$rname . ') | ' .
1680 $e->getMessage(),
1681 Analog::INFO
1682 );
1683 return $this->$rname;
1684 }
1685 }
1686 break;
1687 default:
1688 if (!property_exists($this, $rname)) {
1689 Analog::log(
1690 "Unknown property '$rname'",
1691 Analog::WARNING
1692 );
1693 return null;
1694 } else {
1695 return $this->$rname;
1696 }
1697 break;
1698 }
1699 }
1700 }
1701 }
1702
1703 /**
1704 * Get member email
1705 * If member does not have an email address, but is attached to
1706 * another member, we'll take information from its parent.
1707 *
1708 * @return string
1709 */
1710 public function getEmail()
1711 {
1712 $email = $this->_email;
1713 if (empty($email)) {
1714 $this->loadParent();
1715 $email = $this->parent->email;
1716 }
1717
1718 return $email;
1719 }
1720
1721 /**
1722 * Get member address.
1723 * If member does not have an address, but is attached to another member, we'll take information from its parent.
1724 *
1725 * @return string
1726 */
1727 public function getAddress()
1728 {
1729 $address = $this->_address;
1730 if (empty($address) && $this->hasParent()) {
1731 $this->loadParent();
1732 $address = $this->parent->address;
1733 }
1734
1735 return $address;
1736 }
1737
1738 /**
1739 * Get member address continuation.
1740 * If member does not have an address, but is attached to another member, we'll take information from its parent.
1741 *
1742 * @return string
1743 */
1744 public function getAddressContinuation()
1745 {
1746 $address = $this->_address;
1747 $address_continuation = $this->_address_continuation;
1748 if (empty($address) && $this->hasParent()) {
1749 $this->loadParent();
1750 $address_continuation = $this->parent->address_continuation;
1751 }
1752
1753 return $address_continuation;
1754 }
1755
1756 /**
1757 * Get member zipcode.
1758 * If member does not have an address, but is attached to another member, we'll take information from its parent.
1759 *
1760 * @return string
1761 */
1762 public function getZipcode()
1763 {
1764 $address = $this->_address;
1765 $zip = $this->_zipcode;
1766 if (empty($address) && $this->hasParent()) {
1767 $this->loadParent();
1768 $zip = $this->parent->zipcode;
1769 }
1770
1771 return $zip;
1772 }
1773
1774 /**
1775 * Get member town.
1776 * If member does not have an address, but is attached to another member, we'll take information from its parent.
1777 *
1778 * @return string
1779 */
1780 public function getTown()
1781 {
1782 $address = $this->_address;
1783 $town = $this->_town;
1784 if (empty($address) && $this->hasParent()) {
1785 $this->loadParent();
1786 $town = $this->parent->town;
1787 }
1788
1789 return $town;
1790 }
1791
1792 /**
1793 * Get member country.
1794 * If member does not have an address, but is attached to another member, we'll take information from its parent.
1795 *
1796 * @return string
1797 */
1798 public function getCountry()
1799 {
1800 $address = $this->_address;
1801 $country = $this->_country;
1802 if (empty($address) && $this->hasParent()) {
1803 $this->loadParent();
1804 $country = $this->parent->country;
1805 }
1806
1807 return $country;
1808 }
1809
1810 /**
1811 * Get member age
1812 *
1813 * @return string
1814 */
1815 public function getAge()
1816 {
1817 if ($this->_birthdate == null) {
1818 return '';
1819 }
1820
1821 $d = \DateTime::createFromFormat('Y-m-d', $this->_birthdate);
1822 if ($d === false) {
1823 Analog::log(
1824 'Invalid birthdate: ' . $this->_birthdate,
1825 Analog::ERROR
1826 );
1827 return;
1828 }
1829
1830 return str_replace(
1831 '%age',
1832 $d->diff(new \DateTime())->y,
1833 _T(' (%age years old)')
1834 );
1835 }
1836
1837 /**
1838 * Get parent inherited fields
1839 *
1840 * @return array
1841 */
1842 public function getParentFields()
1843 {
1844 return $this->parent_fields;
1845 }
1846
1847 /**
1848 * Handle files (photo and dynamics files
1849 *
1850 * @param array $files Files sent
1851 *
1852 * @return array|true
1853 */
1854 public function handleFiles($files)
1855 {
1856 $this->errors = [];
1857 // picture upload
1858 if (isset($files['photo'])) {
1859 if ($files['photo']['error'] === UPLOAD_ERR_OK) {
1860 if ($files['photo']['tmp_name'] != '') {
1861 if (is_uploaded_file($files['photo']['tmp_name'])) {
1862 $res = $this->picture->store($files['photo']);
1863 if ($res < 0) {
1864 $this->errors[]
1865 = $this->picture->getErrorMessage($res);
1866 }
1867 }
1868 }
1869 } elseif ($files['photo']['error'] !== UPLOAD_ERR_NO_FILE) {
1870 Analog::log(
1871 $this->picture->getPhpErrorMessage($files['photo']['error']),
1872 Analog::WARNING
1873 );
1874 $this->errors[] = $this->picture->getPhpErrorMessage(
1875 $files['photo']['error']
1876 );
1877 }
1878 }
1879 $this->dynamicsFiles($_FILES);
1880
1881 if (count($this->errors) > 0) {
1882 Analog::log(
1883 'Some errors has been throwed attempting to edit/store a member files' . "\n" .
1884 print_r($this->errors, true),
1885 Analog::ERROR
1886 );
1887 return $this->errors;
1888 } else {
1889 return true;
1890 }
1891 }
1892
1893 /**
1894 * Set member as duplicate
1895 *
1896 * @return void
1897 */
1898 public function setDuplicate()
1899 {
1900 //mark as duplicated
1901 $this->_duplicate = true;
1902 $infos = $this->_others_infos_admin;
1903 $this->_others_infos_admin = str_replace(
1904 ['%name', '%id'],
1905 [$this->sname, $this->_id],
1906 _T('Duplicated from %name (%id)')
1907 );
1908 if (!empty($infos)) {
1909 $this->_others_infos_admin .= "\n" . $infos;
1910 }
1911 //drop id_adh
1912 $this->_id = null;
1913 //drop email, must be unique
1914 $this->_email = null;
1915 //drop creation date
1916 $this->_creation_date = date("Y-m-d");
1917 //drop login
1918 $this->_login = null;
1919 //reset picture
1920 $this->_picture = new Picture();
1921 //remove birthdate
1922 $this->_birthdate = null;
1923 //remove surname
1924 $this->_surname = null;
1925 //not admin
1926 $this->_admin = false;
1927 //not due free
1928 $this->_due_free = false;
1929 }
1930
1931 /**
1932 * Get current errors
1933 *
1934 * @return array
1935 */
1936 public function getErrors()
1937 {
1938 return $this->errors;
1939 }
1940
1941 /**
1942 * Get user groups
1943 *
1944 * @return array
1945 */
1946 public function getGroups()
1947 {
1948 return $this->_groups;
1949 }
1950
1951 /**
1952 * Get user managed groups
1953 *
1954 * @return array
1955 */
1956 public function getManagedGroups()
1957 {
1958 return $this->_managed_groups;
1959 }
1960
1961 /**
1962 * Can current logged in user edit member
1963 *
1964 * @param Login $login Login instance
1965 *
1966 * @return boolean
1967 */
1968 public function canEdit($login)
1969 {
1970 if ($this->id && $login->id == $this->id || $login->isAdmin() || $login->isStaff()) {
1971 return true;
1972 }
1973
1974 //check if requested member is part of managed groups
1975 if ($login->isGroupManager()) {
1976 foreach ($this->getGroups() as $g) {
1977 if ($login->isGroupManager($g->getId())) {
1978 return true;
1979 }
1980 }
1981 }
1982
1983 return false;
1984 }
1985
1986 /**
1987 * Are we currently duplicated a member?
1988 *
1989 * @return boolean
1990 */
1991 public function isDuplicate()
1992 {
1993 return $this->_duplicate;
1994 }
1995
1996 /**
1997 * Flag creation mail sending
1998 *
1999 * @param boolean $send True (default) to send creation email
2000 *
2001 * @return Adherent
2002 */
2003 public function setSendmail($send = true)
2004 {
2005 $this->sendmail = $send;
2006 return $this;
2007 }
2008
2009 /**
2010 * Should we send administrative emails to member?
2011 *
2012 * @return boolean
2013 */
2014 public function sendEMail()
2015 {
2016 return $this->sendmail;
2017 }
2018 }