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