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