3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Member class for galette
10 * Copyright © 2009-2014 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2009-2014 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7dev - 2009-06-02
37 namespace Galette\Entity
;
41 use Laminas\Db\Sql\Expression
;
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
;
52 * Member class for 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
67 public const TABLE
= 'adherents';
68 public const PK
= 'id_adh';
72 public const WOMAN
= 2;
74 public const AFTER_ADD_DEFAULT
= 0;
75 public const AFTER_ADD_TRANS
= 1;
76 public const AFTER_ADD_NEW
= 2;
77 public const AFTER_ADD_SHOW
= 3;
78 public const AFTER_ADD_LIST
= 4;
79 public const AFTER_ADD_HOME
= 5;
84 private $_company_name;
89 private $_birth_place;
97 private $_address_continuation; /** TODO: remove */
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;
117 private $_creation_date;
118 private $_modification_date;
120 private $_others_infos;
121 private $_others_infos_admin;
124 private $_days_remaining;
126 private $_managed_groups;
129 private $_duplicate = false;
131 private $_row_classes;
133 private $_self_adh = false;
134 private $_deps = array(
144 private $preferences;
148 private $parent_fields = [
156 private $errors = [];
158 private $sendmail = false;
161 * Default constructor
163 * @param Db $zdb Database instance
164 * @param mixed $args Either a ResultSet row, its id or its
165 * login or its email for to load s specific
166 * member, or null to just instanciate object
167 * @param false|array $deps Dependencies configuration, see Adherent::$_deps
169 public function __construct(Db
$zdb, $args = null, $deps = null)
175 if ($deps !== null) {
176 if (is_array($deps)) {
177 $this->_deps
= array_merge(
181 } elseif ($deps === false) {
183 $this->_deps
= array_fill_keys(
184 array_keys($this->_deps
),
189 '$deps shoud be an array, ' . gettype($deps) . ' given!',
195 if ($args == null ||
is_int($args)) {
196 if (is_int($args) && $args > 0) {
199 $this->_active
= true;
200 $this->_language
= $i18n->getID();
201 $this->_creation_date
= date("Y-m-d");
202 $this->_status
= $this->getDefaultStatus();
203 $this->_title
= null;
204 $this->_gender
= self
::NC
;
205 $gp = new Password($this->zdb
);
206 $this->_password
= $gp->makeRandomPassword();
207 $this->_picture
= new Picture();
208 $this->_admin
= false;
209 $this->_staff
= false;
210 $this->_due_free
= false;
211 $this->_appears_in_list
= false;
212 $this->_parent
= null;
214 if ($this->_deps
['dynamics'] === true) {
215 $this->loadDynamicFields();
218 } elseif (is_object($args)) {
219 $this->loadFromRS($args);
220 } elseif (is_string($args)) {
221 $this->loadFromLoginOrMail($args);
226 * Loads a member from its id
228 * @param int $id the identifiant for the member to load
230 * @return bool true if query succeed, false otherwise
232 public function load($id)
235 $select = $this->zdb
->select(self
::TABLE
, 'a');
238 array('b' => PREFIX_DB
. Status
::TABLE
),
239 'a.' . Status
::PK
. '=b.' . Status
::PK
,
240 array('priorite_statut')
241 )->where(array(self
::PK
=> $id));
243 $results = $this->zdb
->execute($select);
245 if ($results->count() === 0) {
249 $this->loadFromRS($results->current());
251 } catch (Throwable
$e) {
253 'Cannot load member form id `' . $id . '` | ' . $e->getMessage(),
261 * Loads a member from its login
263 * @param string $login login for the member to load
265 * @return bool true if query succeed, false otherwise
267 public function loadFromLoginOrMail($login)
270 $select = $this->zdb
->select(self
::TABLE
);
271 if (GaletteMail
::isValidEmail($login)) {
272 //we got a valid email address, use it
273 $select->where(array('email_adh' => $login));
275 ///we did not get an email address, consider using login
276 $select->where(array('login_adh' => $login));
279 $results = $this->zdb
->execute($select);
280 $result = $results->current();
282 $this->loadFromRS($result);
284 } catch (Throwable
$e) {
286 'Cannot load member form login `' . $login . '` | ' .
295 * Populate object from a resultset row
297 * @param ResultSet $r the resultset row
301 private function loadFromRS($r)
303 $this->_self_adh
= false;
304 $this->_id
= $r->id_adh
;
306 if ($r->titre_adh
!== null) {
307 $this->_title
= new Title((int)$r->titre_adh
);
309 $this->_company_name
= $r->societe_adh
;
310 $this->_name
= $r->nom_adh
;
311 $this->_surname
= $r->prenom_adh
;
312 $this->_nickname
= $r->pseudo_adh
;
313 if ($r->ddn_adh
!= '1901-01-01') {
314 $this->_birthdate
= $r->ddn_adh
;
316 $this->_birth_place
= $r->lieu_naissance
;
317 $this->_gender
= (int)$r->sexe_adh
;
318 $this->_job
= $r->prof_adh
;
319 $this->_language
= $r->pref_lang
;
320 $this->_active
= ($r->activite_adh
== 1) ?
true : false;
321 $this->_status
= (int)$r->id_statut
;
322 //Contact information
323 $this->_address
= $r->adresse_adh
;
324 /** TODO: remove and merge with address */
325 $this->_address_continuation
= $r->adresse2_adh
;
326 $this->_zipcode
= $r->cp_adh
;
327 $this->_town
= $r->ville_adh
;
328 $this->_country
= $r->pays_adh
;
329 $this->_phone
= $r->tel_adh
;
330 $this->_gsm
= $r->gsm_adh
;
331 $this->_email
= $r->email_adh
;
332 $this->_website
= $r->url_adh
;
334 $this->_msn
= $r->msn_adh
;
336 $this->_icq
= $r->icq_adh
;
338 $this->_jabber
= $r->jabber_adh
;
340 $this->_gnupgid
= $r->gpgid
;
342 $this->_fingerprint
= $r->fingerprint
;
343 //Galette relative information
344 $this->_appears_in_list
= ($r->bool_display_info
== 1) ?
true : false;
345 $this->_admin
= ($r->bool_admin_adh
== 1) ?
true : false;
347 isset($r->priorite_statut
)
348 && $r->priorite_statut
< Members
::NON_STAFF_MEMBERS
350 $this->_staff
= true;
352 $this->_due_free
= ($r->bool_exempt_adh
== 1) ?
true : false;
353 $this->_login
= $r->login_adh
;
354 $this->_password
= $r->mdp_adh
;
355 $this->_creation_date
= $r->date_crea_adh
;
356 if ($r->date_modif_adh
!= '1901-01-01') {
357 $this->_modification_date
= $r->date_modif_adh
;
359 $this->_modification_date
= $this->_creation_date
;
361 $this->_due_date
= $r->date_echeance
;
362 $this->_others_infos
= $r->info_public_adh
;
363 $this->_others_infos_admin
= $r->info_adh
;
365 if ($r->parent_id
!== null) {
366 $this->_parent
= (int)$r->parent_id
;
367 if ($this->_deps
['parent'] === true) {
368 $this->loadParent($r->parent_id
);
372 if ($this->_deps
['children'] === true) {
373 $this->loadChildren();
376 if ($this->_deps
['picture'] === true) {
377 $this->_picture
= new Picture($this->_id
);
380 if ($this->_deps
['groups'] === true) {
384 if ($this->_deps
['dues'] === true) {
388 if ($this->_deps
['dynamics'] === true) {
389 $this->loadDynamicFields();
398 private function loadParent()
400 if (!$this->_parent
instanceof Adherent
) {
401 $deps = array_fill_keys(array_keys($this->_deps
), false);
402 $this->_parent
= new Adherent($this->zdb
, (int)$this->_parent
, $deps);
407 * Load member children
411 private function loadChildren()
413 $this->_children
= array();
416 $select = $this->zdb
->select(self
::TABLE
);
420 'parent_id = ' . $this->_id
423 $results = $this->zdb
->execute($select);
425 if ($results->count() > 0) {
426 foreach ($results as $row) {
427 $deps = $this->_deps
;
428 $deps['children'] = false;
429 $deps['parent'] = false;
430 $this->_children
[] = new Adherent($this->zdb
, (int)$row->$id, $deps);
433 } catch (Throwable
$e) {
435 'Cannot load children for member #' . $this->_id
. ' | ' .
448 public function loadGroups()
450 $this->_groups
= Groups
::loadGroups($this->_id
);
451 $this->_managed_groups
= Groups
::loadManagedGroups($this->_id
);
455 * Retrieve status from preferences
457 * @return pref_statut
460 private function getDefaultStatus()
463 if ($preferences->pref_statut
!= '') {
464 return $preferences->pref_statut
;
467 'Unable to get pref_statut; is it defined in preferences?',
470 return Status
::DEFAULT_STATUS
;
475 * Check for dues status
479 private function checkDues()
481 //how many days since our beloved member has been created
482 $date_now = new \
DateTime();
483 $this->_oldness
= $date_now->diff(
484 new \
DateTime($this->_creation_date
)
487 if ($this->isDueFree()) {
488 //no fee required, we don't care about dates
489 $this->_row_classes
.= ' cotis-exempt';
491 //ok, fee is required. Let's check the dates
492 if ($this->_due_date
== '') {
493 $this->_row_classes
.= ' cotis-never';
495 $date_end = new \
DateTime($this->_due_date
);
496 $date_diff = $date_now->diff($date_end);
497 $this->_days_remaining
= ($date_diff->invert
== 1)
498 ?
$date_diff->days
* -1
501 if ($this->_days_remaining
== 0) {
502 $this->_row_classes
.= ' cotis-lastday';
503 } elseif ($this->_days_remaining
< 0) {
504 //check if member is still active
505 $this->_row_classes
.= $this->isActive() ?
' cotis-late' : ' cotis-old';
506 } elseif ($this->_days_remaining
< 30) {
507 $this->_row_classes
.= ' cotis-soon';
509 $this->_row_classes
.= ' cotis-ok';
520 public function isAdmin()
522 return $this->_admin
;
526 * Is user member of staff?
530 public function isStaff()
532 return $this->_staff
;
536 * Is member freed of dues?
540 public function isDueFree()
542 return $this->_due_free
;
546 * Is member in specified group?
548 * @param string $group_name Group name
552 public function isGroupMember($group_name)
554 if (is_array($this->_groups
)) {
555 foreach ($this->_groups
as $g) {
556 if ($g->getName() == $group_name) {
563 'Calling ' . __METHOD__
. ' without groups loaded!',
571 * Is member manager of specified group?
573 * @param string $group_name Group name
577 public function isGroupManager($group_name)
579 if (is_array($this->_managed_groups
)) {
580 foreach ($this->_managed_groups
as $mg) {
581 if ($mg->getName() == $group_name) {
588 'Calling ' . __METHOD__
. ' without groups loaded!',
596 * Does current member represents a company?
600 public function isCompany()
602 return trim($this->_company_name
) != '';
606 * Is current member a man?
610 public function isMan()
612 return (int)$this->_gender
=== self
::MAN
;
616 * Is current member a woman?
620 public function isWoman()
622 return (int)$this->_gender
=== self
::WOMAN
;
627 * Can member appears in public members list?
631 public function appearsInMembersList()
633 return $this->_appears_in_list
;
641 public function isActive()
643 return $this->_active
;
647 * Does member have uploaded a picture?
651 public function hasPicture()
653 return $this->_picture
->hasPicture();
657 * Does member have a parent?
661 public function hasParent()
663 return !empty($this->_parent
);
667 * Does member have children?
671 public function hasChildren()
673 if ($this->_children
=== null) {
676 'Children has not been loaded!',
682 return count($this->_children
) > 0;
687 * Get row class related to current fee status
689 * @param boolean $public we want the class for public pages
691 * @return string the class to apply
693 public function getRowClass($public = false)
695 $strclass = ($this->isActive()) ?
'active' : 'inactive';
696 if ($public === false) {
697 $strclass .= $this->_row_classes
;
703 * Get current member due status
705 * @return string i18n string representing state of due
707 public function getDues()
710 if ($this->isDueFree()) {
711 $ret = _T("Freed of dues");
712 } elseif ($this->_due_date
== '') {
713 $patterns = array('/%days/', '/%date/');
714 $cdate = new \
DateTime($this->_creation_date
);
717 $cdate->format(__("Y-m-d"))
719 if ($this->_active
) {
723 _T("Never contributed: Registered %days days ago (since %date)")
726 $ret = _T("Never contributed");
728 } elseif ($this->_days_remaining
== 0) {
729 $ret = _T("Last day!");
730 } elseif ($this->_days_remaining
< 0) {
731 $patterns = array('/%days/', '/%date/');
732 $ddate = new \
DateTime($this->_due_date
);
734 $this->_days_remaining
* -1,
735 $ddate->format(__("Y-m-d"))
737 if ($this->_active
) {
741 _T("Late of %days days (since %date)")
744 $ret = _T("No longer member");
747 $patterns = array('/%days/', '/%date/');
748 $ddate = new \
DateTime($this->_due_date
);
750 $this->_days_remaining
,
751 $ddate->format(__("Y-m-d"))
756 _T("%days days remaining (ending on %date)")
763 * Retrieve Full name and surname for the specified member id
765 * @param Db $zdb Database instance
766 * @param integer $id Member id
767 * @param boolean $wid Add member id
768 * @param boolean $wnick Add member nickname
770 * @return string formatted Name and Surname
772 public static function getSName($zdb, $id, $wid = false, $wnick = false)
775 $select = $zdb->select(self
::TABLE
);
776 $select->where(self
::PK
. ' = ' . $id);
778 $results = $zdb->execute($select);
779 $row = $results->current();
780 return self
::getNameWithCase(
784 ($wid === true ?
$row->id_adh
: false),
785 ($wnick === true ?
$row->pseudo_adh
: false)
787 } catch (Throwable
$e) {
789 'Cannot get formatted name for member form id `' . $id . '` | ' .
798 * Get member name with correct case
800 * @param string $name Member name
801 * @param string $surname Mmeber surname
802 * @param false|Title $title Member title to show or false
803 * @param false|integer $id Member id to display or false
804 * @param false|string $nick Member nickname to display or false
808 public static function getNameWithCase($name, $surname, $title = false, $id = false, $nick = false)
812 if ($title !== false && $title instanceof Title
) {
813 $str .= $title->tshort
. ' ';
816 $str .= mb_strtoupper($name, 'UTF-8') . ' ' .
817 ucwords(mb_strtolower($surname, 'UTF-8'), " \t\r\n\f\v-_|");
819 if ($id !== false ||
$nick !== false) {
822 if ($nick !== false) {
826 if ($nick !== false && !empty($nick)) {
831 if ($id !== false ||
$nick !== false) {
838 * Change password for a given user
840 * @param Db $zdb Database instance
841 * @param string $id_adh Member identifier
842 * @param string $pass New password
846 public static function updatePassword(Db
$zdb, $id_adh, $pass)
849 $cpass = password_hash($pass, PASSWORD_BCRYPT
);
851 $update = $zdb->update(self
::TABLE
);
853 array('mdp_adh' => $cpass)
854 )->where(self
::PK
. ' = ' . $id_adh);
855 $zdb->execute($update);
857 'Password for `' . $id_adh . '` has been updated.',
861 } catch (Throwable
$e) {
863 'An error occurred while updating password for `' . $id_adh .
864 '` | ' . $e->getMessage(),
874 * @param string $field Field name
878 private function getFieldLabel($field)
880 $label = $this->fields
[$field]['label'];
881 //remove trailing ':' and then nbsp (for french at least)
882 $label = trim(trim($label, ':'), ' ');
887 * Retrieve fields from database
889 * @param Db $zdb Database instance
893 public static function getDbFields(Db
$zdb)
895 $columns = $zdb->getColumns(self
::TABLE
);
897 foreach ($columns as $col) {
898 $fields[] = $col->getName();
904 * Mark as self membership
908 public function setSelfMembership()
910 $this->_self_adh
= true;
914 * Is member up to date?
918 public function isUp2Date()
920 if ($this->_deps
['dues']) {
921 if ($this->isDueFree()) {
922 //member is due free, he's up to date.
925 //let's check from end date, if present
926 if ($this->_due_date
== null) {
929 $ech = new \
DateTime($this->_due_date
);
930 $now = new \
DateTime();
931 $now->setTime(0, 0, 0);
936 throw new \
RuntimeException(
937 'Cannot check if member is up to date, dues deps is disabled!'
945 * @param Preferences $preferences Preferences instance
946 * @param array $fields Members fields configuration
947 * @param History $history History instance
951 public function setDependencies(
952 Preferences
$preferences,
956 $this->preferences
= $preferences;
957 $this->fields
= $fields;
958 $this->history
= $history;
962 * Check posted values validity
964 * @param array $values All values to check, basically the $_POST array
965 * after sending the form
966 * @param array $required Array of required fields
967 * @param array $disabled Array of disabled fields
971 public function check($values, $required, $disabled)
973 $this->errors
= array();
975 $fields = self
::getDbFields($this->zdb
);
977 //reset company name if needeed
978 if (!isset($values['is_company'])) {
979 unset($values['is_company']);
980 $values['societe_adh'] = '';
983 //no parent if checkbox was unchecked
985 !isset($values['attach'])
987 && isset($values['parent_id'])
989 unset($values['parent_id']);
992 if (isset($values['duplicate'])) {
993 //if we're duplicating, keep a trace (if an error occurs)
994 $this->_duplicate
= true;
997 foreach ($fields as $key) {
998 //first of all, let's sanitize values
999 $key = strtolower($key);
1000 $prop = '_' . $this->fields
[$key]['propname'];
1002 if (isset($values[$key])) {
1003 $value = $values[$key];
1004 if ($value !== true && $value !== false) {
1005 $value = trim($value);
1007 } elseif ($this->_id
== '' ||
$this->_id
== null) {
1009 case 'bool_admin_adh':
1010 case 'bool_exempt_adh':
1011 case 'bool_display_info':
1014 case 'activite_adh':
1015 //values that are setted at object instanciation
1018 case 'date_crea_adh':
1024 //values that are setted at object instanciation
1025 $value = $this->$prop;
1031 //keep stored value on update
1032 if ($prop != '_password' ||
isset($values['mdp_adh']) && isset($values['mdp_adh2'])) {
1033 $value = $this->$prop;
1039 // if the field is enabled, check it
1040 if (!isset($disabled[$key])) {
1041 // fill up the adherent structure
1042 if ($value !== null && $value !== true && $value !== false && !is_object($value)) {
1043 $value = stripslashes($value);
1045 $this->$prop = $value;
1047 // now, check validity
1048 if ($value !== null && $value != '') {
1049 $this->validate($key, $value, $values);
1051 ($key == 'login_adh' && !isset($required['login_adh']))
1052 ||
($key == 'mdp_adh' && !isset($required['mdp_adh']))
1053 && !isset($this->_id
)
1055 $p = new Password($this->zdb
);
1056 $this->$prop = $p->makeRandomPassword(15);
1061 // missing required fields?
1062 foreach ($required as $key => $val) {
1063 $prop = '_' . $this->fields
[$key]['propname'];
1065 if (!isset($disabled[$key])) {
1066 $mandatory_missing = false;
1067 if (!isset($this->$prop) ||
$this->$prop == '') {
1068 $mandatory_missing = true;
1069 } elseif ($key === 'titre_adh' && $this->$prop == '-1') {
1070 $mandatory_missing = true;
1073 if ($mandatory_missing === true) {
1074 $this->errors
[] = str_replace(
1076 '<a href="#' . $key . '">' . $this->getFieldLabel($key) . '</a>',
1077 _T("- Mandatory field %field empty.")
1083 //attach to/detach from parent
1084 if (isset($values['detach_parent'])) {
1085 $this->_parent
= null;
1088 $this->dynamicsCheck($values, $required, $disabled);
1090 if (count($this->errors
) > 0) {
1092 'Some errors has been throwed attempting to edit/store a member' . "\n" .
1093 print_r($this->errors
, true),
1096 return $this->errors
;
1101 'Member checked successfully.',
1109 * Validate data for given key
1110 * Set valid data in current object, also resets errors list
1112 * @param string $field Field name
1113 * @param mixed $value Value we want to set
1114 * @param array $values All values, for some references
1118 public function validate($field, $value, $values)
1120 global $preferences;
1122 $prop = '_' . $this->fields
[$field]['propname'];
1124 if ($value === null ||
(is_string($value) && trim($value) == '')) {
1125 //empty values are OK
1126 $this->$prop = $value;
1132 case 'date_crea_adh':
1133 case 'date_modif_adh_':
1135 case 'date_echeance':
1137 $d = \DateTime
::createFromFormat(__("Y-m-d"), $value);
1139 //try with non localized date
1140 $d = \DateTime
::createFromFormat("Y-m-d", $value);
1142 throw new \
Exception('Incorrect format');
1146 if ($field === 'ddn_adh') {
1147 $now = new \
DateTime();
1148 $now->setTime(0, 0, 0);
1149 $d->setTime(0, 0, 0);
1151 $diff = $now->diff($d);
1152 $days = (int)$diff->format('%R%a');
1154 $this->errors
[] = _T('- Birthdate must be set in the past!');
1157 $years = (int)$diff->format('%R%Y');
1158 if ($years <= -200) {
1159 $this->errors
[] = str_replace(
1162 _T('- Members must be less than 200 years old (currently %years)!')
1166 $this->$prop = $d->format('Y-m-d');
1167 } catch (Throwable
$e) {
1169 'Wrong date format. field: ' . $field .
1170 ', value: ' . $value . ', expected fmt: ' .
1171 __("Y-m-d") . ' | ' . $e->getMessage(),
1174 $this->errors
[] = str_replace(
1181 $this->getFieldLabel($field)
1183 _T("- Wrong date format (%date_format) for %field!")
1188 if ($value !== null && $value !== '') {
1189 if ($value == '-1') {
1190 $this->$prop = null;
1191 } elseif (!$value instanceof Title
) {
1192 $this->$prop = new Title((int)$value);
1195 $this->$prop = null;
1200 if (!GaletteMail
::isValidEmail($value)) {
1201 $this->errors
[] = _T("- Non-valid E-Mail address!") .
1202 ' (' . $this->getFieldLabel($field) . ')';
1204 if ($field == 'email_adh') {
1206 $select = $this->zdb
->select(self
::TABLE
);
1209 )->where(array('email_adh' => $value));
1210 if ($this->_id
!= '' && $this->_id
!= null) {
1212 self
::PK
. ' != ' . $this->_id
1216 $results = $this->zdb
->execute($select);
1217 if ($results->count() !== 0) {
1218 $this->errors
[] = _T("- This E-Mail address is already used by another member!");
1220 } catch (Throwable
$e) {
1222 'An error occurred checking member email unicity.',
1225 $this->errors
[] = _T("An error has occurred while looking if login already exists.");
1230 if ($value == 'http://') {
1232 } elseif (!isValidWebUrl($value)) {
1233 $this->errors
[] = _T("- Non-valid Website address! Maybe you've skipped the http://?");
1237 /** FIXME: add a preference for login lenght */
1238 if (strlen($value) < 2) {
1239 $this->errors
[] = str_replace(
1242 _T("- The username must be composed of at least %i characters!")
1245 //check if login does not contain the @ character
1246 if (strpos($value, '@') != false) {
1247 $this->errors
[] = _T("- The username cannot contain the @ character");
1249 //check if login is already taken
1251 $select = $this->zdb
->select(self
::TABLE
);
1254 )->where(array('login_adh' => $value));
1255 if ($this->_id
!= '' && $this->_id
!= null) {
1257 self
::PK
. ' != ' . $this->_id
1261 $results = $this->zdb
->execute($select);
1263 $results->count() !== 0
1264 ||
$value == $preferences->pref_admin_login
1266 $this->errors
[] = _T("- This username is already in use, please choose another one!");
1268 } catch (Throwable
$e) {
1270 'An error occurred checking member login unicity.',
1273 $this->errors
[] = _T("An error has occurred while looking if login already exists.");
1280 $this->_self_adh
!== true
1281 && (!isset($values['mdp_adh2'])
1282 ||
$values['mdp_adh2'] != $value)
1284 $this->errors
[] = _T("- The passwords don't match!");
1286 $this->_self_adh
=== true
1287 && !crypt($value, $values['mdp_crypt']) == $values['mdp_crypt']
1289 $this->errors
[] = _T("Password misrepeated: ");
1291 $pinfos = password_get_info($value);
1292 //check if value is already a hash
1293 if ($pinfos['algo'] == 0) {
1294 $this->$prop = password_hash(
1299 $pwcheck = new \Galette\Util\
Password($preferences);
1300 $pwcheck->setAdherent($this);
1301 if (!$pwcheck->isValid($value)) {
1302 $this->errors
= array_merge(
1304 $pwcheck->getErrors()
1312 $this->$prop = (int)$value;
1313 //check if status exists
1314 $select = $this->zdb
->select(Status
::TABLE
);
1315 $select->where(Status
::PK
. '= ' . $value);
1317 $results = $this->zdb
->execute($select);
1318 $result = $results->current();
1320 $this->errors
[] = str_replace(
1323 _T("Status #%id does not exists in database.")
1327 } catch (Throwable
$e) {
1329 'An error occurred checking status existance: ' . $e->getMessage(),
1332 $this->errors
[] = _T("An error has occurred while looking if status does exists.");
1336 if (in_array($value, [self
::NC
, self
::MAN
, self
::WOMAN
])) {
1337 $this->$prop = (int)$value;
1339 $this->errors
[] = _T("Gender %gender does not exists!");
1343 $this->$prop = ($value instanceof Adherent
) ?
(int)$value->id
: (int)$value;
1344 $this->loadParent();
1354 public function store()
1356 global $hist, $emitter;
1361 $fields = self
::getDbFields($this->zdb
);
1363 foreach ($fields as $field) {
1365 $field !== 'date_modif_adh'
1366 ||
!isset($this->_id
)
1369 $prop = '_' . $this->fields
[$field]['propname'];
1371 ($field === 'bool_admin_adh'
1372 ||
$field === 'bool_exempt_adh'
1373 ||
$field === 'bool_display_info'
1374 ||
$field === 'activite_adh')
1375 && $this->$prop === false
1377 //Handle booleans for postgres ; bugs #18899 and #19354
1378 $values[$field] = $this->zdb
->isPostgres() ?
'false' : 0;
1379 } elseif ($field === 'parent_id') {
1381 if ($this->_parent
=== null) {
1382 $values['parent_id'] = new Expression('NULL');
1383 } elseif ($this->parent
instanceof Adherent
) {
1384 $values['parent_id'] = $this->_parent
->id
;
1386 $values['parent_id'] = $this->_parent
;
1389 $values[$field] = $this->$prop;
1394 //an empty value will cause date to be set to 1901-01-01, a null
1395 //will result in 0000-00-00. We want a database NULL value here.
1396 if (!$this->_birthdate
) {
1397 $values['ddn_adh'] = new Expression('NULL');
1399 if (!$this->_due_date
) {
1400 $values['date_echeance'] = new Expression('NULL');
1403 if ($this->_title
instanceof Title
) {
1404 $values['titre_adh'] = $this->_title
->id
;
1406 $values['titre_adh'] = new Expression('NULL');
1409 if (!$this->_parent
) {
1410 $values['parent_id'] = new Expression('NULL');
1413 //fields that cannot be null
1415 '_surname' => 'prenom_adh',
1416 '_nickname' => 'pseudo_adh',
1417 '_address' => 'adresse_adh',
1418 '_zipcode' => 'cp_adh',
1419 '_town' => 'ville_adh'
1421 foreach ($notnull as $prop => $field) {
1422 if ($this->$prop === null) {
1423 $values[$field] = '';
1428 if (!isset($this->_id
) ||
$this->_id
== '') {
1429 //we're inserting a new member
1430 unset($values[self
::PK
]);
1431 //set modification date
1432 $this->_modification_date
= date('Y-m-d');
1433 $values['date_modif_adh'] = $this->_modification_date
;
1435 $insert = $this->zdb
->insert(self
::TABLE
);
1436 $insert->values($values);
1437 $add = $this->zdb
->execute($insert);
1438 if ($add->count() > 0) {
1439 if ($this->zdb
->isPostgres()) {
1440 $this->_id
= $this->zdb
->driver
->getLastGeneratedValue(
1441 PREFIX_DB
. 'adherents_id_seq'
1444 $this->_id
= $this->zdb
->driver
->getLastGeneratedValue();
1446 $this->_picture
= new Picture($this->_id
);
1448 if ($this->_self_adh
) {
1450 _T("Self_subscription as a member: ") .
1451 $this->getNameWithCase($this->_name
, $this->_surname
),
1456 _T("Member card added"),
1462 $event = 'member.add';
1464 $hist->add(_T("Fail to add new member."));
1465 throw new \
Exception(
1466 'An error occurred inserting new member!'
1470 //we're editing an existing member
1471 if (!$this->isDueFree()) {
1473 $due_date = Contribution
::getDueDate($this->zdb
, $this->_id
);
1475 $values['date_echeance'] = $due_date;
1479 if (!$this->_password
) {
1480 unset($values['mdp_adh']);
1483 $update = $this->zdb
->update(self
::TABLE
);
1484 $update->set($values);
1486 self
::PK
. '=' . $this->_id
1489 $edit = $this->zdb
->execute($update);
1491 //edit == 0 does not mean there were an error, but that there
1492 //were nothing to change
1493 if ($edit->count() > 0) {
1494 $this->updateModificationDate();
1496 _T("Member card updated"),
1502 $event = 'member.edit';
1507 $success = $this->dynamicsStore();
1510 //send event at the end of process, once all has been stored
1511 if ($event !== null) {
1512 $emitter->emit($event, $this);
1515 } catch (Throwable
$e) {
1517 'Something went wrong :\'( | ' . $e->getMessage() . "\n" .
1518 $e->getTraceAsString(),
1526 * Update member modification date
1530 private function updateModificationDate()
1533 $modif_date = date('Y-m-d');
1534 $update = $this->zdb
->update(self
::TABLE
);
1536 array('date_modif_adh' => $modif_date)
1537 )->where(self
::PK
. '=' . $this->_id
);
1539 $edit = $this->zdb
->execute($update);
1540 $this->_modification_date
= $modif_date;
1541 } catch (Throwable
$e) {
1543 'Something went wrong updating modif date :\'( | ' .
1544 $e->getMessage() . "\n" . $e->getTraceAsString(),
1551 * Global getter method
1553 * @param string $name name of the property we want to retrive
1555 * @return false|object the called property
1557 public function __get($name)
1560 'admin', 'staff', 'due_free', 'appears_in_list', 'active',
1565 'sadmin', 'sstaff', 'sdue_free', 'sappears_in_list', 'sactive',
1566 'stitle', 'sstatus', 'sfullname', 'sname', 'rowclass', 'saddress',
1567 'rbirthdate', 'sgender', 'contribstatus'
1570 if (in_array($name, $forbidden)) {
1573 return $this->isAdmin();
1576 return $this->isStaff();
1579 return $this->isDueFree();
1581 case 'appears_in_list':
1582 return $this->appearsInMembersList();
1585 return $this->isActive();
1588 throw new \
RuntimeException("Call to __get for '$name' is forbidden!");
1591 if (in_array($name, $virtuals)) {
1592 if (substr($name, 0, 1) !== '_') {
1593 $real = '_' . substr($name, 1);
1600 case 'sappears_in_list':
1602 return (($this->$real) ?
_T("Yes") : _T("No"));
1605 return (($this->$real) ?
_T("Active") : _T("Inactive"));
1608 if (isset($this->_title
)) {
1609 return $this->_title
->tshort
;
1615 $status = new Status($this->zdb
);
1616 return $status->getLabel($this->_status
);
1619 return $this->getNameWithCase(
1622 (isset($this->_title
) ?
$this->title
: false)
1626 $address = $this->_address
;
1627 if ($this->_address_continuation
!== '' && $this->_address_continuation
!== null) {
1628 $address .= "\n" . $this->_address_continuation
;
1633 return $this->getNameWithCase($this->_name
, $this->_surname
);
1636 return $this->_birthdate
;
1639 switch ($this->gender
) {
1645 return __('Unspecified');
1648 case 'contribstatus':
1649 return $this->getDues();
1653 if (substr($name, 0, 1) !== '_') {
1654 $rname = '_' . $name;
1662 if ($this->$rname !== null) {
1663 return (int)$this->$rname;
1669 case 'creation_date':
1670 case 'modification_date':
1672 if ($this->$rname != '') {
1674 $d = new \
DateTime($this->$rname);
1675 return $d->format(__("Y-m-d"));
1676 } catch (Throwable
$e) {
1677 //oops, we've got a bad date :/
1679 'Bad date (' . $this->$rname . ') | ' .
1683 return $this->$rname;
1688 if (!property_exists($this, $rname)) {
1690 "Unknown property '$rname'",
1695 return $this->$rname;
1705 * If member does not have an email address, but is attached to
1706 * another member, we'll take information from its parent.
1710 public function getEmail()
1712 $email = $this->_email
;
1713 if (empty($email)) {
1714 $this->loadParent();
1715 $email = $this->parent
->email
;
1722 * Get member address.
1723 * If member does not have an address, but is attached to another member, we'll take information from its parent.
1727 public function getAddress()
1729 $address = $this->_address
;
1730 if (empty($address) && $this->hasParent()) {
1731 $this->loadParent();
1732 $address = $this->parent
->address
;
1739 * Get member address continuation.
1740 * If member does not have an address, but is attached to another member, we'll take information from its parent.
1744 public function getAddressContinuation()
1746 $address = $this->_address
;
1747 $address_continuation = $this->_address_continuation
;
1748 if (empty($address) && $this->hasParent()) {
1749 $this->loadParent();
1750 $address_continuation = $this->parent
->address_continuation
;
1753 return $address_continuation;
1757 * Get member zipcode.
1758 * If member does not have an address, but is attached to another member, we'll take information from its parent.
1762 public function getZipcode()
1764 $address = $this->_address
;
1765 $zip = $this->_zipcode
;
1766 if (empty($address) && $this->hasParent()) {
1767 $this->loadParent();
1768 $zip = $this->parent
->zipcode
;
1776 * If member does not have an address, but is attached to another member, we'll take information from its parent.
1780 public function getTown()
1782 $address = $this->_address
;
1783 $town = $this->_town
;
1784 if (empty($address) && $this->hasParent()) {
1785 $this->loadParent();
1786 $town = $this->parent
->town
;
1793 * Get member country.
1794 * If member does not have an address, but is attached to another member, we'll take information from its parent.
1798 public function getCountry()
1800 $address = $this->_address
;
1801 $country = $this->_country
;
1802 if (empty($address) && $this->hasParent()) {
1803 $this->loadParent();
1804 $country = $this->parent
->country
;
1815 public function getAge()
1817 if ($this->_birthdate
== null) {
1821 $d = \DateTime
::createFromFormat('Y-m-d', $this->_birthdate
);
1824 'Invalid birthdate: ' . $this->_birthdate
,
1832 $d->diff(new \
DateTime())->y
,
1833 _T(' (%age years old)')
1838 * Get parent inherited fields
1842 public function getParentFields()
1844 return $this->parent_fields
;
1848 * Handle files (photo and dynamics files
1850 * @param array $files Files sent
1852 * @return array|true
1854 public function handleFiles($files)
1858 if (isset($files['photo'])) {
1859 if ($files['photo']['error'] === UPLOAD_ERR_OK
) {
1860 if ($files['photo']['tmp_name'] != '') {
1861 if (is_uploaded_file($files['photo']['tmp_name'])) {
1862 $res = $this->picture
->store($files['photo']);
1865 = $this->picture
->getErrorMessage($res);
1869 } elseif ($files['photo']['error'] !== UPLOAD_ERR_NO_FILE
) {
1871 $this->picture
->getPhpErrorMessage($files['photo']['error']),
1874 $this->errors
[] = $this->picture
->getPhpErrorMessage(
1875 $files['photo']['error']
1879 $this->dynamicsFiles($_FILES);
1881 if (count($this->errors
) > 0) {
1883 'Some errors has been throwed attempting to edit/store a member files' . "\n" .
1884 print_r($this->errors
, true),
1887 return $this->errors
;
1894 * Set member as duplicate
1898 public function setDuplicate()
1900 //mark as duplicated
1901 $this->_duplicate
= true;
1902 $infos = $this->_others_infos_admin
;
1903 $this->_others_infos_admin
= str_replace(
1905 [$this->sname
, $this->_id
],
1906 _T('Duplicated from %name (%id)')
1908 if (!empty($infos)) {
1909 $this->_others_infos_admin
.= "\n" . $infos;
1913 //drop email, must be unique
1914 $this->_email
= null;
1915 //drop creation date
1916 $this->_creation_date
= date("Y-m-d");
1918 $this->_login
= null;
1920 $this->_picture
= new Picture();
1922 $this->_birthdate
= null;
1924 $this->_surname
= null;
1926 $this->_admin
= false;
1928 $this->_due_free
= false;
1932 * Get current errors
1936 public function getErrors()
1938 return $this->errors
;
1946 public function getGroups()
1948 return $this->_groups
;
1952 * Get user managed groups
1956 public function getManagedGroups()
1958 return $this->_managed_groups
;
1962 * Can current logged in user edit member
1964 * @param Login $login Login instance
1968 public function canEdit($login)
1970 if ($this->id
&& $login->id
== $this->id ||
$login->isAdmin() ||
$login->isStaff()) {
1974 //check if requested member is part of managed groups
1975 if ($login->isGroupManager()) {
1976 foreach ($this->getGroups() as $g) {
1977 if ($login->isGroupManager($g->getId())) {
1987 * Are we currently duplicated a member?
1991 public function isDuplicate()
1993 return $this->_duplicate
;
1997 * Flag creation mail sending
1999 * @param boolean $send True (default) to send creation email
2003 public function setSendmail($send = true)
2005 $this->sendmail
= $send;
2010 * Should we send administrative emails to member?
2014 public function sendEMail()
2016 return $this->sendmail
;