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' => 'First Name',
73 'Surname' => 'Last Name',
74 'Email' => '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 $this->extend('updateMemberFormFields', $fields);
526 return $fields;
527 }
528
529 function getValidator() {
530 $obj = new Member_Validator();
531 if (!$this->IsInDB())
532 $obj->addRequiredfield('Password');
533 $this->extend('updateValidator', $obj);
534 return $obj;
535 }
536
537
538 539 540 541 542 543
544 static function currentUser() {
545 $id = Member::currentUserID();
546 if($id) {
547 return DataObject::get_one("Member", "\"Member\".\"ID\" = $id");
548 }
549 }
550
551
552 553 554 555 556
557 static function currentUserID() {
558 $id = Session::get("loggedInAs");
559 if(!$id && !self::$_already_tried_to_auto_log_in) {
560 self::autoLogin();
561 $id = Session::get("loggedInAs");
562 }
563
564 return is_numeric($id) ? $id : 0;
565 }
566 private static $_already_tried_to_auto_log_in = false;
567
568
569 570 571 572 573 574
575 static function create_new_password() {
576 if(file_exists(Security::get_word_list())) {
577 $words = file(Security::get_word_list());
578
579 list($usec, $sec) = explode(' ', microtime());
580 srand($sec + ((float) $usec * 100000));
581
582 $word = trim($words[rand(0,sizeof($words)-1)]);
583 $number = rand(10,999);
584
585 return $word . $number;
586 } else {
587 $random = rand();
588 $string = md5($random);
589 $output = substr($string, 0, 6);
590 return $output;
591 }
592 }
593
594 595 596
597 function onBeforeWrite() {
598 if($this->SetPassword) $this->Password = $this->SetPassword;
599
600
601
602
603 $identifierField = self::$unique_identifier_field;
604 if($this->$identifierField) {
605
606 $idClause = ($this->ID) ? sprintf(" AND \"Member\".\"ID\" <> %d", (int)$this->ID) : '';
607 $existingRecord = DataObject::get_one(
608 'Member',
609 sprintf(
610 "\"%s\" = '%s' %s",
611 $identifierField,
612 Convert::raw2sql($this->$identifierField),
613 $idClause
614 )
615 );
616 if($existingRecord) {
617 throw new ValidationException(new ValidationResult(false, sprintf(
618 _t(
619 'Member.ValidationIdentifierFailed',
620 'Can\'t overwrite existing member #%d with identical identifier (%s = %s))',
621 PR_MEDIUM,
622 'The values in brackets show a fieldname mapped to a value, usually denoting an existing email address'
623 ),
624 $existingRecord->ID,
625 $identifierField,
626 $this->$identifierField
627 )));
628 }
629 }
630
631
632
633 if(
634 (Director::isLive() || Email::mailer() instanceof TestMailer)
635 && $this->isChanged('Password')
636 && $this->record['Password']
637 && Member::$notify_password_change
638 ) {
639 $this->sendInfo('changePassword');
640 }
641
642
643
644
645 if(!$this->ID || $this->isChanged('Password')) {
646
647 $encryption_details = Security::encrypt_password(
648 $this->Password,
649 $this->Salt,
650 $this->PasswordEncryption,
651 $this
652 );
653
654 $this->Password = $encryption_details['password'];
655 $this->Salt = $encryption_details['salt'];
656 $this->PasswordEncryption = $encryption_details['algorithm'];
657
658
659 if(!$this->isChanged('PasswordExpiry')) {
660
661 if(self::$password_expiry_days) {
662 $this->PasswordExpiry = date('Y-m-d', time() + 86400 * self::$password_expiry_days);
663 } else {
664 $this->PasswordExpiry = null;
665 }
666 }
667 }
668
669
670 if(!$this->Locale) {
671 $this->Locale = i18n::get_locale();
672 }
673
674 parent::onBeforeWrite();
675 }
676
677 function onAfterWrite() {
678 parent::onAfterWrite();
679
680 if($this->isChanged('Password')) {
681 MemberPassword::log($this);
682 }
683 }
684
685
686 687 688 689 690 691 692
693 public function inGroups($groups, $strict = false) {
694 if($groups) foreach($groups as $group) {
695 if($this->inGroup($group, $strict)) return true;
696 }
697
698 return false;
699 }
700
701
702 703 704 705 706 707 708
709 public function inGroup($group, $strict = false) {
710 if(is_numeric($group)) {
711 $groupCheckObj = DataObject::get_by_id('Group', $group);
712 } elseif(is_string($group)) {
713 $SQL_group = Convert::raw2sql($group);
714 $groupCheckObj = DataObject::get_one('Group', "\"Code\" = '{$SQL_group}'");
715 } elseif($group instanceof Group) {
716 $groupCheckObj = $group;
717 } else {
718 user_error('Member::inGroup(): Wrong format for $group parameter', E_USER_ERROR);
719 }
720
721 if(!$groupCheckObj) return false;
722
723 $groupCandidateObjs = ($strict) ? $this->getManyManyComponents("Groups") : $this->Groups();
724 if($groupCandidateObjs) foreach($groupCandidateObjs as $groupCandidateObj) {
725 if($groupCandidateObj->ID == $groupCheckObj->ID) return true;
726 }
727
728 return false;
729 }
730
731 732 733 734 735 736 737
738 function isAdmin() {
739 return Permission::checkMember($this, 'ADMIN');
740 }
741
742 743 744 745
746 function EditProfileLink(){
747 if($this->extend('EditProfileLink')){
748 $a = $this->extend('EditProfileLink');
749 return $a[0];
750 }
751 return 'myprofile';
752 }
753
754 755 756 757
758 static function set_title_columns($columns, $sep = ' ') {
759 if (!is_array($columns)) $columns = array($columns);
760 self::$title_format = array('columns' => $columns, 'sep' => $sep);
761 }
762
763
764
765 766 767 768 769 770 771 772 773 774
775 public function getTitle() {
776 if (self::$title_format) {
777 $values = array();
778 foreach(self::$title_format['columns'] as $col) {
779 $values[] = $this->getField($col);
780 }
781 return join(self::$title_format['sep'], $values);
782 }
783 if($this->getField('ID') === 0)
784 return $this->getField('Surname');
785 else{
786 if($this->getField('Surname') && $this->getField('FirstName')){
787 return $this->getField('Surname') . ', ' . $this->getField('FirstName');
788 }elseif($this->getField('Surname')){
789 return $this->getField('Surname');
790 }elseif($this->getField('FirstName')){
791 return $this->getField('FirstName');
792 }else{
793 return null;
794 }
795 }
796 }
797
798 799 800 801 802 803 804
805 static function get_title_sql($tableName = 'Member') {
806
807 if (self::$title_format) {
808 $columnsWithTablename = array();
809 foreach(self::$title_format['columns'] as $column) {
810 $columnsWithTablename[] = "\"$tableName\".\"$column\"";
811 }
812
813 return "(\"".join("'".self::$title_format['sep']."'" || $columnsWithTablename)."\")";
814 } else {
815 return "(\"$tableName\".\"Surname\" || ' ' || \"$tableName\".\"FirstName\")";
816 }
817 }
818
819
820 821 822 823 824
825 public function getName() {
826 return $this->FirstName . ' ' . $this->Surname;
827 }
828
829
830 831 832 833 834 835 836 837
838 public function setName($name) {
839 $nameParts = explode(' ', $name);
840 $this->Surname = array_pop($nameParts);
841 $this->FirstName = join(' ', $nameParts);
842 }
843
844
845 846 847 848 849 850
851 public function splitName($name) {
852 return $this->setName($name);
853 }
854
855
856
857
858 859 860 861 862 863 864
865 public function Groups() {
866 $groups = $this->getManyManyComponents("Groups");
867 $groupIDs = $groups->column();
868 $collatedGroups = array();
869
870 if($groups) {
871 foreach($groups as $group) {
872 $collatedGroups = array_merge((array)$collatedGroups, $group->collateAncestorIDs());
873 }
874 }
875
876 $table = "Group_Members";
877
878 if(count($collatedGroups) > 0) {
879 $collatedGroups = implode(", ", array_unique($collatedGroups));
880
881 $unfilteredGroups = singleton('Group')->instance_get("`Group`.`ID` IN ($collatedGroups)", "`Group`.`ID`", "", "", "Member_GroupSet");
882 $result = new ComponentSet();
883
884
885 $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
886 foreach($unfilteredGroups as $group) {
887 if($group->allowedIPAddress($ip)) $result->push($group);
888 }
889 } else {
890 $result = new Member_GroupSet();
891 }
892
893 $result->setComponentInfo("many-to-many", $this, "Member", $table, "Group");
894
895 return $result;
896 }
897
898
899 900 901 902 903 904 905 906 907 908
909 public function map($filter = "", $sort = "", $blank="") {
910 $ret = new SQLMap(singleton('Member')->extendedSQL($filter, $sort));
911 if($blank) {
912 $blankMember = Object::create('Member');
913 $blankMember->Surname = $blank;
914 $blankMember->ID = 0;
915
916 $ret->getItems()->shift($blankMember);
917 }
918
919 return $ret;
920 }
921
922
923 924 925 926 927 928 929 930 931 932 933
934 public static function mapInGroups($groups = null) {
935 if(!$groups)
936 return Member::map();
937
938 $groupIDList = array();
939
940 if(is_a($groups, 'DataObjectSet')) {
941 foreach( $groups as $group )
942 $groupIDList[] = $group->ID;
943 } elseif(is_array($groups)) {
944 $groupIDList = $groups;
945 } else {
946 $groupIDList[] = $groups;
947 }
948
949 if(empty($groupIDList))
950 return Member::map();
951
952 return new SQLMap(singleton('Member')->extendedSQL(
953 "\"GroupID\" IN (" . implode( ',', $groupIDList ) .
954 ")", "Surname, FirstName", "", "INNER JOIN \"Group_Members\" ON \"MemberID\"=\"Member\".\"ID\""));
955 }
956
957
958 959 960 961 962 963 964 965 966 967
968 public static function mapInCMSGroups($groups = null) {
969 if(!$groups || $groups->Count() == 0) {
970 $perms = array('ADMIN', 'CMS_ACCESS_AssetAdmin');
971
972 $cmsPerms = singleton('CMSMain')->providePermissions();
973
974 if(!empty($cmsPerms)) {
975 $perms = array_unique(array_merge($perms, array_keys($cmsPerms)));
976 }
977
978 $SQL_perms = "'" . implode("', '", Convert::raw2sql($perms)) . "'";
979
980 $groups = DataObject::get('Group', "", "",
981 "INNER JOIN \"Permission\" ON \"Permission\".\"GroupID\" = \"Group\".\"ID\" AND \"Permission\".\"Code\" IN ($SQL_perms)");
982 }
983
984 $groupIDList = array();
985
986 if(is_a($groups, 'DataObjectSet')) {
987 foreach($groups as $group) {
988 $groupIDList[] = $group->ID;
989 }
990 } elseif(is_array($groups)) {
991 $groupIDList = $groups;
992 }
993
994 $filterClause = ($groupIDList)
995 ? "\"GroupID\" IN (" . implode( ',', $groupIDList ) . ")"
996 : "";
997
998 return new SQLMap(singleton('Member')->extendedSQL($filterClause,
999 "Surname, FirstName", "",
1000 "INNER JOIN \"Group_Members\" ON \"MemberID\"=\"Member\".\"ID\" INNER JOIN \"Group\" ON \"Group\".\"ID\"=\"GroupID\""));
1001 }
1002
1003
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
1015 public function memberNotInGroups($groupList, $memberGroups = null){
1016 if(!$memberGroups) $memberGroups = $this->Groups();
1017
1018 foreach($memberGroups as $group) {
1019 if(in_array($group->Code, $groupList)) {
1020 $index = array_search($group->Code, $groupList);
1021 unset($groupList[$index]);
1022 }
1023 }
1024
1025 return $groupList;
1026 }
1027
1028
1029 1030 1031 1032 1033 1034 1035
1036 public function getCMSFields() {
1037 $fields = parent::getCMSFields();
1038
1039 $mainFields = $fields->fieldByName("Root")->fieldByName("Main")->Children;
1040
1041 $password = new ConfirmedPasswordField(
1042 'Password',
1043 null,
1044 null,
1045 null,
1046 true
1047 );
1048 $password->setCanBeEmpty(true);
1049 if(!$this->ID) $password->showOnClick = false;
1050 $mainFields->replaceField('Password', $password);
1051
1052 $mainFields->insertBefore(
1053 new HeaderField('MemberDetailsHeader',_t('Member.PERSONALDETAILS', "Personal Details", PR_MEDIUM, 'Headline for formfields')),
1054 'FirstName'
1055 );
1056
1057 $mainFields->insertBefore(
1058 new HeaderField('MemberUserDetailsHeader',_t('Member.USERDETAILS', "User Details", PR_MEDIUM, 'Headline for formfields')),
1059 'Email'
1060 );
1061
1062 $mainFields->replaceField('Locale', new DropdownField(
1063 "Locale",
1064 _t('Member.INTERFACELANG', "Interface Language", PR_MEDIUM, 'Language of the CMS'),
1065 i18n::get_existing_translations()
1066 ));
1067
1068 $mainFields->removeByName('Bounced');
1069 $mainFields->removeByName('RememberLoginToken');
1070 $mainFields->removeByName('AutoLoginHash');
1071 $mainFields->removeByName('AutoLoginExpired');
1072 $mainFields->removeByName('PasswordEncryption');
1073 $mainFields->removeByName('PasswordExpiry');
1074 $mainFields->removeByName('LockedOutUntil');
1075
1076 if(!self::$lock_out_after_incorrect_logins) {
1077 $mainFields->removeByName('FailedLoginCount');
1078 }
1079
1080 $mainFields->removeByName('Salt');
1081 $mainFields->removeByName('NumVisit');
1082 $mainFields->removeByName('LastVisited');
1083
1084 $fields->removeByName('Subscriptions');
1085
1086
1087
1088 $fields->removeByName('Groups');
1089
1090 if(Permission::check('EDIT_PERMISSIONS')) {
1091 $groupsField = new TreeMultiselectField('Groups', false, 'Group');
1092 $fields->findOrMakeTab('Root.Groups', singleton('Group')->i18n_plural_name());
1093 $fields->addFieldToTab('Root.Groups', $groupsField);
1094
1095
1096
1097
1098 if($this->ID) {
1099 $permissionsField = new PermissionCheckboxSetField_Readonly(
1100 'Permissions',
1101 singleton('Permission')->i18n_plural_name(),
1102 'Permission',
1103 'GroupID',
1104
1105 $this->getManyManyComponents('Groups')
1106 );
1107 $fields->findOrMakeTab('Root.Permissions', singleton('Permission')->i18n_plural_name());
1108 $fields->addFieldToTab('Root.Permissions', $permissionsField);
1109 }
1110 }
1111
1112 $this->extend('updateCMSFields', $fields);
1113
1114 return $fields;
1115 }
1116
1117 1118 1119 1120 1121
1122 function fieldLabels($includerelations = true) {
1123 $labels = parent::fieldLabels($includerelations);
1124
1125 $labels['FirstName'] = _t('Member.FIRSTNAME', 'First Name');
1126 $labels['Surname'] = _t('Member.SURNAME', 'Surname');
1127 $labels['Email'] = _t('Member.EMAIL', 'Email');
1128 $labels['Password'] = _t('Member.db_Password', 'Password');
1129 $labels['NumVisit'] = _t('Member.db_NumVisit', 'Number of Visits');
1130 $labels['LastVisited'] = _t('Member.db_LastVisited', 'Last Visited Date');
1131 $labels['PasswordExpiry'] = _t('Member.db_PasswordExpiry', 'Password Expiry Date', PR_MEDIUM, 'Password expiry date');
1132 $labels['LockedOutUntil'] = _t('Member.db_LockedOutUntil', 'Locked out until', PR_MEDIUM, 'Security related date');
1133 $labels['Locale'] = _t('Member.db_Locale', 'Interface Locale');
1134 $labels['Title'] = _t('Member.TITLE', 'Full Name');
1135 $labels['Created'] = _t('Member.db_Created', 'Created');
1136 if($includerelations){
1137 $labels['Groups'] = _t('Member.belongs_many_many_Groups', 'Groups', PR_MEDIUM, 'Security Groups this member belongs to');
1138 }
1139 return $labels;
1140 }
1141
1142 1143 1144 1145 1146
1147 function canView($member = null) {
1148 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
1149
1150
1151 $results = $this->extend('canView', $member);
1152 if($results && is_array($results)) {
1153 if(!min($results)) return false;
1154 else return true;
1155 }
1156
1157
1158 if($member && $this->ID == $member->ID) return true;
1159
1160 if(
1161 Permission::checkMember($member, 'ADMIN')
1162 || Permission::checkMember($member, 'CMS_ACCESS_SecurityAdmin')
1163 ) {
1164 return true;
1165 }
1166
1167 return false;
1168 }
1169
1170 1171 1172 1173
1174 function canEdit($member = null) {
1175 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
1176
1177
1178 $results = $this->extend('canEdit', $member);
1179 if($results && is_array($results)) {
1180 if(!min($results)) return false;
1181 else return true;
1182 }
1183
1184
1185 if(!($member && $member->exists())) return false;
1186
1187 return $this->canView($member);
1188 }
1189
1190 1191 1192 1193
1194 function canDelete($member = null) {
1195 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
1196
1197
1198 $results = $this->extend('canDelete', $member);
1199 if($results && is_array($results)) {
1200 if(!min($results)) return false;
1201 else return true;
1202 }
1203
1204
1205 if(!($member && $member->exists())) return false;
1206
1207 return $this->canEdit($member);
1208 }
1209
1210
1211 1212 1213
1214 function validate() {
1215 $valid = parent::validate();
1216
1217 if(!$this->ID || $this->isChanged('Password')) {
1218 if($this->Password && self::$password_validator) {
1219 $valid->combineAnd(self::$password_validator->validate($this->Password, $this));
1220 }
1221 }
1222
1223 if((!$this->ID && $this->SetPassword) || $this->isChanged('SetPassword')) {
1224 if($this->SetPassword && self::$password_validator) {
1225 $valid->combineAnd(self::$password_validator->validate($this->SetPassword, $this));
1226 }
1227 }
1228 $this->extend('updateValidation', $valid);
1229 return $valid;
1230 }
1231
1232 1233 1234 1235 1236 1237
1238 function changePassword($password) {
1239 $this->Password = $password;
1240 $valid = $this->validate();
1241
1242 if($valid->valid()) {
1243 $this->AutoLoginHash = null;
1244 $this->write();
1245 }
1246
1247 return $valid;
1248 }
1249
1250 1251 1252 1253
1254 function registerFailedLogin() {
1255 if(self::$lock_out_after_incorrect_logins) {
1256
1257 $this->FailedLoginCount = $this->FailedLoginCount + 1;
1258 $this->write();
1259
1260 if($this->FailedLoginCount >= self::$lock_out_after_incorrect_logins) {
1261 $this->LockedOutUntil = date('Y-m-d H:i:s', time() + 15*60);
1262 $this->write();
1263 }
1264 }
1265 }
1266
1267 1268 1269 1270 1271 1272 1273
1274 function getHtmlEditorConfigForCMS() {
1275 $currentName = '';
1276 $currentPriority = 0;
1277
1278 foreach($this->Groups() as $group) {
1279 $configName = $group->HtmlEditorConfig;
1280 if($configName) {
1281 $config = HtmlEditorConfig::get($group->HtmlEditorConfig);
1282 if($config && $config->getOption('priority') > $currentPriority) {
1283 $currentName = $configName;
1284 }
1285 }
1286 }
1287
1288
1289 return $currentName ? $currentName : 'cms';
1290 }
1291
1292 }
1293
1294 1295 1296 1297 1298 1299
1300 class Member_GroupSet extends ComponentSet {
1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312
1313 function setByCheckboxes(array $checkboxes, array $data) {
1314 foreach($checkboxes as $checkbox) {
1315 if($data[$checkbox]) {
1316 $add[] = $checkbox;
1317 } else {
1318 $remove[] = $checkbox;
1319 }
1320 }
1321
1322 if($add)
1323 $this->addManyByCodename($add);
1324
1325 if($remove)
1326 $this->removeManyByCodename($remove);
1327 }
1328
1329
1330 1331 1332 1333 1334 1335 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 function setByCheckboxSetField(CheckboxSetField $checkboxsetfield) {
1361
1362 $values = $checkboxsetfield->Value();
1363 $sourceItems = $checkboxsetfield->getSource();
1364
1365 if($sourceItems) {
1366
1367 if($values) {
1368
1369 foreach($sourceItems as $k => $item) {
1370 if(in_array($k,$values)) {
1371 $add[] = $k;
1372 } else {
1373 $remove[] = $k;
1374 }
1375 }
1376
1377
1378 } else {
1379 $remove = array_keys($sourceItems);
1380 }
1381
1382 if($add)
1383 $this->addManyByGroupID($add);
1384
1385 if($remove)
1386 $this->RemoveManyByGroupID($remove);
1387
1388 } else {
1389 USER_ERROR("Member::setByCheckboxSetField() - No source items could be found for checkboxsetfield " .
1390 $checkboxsetfield->Name(), E_USER_WARNING);
1391 }
1392 }
1393
1394
1395 1396 1397 1398 1399
1400 function addManyByGroupID($groupIds){
1401 $groups = $this->getGroupsFromIDs($groupIds);
1402 if($groups) {
1403 foreach($groups as $group) {
1404 $this->add($group);
1405 }
1406 }
1407 }
1408
1409
1410 1411 1412 1413 1414
1415 function removeManyByGroupID($groupIds) {
1416 $groups = $this->getGroupsFromIDs($groupIds);
1417 if($groups) {
1418 foreach($groups as $group) {
1419 $this->remove($group);
1420 }
1421 }
1422 }
1423
1424
1425 1426 1427 1428 1429 1430
1431 function getGroupsFromIDs($ids){
1432 if($ids && count($ids) > 1) {
1433 return DataObject::get("Group", "\"ID\" IN (" . implode(",", $ids) . ")");
1434 } else {
1435 return DataObject::get_by_id("Group", $ids[0]);
1436 }
1437 }
1438
1439
1440 1441 1442 1443 1444
1445 function addManyByCodename($codenames) {
1446 $groups = $this->codenamesToGroups($codenames);
1447 if($groups) {
1448 foreach($groups as $group){
1449 $this->add($group);
1450 }
1451 }
1452 }
1453
1454
1455 1456 1457 1458 1459
1460 function removeManyByCodename($codenames) {
1461 $groups = $this->codenamesToGroups($codenames);
1462 if($groups) {
1463 foreach($groups as $group) {
1464 $this->remove($group);
1465 }
1466 }
1467 }
1468
1469
1470 1471 1472 1473 1474 1475
1476 protected function codenamesToGroups($codenames) {
1477 $list = "'" . implode("', '", $codenames) . "'";
1478 $output = DataObject::get("Group", "\"Code\" IN ($list)");
1479
1480
1481 if(!$output || ($output->Count() != sizeof($list))) {
1482 foreach($codenames as $codename)
1483 $missing[$codename] = $codename;
1484
1485 if($output) {
1486 foreach($output as $record)
1487 unset($missing[$record->Code]);
1488 }
1489
1490 if($missing)
1491 user_error("The following group-codes aren't matched to any groups: " .
1492 implode(", ", $missing) .
1493 ". You probably need to link up the correct group codes in phpMyAdmin",
1494 E_USER_WARNING);
1495 }
1496
1497 return $output;
1498 }
1499 }
1500
1501
1502
1503 1504 1505 1506 1507
1508 class Member_ProfileForm extends Form {
1509
1510 function __construct($controller, $name, $member) {
1511 Requirements::clear();
1512 Requirements::css(CMS_DIR . '/css/typography.css');
1513 Requirements::css(CMS_DIR . '/css/cms_right.css');
1514 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/prototype/prototype.js");
1515 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
1516 Requirements::javascript(SAPPHIRE_DIR . "/javascript/prototype_improvements.js");
1517 Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/scriptaculous.js");
1518 Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/controls.js");
1519 Requirements::javascript(SAPPHIRE_DIR . "/javascript/layout_helpers.js");
1520 Requirements::css(SAPPHIRE_DIR . "/css/Form.css");
1521
1522 Requirements::css(SAPPHIRE_DIR . "/css/MemberProfileForm.css");
1523
1524
1525 $fields = singleton('Member')->getCMSFields();
1526 $fields->push(new HiddenField('ID','ID',$member->ID));
1527
1528 $actions = new FieldSet(
1529 new FormAction('dosave', _t('CMSMain.SAVE', 'Save'))
1530 );
1531
1532 $validator = new Member_Validator();
1533
1534 parent::__construct($controller, $name, $fields, $actions, $validator);
1535
1536 $this->loadDataFrom($member);
1537 }
1538
1539 function dosave($data, $form) {
1540
1541 if(!isset($data['ID']) || $data['ID'] != Member::currentUserID()) {
1542 return Director::redirectBack();
1543 }
1544
1545 $SQL_data = Convert::raw2sql($data);
1546 $member = DataObject::get_by_id("Member", $SQL_data['ID']);
1547
1548 if($SQL_data['Locale'] != $member->Locale) {
1549 $form->addErrorMessage("Generic", _t('Member.REFRESHLANG'),"good");
1550 }
1551
1552 $form->saveInto($member);
1553 $member->write();
1554
1555 $closeLink = sprintf(
1556 '<small><a href="' . $_SERVER['HTTP_REFERER'] . '" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>',
1557 _t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
1558 );
1559 $message = _t('Member.PROFILESAVESUCCESS', 'Successfully saved.') . ' ' . $closeLink;
1560 $form->sessionMessage($message, 'good');
1561
1562 Director::redirectBack();
1563 }
1564 }
1565
1566 1567 1568 1569 1570
1571 class Member_SignupEmail extends Email {
1572 protected $from = '';
1573 protected $subject = '';
1574 protected $body = '';
1575
1576 function __construct() {
1577 parent::__construct();
1578 $this->subject = _t('Member.EMAILSIGNUPSUBJECT', "Thanks for signing up");
1579 $this->body = '
1580 <h1>' . _t('Member.GREETING','Welcome') . ', $FirstName.</h1>
1581 <p>' . _t('Member.EMAILSIGNUPINTRO1','Thanks for signing up to become a new member, your details are listed below for future reference.') . '</p>
1582
1583 <p>' . _t('Member.EMAILSIGNUPINTRO2','You can login to the website using the credentials listed below') . ':
1584 <ul>
1585 <li><strong>' . _t('Member.EMAIL') . '</strong>$Email</li>
1586 <li><strong>' . _t('Member.PASSWORD') . ':</strong>$Password</li>
1587 </ul>
1588 </p>
1589
1590 <h3>' . _t('Member.CONTACTINFO','Contact Information') . '</h3>
1591 <ul>
1592 <li><strong>' . _t('Member.NAME','Name') . ':</strong> $FirstName $Surname</li>
1593 <% if Phone %>
1594 <li><strong>' . _t('Member.PHONE','Phone') . ':</strong> $Phone</li>
1595 <% end_if %>
1596
1597 <% if Mobile %>
1598 <li><strong>' . _t('Member.MOBILE','Mobile') . ':</strong> $Mobile</li>
1599 <% end_if %>
1600
1601 <li><strong>' . _t('Member.ADDRESS','Address') . ':</strong>
1602 <br/>
1603 $Number $Street $StreetType<br/>
1604 $Suburb<br/>
1605 $City $Postcode
1606 </li>
1607
1608 </ul>
1609 ';
1610 }
1611 }
1612
1613
1614
1615 1616 1617 1618 1619 1620
1621 class Member_ChangePasswordEmail extends Email {
1622 protected $from = '';
1623 protected $subject = '';
1624 protected $ss_template = 'ChangePasswordEmail';
1625
1626 function __construct() {
1627 parent::__construct();
1628 $this->subject = _t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", PR_MEDIUM, 'Email subject');
1629 }
1630 }
1631
1632
1633
1634 1635 1636 1637 1638
1639 class Member_ForgotPasswordEmail extends Email {
1640 protected $from = '';
1641 protected $subject = '';
1642 protected $ss_template = 'ForgotPasswordEmail';
1643
1644 function __construct() {
1645 parent::__construct();
1646 $this->subject = _t('Member.SUBJECTPASSWORDRESET', "Your password reset link", PR_MEDIUM, 'Email subject');
1647 }
1648 }
1649
1650 1651 1652 1653 1654
1655 class Member_Validator extends RequiredFields {
1656
1657 protected $customRequired = array('FirstName', 'Email');
1658
1659
1660 1661 1662
1663 public function __construct() {
1664 $required = func_get_args();
1665 if(isset($required[0]) && is_array($required[0])) {
1666 $required = $required[0];
1667 }
1668 $required = array_merge($required, $this->customRequired);
1669
1670 parent::__construct($required);
1671 }
1672
1673
1674 1675 1676 1677 1678 1679 1680 1681 1682 1683
1684 function php($data) {
1685 $valid = parent::php($data);
1686
1687 $identifierField = Member::get_unique_identifier_field();
1688
1689 $SQL_identifierField = Convert::raw2sql($data[$identifierField]);
1690 $member = DataObject::get_one('Member', "\"$identifierField\" = '{$SQL_identifierField}'");
1691
1692
1693 if(isset($_REQUEST['ctf']['childID'])) {
1694 $id = $_REQUEST['ctf']['childID'];
1695 } elseif(isset($_REQUEST['ctf']['ID'])) {
1696 $id = $_REQUEST['ctf']['ID'];
1697 } elseif(isset($_REQUEST['ID'])) {
1698 $id = $_REQUEST['ID'];
1699 } else {
1700 $id = null;
1701 }
1702
1703 if(is_object($member) && (($id && $member->ID != $id)||(!$id && $member->ID))) {
1704 $uniqueField = $this->form->dataFieldByName($identifierField);
1705 $this->validationError(
1706 $uniqueField->id(),
1707 sprintf(
1708 _t(
1709 'Member.VALIDATIONMEMBEREXISTS',
1710 'A member already exists with the same %s'
1711 ),
1712 strtolower(singleton('Member')->fieldLabel($identifierField))
1713 ),
1714 'required'
1715 );
1716 $valid = false;
1717 }
1718
1719
1720 if($this->extension_instances) {
1721 foreach($this->extension_instances as $extension) {
1722 if(method_exists($extension, 'hasMethod') && $extension->hasMethod('updatePHP')) {
1723 $valid &= $extension->updatePHP($data, $this->form);
1724 }
1725 }
1726 }
1727
1728 return $valid;
1729 }
1730
1731
1732 1733 1734 1735 1736 1737 1738
1739 function javascript() {
1740 $js = parent::javascript();
1741
1742
1743 if($this->extension_instances) {
1744 foreach($this->extension_instances as $extension) {
1745 if(method_exists($extension, 'hasMethod') && $extension->hasMethod('updateJavascript')) {
1746 $extension->updateJavascript($js, $this->form);
1747 }
1748 }
1749 }
1750
1751 return $js;
1752 }
1753
1754 }
1755 ?>
[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.
-