OSDN Git Service

e26d5db908504f18886022cdf680593577b5be03
[nucleus-jp/nucleus-next.git] / nucleus / libs / MEMBER.php
1 <?php
2
3 /* 
4  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
5  * Copyright (C) 2002-2009 The Nucleus Group
6  * 
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  * (see nucleus/documentation/index.html#license for more info)
12  */
13 /**
14  * A class representing site members
15  * 
16  * @license http://nucleuscms.org/license.txt GNU General Public License
17  * @copyright Copyright (C) 2002-2009 The Nucleus Group
18  * @version $Id: MEMBER.php 1616 2012-01-08 09:48:15Z sakamocchi $
19  */
20 class Member
21 {
22         // 1 when authenticated, 0 when not
23         public $loggedin = 0;
24         public $password;               // not the actual password, but rather a MD5 hash
25         private $algorism = 'md5';
26         
27         public $cookiekey;              // value that should also be in the client cookie to allow authentication
28         private $cookie_salt = FALSE;
29         
30         // member info
31         public $id = -1;
32         public $realname;
33         public $displayname;
34         public $email;
35         public $url;
36         public $admin = 0;                      // (either 0 or 1)
37         public $canlogin = 0;           // (either 0 or 1)
38         public $notes;
39         public $autosave = 1;           // if the member use the autosave draft function
40         public $adminskin = 0;          // if the member use the autosave draft function
41         public $bookmarklet = 0;                // if the member use the autosave draft function
42         private $locale = '';
43         
44         /**
45          * Member::__construct()
46          * Constructor for a member object
47          * 
48          * @param       Void
49          * @return      Void
50          * 
51          */
52         public function __construct()
53         {
54                 return;
55         }
56         
57         /**
58          * Member::createFromName()
59          * Create a member object for a given displayname
60          * 
61          * @static
62          * @param       String  $displayname    login name
63          * @return      Object  member object
64          * 
65          */
66         public static function &createFromName($displayname)
67         {
68                 $mem = new Member();
69                 $mem->readFromName($displayname);
70                 return $mem;
71         }
72         
73         /**
74          * Member::createFromID()
75          * Create a member object for a given ID
76          * 
77          * @static
78          * @param       Integer $id     id for member
79          */
80         public static function &createFromID($id)
81         {
82                 $mem = new Member();
83                 $mem->readFromID($id);
84                 return $mem;
85         }
86         
87         /**
88          * Member::readFromName()
89          * Read member table in database
90          * 
91          * @param       String  $displayname    login name
92          * @return      Object  SQL resource
93          * 
94          */
95         public function readFromName($displayname)
96         {
97                 return $this->read('mname='.DB::quoteValue($displayname));
98         }
99         
100         /**
101          * Member::readFromID()
102          * Read member table in database
103          * 
104          * @param       Integer $id     id for member
105          * @return      Object  SQL resource
106          * 
107          */
108         public function readFromID($id)
109         {
110                 return $this->read("mnumber=" . intval($id));
111         }
112         
113         /**
114          * Member::hash()
115          * hash the target string
116          * 
117          * @param       String  $string target string
118          * @return      Void    hashed string
119          */
120         public function hash($string)
121         {
122                 switch ( $this->algorism )
123                 {
124                         case 'md5':
125                         default:
126                                 $string = md5($string);
127                 }
128                 return $string;
129         }
130         
131         /**
132          * Member::set_cookie_salt()
133          * 
134          * @param       integer $key    secureCookieKey value
135          * @return      void
136          * 
137          */
138         private function set_cookie_salt($key = 0)
139         {
140                 if ( !$key )
141                 {
142                         $key = 24;
143                 }
144                 
145                 switch( $key )
146                 {
147                         case 8:
148                                 $this->cookie_salt = preg_replace('/\.[0-9]+\.[0-9]+\.[0-9]+$/', '', serverVar('REMOTE_ADDR'));
149                                 break;
150                         case 16:
151                                 $this->cookie_salt = preg_replace('/\.[0-9]+\.[0-9]+$/', '', serverVar('REMOTE_ADDR'));
152                                 break;
153                         case 24:
154                                 $this->cookie_salt = preg_replace('/\.[0-9]+$/', '', serverVar('REMOTE_ADDR'));
155                                 break;
156                         case 32:
157                                 $this->cookie_salt = serverVar('REMOTE_ADDR');
158                                 break;
159                         default:
160                                 $this->cookie_salt = 'none';
161                 }
162                 return;
163         }
164         
165         /**
166          * Member::login()
167          * Tries to login as a given user.
168          * Returns true when succeeded, returns false when failed
169          * 3.40 adds CustomLogin event
170          * 
171          * @param       String  $login  login name for member
172          * @param       String  $password       password for member
173          * @param       Integer $shared whether the user agent is shared or not
174          * 
175          */
176         public function login($login, $password, $shared=1)
177         {
178                 global $CONF, $errormessage, $manager;
179                 
180                 /* TODO: validation for $login, $password, $shared */
181                 if ( $login == '' || $password == '' )
182                 {
183                         return 0;
184                 }
185                 /* limiting the length of password to avoid hash collision */
186                 $password=i18n::substr($password, 0, 40);
187                 
188                 /* 
189                  * generate cookie salt from secure cookie key settings
190                 * (either 'none', 0, 8, 16, 24, or 32)
191                 */
192                 if ( !$this->cookie_salt )
193                 {
194                         $salt = 0;
195                         if ( array_key_exists('secureCookieKey', $CONF) )
196                         {
197                                 $salt = $CONF['secureCookieKey'];
198                         }
199                         $this->set_cookie_salt($salt);
200                 }
201                 
202                 $success = 0;
203                 $allowlocal = 1;
204                 $data = array('login' => &$login, 'password'=>&$password, 'success'=>&$success, 'allowlocal'=>&$allowlocal);
205                 $manager->notify('CustomLogin', $data);
206                 
207                 $this->loggedin = 0;
208                 if ( $success )
209                 {
210                         $this->loggedin = ( $this->readFromName($login) );
211                 }
212                 elseif ( $allowlocal )
213                 {
214                         $this->loggedin = ( $this->readFromName($login) && $this->checkPassword($password) );
215                 }
216                 
217                 /* login failed */
218                 if ( !$this->loggedin )
219                 {
220                         $trimlogin = trim($login);
221                         if ( empty($trimlogin) )
222                         {
223                                 $errormessage = "Please enter a username.";
224                         }
225                         else
226                         {
227                                 $errormessage = 'Login failed for ' . $login;
228                         }
229                         $data = array('username' => $login);
230                         $manager->notify('LoginFailed', $data);
231                         ActionLog::add(INFO, $errormessage);
232                 }
233                 /* login success */
234                 else
235                 {
236                         /* For lower compatibility */
237                         if ( strlen($this->password) === 32 )
238                         {
239                                 $this->password = $this->hash($password);
240                         }
241                         
242                         $this->newCookieKey();
243                         $this->setCookies($shared);
244                         
245                         if ( $this->cookie_salt !== 'none' )
246                         {
247                                 /* secure cookie key */
248                                 $this->setCookieKey($this->hash($this->getCookieKey() . $this->cookie_salt));
249                                 $this->write();
250                         }
251                         
252                         $errormessage = '';
253                         $data = array('member' => $this, 'username' => $login);
254                         $manager->notify('LoginSuccess', $data);
255                         ActionLog::add(INFO, "Login successful for $login (sharedpc=$shared)");
256                 }
257                 
258                 return $this->loggedin;
259         }
260         
261         /**
262          * Member::cookielogin()
263          * Login using cookie key
264          * 
265          * @param       String  $login  not used
266          * @param       String  $cookiekey      not used
267          * @return      Boolean login or not
268          */
269         public function cookielogin($login='', $cookiekey='')
270         {
271                 global $CONF, $manager;
272                 
273                 if ( !headers_sent() && cookieVar("{$CONF['CookiePrefix']}user") )
274                 {
275                         /* Cookie Authentication */
276                         $ck = cookieVar("{$CONF['CookiePrefix']}loginkey");
277                                 
278                         /* TODO: validation for each cookie values */
279                                 
280                         /* limiting the length of password to avoid hash collision */
281                         $ck = i18n::substr($ck,0,32);
282                                 
283                         /* 
284                          * generate cookie salt from secure cookie key settings
285                         * (either 'none', 0, 8, 16, 24, or 32)
286                         */
287                         if ( !$this->cookie_salt )
288                         {
289                                 $salt = 0;
290                                 if ( array_key_exists('secureCookieKey', $CONF) )
291                                 {
292                                         $salt = $CONF['secureCookieKey'];
293                                 }
294                                 $this->set_cookie_salt($salt);
295                         }
296                         
297                         if ( $this->cookie_salt !== 'none' )
298                         {
299                                 $ck = $this->hash($ck . $this->cookie_salt);
300                         }
301                         $this->loggedin = ( $this->readFromName(cookieVar("{$CONF['CookiePrefix']}user")) && $this->checkCookieKey($ck) );
302                         unset($ck);
303                                 
304                         /* renew cookies when not on a shared computer */
305                         if ( $this->loggedin && (cookieVar($CONF['CookiePrefix'] . 'sharedpc') != 1) )
306                         {
307                                 $this->setCookieKey(cookieVar("{$CONF['CookiePrefix']}loginkey"));
308                                 $this->setCookies();
309                         }
310                 }
311                 return $this->loggedin;
312         }
313         
314         /**
315          * Member::logout()
316          * logout and expire cookie
317          * 
318          * @param       Void
319          * @return      Void
320          */
321         public function logout()
322         {
323                 global $CONF, $manager;
324                 
325                 if ( !headers_sent() && cookieVar("{$CONF['CookiePrefix']}user") )
326                 {
327                         /* remove cookies on logout */
328                         setcookie("{$CONF['CookiePrefix']}user", '', (time() - 2592000), $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
329                         setcookie("{$CONF['CookiePrefix']}loginkey", '', (time() - 2592000), $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
330                         $data = array('username' => cookieVar("{$CONF['CookiePrefix']}user") );
331                         $manager->notify('Logout', $data);
332                 }
333                 
334                 $this->loggedin = 0;
335                 return;
336         }
337         
338         /**
339          * Member::isLoggedIn()
340          * return member is loggedin or not
341          * 
342          * @param       Void
343          * @return      Void
344          */
345         public function isLoggedIn()
346         {
347                 return $this->loggedin;
348         }
349         
350         /**
351          * MEMBER:read()
352          * Read member information from the database
353          * 
354          * @param       String  $where  where statement
355          * @return      Resource        SQL resource
356          * 
357          */
358         public function read($where)
359         {
360                 // read info
361                 $query =  'SELECT * FROM '.sql_table('member') . ' WHERE ' . $where;
362                 
363                 $row = DB::getRow($query);
364                 
365                 $this->setRealName($row['mrealname']);
366                 $this->setEmail($row['memail']);
367                 $this->password = $row['mpassword'];
368                 $this->setCookieKey($row['mcookiekey']);
369                 $this->setURL($row['murl']);
370                 $this->setDisplayName($row['mname']);
371                 $this->setAdmin($row['madmin']);
372                 $this->id = $row['mnumber'];
373                 $this->setCanLogin($row['mcanlogin']);
374                 $this->setNotes($row['mnotes']);
375                 $this->setLocale($row['mlocale']);
376                 $this->setAutosave($row['mautosave']);
377                 $this->setAdminSkin($row['madminskin']);
378                 $this->setBookmarklet($row['mbkmklt']);
379                 
380                 return $row ? TRUE : FALSE;
381         }
382         
383         /**
384          * Member::isBlogAdmin()
385          * Returns true if member is an admin for the given blog
386          * (returns false if not a team member)
387          * 
388          * @param       Integer $blogid weblog id
389          * @return      Integer weblog admin or not
390          * 
391          */
392         public function isBlogAdmin($blogid)
393         {
394                 $query = 'SELECT tadmin FROM '.sql_table('team').' WHERE'
395                 . ' tblog=' . intval($blogid)
396                 . ' and tmember='. $this->getID();
397                 $res = DB::getValue($query);
398                 if ( $res )
399                         return ($res == 1);
400                 else
401                         return 0;
402         }
403         
404         /**
405          * Member::blogAdminRights()
406          * 
407          * @param       integer $blogid ID of target weblog
408          * @return      boolean whether to have admin rights to the weblog or not
409          * 
410          */
411         public function blogAdminRights($blogid)
412         {
413                 return ($this->isAdmin() || $this->isBlogAdmin($blogid));
414         }
415         
416         /**
417          * Member::teamRights()
418          * 
419          * @param       integer $blogid ID of target weblog
420          * @return      boolean whether to have admin right to the weblog or not
421          * 
422          */
423         public function teamRights($blogid)
424         {
425                 return ($this->isAdmin() || $this->isTeamMember($blogid));
426         }
427         
428         /**
429          * Member::isTeamMember()
430          * Returns true if this member is a team member of the given blog
431          * 
432          * @param       integer $blogid ID of target weblog
433          * @return      boolean whether to join the weblog or not
434          * 
435          */
436         public function isTeamMember($blogid)
437         {
438                 $query = 'SELECT * FROM '.sql_table('team').' WHERE'
439                            . ' tblog=' . intval($blogid)
440                            . ' and tmember='. $this->getID();
441                 $res = DB::getResult($query);
442                 return ($res->rowCount() != 0);
443         }
444         
445         /**
446          * Member::canAddItem()
447          * 
448          * @param       integer $catid  ID of target category
449          * @return      boolean whether to be able to add items to the category or not
450          * 
451          */
452         public function canAddItem($catid)
453         {
454                 global $manager;
455                 
456                 // if this is a 'newcat' style newcat
457                 // no blog admin of destination blog -> NOK
458                 // blog admin of destination blog -> OK
459                 if ( i18n::strpos($catid,'newcat') === 0 )
460                 {
461                         // get blogid
462                         list($blogid) = sscanf($catid,"newcat-%d");
463                         return $this->blogAdminRights($blogid);
464                 }
465                 
466                 // category does not exist -> NOK
467                 if ( !$manager->existsCategory($catid) )
468                 {
469                         return 0;
470                 }
471                 
472                 $blogid = getBlogIDFromCatID($catid);
473                 
474                 // no team rights for blog -> NOK
475                 if (!$this->teamRights($blogid))
476                 {
477                         return 0;
478                 }
479                 
480                 // all other cases: OK
481                 return 1;
482         }
483         
484         /**
485          * Member::canAlterComment()
486          * Returns true if this member can edit/delete a commentitem. This can be in the
487          * following cases:
488          *        - member is a super-admin
489          *   - member is the author of the comment
490          *   - member is admin of the blog associated with the comment
491          *   - member is author of the item associated with the comment
492          * 
493          * @param       integer $commentid      ID of target comment
494          * @return      boolean delete/edit the comment or not
495          * 
496          */
497         public function canAlterComment($commentid)
498         {
499                 if ( $this->isAdmin() )
500                 {
501                         return 1;
502                 }
503                 
504                 $query =  'SELECT citem as itemid, iblog as blogid, cmember as cauthor, iauthor'
505                            . ' FROM '.sql_table('comment') .', '.sql_table('item').', '.sql_table('blog')
506                            . ' WHERE citem=inumber and iblog=bnumber and cnumber=' . intval($commentid);
507                 $res = DB::getRow($query);
508                 
509                 return ($res['cauthor'] == $this->getID()) or $this->isBlogAdmin($res['blogid']) or ($res['iauthor'] == $this->getID());
510         }
511         
512         /**
513          * Member::canAlterItem()
514          * Returns true if this member can edit/delete an item. This is true in the following
515          * cases: - member is a super-admin
516          *             - member is the author of the item
517          *        - member is admin of the the associated blog
518          * 
519          * @param       integer $itemid ID of target item
520          * @return      boolean delete/edit the item or not
521          * 
522          */
523         public function canAlterItem($itemid)
524         {
525                 if ($this->isAdmin()) return 1;
526                 
527                 $query =  'SELECT iblog, iauthor FROM '.sql_table('item').' WHERE inumber=' . intval($itemid);
528                 $res = DB::getRow($query);
529                 return ($res['iauthor'] == $this->getID()) or $this->isBlogAdmin($res['iblog']);
530         }
531         
532         /**
533          * Member::canBeDeleted()
534          * Return true if member can be deleted. This means that there are no items posted by the member left
535          * 
536          * @param       void
537          * @return      boolean whether there is no items or exists
538          * 
539          */
540         public function canBeDeleted()
541         {
542                 $res = DB::getResult('SELECT * FROM '.sql_table('item').' WHERE iauthor=' . $this->getID());
543                 return ( $res->rowCount() == 0 );
544         }
545         
546         /**
547          * Member::canUpdateItem()
548          * returns true if this member can move/update an item to a given category,
549          * false if not (see comments fot the tests that are executed)
550          * 
551          * @param       integer $itemid
552          * @param       string  $newcat (can also be of form 'newcat-x' with x=blogid)
553          * @return      boolean whether being able to update the item or not
554          * 
555          */
556         public function canUpdateItem($itemid, $newcat)
557         {
558                 global $manager;
559                 
560                 // item does not exists -> NOK
561                 if ( !$manager->existsItem($itemid,1,1) )
562                 {
563                         return 0;
564                 }
565                 
566                 // cannot alter item -> NOK
567                 if (!$this->canAlterItem($itemid))
568                 {
569                         return 0;
570                 }
571                 
572                 // if this is a 'newcat' style newcat
573                 // no blog admin of destination blog -> NOK
574                 // blog admin of destination blog -> OK
575                 if ( i18n::strpos($newcat, 'newcat') === 0 )
576                 {
577                         // get blogid
578                         list($blogid) = sscanf($newcat, 'newcat-%d');
579                         return $this->blogAdminRights($blogid);
580                 }
581                 
582                 // category does not exist -> NOK
583                 if (!$manager->existsCategory($newcat))
584                 {
585                         return 0;
586                 }
587                 
588                 // get item
589                 $item =& $manager->getItem($itemid,1,1);
590                 
591                 // old catid = new catid -> OK
592                 if ($item['catid'] == $newcat)
593                 {
594                         return 1;
595                 }
596                 
597                 // not a valid category -> NOK
598                 $validCat = DB::getValue('SELECT COUNT(*) AS result FROM '.sql_table('category').' WHERE catid='.intval($newcat));
599                 if ( !$validCat )
600                 {
601                         return 0;
602                 }
603                 
604                 // get destination blog
605                 $item =& $manager->getItem($itemid, 1, 1);
606                 $source_blogid = $item['blogid'];
607                 $dest_blogid = getBlogIDFromCatID($newcat);
608                 
609                 // not a team member of destination blog -> NOK
610                 if ( !$this->teamRights($dest_blogid) )
611                 {
612                         return 0;
613                 }
614                 
615                 // if member is author of item -> OK
616                 if ( $item['authorid'] == $this->getID() )
617                 {
618                         return 1;
619                 }
620                 
621                 // if member has admin rights on both blogs: OK
622                 if ( ($this->blogAdminRights($dest_blogid)) && ($this->blogAdminRights($source_blogid)) )
623                 {
624                         return 1;
625                 }
626                 
627                 // all other cases: NOK
628                 return 0;
629         }
630         
631         /**
632          * Member::setCookies()
633          * Sets the cookies for the member
634          * 
635          * @param boolean       $shared set this to 1 when using a shared computer. Cookies will expire
636          *                              at the end of the session in this case.
637          * @return      void
638          * 
639          */
640         public function setCookies($shared = 0)
641         {
642                 global $CONF;
643                 
644                 if ( $CONF['SessionCookie'] || $shared )
645                 {
646                         $lifetime = 0;
647                 }
648                 else
649                 {
650                         $lifetime = time()+2592000;
651                 }
652                 
653                 setcookie($CONF['CookiePrefix'] . 'user', $this->getDisplayName(), $lifetime, $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
654                 setcookie($CONF['CookiePrefix'] . 'loginkey', $this->getCookieKey(), $lifetime, $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
655                 
656                 // make sure cookies on shared pcs don't get renewed
657                 if ( $shared )
658                 {
659                         setcookie($CONF['CookiePrefix'] .'sharedpc', '1',$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
660                 }
661                 return;
662         }
663         
664         /**
665          * Member::sendActivationLink()
666          * Send activation mail
667          * 
668          * @param       string  $type   activation type
669          * @param       string  $extra  extra info
670          * @return      void
671          */
672         public function sendActivationLink($type, $extra='')
673         {
674                 global $CONF;
675                 
676                 if ( !isset($CONF['ActivationDays']) )
677                 {
678                         $CONF['ActivationDays'] = 2;
679                 }
680                 
681                 // generate key and URL
682                 $key = $this->generateActivationEntry($type, $extra);
683                 $url = $CONF['AdminURL'] . 'index.php?action=activate&key=' . $key;
684                 
685                 // choose text to use in mail
686                 switch ( $type )
687                 {
688                         case 'register':
689                                 $message = _ACTIVATE_REGISTER_MAIL;
690                                 $subject = _ACTIVATE_REGISTER_MAILTITLE;
691                                 break;
692                         case 'forgot':
693                                 $message = _ACTIVATE_FORGOT_MAIL;
694                                 $subject = _ACTIVATE_FORGOT_MAILTITLE;
695                                 break;
696                         case 'addresschange':
697                                 $message = _ACTIVATE_CHANGE_MAIL;
698                                 $subject = _ACTIVATE_CHANGE_MAILTITLE;
699                                 break;
700                         default;
701                 }
702                 
703                 // fill out variables in text
704                 $aVars = array(
705                         'siteName' => $CONF['SiteName'],
706                         'siteUrl' => $CONF['IndexURL'],
707                         'memberName' => $this->getDisplayName(),
708                         'activationUrl' => $url,
709                         'activationDays' => $CONF['ActivationDays']
710                 );
711                 
712                 $message = Template::fill($message, $aVars);
713                 $subject = Template::fill($subject, $aVars);
714                 
715                 // send mail
716                 NOTIFICATION::mail($this->getEmail(), $subject ,$message, $CONF['AdminEmail'], i18n::get_current_charset());
717                 
718                 ActionLog::add(INFO, _ACTIONLOG_ACTIVATIONLINK . ' (' . $this->getDisplayName() . ' / type: ' . $type . ')');
719                 return;
720         }
721         
722         /**
723          * Member::getAdminBlogs()
724          * Returns an array of all blogids for which member has admin rights
725          * 
726          * @param       void
727          * @return      array   weblog IDs in which this member has admin rights
728          * 
729          */
730         public function getAdminBlogs()
731         {
732                 $blogs = array();
733                 
734                 if ($this->isAdmin())
735                 {
736                         $query = 'SELECT bnumber as blogid from '.sql_table('blog');
737                 }
738                 else
739                 {
740                         $query = 'SELECT tblog as blogid from '.sql_table('team').' where tadmin=1 and tmember=' . $this->getID();
741                 }
742                 
743                 $res = DB::getResult($query);
744                 if ( $res->rowCount() > 0 )
745                 {
746                         foreach ( $res as $row )
747                         {
748                                 array_push($blogs, $row['blogid']);
749                         }
750                 }
751                 return $blogs;
752         }
753         
754         /**
755          * Member::getTeamBlogs()
756          * Returns an array of all blogids for which member has team rights
757          * 
758          * @param       boolean $incAdmin       whether checking weblog admin rights or not
759          * @return      array   weblog IDs in which this member join
760          * 
761          */
762         public function getTeamBlogs($incAdmin = 1)
763         {
764                 $incAdmin = intval($incAdmin);
765                 $blogs = array();
766                 
767                 if ( $this->isAdmin() && $incAdmin )
768                 {
769                         $query = 'SELECT bnumber as blogid from '.sql_table('blog');
770                 }
771                 else
772                 {
773                         $query = 'SELECT tblog as blogid from '.sql_table('team').' where tmember=' . $this->getID();
774                 }
775                 
776                 $res = DB::getResult($query);
777                 if ( $res->rowCount() > 0 )
778                 {
779                         foreach ( $res as $row )
780                         {
781                                 array_push($blogs, $row['blogid']);
782                         }
783                 }
784                 return $blogs;
785         }
786         
787         /**
788          * Member::getNotifyFromMailAddress()
789          * 
790          * Returns an email address from which notification of commenting/karma voting can
791          * be sent. A suggestion can be given for when the member is not logged in
792          * 
793          * @param       String  $suggest
794          * @return      String  mail address or suggestion
795          */
796         public function getNotifyFromMailAddress($suggest = "")
797         {
798                 global $CONF;
799                 if ( $this->isLoggedIn() )
800                 {
801                         return $this->getDisplayName() . " <" . $this->getEmail() . ">";
802                 }
803                 else if ( NOTIFICATION::address_validation($suggest) )
804                 {
805                         return $suggest;
806                 }
807                 return $CONF['AdminEmail'];
808         }
809         
810         /**
811          * Member::write()
812          * Write data to database
813          * 
814          * @param       void
815          * @return      void
816          * 
817          */
818         public function write()
819         {
820                 $query =  'UPDATE '.sql_table('member')
821                         . ' SET mname=' . DB::quoteValue($this->displayname) . ', '
822                            . 'mrealname='. DB::quoteValue($this->realname) . ', '
823                            . 'mpassword='. DB::quoteValue($this->password) . ', '
824                            . 'mcookiekey='. DB::quoteValue($this->cookiekey) . ', '
825                            . 'murl=' . DB::quoteValue($this->url) . ', '
826                            . 'memail=' . DB::quoteValue($this->email) . ', '
827                            . 'madmin=' . intval($this->admin) . ', '
828                            . 'mnotes=' . DB::quoteValue($this->notes) . ', '
829                            . 'mcanlogin=' . intval($this->canlogin) . ', '
830                            . 'mlocale=' . DB::quoteValue($this->locale) . ', '
831                            . 'madminskin=' . DB::quoteValue($this->adminskin) . ', '
832                            . 'mbkmklt=' . DB::quoteValue($this->bookmarklet) . ', '
833                            . 'mautosave=' . intval($this->autosave) . ' '
834                         . 'WHERE mnumber=' . intval($this->id);
835                 DB::execute($query);
836                 return;
837         }
838         
839         public function checkCookieKey($key)
840         {
841                 return ( ($key != '') && ( $key == $this->getCookieKey() ) );
842         }
843         
844         public function checkPassword($pw)
845         {
846                 /* for lower compatibility (md5) */
847                 if ( strlen($this->password) === 32 )
848                 {
849                         return (md5($pw) == $this->password);
850                 }
851                 return ($this->hash($pw) == $this->password);
852         }
853         
854         public function getRealName()
855         {
856                 return $this->realname;
857         }
858         
859         public function setRealName($name)
860         {
861                 $this->realname = $name;
862         }
863         
864         public function getEmail()
865         {
866                 return $this->email;
867         }
868         
869         public function setEmail($email)
870         {
871                 $this->email = $email;
872         }
873         
874         public function getPassword()
875         {
876                 return $this->password;
877         }
878         
879         public function setPassword($pwd)
880         {
881                 $this->password = $this->hash($pwd);
882         }
883         
884         public function getCookieKey()
885         {
886                 return $this->cookiekey;
887         }
888         
889         /**
890          * Member::newCookieKey()
891          * Generate new cookiekey, save it, and return it
892          * 
893          * @param       void
894          * @return      void
895          * 
896          */
897         public function newCookieKey()
898         {
899                 mt_srand( (double) microtime() * 1000000);
900                 $this->cookiekey = $this->hash(uniqid(mt_rand()));
901                 $this->write();
902                 return $this->cookiekey;
903         }
904         
905         public function setCookieKey($val)
906         {
907                 $this->cookiekey = $val;
908         }
909         
910         public function getURL()
911         {
912                 return $this->url;
913         }
914         
915         public function setURL($site)
916         {
917                 $this->url = $site;
918         }
919         
920         public function setAdminSkin($skin)
921         {
922                 $this->adminskin = $skin;
923         }
924
925         public function setBookmarklet($skin)
926         {
927                 $this->bookmarklet = $skin;
928         }
929         
930         public function getAdminSkin()
931         {
932                 return $this->adminskin;
933         }
934
935         public function getBookmarklet()
936         {
937                 return $this->bookmarklet;
938         }
939
940         public function getLocale()
941         {
942                 return $this->locale;
943         }
944         
945         public function setLocale($locale)
946         {
947                 if ( !preg_match('#^(.+)_(.+)_(.+)$#', $locale)
948                  && ($locale = i18n::convert_old_language_file_name_to_locale($locale)) === FALSE )
949                 {
950                         $locale = '';
951                 }
952                 $this->locale = $locale;
953                 return;
954         }
955         
956         public function setDisplayName($nick)
957         {
958                 $this->displayname = $nick;
959         }
960         
961         public function getDisplayName()
962         {
963                 return $this->displayname;
964         }
965         
966         public function isAdmin()
967         {
968                 return $this->admin;
969         }
970         
971         public function setAdmin($val)
972         {
973                 $this->admin = $val;
974         }
975         
976         public function canLogin()
977         {
978                 return $this->canlogin;
979         }
980         
981         public function setCanLogin($val)
982         {
983                 $this->canlogin = $val;
984         }
985         
986         public function getNotes()
987         {
988                 return $this->notes;
989         }
990         
991         public function setNotes($val)
992         {
993                 $this->notes = $val;
994         }
995         
996         public function getAutosave()
997         {
998                 return $this->autosave;
999         }
1000         
1001         public function setAutosave($val)
1002         {
1003                 $this->autosave = $val;
1004                 return;
1005         }
1006         
1007         /**
1008          * Member::getID()
1009          * 
1010          * @param       void
1011          * @return      integer id of this member object
1012          * 
1013          */
1014         public function getID()
1015         {
1016                 return $this->id;
1017         }
1018         
1019         /**
1020          * Member::exists()
1021          * Returns true if there is a member with the given login name
1022          * 
1023          * @static
1024          * @param       string  $name   target name
1025          * @return      boolean whether target name exists or not
1026          */
1027         public static function exists($name)
1028         {
1029                 $r = DB::getResult('SELECT * FROM ' . sql_table('member') . ' WHERE mname=' . DB::quoteValue($name));
1030                 return ( $r->rowCount() != 0 );
1031         }
1032         
1033         /**
1034          * Member::existsID()
1035          * Returns true if there is a member with the given ID
1036          * 
1037          * @static
1038          * @param       integer $id     target id
1039          * @return      boolean whether target id exists or not
1040          * 
1041          */
1042         public static function existsID($id)
1043         {
1044                 $r = DB::getResult('SELECT * FROM ' . sql_table('member') . ' WHERE mnumber=' . intval($id));
1045                 return ( $r->rowCount() != 0 );
1046         }
1047         
1048         /**
1049          * Member::isNameProtected()
1050          *  Checks if a username is protected.
1051          *  If so, it can not be used on anonymous comments
1052          * 
1053          * @param       string  $name   target name
1054          * @return      boolean whether the name exists or not
1055          * 
1056          */
1057         public function isNameProtected($name)
1058         {
1059                 // extract name
1060                 $name = strip_tags($name);
1061                 $name = trim($name);
1062                 return self::exists($name);
1063         }
1064         
1065         /**
1066          * Member::create()
1067          * Adds a new member
1068          * 
1069          * @static
1070          * @param       String  $name
1071          * @param       String  $realname
1072          * @param       String  $password
1073          * @param       String  $email
1074          * @param       String  $url
1075          * @param       String  $admin
1076          * @param       String  $canlogin
1077          * @param       String  $notes
1078          * @return      String  1 if success, others if fail
1079          */
1080         static public function create($name, $realname, $password, $email, $url, $admin, $canlogin, $notes)
1081         {
1082                 if ( !NOTIFICATION::address_validation($email) )
1083                 {
1084                         return _ERROR_BADMAILADDRESS;
1085                 }
1086                 
1087                 /* TODO: this method should be in MEMBER class, not globalfunctions */
1088                 if ( !isValidDisplayName($name) )
1089                 {
1090                         return _ERROR_BADNAME;
1091                 }
1092                 
1093                 if ( self::exists($name) )
1094                 {
1095                         return _ERROR_NICKNAMEINUSE;
1096                 }
1097                 
1098                 if ( !$realname )
1099                 {
1100                         return _ERROR_REALNAMEMISSING;
1101                 }
1102                 
1103                 /* TODO: check the number of characters */
1104                 if ( !$password )
1105                 {
1106                         return _ERROR_PASSWORDMISSING;
1107                 }
1108                 
1109                 /* 
1110                  *  begin if: sometimes user didn't prefix the URL with http:// or https://,
1111                  *  this cause a malformed URL. Let's fix it.
1112                  */
1113                 
1114                 if ( !preg_match('#^https?://#', $url) )
1115                 {
1116                         $url = 'http://' . $url;
1117                 }
1118                 
1119                 $name           = DB::quoteValue($name);
1120                 $realname       = DB::quoteValue($realname);
1121                 /* NOTE: hashed password is automatically updated if the length is 32 bytes when logging in */
1122                 $password       = DB::quoteValue(md5($password));
1123                 $email          = DB::quoteValue($email);
1124                 $url            = DB::quoteValue($url);
1125                 $admin          = (integer) $admin;
1126                 $canlogin       = (integer) $canlogin;
1127                 $notes          = DB::quoteValue($notes);
1128                 
1129                 $query = "INSERT INTO %s"
1130                        . " (MNAME,MREALNAME,MPASSWORD,MEMAIL,MURL, MADMIN, MCANLOGIN, MNOTES)"
1131                        . " VALUES (%s, %s, %s, %s, %s, %d, %d, %s)";
1132                 $query = sprintf($query, sql_table('member'), $name, $realname, $password, $email, $url, $admin, $canlogin, $notes);
1133                 DB::execute($query);
1134                 
1135                 ActionLog::add(INFO, _ACTIONLOG_NEWMEMBER . ' ' . $name);
1136                 
1137                 return 1;
1138         }
1139         
1140         /**
1141          * Member::getActivationInfo()
1142          * Returns activation info for a certain key (an object with properties vkey, vmember, ...)
1143          * 
1144          * @static
1145          * @param       string  $key    activation key
1146          * @return      mixed   return 0 if failed, else return activation table object
1147          * 
1148          */
1149         public static function getActivationInfo($key)
1150         {
1151                 $query = 'SELECT * FROM ' . sql_table('activation') . ' WHERE vkey=' . DB::quoteValue($key);
1152                 $res = DB::getResult($query);
1153                 
1154                 if ( !$res || ($res->rowCount() == 0) )
1155                 {
1156                         return 0;
1157                 }
1158                 return $res->fetch();
1159         }
1160         
1161         /**
1162          * Member::generateActivationEntry()
1163          * Creates an account activation key
1164          * addresschange -> old email address
1165          * 
1166          * @param       string  $type   one of the following values (determines what to do when activation expires)
1167          *                              'register'      (new member registration)
1168          *                              'forgot'        (forgotton password)
1169          *                              'addresschange' (member address has changed)
1170          * @param       string  $extra  extra info (needed when validation link expires)
1171          * @return      string  activation key
1172          */
1173         public function generateActivationEntry($type, $extra = '')
1174         {
1175                 // clean up old entries
1176                 $this->cleanupActivationTable();
1177                 
1178                 // kill any existing entries for the current member (delete is ok)
1179                 // (only one outstanding activation key can be present for a member)
1180                 DB::execute('DELETE FROM ' . sql_table('activation') . ' WHERE vmember=' . intval($this->getID()));
1181                 
1182                 // indicates if the member can log in while the link is active
1183                 $canLoginWhileActive = false;
1184                 switch ( $type )
1185                 {
1186                         case 'forgot':
1187                                 $canLoginWhileActive = true;
1188                                 break;
1189                         case 'register':
1190                                 break;
1191                         case 'addresschange':
1192                                 $extra = $extra . '/' . ( $this->canLogin() ? '1' : '0' );
1193                                 break;
1194                 }
1195                 
1196                 $ok = false;
1197                 while ( !$ok )
1198                 {
1199                         // generate a random key
1200                         srand((double)microtime()*1000000);
1201                         $key = $this->hash(uniqid(rand(), true));
1202                         
1203                         // attempt to add entry in database
1204                         // add in database as non-active
1205                         $query = 'INSERT INTO %s (vkey, vtime, vmember, vtype, vextra) VALUES (%s, %s, %d, %s, %s)';
1206                         $query = sprintf($query
1207                                 , sql_table('activation')
1208                                 , DB::quoteValue($key)
1209                                 , DB::formatDateTime()
1210                                 , intval($this->getID())
1211                                 , DB::quoteValue($type)
1212                                 , DB::quoteValue($extra)
1213                         );
1214                         if ( DB::execute($query) !== FALSE )
1215                                 $ok = true;
1216                 }
1217                 
1218                 // mark member as not allowed to log in
1219                 if ( !$canLoginWhileActive )
1220                 {
1221                         $this->setCanLogin(0);
1222                         $this->write();
1223                 }
1224                 
1225                 // return the key
1226                 return $key;
1227         }
1228         
1229         /**
1230          * Member::activate()
1231          * Inidicates that an activation link has been clicked and any forms displayed
1232          * there have been successfully filled out.
1233          * 
1234          * @param       string  $key    activation key
1235          * @return      boolean
1236          * 
1237          */
1238         public function activate($key)
1239         {
1240                 // get activate info
1241                 $info = self::getActivationInfo($key);
1242                 
1243                 // no active key
1244                 if ( !$info )
1245                 {
1246                         return false;
1247                 }
1248                 
1249                 switch ( $info['vtype'] )
1250                 {
1251                         case 'forgot':
1252                                 // nothing to do
1253                                 break;
1254                         case 'register':
1255                                 // set canlogin value
1256                                 global $CONF;
1257                                 DB::execute('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($CONF['NewMemberCanLogon']). ' WHERE mnumber=' . intval($info['vmember']));
1258                                 break;
1259                         case 'addresschange':
1260                                 // reset old 'canlogin' value
1261                                 list($oldEmail, $oldCanLogin) = preg_split('#/#', $info['vextra']);
1262                                 DB::execute('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ' WHERE mnumber=' . intval($info['vmember']));
1263                                 break;
1264                 }
1265                 
1266                 // delete from activation table
1267                 DB::execute('DELETE FROM ' . sql_table('activation') . ' WHERE vkey=' . DB::quoteValue($key));
1268                 
1269                 // success!
1270                 return true;
1271         }
1272         
1273         /**
1274          * Member::cleanupActivationTable()
1275          * Cleans up entries in the activation table. All entries older than 2 days are removed.
1276          * (static)
1277          * 
1278          * @param       void
1279          * @return      void
1280          */
1281         public function cleanupActivationTable()
1282         {
1283                 $actdays = 2;
1284                 if ( isset($CONF['ActivationDays']) && intval($CONF['ActivationDays']) > 0 )
1285                 {
1286                         $actdays = intval($CONF['ActivationDays']);
1287                 }
1288                 else
1289                 {
1290                         $CONF['ActivationDays'] = 2;
1291                 }
1292                 $boundary = time() - (60 * 60 * 24 * $actdays);
1293                 
1294                 // 1. walk over all entries, and see if special actions need to be performed
1295                 $query = sprintf('SELECT * FROM %s WHERE vtime < %s', sql_table('activation'), DB::formatDateTime($boundary));
1296                 $res = DB::getResult($query);
1297                 
1298                 foreach ( $res as $row )
1299                 {
1300                         switch ( $row['vtype'] )
1301                         {
1302                                 case 'register':
1303                                         // delete all information about this site member. registration is undone because there was
1304                                         // no timely activation
1305                                         include_once($DIR_LIBS . 'ADMIN.php');
1306                                         Admin::deleteOneMember(intval($row['vmember']));
1307                                         break;
1308                                 case 'addresschange':
1309                                         // revert the e-mail address of the member back to old address
1310                                         list($oldEmail, $oldCanLogin) = preg_split('#/#', $row['vextra']);
1311                                         DB::execute('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ', memail=' . DB::quoteValue($oldEmail). ' WHERE mnumber=' . intval($row['vmember']));
1312                                         break;
1313                                 case 'forgot':
1314                                         // delete the activation link and ignore. member can request a new password using the
1315                                         // forgot password link
1316                                         break;
1317                         }
1318                 }
1319                 
1320                 // 2. delete activation entries for real
1321                 $query = sprintf('DELETE FROM %s WHERE vtime < %s', sql_table('activation'), DB::formatDateTime($boundary));
1322                 DB::execute($query);
1323                 return;
1324         }
1325         
1326         /**
1327          * Member::$language
1328          * 
1329          * @obsolete
1330          * @param       void
1331          * @return      void
1332          * 
1333          */
1334         public $language = '';
1335         /**
1336          * Member::getLanguage()
1337          * 
1338          * @obsolete
1339          * @param       void
1340          * @return      void
1341          * 
1342          */
1343         public function getLanguage()
1344         {
1345                 if ( ($language = i18n::convert_locale_to_old_language_file_name($this->locale)) === FALSE )
1346                 {
1347                         $language = '';
1348                 }
1349                 return $language;
1350         }
1351 }