1 <?php
2 3 4 5 6
7 class Member extends DataObject {
8
9 static $db = array(
10 'FirstName' => 'Varchar',
11 'Surname' => 'Varchar',
12 'Email' => 'Varchar',
13 'Password' => 'Varchar(160)',
14 'RememberLoginToken' => 'Varchar(50)',
15 'NumVisit' => 'Int',
16 'LastVisited' => 'SS_Datetime',
17 'Bounced' => 'Boolean',
18 'AutoLoginHash' => 'Varchar(30)',
19 'AutoLoginExpired' => 'SS_Datetime',
20
21
22
23
24 'PasswordEncryption' => "Varchar(50)",
25 'Salt' => 'Varchar(50)',
26 'PasswordExpiry' => 'Date',
27 'LockedOutUntil' => 'SS_Datetime',
28 'Locale' => 'Varchar(6)',
29
30 'FailedLoginCount' => 'Int',
31 );
32
33 static $belongs_many_many = array(
34 'Groups' => 'Group',
35 );
36
37 static $has_one = array();
38
39 static $has_many = array();
40
41 static $many_many = array();
42
43 static = array();
44
45 static $default_sort = '"Surname", "FirstName"';
46
47 static $indexes = array(
48 'Email' => true,
49
50 );
51
52 static $notify_password_change = false;
53
54 55 56 57 58 59 60 61 62 63 64
65 static $searchable_fields = array(
66 'FirstName',
67 'Surname',
68 'Email',
69 );
70
71 static $summary_fields = array(
72 'FirstName',
73 'Surname',
74 'Email',
75 );
76
77 78 79
80 static $title_format = null;
81
82 83 84 85 86 87 88
89 protected static $unique_identifier_field = 'Email';
90
91 92 93
94 protected static $password_validator = null;
95
96 97 98 99
100 protected static $password_expiry_days = null;
101
102 protected static $lock_out_after_incorrect_logins = null;
103
104 105 106 107
108 protected static $login_marker_cookie = null;
109
110
111 112 113
114 public function populateDefaults() {
115 parent::populateDefaults();
116 $this->Locale = i18n::get_locale();
117 }
118
119 function requireDefaultRecords() {
120
121
122
123 $adminGroups = Permission::get_groups_by_permission('ADMIN');
124 if(!$adminGroups) {
125 singleton('Group')->requireDefaultRecords();
126 $adminGroups = Permission::get_groups_by_permission('ADMIN');
127 }
128 $adminGroup = $adminGroups->First();
129
130
131
132 $admins = Permission::get_members_by_permission('ADMIN');
133 if(!$admins) {
134
135
136 $admin = Object::create('Member');
137 $admin->FirstName = _t('Member.DefaultAdminFirstname', 'Default Admin');
138 $admin->write();
139 $admin->Groups()->add($adminGroup);
140 }
141 }
142
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
162 static function set_login_marker_cookie($cookieName) {
163 self::$login_marker_cookie = $cookieName;
164 }
165
166 167 168 169 170 171
172 public function checkPassword($password) {
173 $result = $this->canLogIn();
174
175 if(!$result->valid()){
176 return $result;
177 }
178
179 $spec = Security::encrypt_password(
180 $password,
181 $this->Salt,
182 $this->PasswordEncryption,
183 $this
184 );
185 $e = $spec['encryptor'];
186
187 if(!$e->compare($this->Password, $spec['password'])) {
188 $result->error(_t (
189 'Member.ERRORWRONGCRED',
190 'That doesn\'t seem to be the right e-mail address or password. Please try again.'
191 ));
192 }
193
194 return $result;
195 }
196
197 198 199 200 201 202 203 204
205 public function canLogIn() {
206 $result = new ValidationResult();
207
208 if($this->isLockedOut()) {
209 $result->error(_t (
210 'Member.ERRORLOCKEDOUT',
211 'Your account has been temporarily disabled because of too many failed attempts at ' .
212 'logging in. Please try again in 20 minutes.'
213 ));
214 }
215
216 $this->extend('canLogIn', $result);
217 return $result;
218 }
219
220 221 222
223 public function isLockedOut() {
224 return $this->LockedOutUntil && time() < strtotime($this->LockedOutUntil);
225 }
226
227 228 229 230 231 232
233 static function session_regenerate_id() {
234
235 if(Director::is_cli()) return;
236
237 $file = '';
238 $line = '';
239
240
241
242 if(!headers_sent($file, $line)) @session_regenerate_id(true);
243 }
244
245 246 247 248 249 250
251 static function get_unique_identifier_field() {
252 return self::$unique_identifier_field;
253 }
254
255 256 257 258 259 260
261 static function set_unique_identifier_field($field) {
262 self::$unique_identifier_field = $field;
263 }
264
265 266 267
268 static function set_password_validator($pv) {
269 self::$password_validator = $pv;
270 }
271
272 273 274
275 static function password_validator() {
276 return self::$password_validator;
277 }
278
279 280 281 282
283 static function set_password_expiry($days) {
284 self::$password_expiry_days = $days;
285 }
286
287 288 289
290 static function lock_out_after_incorrect_logins($numLogins) {
291 self::$lock_out_after_incorrect_logins = $numLogins;
292 }
293
294
295 function isPasswordExpired() {
296 if(!$this->PasswordExpiry) return false;
297 return strtotime(date('Y-m-d')) >= strtotime($this->PasswordExpiry);
298 }
299
300 301 302 303 304
305 function logIn($remember = false) {
306 self::session_regenerate_id();
307
308 Session::set("loggedInAs", $this->ID);
309
310 if(self::$login_marker_cookie) Cookie::set(self::$login_marker_cookie, 1, 0);
311
312 $this->NumVisit++;
313
314 if($remember) {
315 $token = substr(md5(uniqid(rand(), true)), 0, 49 - strlen($this->ID));
316 $this->RememberLoginToken = $token;
317 Cookie::set('alc_enc', $this->ID . ':' . $token, 90, null, null, null, true);
318 } else {
319 $this->RememberLoginToken = null;
320 Cookie::set('alc_enc', null);
321 Cookie::forceExpiry('alc_enc');
322 }
323
324
325 if(self::$lock_out_after_incorrect_logins) {
326 $this->FailedLoginCount = 0;
327 }
328
329
330 if(array_key_exists('LockedOutUntil', DB::fieldList('Member'))) {
331 $this->LockedOutUntil = null;
332 }
333
334 $this->write();
335
336
337 $this->extend('memberLoggedIn');
338 }
339
340 341 342 343 344 345 346
347 static function logged_in_session_exists() {
348 if($id = Member::currentUserID()) {
349 if($member = DataObject::get_by_id('Member', $id)) {
350 if($member->exists()) return true;
351 }
352 }
353
354 return false;
355 }
356
357 358 359 360 361 362
363 static function autoLogin() {
364
365 self::$_already_tried_to_auto_log_in = true;
366
367 if(strpos(Cookie::get('alc_enc'), ':') && !Session::get("loggedInAs")) {
368 list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2);
369 $SQL_uid = Convert::raw2sql($uid);
370
371 $member = DataObject::get_one("Member", "\"Member\".\"ID\" = '$SQL_uid'");
372
373
374 if($member && (!$member->RememberLoginToken || $member->RememberLoginToken != $token)) {
375 $member = null;
376 }
377
378 if($member) {
379 self::session_regenerate_id();
380 Session::set("loggedInAs", $member->ID);
381
382 if(self::$login_marker_cookie) Cookie::set(self::$login_marker_cookie, 1, 0, null, null, false, true);
383
384 $token = substr(md5(uniqid(rand(), true)), 0, 49 - strlen($member->ID));
385 $member->RememberLoginToken = $token;
386 Cookie::set('alc_enc', $member->ID . ':' . $token, 90, null, null, false, true);
387
388 $member->NumVisit++;
389 $member->write();
390
391
392 $member->extend('memberAutoLoggedIn');
393 }
394 }
395 }
396
397 398 399
400 function logOut() {
401 Session::clear("loggedInAs");
402 if(self::$login_marker_cookie) Cookie::set(self::$login_marker_cookie, null, 0);
403 self::session_regenerate_id();
404
405 $this->extend('memberBeforeLoggedOut');
406
407 $this->RememberLoginToken = null;
408 Cookie::set('alc_enc', null);
409 Cookie::forceExpiry('alc_enc');
410
411 $this->write();
412
413
414 $this->extend('memberLoggedOut');
415 }
416
417
418 419 420 421 422 423 424 425 426
427 function generateAutologinHash($lifetime = 2) {
428
429 do {
430 $hash = substr(base_convert(md5(uniqid(mt_rand(), true)), 16, 36),
431 0, 30);
432 } while(DataObject::get_one('Member', "\"AutoLoginHash\" = '$hash'"));
433
434 $this->AutoLoginHash = $hash;
435 $this->AutoLoginExpired = date('Y-m-d', time() + (86400 * $lifetime));
436
437 $this->write();
438 }
439
440 441 442 443 444
445 static function member_from_autologinhash($RAW_hash, $login = false) {
446 $SQL_hash = Convert::raw2sql($RAW_hash);
447
448 $member = DataObject::get_one('Member',"\"AutoLoginHash\"='" . $SQL_hash . "' AND \"AutoLoginExpired\" > " . DB::getConn()->now());
449
450 if($login && $member)
451 $member->logIn();
452
453 return $member;
454 }
455
456
457 458 459 460 461 462
463 function sendInfo($type = 'signup', $data = null) {
464 switch($type) {
465 case "signup":
466 $e = Object::create('Member_SignupEmail');
467 break;
468 case "changePassword":
469 $e = Object::create('Member_ChangePasswordEmail');
470 break;
471 case "forgotPassword":
472 $e = Object::create('Member_ForgotPasswordEmail');
473 break;
474 }
475
476 if(is_array($data)) {
477 foreach($data as $key => $value)
478 $e->$key = $value;
479 }
480
481 $e->populateTemplate($this);
482 $e->setTo($this->Email);
483 $e->send();
484 }
485
486 487 488 489 490 491 492 493
494 public function getMemberFormFields($newUser = false) {
495 $fields = parent::getFrontendFields();
496
497 $fields->replaceField('Password', $password = new ConfirmedPasswordField (
498 'Password',
499 $this->fieldLabel('Password'),
500 null,
501 null,
502 (bool) $this->ID
503 ));
504 $password->setCanBeEmpty($this->ID);
505
506 $fields->replaceField('Locale', new DropdownField (
507 'Locale',
508 $this->fieldLabel('Locale'),
509 i18n::get_existing_translations()
510 ));
511
512 $fields->removeByName('RememberLoginToken');
513 $fields->removeByName('NumVisit');
514 $fields->removeByName('LastVisited');
515 $fields->removeByName('Bounced');
516 $fields->removeByName('AutoLoginHash');
517 $fields->removeByName('AutoLoginExpired');
518 $fields->removeByName('PasswordEncryption');
519 $fields->removeByName('Salt');
520 $fields->removeByName('PasswordExpiry');
521 $fields->removeByName('FailedLoginCount');
522 $fields->removeByName('LastViewed');
523 $fields->removeByName('LockedOutUntil');
524
525 if ($f = $fields->dataFieldByName('FirstName')) {
526 $f->setAutocomplete('given-name');
527 }
528 if ($f = $fields->dataFieldByName('Surname')) {
529 $f->setAutocomplete('family-name');
530 }
531 $fields->replaceField('Email', new EmailField('Email', $this->fieldLabel('Email')));
532 $fields->replaceField('Phone', new PhoneField('Phone', $this->fieldLabel('Phone')));
533
534 $this->extend('updateMemberFormFields', $fields);
535 return $fields;
536 }
537
538 function getValidator() {
539 $obj = new Member_Validator();
540 if (!$this->IsInDB())
541 $obj->addRequiredfield('Password');
542 $this->extend('updateValidator', $obj);
543 return $obj;
544 }
545
546
547 548 549 550 551 552
553 static function currentUser() {
554 $id = Member::currentUserID();
555 if($id) {
556 return DataObject::get_one("Member", "\"Member\".\"ID\" = $id");
557 }
558 }
559
560
561 562 563 564 565
566 static function currentUserID() {
567 $id = Session::get("loggedInAs");
568 if(!$id && !self::$_already_tried_to_auto_log_in) {
569 self::autoLogin();
570 $id = Session::get("loggedInAs");
571 }
572
573 return is_numeric($id) ? $id : 0;
574 }
575 private static $_already_tried_to_auto_log_in = false;
576
577
578 579 580 581 582 583
584 static function create_new_password() {
585 if(file_exists(Security::get_word_list())) {
586 $words = file(Security::get_word_list());
587
588 list($usec, $sec) = explode(' ', microtime());
589 srand($sec + ((float) $usec * 100000));
590
591 $word = trim($words[rand(0,sizeof($words)-1)]);
592 $number = rand(10,999);
593
594 return $word . $number;
595 } else {
596 $random = rand();
597 $string = md5($random);
598 $output = substr($string, 0, 6);
599 return $output;
600 }
601 }
602
603 604 605
606 function onBeforeWrite() {
607 if($this->SetPassword) $this->Password = $this->SetPassword;
608
609
610
611
612 $identifierField = self::$unique_identifier_field;
613 if($this->$identifierField) {
614
615 $idClause = ($this->ID) ? sprintf(" AND \"Member\".\"ID\" <> %d", (int)$this->ID) : '';
616 $existingRecord = DataObject::get_one(
617 'Member',
618 sprintf(
619 "\"%s\" = '%s' %s",
620 $identifierField,
621 Convert::raw2sql($this->$identifierField),
622 $idClause
623 )
624 );
625 if($existingRecord) {
626 throw new ValidationException(new ValidationResult(false, sprintf(
627 _t(
628 'Member.ValidationIdentifierFailed',
629 'Can\'t overwrite existing member #%d with identical identifier (%s = %s))',
630 PR_MEDIUM,
631 'The values in brackets show a fieldname mapped to a value, usually denoting an existing email address'
632 ),
633 $existingRecord->ID,
634 $identifierField,
635 $this->$identifierField
636 )));
637 }
638 }
639
640
641
642 if(
643 (Director::isLive() || Email::mailer() instanceof TestMailer)
644 && $this->isChanged('Password')
645 && $this->record['Password']
646 && Member::$notify_password_change
647 ) {
648 $this->sendInfo('changePassword');
649 }
650
651
652
653
654 if(!$this->ID || $this->isChanged('Password')) {
655
656 $encryption_details = Security::encrypt_password(
657 $this->Password,
658 $this->Salt,
659 $this->PasswordEncryption,
660 $this
661 );
662
663 $this->Password = $encryption_details['password'];
664 $this->Salt = $encryption_details['salt'];
665 $this->PasswordEncryption = $encryption_details['algorithm'];
666
667
668 if(!$this->isChanged('PasswordExpiry')) {
669
670 if(self::$password_expiry_days) {
671 $this->PasswordExpiry = date('Y-m-d', time() + 86400 * self::$password_expiry_days);
672 } else {
673 $this->PasswordExpiry = null;
674 }
675 }
676 }
677
678
679 if(!$this->Locale) {
680 $this->Locale = i18n::get_locale();
681 }
682
683 parent::onBeforeWrite();
684 }
685
686 function onAfterWrite() {
687 parent::onAfterWrite();
688
689 if($this->isChanged('Password')) {
690 MemberPassword::log($this);
691 }
692 }
693
694
695 696 697 698 699 700 701
702 public function inGroups($groups, $strict = false) {
703 if($groups) foreach($groups as $group) {
704 if($this->inGroup($group, $strict)) return true;
705 }
706
707 return false;
708 }
709
710
711 712 713 714 715 716 717
718 public function inGroup($group, $strict = false) {
719 if(is_numeric($group)) {
720 $groupCheckObj = DataObject::get_by_id('Group', $group);
721 } elseif(is_string($group)) {
722 $SQL_group = Convert::raw2sql($group);
723 $groupCheckObj = DataObject::get_one('Group', "\"Code\" = '{$SQL_group}'");
724 } elseif($group instanceof Group) {
725 $groupCheckObj = $group;
726 } else {
727 user_error('Member::inGroup(): Wrong format for $group parameter', E_USER_ERROR);
728 }
729
730 if(!$groupCheckObj) return false;
731
732 $groupCandidateObjs = ($strict) ? $this->getManyManyComponents("Groups") : $this->Groups();
733 if($groupCandidateObjs) foreach($groupCandidateObjs as $groupCandidateObj) {
734 if($groupCandidateObj->ID == $groupCheckObj->ID) return true;
735 }
736
737 return false;
738 }
739
740 741 742 743 744 745 746
747 function isAdmin() {
748 return Permission::checkMember($this, 'ADMIN');
749 }
750
751 752 753 754
755 function EditProfileLink(){
756 if($this->extend('EditProfileLink')){
757 $a = $this->extend('EditProfileLink');
758 return $a[0];
759 }
760 return 'myprofile';
761 }
762
763 764 765 766
767 static function set_title_columns($columns, $sep = ' ') {
768 if (!is_array($columns)) $columns = array($columns);
769 self::$title_format = array('columns' => $columns, 'sep' => $sep);
770 }
771
772
773
774 775 776 777 778 779 780 781 782 783
784 public function getTitle() {
785 if (self::$title_format) {
786 $values = array();
787 foreach(self::$title_format['columns'] as $col) {
788 $values[] = $this->getField($col);
789 }
790 return join(self::$title_format['sep'], $values);
791 }
792 if($this->getField('ID') === 0)
793 return $this->getField('Surname');
794 else{
795 if($this->getField('Surname') && $this->getField('FirstName')){
796 return $this->getField('Surname') . ', ' . $this->getField('FirstName');
797 }elseif($this->getField('Surname')){
798 return $this->getField('Surname');
799 }elseif($this->getField('FirstName')){
800 return $this->getField('FirstName');
801 }else{
802 return null;
803 }
804 }
805 }
806
807 808 809 810 811 812 813
814 static function get_title_sql($tableName = 'Member') {
815
816 if (self::$title_format) {
817 $columnsWithTablename = array();
818 foreach(self::$title_format['columns'] as $column) {
819 $columnsWithTablename[] = "\"$tableName\".\"$column\"";
820 }
821
822 return "(\"".join("'".self::$title_format['sep']."'" || $columnsWithTablename)."\")";
823 } else {
824 return "(\"$tableName\".\"Surname\" || ' ' || \"$tableName\".\"FirstName\")";
825 }
826 }
827
828
829 830 831 832 833
834 public function getName() {
835 return $this->FirstName . ' ' . $this->Surname;
836 }
837
838
839 840 841 842 843 844 845 846
847 public function setName($name) {
848 $nameParts = explode(' ', $name);
849 $this->Surname = array_pop($nameParts);
850 $this->FirstName = join(' ', $nameParts);
851 }
852
853
854 855 856 857 858 859
860 public function splitName($name) {
861 return $this->setName($name);
862 }
863
864
865
866
867 868 869 870 871 872 873
874 public function Groups() {
875 $groups = $this->getManyManyComponents("Groups");
876 $groupIDs = $groups->column();
877 $collatedGroups = array();
878
879 if($groups) {
880 foreach($groups as $group) {
881 $collatedGroups = array_merge((array)$collatedGroups, $group->collateAncestorIDs());
882 }
883 }
884
885 $table = "Group_Members";
886
887 if(count($collatedGroups) > 0) {
888 $collatedGroups = implode(", ", array_unique($collatedGroups));
889
890 $unfilteredGroups = singleton('Group')->instance_get("`Group`.`ID` IN ($collatedGroups)", "`Group`.`ID`", "", "", "Member_GroupSet");
891 $result = new ComponentSet();
892
893
894 $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
895 foreach($unfilteredGroups as $group) {
896 if($group->allowedIPAddress($ip)) $result->push($group);
897 }
898 } else {
899 $result = new Member_GroupSet();
900 }
901
902 $result->setComponentInfo("many-to-many", $this, "Member", $table, "Group");
903
904 return $result;
905 }
906
907
908 909 910 911 912 913 914 915 916 917
918 public function map($filter = "", $sort = "", $blank="") {
919 $ret = new SQLMap(singleton('Member')->extendedSQL($filter, $sort));
920 if($blank) {
921 $blankMember = Object::create('Member');
922 $blankMember->Surname = $blank;
923 $blankMember->ID = 0;
924
925 $ret->getItems()->shift($blankMember);
926 }
927
928 return $ret;
929 }
930
931
932 933 934 935 936 937 938 939 940 941 942
943 public static function mapInGroups($groups = null) {
944 if(!$groups)
945 return Member::map();
946
947 $groupIDList = array();
948
949 if(is_a($groups, 'DataObjectSet')) {
950 foreach( $groups as $group )
951 $groupIDList[] = $group->ID;
952 } elseif(is_array($groups)) {
953 $groupIDList = $groups;
954 } else {
955 $groupIDList[] = $groups;
956 }
957
958 if(empty($groupIDList))
959 return Member::map();
960
961 return new SQLMap(singleton('Member')->extendedSQL(
962 "\"GroupID\" IN (" . implode( ',', $groupIDList ) .
963 ")", "Surname, FirstName", "", "INNER JOIN \"Group_Members\" ON \"MemberID\"=\"Member\".\"ID\""));
964 }
965
966
967 968 969 970 971 972 973 974 975 976
977 public static function mapInCMSGroups($groups = null) {
978 if(!$groups || $groups->Count() == 0) {
979 $perms = array('ADMIN', 'CMS_ACCESS_AssetAdmin');
980
981 $cmsPerms = singleton('CMSMain')->providePermissions();
982
983 if(!empty($cmsPerms)) {
984 $perms = array_unique(array_merge($perms, array_keys($cmsPerms)));
985 }
986
987 $SQL_perms = "'" . implode("', '", Convert::raw2sql($perms)) . "'";
988
989 $groups = DataObject::get('Group', "", "",
990 "INNER JOIN \"Permission\" ON \"Permission\".\"GroupID\" = \"Group\".\"ID\" AND \"Permission\".\"Code\" IN ($SQL_perms)");
991 }
992
993 $groupIDList = array();
994
995 if(is_a($groups, 'DataObjectSet')) {
996 foreach($groups as $group) {
997 $groupIDList[] = $group->ID;
998 }
999 } elseif(is_array($groups)) {
1000 $groupIDList = $groups;
1001 }
1002
1003 $filterClause = ($groupIDList)
1004 ? "\"GroupID\" IN (" . implode( ',', $groupIDList ) . ")"
1005 : "";
1006
1007 return new SQLMap(singleton('Member')->extendedSQL($filterClause,
1008 "Surname, FirstName", "",
1009 "INNER JOIN \"Group_Members\" ON \"MemberID\"=\"Member\".\"ID\" INNER JOIN \"Group\" ON \"Group\".\"ID\"=\"GroupID\""));
1010 }
1011
1012
1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023
1024 public function memberNotInGroups($groupList, $memberGroups = null){
1025 if(!$memberGroups) $memberGroups = $this->Groups();
1026
1027 foreach($memberGroups as $group) {
1028 if(in_array($group->Code, $groupList)) {
1029 $index = array_search($group->Code, $groupList);
1030 unset($groupList[$index]);
1031 }
1032 }
1033
1034 return $groupList;
1035 }
1036
1037
1038 1039 1040 1041 1042 1043 1044
1045 public function getCMSFields() {
1046 $fields = parent::getCMSFields();
1047
1048 $mainFields = $fields->fieldByName("Root")->fieldByName("Main")->Children;
1049
1050 $password = new ConfirmedPasswordField(
1051 'Password',
1052 null,
1053 null,
1054 null,
1055 true
1056 );
1057 $password->setCanBeEmpty(true);
1058 if(!$this->ID) $password->showOnClick = false;
1059 $mainFields->replaceField('Password', $password);
1060
1061 $mainFields->insertBefore(
1062 new HeaderField('MemberDetailsHeader',_t('Member.PERSONALDETAILS', "Personal Details", PR_MEDIUM, 'Headline for formfields')),
1063 'FirstName'
1064 );
1065
1066 $mainFields->insertBefore(
1067 new HeaderField('MemberUserDetailsHeader',_t('Member.USERDETAILS', "User Details", PR_MEDIUM, 'Headline for formfields')),
1068 'Email'
1069 );
1070
1071 $mainFields->replaceField('Locale', new DropdownField(
1072 "Locale",
1073 _t('Member.INTERFACELANG', "Interface Language", PR_MEDIUM, 'Language of the CMS'),
1074 i18n::get_existing_translations()
1075 ));
1076
1077 $mainFields->removeByName('Bounced');
1078 $mainFields->removeByName('RememberLoginToken');
1079 $mainFields->removeByName('AutoLoginHash');
1080 $mainFields->removeByName('AutoLoginExpired');
1081 $mainFields->removeByName('PasswordEncryption');
1082 $mainFields->removeByName('PasswordExpiry');
1083 $mainFields->removeByName('LockedOutUntil');
1084
1085 if(!self::$lock_out_after_incorrect_logins) {
1086 $mainFields->removeByName('FailedLoginCount');
1087 }
1088
1089 $mainFields->removeByName('Salt');
1090 $mainFields->removeByName('NumVisit');
1091 $mainFields->removeByName('LastVisited');
1092
1093 $fields->removeByName('Subscriptions');
1094
1095
1096
1097 $fields->removeByName('Groups');
1098
1099 if(Permission::check('EDIT_PERMISSIONS')) {
1100 $groupsField = new TreeMultiselectField('Groups', false, 'Group');
1101 $fields->findOrMakeTab('Root.Groups', singleton('Group')->i18n_plural_name());
1102 $fields->addFieldToTab('Root.Groups', $groupsField);
1103
1104
1105
1106
1107 if($this->ID) {
1108 $permissionsField = new PermissionCheckboxSetField_Readonly(
1109 'Permissions',
1110 singleton('Permission')->i18n_plural_name(),
1111 'Permission',
1112 'GroupID',
1113
1114 $this->getManyManyComponents('Groups')
1115 );
1116 $fields->findOrMakeTab('Root.Permissions', singleton('Permission')->i18n_plural_name());
1117 $fields->addFieldToTab('Root.Permissions', $permissionsField);
1118 }
1119 }
1120
1121 $this->extend('updateCMSFields', $fields);
1122
1123 return $fields;
1124 }
1125
1126 1127 1128 1129 1130
1131 function fieldLabels($includerelations = true) {
1132 $labels = parent::fieldLabels($includerelations);
1133
1134 $labels['FirstName'] = _t('Member.FIRSTNAME', 'First Name');
1135 $labels['Surname'] = _t('Member.SURNAME', 'Surname');
1136 $labels['Email'] = _t('Member.EMAIL', 'Email');
1137 $labels['Password'] = _t('Member.db_Password', 'Password');
1138 $labels['NumVisit'] = _t('Member.db_NumVisit', 'Number of Visits');
1139 $labels['LastVisited'] = _t('Member.db_LastVisited', 'Last Visited Date');
1140 $labels['PasswordExpiry'] = _t('Member.db_PasswordExpiry', 'Password Expiry Date', PR_MEDIUM, 'Password expiry date');
1141 $labels['LockedOutUntil'] = _t('Member.db_LockedOutUntil', 'Locked out until', PR_MEDIUM, 'Security related date');
1142 $labels['Locale'] = _t('Member.db_Locale', 'Interface Locale');
1143 $labels['Title'] = _t('Member.TITLE', 'Full Name');
1144 $labels['Created'] = _t('Member.db_Created', 'Created');
1145 if($includerelations){
1146 $labels['Groups'] = _t('Member.belongs_many_many_Groups', 'Groups', PR_MEDIUM, 'Security Groups this member belongs to');
1147 }
1148 return $labels;
1149 }
1150
1151 1152 1153 1154 1155
1156 function canView($member = null) {
1157 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
1158
1159
1160 $results = $this->extend('canView', $member);
1161 if($results && is_array($results)) {
1162 if(!min($results)) return false;
1163 else return true;
1164 }
1165
1166
1167 if($member && $this->ID == $member->ID) return true;
1168
1169 if(
1170 Permission::checkMember($member, 'ADMIN')
1171 || Permission::checkMember($member, 'CMS_ACCESS_SecurityAdmin')
1172 ) {
1173 return true;
1174 }
1175
1176 return false;
1177 }
1178
1179 1180 1181 1182
1183 function canEdit($member = null) {
1184 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
1185
1186
1187 $results = $this->extend('canEdit', $member);
1188 if($results && is_array($results)) {
1189 if(!min($results)) return false;
1190 else return true;
1191 }
1192
1193
1194 if(!($member && $member->exists())) return false;
1195
1196 return $this->canView($member);
1197 }
1198
1199 1200 1201 1202
1203 function canDelete($member = null) {
1204 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
1205
1206
1207 $results = $this->extend('canDelete', $member);
1208 if($results && is_array($results)) {
1209 if(!min($results)) return false;
1210 else return true;
1211 }
1212
1213
1214 if(!($member && $member->exists())) return false;
1215
1216 return $this->canEdit($member);
1217 }
1218
1219
1220 1221 1222
1223 function validate() {
1224 $valid = parent::validate();
1225
1226 if(!$this->ID || $this->isChanged('Password')) {
1227 if($this->Password && self::$password_validator) {
1228 $valid->combineAnd(self::$password_validator->validate($this->Password, $this));
1229 }
1230 }
1231
1232 if((!$this->ID && $this->SetPassword) || $this->isChanged('SetPassword')) {
1233 if($this->SetPassword && self::$password_validator) {
1234 $valid->combineAnd(self::$password_validator->validate($this->SetPassword, $this));
1235 }
1236 }
1237 $this->extend('updateValidation', $valid);
1238 return $valid;
1239 }
1240
1241 1242 1243 1244 1245 1246
1247 function changePassword($password) {
1248 $this->Password = $password;
1249 $valid = $this->validate();
1250
1251 if($valid->valid()) {
1252 $this->AutoLoginHash = null;
1253 $this->write();
1254 }
1255
1256 return $valid;
1257 }
1258
1259 1260 1261 1262
1263 function registerFailedLogin() {
1264 if(self::$lock_out_after_incorrect_logins) {
1265
1266 $this->FailedLoginCount = $this->FailedLoginCount + 1;
1267 $this->write();
1268
1269 if($this->FailedLoginCount >= self::$lock_out_after_incorrect_logins) {
1270 $this->LockedOutUntil = date('Y-m-d H:i:s', time() + 15*60);
1271 $this->write();
1272 }
1273 }
1274 }
1275
1276 1277 1278 1279 1280 1281 1282
1283 function getHtmlEditorConfigForCMS() {
1284 $currentName = '';
1285 $currentPriority = 0;
1286
1287 foreach($this->Groups() as $group) {
1288 $configName = $group->HtmlEditorConfig;
1289 if($configName) {
1290 $config = HtmlEditorConfig::get($group->HtmlEditorConfig);
1291 if($config && $config->getOption('priority') > $currentPriority) {
1292 $currentName = $configName;
1293 }
1294 }
1295 }
1296
1297
1298 return $currentName ? $currentName : 'cms';
1299 }
1300
1301 }
1302
1303 1304 1305 1306 1307 1308
1309 class Member_GroupSet extends ComponentSet {
1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321
1322 function setByCheckboxes(array $checkboxes, array $data) {
1323 foreach($checkboxes as $checkbox) {
1324 if($data[$checkbox]) {
1325 $add[] = $checkbox;
1326 } else {
1327 $remove[] = $checkbox;
1328 }
1329 }
1330
1331 if($add)
1332 $this->addManyByCodename($add);
1333
1334 if($remove)
1335 $this->removeManyByCodename($remove);
1336 }
1337
1338
1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368
1369 function setByCheckboxSetField(CheckboxSetField $checkboxsetfield) {
1370
1371 $values = $checkboxsetfield->Value();
1372 $sourceItems = $checkboxsetfield->getSource();
1373
1374 if($sourceItems) {
1375
1376 if($values) {
1377
1378 foreach($sourceItems as $k => $item) {
1379 if(in_array($k,$values)) {
1380 $add[] = $k;
1381 } else {
1382 $remove[] = $k;
1383 }
1384 }
1385
1386
1387 } else {
1388 $remove = array_keys($sourceItems);
1389 }
1390
1391 if($add)
1392 $this->addManyByGroupID($add);
1393
1394 if($remove)
1395 $this->RemoveManyByGroupID($remove);
1396
1397 } else {
1398 USER_ERROR("Member::setByCheckboxSetField() - No source items could be found for checkboxsetfield " .
1399 $checkboxsetfield->Name(), E_USER_WARNING);
1400 }
1401 }
1402
1403
1404 1405 1406 1407 1408
1409 function addManyByGroupID($groupIds){
1410 $groups = $this->getGroupsFromIDs($groupIds);
1411 if($groups) {
1412 foreach($groups as $group) {
1413 $this->add($group);
1414 }
1415 }
1416 }
1417
1418
1419 1420 1421 1422 1423
1424 function removeManyByGroupID($groupIds) {
1425 $groups = $this->getGroupsFromIDs($groupIds);
1426 if($groups) {
1427 foreach($groups as $group) {
1428 $this->remove($group);
1429 }
1430 }
1431 }
1432
1433
1434 1435 1436 1437 1438 1439
1440 function getGroupsFromIDs($ids){
1441 if($ids && count($ids) > 1) {
1442 return DataObject::get("Group", "\"ID\" IN (" . implode(",", $ids) . ")");
1443 } else {
1444 return DataObject::get_by_id("Group", $ids[0]);
1445 }
1446 }
1447
1448
1449 1450 1451 1452 1453
1454 function addManyByCodename($codenames) {
1455 $groups = $this->codenamesToGroups($codenames);
1456 if($groups) {
1457 foreach($groups as $group){
1458 $this->add($group);
1459 }
1460 }
1461 }
1462
1463
1464 1465 1466 1467 1468
1469 function removeManyByCodename($codenames) {
1470 $groups = $this->codenamesToGroups($codenames);
1471 if($groups) {
1472 foreach($groups as $group) {
1473 $this->remove($group);
1474 }
1475 }
1476 }
1477
1478
1479 1480 1481 1482 1483 1484
1485 protected function codenamesToGroups($codenames) {
1486 $list = "'" . implode("', '", $codenames) . "'";
1487 $output = DataObject::get("Group", "\"Code\" IN ($list)");
1488
1489
1490 if(!$output || ($output->Count() != sizeof($list))) {
1491 foreach($codenames as $codename)
1492 $missing[$codename] = $codename;
1493
1494 if($output) {
1495 foreach($output as $record)
1496 unset($missing[$record->Code]);
1497 }
1498
1499 if($missing)
1500 user_error("The following group-codes aren't matched to any groups: " .
1501 implode(", ", $missing) .
1502 ". You probably need to link up the correct group codes in phpMyAdmin",
1503 E_USER_WARNING);
1504 }
1505
1506 return $output;
1507 }
1508 }
1509
1510
1511
1512 1513 1514 1515 1516
1517 class Member_ProfileForm extends Form {
1518
1519 function __construct($controller, $name, $member) {
1520 Requirements::clear();
1521 Requirements::css(CMS_DIR . '/css/typography.css');
1522 Requirements::css(CMS_DIR . '/css/cms_right.css');
1523 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/prototype/prototype.js");
1524 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
1525 Requirements::javascript(SAPPHIRE_DIR . "/javascript/prototype_improvements.js");
1526 Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/scriptaculous.js");
1527 Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/controls.js");
1528 Requirements::javascript(SAPPHIRE_DIR . "/javascript/layout_helpers.js");
1529 Requirements::css(SAPPHIRE_DIR . "/css/Form.css");
1530
1531 Requirements::css(SAPPHIRE_DIR . "/css/MemberProfileForm.css");
1532
1533
1534 $fields = singleton('Member')->getCMSFields();
1535 $fields->push(new HiddenField('ID','ID',$member->ID));
1536
1537 $actions = new FieldSet(
1538 new FormAction('dosave', _t('CMSMain.SAVE', 'Save'))
1539 );
1540
1541 $validator = new Member_Validator();
1542
1543 parent::__construct($controller, $name, $fields, $actions, $validator);
1544
1545 $this->loadDataFrom($member);
1546 }
1547
1548 function dosave($data, $form) {
1549
1550 if(!isset($data['ID']) || $data['ID'] != Member::currentUserID()) {
1551 return Director::redirectBack();
1552 }
1553
1554 $SQL_data = Convert::raw2sql($data);
1555 $member = DataObject::get_by_id("Member", $SQL_data['ID']);
1556
1557 if($SQL_data['Locale'] != $member->Locale) {
1558 $form->addErrorMessage("Generic", _t('Member.REFRESHLANG'),"good");
1559 }
1560
1561 $form->saveInto($member);
1562 $member->write();
1563
1564 $closeLink = sprintf(
1565 '<small><a href="' . $_SERVER['HTTP_REFERER'] . '" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>',
1566 _t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
1567 );
1568 $message = _t('Member.PROFILESAVESUCCESS', 'Successfully saved.') . ' ' . $closeLink;
1569 $form->sessionMessage($message, 'good');
1570
1571 Director::redirectBack();
1572 }
1573 }
1574
1575 1576 1577 1578 1579
1580 class Member_SignupEmail extends Email {
1581 protected $from = '';
1582 protected $subject = '';
1583 protected $body = '';
1584
1585 function __construct() {
1586 parent::__construct();
1587 $this->subject = _t('Member.EMAILSIGNUPSUBJECT', "Thanks for signing up");
1588 $this->body = '
1589 <h1>' . _t('Member.GREETING','Welcome') . ', $FirstName.</h1>
1590 <p>' . _t('Member.EMAILSIGNUPINTRO1','Thanks for signing up to become a new member, your details are listed below for future reference.') . '</p>
1591
1592 <p>' . _t('Member.EMAILSIGNUPINTRO2','You can login to the website using the credentials listed below') . ':
1593 <ul>
1594 <li><strong>' . _t('Member.EMAIL') . '</strong>$Email</li>
1595 <li><strong>' . _t('Member.PASSWORD') . ':</strong>$Password</li>
1596 </ul>
1597 </p>
1598
1599 <h3>' . _t('Member.CONTACTINFO','Contact Information') . '</h3>
1600 <ul>
1601 <li><strong>' . _t('Member.NAME','Name') . ':</strong> $FirstName $Surname</li>
1602 <% if Phone %>
1603 <li><strong>' . _t('Member.PHONE','Phone') . ':</strong> $Phone</li>
1604 <% end_if %>
1605
1606 <% if Mobile %>
1607 <li><strong>' . _t('Member.MOBILE','Mobile') . ':</strong> $Mobile</li>
1608 <% end_if %>
1609
1610 <li><strong>' . _t('Member.ADDRESS','Address') . ':</strong>
1611 <br/>
1612 $Number $Street $StreetType<br/>
1613 $Suburb<br/>
1614 $City $Postcode
1615 </li>
1616
1617 </ul>
1618 ';
1619 }
1620 }
1621
1622
1623
1624 1625 1626 1627 1628 1629
1630 class Member_ChangePasswordEmail extends Email {
1631 protected $from = '';
1632 protected $subject = '';
1633 protected $ss_template = 'ChangePasswordEmail';
1634
1635 function __construct() {
1636 parent::__construct();
1637 $this->subject = _t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", PR_MEDIUM, 'Email subject');
1638 }
1639 }
1640
1641
1642
1643 1644 1645 1646 1647
1648 class Member_ForgotPasswordEmail extends Email {
1649 protected $from = '';
1650 protected $subject = '';
1651 protected $ss_template = 'ForgotPasswordEmail';
1652
1653 function __construct() {
1654 parent::__construct();
1655 $this->subject = _t('Member.SUBJECTPASSWORDRESET', "Your password reset link", PR_MEDIUM, 'Email subject');
1656 }
1657 }
1658
1659 1660 1661 1662 1663
1664 class Member_Validator extends RequiredFields {
1665
1666 protected $customRequired = array('FirstName', 'Email');
1667
1668
1669 1670 1671
1672 public function __construct() {
1673 $required = func_get_args();
1674 if(isset($required[0]) && is_array($required[0])) {
1675 $required = $required[0];
1676 }
1677 $required = array_merge($required, $this->customRequired);
1678
1679 parent::__construct($required);
1680 }
1681
1682
1683 1684 1685 1686 1687 1688 1689 1690 1691 1692
1693 function php($data) {
1694 $valid = parent::php($data);
1695
1696 $identifierField = Member::get_unique_identifier_field();
1697
1698 $SQL_identifierField = Convert::raw2sql($data[$identifierField]);
1699 $member = DataObject::get_one('Member', "\"$identifierField\" = '{$SQL_identifierField}'");
1700
1701
1702 if(isset($_REQUEST['ctf']['childID'])) {
1703 $id = $_REQUEST['ctf']['childID'];
1704 } elseif(isset($_REQUEST['ctf']['ID'])) {
1705 $id = $_REQUEST['ctf']['ID'];
1706 } elseif(isset($_REQUEST['ID'])) {
1707 $id = $_REQUEST['ID'];
1708 } else {
1709 $id = null;
1710 }
1711
1712 if(is_object($member) && (($id && $member->ID != $id)||(!$id && $member->ID))) {
1713 $uniqueField = $this->form->dataFieldByName($identifierField);
1714 $this->validationError(
1715 $uniqueField->id(),
1716 sprintf(
1717 _t(
1718 'Member.VALIDATIONMEMBEREXISTS',
1719 'A member already exists with the same %s'
1720 ),
1721 strtolower(singleton('Member')->fieldLabel($identifierField))
1722 ),
1723 'required'
1724 );
1725 $valid = false;
1726 }
1727
1728
1729 if($this->extension_instances) {
1730 foreach($this->extension_instances as $extension) {
1731 if(method_exists($extension, 'hasMethod') && $extension->hasMethod('updatePHP')) {
1732 $valid &= $extension->updatePHP($data, $this->form);
1733 }
1734 }
1735 }
1736
1737 return $valid;
1738 }
1739
1740
1741 1742 1743 1744 1745 1746 1747
1748 function javascript() {
1749 $js = parent::javascript();
1750
1751
1752 if($this->extension_instances) {
1753 foreach($this->extension_instances as $extension) {
1754 if(method_exists($extension, 'hasMethod') && $extension->hasMethod('updateJavascript')) {
1755 $extension->updateJavascript($js, $this->form);
1756 }
1757 }
1758 }
1759
1760 return $js;
1761 }
1762
1763 }
1764 ?>
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-