4 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
5 * Copyright (C) 2002-2009 The Nucleus Group
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)
14 * A class representing site members
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 $
22 // 1 when authenticated, 0 when not
24 public $password; // not the actual password, but rather a MD5 hash
26 public $cookiekey; // value that should also be in the client cookie to allow authentication
34 public $admin = 0; // (either 0 or 1)
35 public $canlogin = 0; // (either 0 or 1)
37 public $autosave = 1; // if the member use the autosave draft function
40 * NOTE: $locale value obsoleted $language value since version 4.0
42 public $language = '';
46 * Constructor for a member object
48 public function MEMBER()
55 * Create a member object for a given displayname
59 public static function &createFromName($displayname)
62 $mem->readFromName($displayname);
67 * Create a member object for a given ID
71 public static function &createFromID($id)
74 $mem->readFromID($id);
78 public function readFromName($displayname)
80 return $this->read("mname='".sql_real_escape_string($displayname)."'");
83 public function readFromID($id)
85 return $this->read("mnumber=" . intval($id));
89 * Tries to login as a given user.
90 * Returns true when succeeded, returns false when failed
91 * 3.40 adds CustomLogin event
93 public function login($login, $password)
97 if ( $login == '' || $password == '' )
104 $manager->notify('CustomLogin', array('login' => &$login, 'password'=>&$password, 'success'=>&$success, 'allowlocal'=>&$allowlocal));
109 $this->loggedin = ( $this->readFromName($login) );
111 elseif ( $allowlocal )
113 $this->loggedin = ( $this->readFromName($login) && $this->checkPassword($password) );
115 return $this->loggedin;
119 * Login using cookie key
121 public function cookielogin($login, $cookiekey)
123 $this->loggedin = ( $this->readFromName($login) && $this->checkCookieKey($cookiekey) );
124 return $this->loggedin;
127 public function logout()
133 public function isLoggedIn()
135 return $this->loggedin;
139 * Read member information from the database
141 public function read($where) {
143 $query = 'SELECT * FROM '.sql_table('member') . ' WHERE ' . $where;
145 $res = sql_query($query);
146 $obj = sql_fetch_object($res);
148 $this->setRealName($obj->mrealname);
149 $this->setEmail($obj->memail);
150 $this->password = $obj->mpassword;
151 $this->setCookieKey($obj->mcookiekey);
152 $this->setURL($obj->murl);
153 $this->setDisplayName($obj->mname);
154 $this->setAdmin($obj->madmin);
155 $this->id = $obj->mnumber;
156 $this->setCanLogin($obj->mcanlogin);
157 $this->setNotes($obj->mnotes);
159 * FIXME: the name of this field should be 'mlocale', not deflang.
161 $this->setLocale($obj->deflang);
162 $this->setAutosave($obj->mautosave);
164 return sql_num_rows($res);
168 * Returns true if member is an admin for the given blog
169 * (returns false if not a team member)
171 public function isBlogAdmin($blogid)
173 $query = 'SELECT tadmin FROM '.sql_table('team').' WHERE'
174 . ' tblog=' . intval($blogid)
175 . ' and tmember='. $this->getID();
176 $res = sql_query($query);
177 if ( sql_num_rows($res) == 0 )
180 return ( sql_result($res,0,0) == 1 );
183 public function blogAdminRights($blogid)
185 return ($this->isAdmin() || $this->isBlogAdmin($blogid));
188 public function teamRights($blogid)
190 return ($this->isAdmin() || $this->isTeamMember($blogid));
194 * Returns true if this member is a team member of the given blog
196 function isTeamMember($blogid)
198 $query = 'SELECT * FROM '.sql_table('team').' WHERE'
199 . ' tblog=' . intval($blogid)
200 . ' and tmember='. $this->getID();
201 $res = sql_query($query);
202 return (sql_num_rows($res) != 0);
205 function canAddItem($catid)
209 // if this is a 'newcat' style newcat
210 // no blog admin of destination blog -> NOK
211 // blog admin of destination blog -> OK
212 if ( strstr($catid,'newcat') )
215 list($blogid) = sscanf($catid,"newcat-%d");
216 return $this->blogAdminRights($blogid);
219 // category does not exist -> NOK
220 if ( !$manager->existsCategory($catid) )
225 $blogid = getBlogIDFromCatID($catid);
227 // no team rights for blog -> NOK
228 if (!$this->teamRights($blogid))
233 // all other cases: OK
238 * Returns true if this member can edit/delete a commentitem. This can be in the
240 * - member is a super-admin
241 * - member is the author of the comment
242 * - member is admin of the blog associated with the comment
243 * - member is author of the item associated with the comment
245 public function canAlterComment($commentid)
247 if ( $this->isAdmin() )
252 $query = 'SELECT citem as itemid, iblog as blogid, cmember as cauthor, iauthor'
253 . ' FROM '.sql_table('comment') .', '.sql_table('item').', '.sql_table('blog')
254 . ' WHERE citem=inumber and iblog=bnumber and cnumber=' . intval($commentid);
255 $res = sql_query($query);
256 $obj = sql_fetch_object($res);
258 return ($obj->cauthor == $this->getID()) or $this->isBlogAdmin($obj->blogid) or ($obj->iauthor == $this->getID());
262 * Returns true if this member can edit/delete an item. This is true in the following
263 * cases: - member is a super-admin
264 * - member is the author of the item
265 * - member is admin of the the associated blog
267 public function canAlterItem($itemid)
269 if ($this->isAdmin()) return 1;
271 $query = 'SELECT iblog, iauthor FROM '.sql_table('item').' WHERE inumber=' . intval($itemid);
272 $res = sql_query($query);
273 $obj = sql_fetch_object($res);
274 return ($obj->iauthor == $this->getID()) or $this->isBlogAdmin($obj->iblog);
278 * Return true if member can be deleted. This means that there are no items
279 * posted by the member left
281 public function canBeDeleted()
283 $res = sql_query('SELECT * FROM '.sql_table('item').' WHERE iauthor=' . $this->getID());
284 return (sql_num_rows($res) == 0);
288 * returns true if this member can move/update an item to a given category,
289 * false if not (see comments fot the tests that are executed)
292 * @param newcat (can also be of form 'newcat-x' with x=blogid)
294 public function canUpdateItem($itemid, $newcat)
298 // item does not exists -> NOK
299 if ( !$manager->existsItem($itemid,1,1) )
304 // cannot alter item -> NOK
305 if (!$this->canAlterItem($itemid))
310 // if this is a 'newcat' style newcat
311 // no blog admin of destination blog -> NOK
312 // blog admin of destination blog -> OK
313 if (strstr($newcat,'newcat'))
316 list($blogid) = sscanf($newcat,'newcat-%d');
317 return $this->blogAdminRights($blogid);
320 // category does not exist -> NOK
321 if (!$manager->existsCategory($newcat))
327 $item =& $manager->getItem($itemid,1,1);
329 // old catid = new catid -> OK
330 if ($item['catid'] == $newcat)
335 // not a valid category -> NOK
336 $validCat = quickQuery('SELECT COUNT(*) AS result FROM '.sql_table('category').' WHERE catid='.intval($newcat));
342 // get destination blog
343 $source_blogid = getBlogIDFromItemID($itemid);
344 $dest_blogid = getBlogIDFromCatID($newcat);
346 // not a team member of destination blog -> NOK
347 if ( !$this->teamRights($dest_blogid) )
352 // if member is author of item -> OK
353 if ( $item['authorid'] == $this->getID() )
358 // if member has admin rights on both blogs: OK
359 if ( ($this->blogAdminRights($dest_blogid)) && ($this->blogAdminRights($source_blogid)) )
364 // all other cases: NOK
369 * Sets the cookies for the member
372 * set this to 1 when using a shared computer. Cookies will expire
373 * at the end of the session in this case.
375 public function setCookies($shared = 0)
379 if ( $CONF['SessionCookie'] || $shared )
385 $lifetime = time()+2592000;
388 setcookie($CONF['CookiePrefix'] .'user',$this->getDisplayName(),$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
389 setcookie($CONF['CookiePrefix'] .'loginkey', $this->getCookieKey(),$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
391 // make sure cookies on shared pcs don't get renewed
394 setcookie($CONF['CookiePrefix'] .'sharedpc', '1',$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
400 * MEMBER::sendActivationLink()
401 * Send activation mail
403 * @param String $type activation type
404 * @param String $extra extra info
407 public function sendActivationLink($type, $extra='')
411 if ( !isset($CONF['ActivationDays']) )
413 $CONF['ActivationDays'] = 2;
416 // generate key and URL
417 $key = $this->generateActivationEntry($type, $extra);
418 $url = $CONF['AdminURL'] . 'index.php?action=activate&key=' . $key;
420 // choose text to use in mail
424 $message = _ACTIVATE_REGISTER_MAIL;
425 $subject = _ACTIVATE_REGISTER_MAILTITLE;
428 $message = _ACTIVATE_FORGOT_MAIL;
429 $subject = _ACTIVATE_FORGOT_MAILTITLE;
431 case 'addresschange':
432 $message = _ACTIVATE_CHANGE_MAIL;
433 $subject = _ACTIVATE_CHANGE_MAILTITLE;
438 // fill out variables in text
440 'siteName' => $CONF['SiteName'],
441 'siteUrl' => $CONF['IndexURL'],
442 'memberName' => $this->getDisplayName(),
443 'activationUrl' => $url,
444 'activationDays' => $CONF['ActivationDays']
447 $message = TEMPLATE::fill($message, $aVars);
448 $subject = TEMPLATE::fill($subject, $aVars);
451 NOTIFICATION::mail($this->getEmail(), $subject ,$message, $CONF['AdminEmail'], i18n::get_current_charset());
453 ACTIONLOG::add(INFO, _ACTIONLOG_ACTIVATIONLINK . ' (' . $this->getDisplayName() . ' / type: ' . $type . ')');
458 * Returns an array of all blogids for which member has admin rights
460 public function getAdminBlogs()
464 if ($this->isAdmin())
466 $query = 'SELECT bnumber as blogid from '.sql_table('blog');
470 $query = 'SELECT tblog as blogid from '.sql_table('team').' where tadmin=1 and tmember=' . $this->getID();
473 $res = sql_query($query);
474 if ( sql_num_rows($res) > 0 )
476 while ( $obj = sql_fetch_object($res) )
478 array_push($blogs, $obj->blogid);
485 * Returns an array of all blogids for which member has team rights
487 public function getTeamBlogs($incAdmin = 1)
489 $incAdmin = intval($incAdmin);
492 if ( $this->isAdmin() && $incAdmin )
494 $query = 'SELECT bnumber as blogid from '.sql_table('blog');
498 $query = 'SELECT tblog as blogid from '.sql_table('team').' where tmember=' . $this->getID();
501 $res = sql_query($query);
502 if ( sql_num_rows($res) > 0 )
504 while ( $obj = sql_fetch_object($res) )
506 array_push($blogs, $obj->blogid);
513 * MEMBER::getNotifyFromMailAddress()
515 * Returns an email address from which notification of commenting/karma voting can
516 * be sent. A suggestion can be given for when the member is not logged in
518 * @param String $suggest
519 * @return String mail address or suggestion
521 public function getNotifyFromMailAddress($suggest = "")
524 if ( $this->isLoggedIn() )
526 return $this->getDisplayName() . " <" . $this->getEmail() . ">";
528 else if ( NOTIFICATION::address_validation($suggest) )
534 return $CONF['AdminEmail'];
539 * Write data to database
543 $query = 'UPDATE '.sql_table('member')
544 . " SET mname='" . sql_real_escape_string($this->getDisplayName()) . "',"
545 . " mrealname='". sql_real_escape_string($this->getRealName()) . "',"
546 . " mpassword='". sql_real_escape_string($this->getPassword()) . "',"
547 . " mcookiekey='". sql_real_escape_string($this->getCookieKey()) . "',"
548 . " murl='" . sql_real_escape_string($this->getURL()) . "',"
549 . " memail='" . sql_real_escape_string($this->getEmail()) . "',"
550 . " madmin=" . $this->isAdmin() . ","
551 . " mnotes='" . sql_real_escape_string($this->getNotes()) . "',"
552 . " mcanlogin=" . $this->canLogin() . ","
553 . " deflang='" . sql_real_escape_string($this->getLocale()) . "',"
554 . " mautosave=" . intval($this->getAutosave()) . ""
555 . " WHERE mnumber=" . $this->getID();
560 public function checkCookieKey($key)
562 return ( ($key != '') && ( $key == $this->getCookieKey() ) );
565 public function checkPassword($pw)
567 return (md5($pw) == $this->getPassword());
570 public function getRealName()
572 return $this->realname;
575 public function setRealName($name)
577 $this->realname = $name;
580 public function getEmail()
585 public function setEmail($email)
587 $this->email = $email;
590 public function getPassword()
592 return $this->password;
595 public function setPassword($pwd)
597 $this->password = md5($pwd);
600 public function getCookieKey()
602 return $this->cookiekey;
606 * Generate new cookiekey, save it, and return it
608 public function newCookieKey()
610 mt_srand( (double) microtime() * 1000000);
611 $this->cookiekey = md5(uniqid(mt_rand()));
613 return $this->cookiekey;
616 public function setCookieKey($val)
618 $this->cookiekey = $val;
621 public function getURL()
626 public function setURL($site)
632 * FIXME: $this->locale is always correct or not?
633 * NOTE: Deprecated, this will be obsoleted soon.
635 public function getLanguage()
637 if ( ($language = i18n::convert_locale_to_old_language_file_name($this->locale)) === FALSE )
644 public function getLocale()
646 return $this->locale;
650 * FIXME: $locale value should obsolete $language value near future
652 public function setLocale($locale)
654 if ( !!preg_match('#^(.+)_(.+)_(.+)$#', $locale)
655 && ($locale = i18n::convert_old_language_file_name_to_locale($locale)) === FALSE )
659 $this->locale = $locale;
663 public function setDisplayName($nick)
665 $this->displayname = $nick;
668 public function getDisplayName()
670 return $this->displayname;
673 public function isAdmin()
678 public function setAdmin($val)
683 public function canLogin()
685 return $this->canlogin;
688 public function setCanLogin($val)
690 $this->canlogin = $val;
693 public function getNotes()
698 public function setNotes($val)
703 public function getAutosave()
705 return $this->autosave;
708 public function setAutosave($val)
710 $this->autosave = $val;
713 public function getID()
719 * Returns true if there is a member with the given login name
723 public static function exists($name)
725 $r = sql_query('select * FROM '.sql_table('member')." WHERE mname='".sql_real_escape_string($name)."'");
726 return ( sql_num_rows($r) != 0 );
730 * Returns true if there is a member with the given ID
734 public static function existsID($id)
736 $r = sql_query('select * FROM '.sql_table('member')." WHERE mnumber='".intval($id)."'");
737 return (sql_num_rows($r) != 0);
741 * Checks if a username is protected.
742 * If so, it can not be used on anonymous comments
744 function isNameProtected($name)
747 $name = strip_tags($name);
749 return MEMBER::exists($name);
758 * @param String $name
759 * @param String $realname
760 * @param String $password
761 * @param String $email
763 * @param String $admin
764 * @param String $canlogin
765 * @param String $notes
766 * @return String 1 if success, others if fail
768 public static function create($name, $realname, $password, $email, $url, $admin, $canlogin, $notes)
770 if ( !NOTIFICATION::address_validation($email) )
772 return _ERROR_BADMAILADDRESS;
775 if ( !isValidDisplayName($name) )
777 return _ERROR_BADNAME;
780 if ( MEMBER::exists($name) )
782 return _ERROR_NICKNAMEINUSE;
787 return _ERROR_REALNAMEMISSING;
792 return _ERROR_PASSWORDMISSING;
796 * begin if: sometimes user didn't prefix the URL with http:// or https://,
797 * this cause a malformed URL. Let's fix it.
800 if ( !preg_match('#^https?://#', $url) )
802 $url = 'http://' . $url;
805 $name = sql_real_escape_string($name);
806 $realname = sql_real_escape_string($realname);
807 $password = sql_real_escape_string(md5($password));
808 $email = sql_real_escape_string($email);
809 $url = sql_real_escape_string($url);
810 $admin = intval($admin);
811 $canlogin = intval($canlogin);
812 $notes = sql_real_escape_string($notes);
814 $query = 'INSERT INTO '.sql_table('member')." (MNAME,MREALNAME,MPASSWORD,MEMAIL,MURL, MADMIN, MCANLOGIN, MNOTES) "
815 . "VALUES ('$name','$realname','$password','$email','$url',$admin, $canlogin, '$notes')";
818 ACTIONLOG::add(INFO, _ACTIONLOG_NEWMEMBER . ' ' . $name);
824 * Returns activation info for a certain key (an object with properties vkey, vmember, ...)
829 public static function getActivationInfo($key)
831 $query = 'SELECT * FROM ' . sql_table('activation') . ' WHERE vkey=\'' . sql_real_escape_string($key). '\'';
832 $res = sql_query($query);
834 if ( !$res || (sql_num_rows($res) == 0) )
840 return sql_fetch_object($res);
845 * Creates an account activation key
847 * @param $type one of the following values (determines what to do when activation expires)
848 * 'register' (new member registration)
849 * 'forgot' (forgotton password)
850 * 'addresschange' (member address has changed)
851 * @param $extra extra info (needed when validation link expires)
852 * addresschange -> old email address
855 public function generateActivationEntry($type, $extra = '')
857 // clean up old entries
858 $this->cleanupActivationTable();
860 // kill any existing entries for the current member (delete is ok)
861 // (only one outstanding activation key can be present for a member)
862 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vmember=' . intval($this->getID()));
864 // indicates if the member can log in while the link is active
865 $canLoginWhileActive = false;
869 $canLoginWhileActive = true;
873 case 'addresschange':
874 $extra = $extra . '/' . ( $this->canLogin() ? '1' : '0' );
881 // generate a random key
882 srand((double)microtime()*1000000);
883 $key = md5(uniqid(rand(), true));
885 // attempt to add entry in database
886 // add in database as non-active
887 $query = 'INSERT INTO ' . sql_table('activation'). ' (vkey, vtime, vmember, vtype, vextra) ';
888 $query .= 'VALUES (\'' . sql_real_escape_string($key). '\', \'' . date('Y-m-d H:i:s',time()) . '\', \'' . intval($this->getID()). '\', \'' . sql_real_escape_string($type). '\', \'' . sql_real_escape_string($extra). '\')';
889 if ( sql_query($query) )
893 // mark member as not allowed to log in
894 if ( !$canLoginWhileActive )
896 $this->setCanLogin(0);
905 * Inidicates that an activation link has been clicked and any forms displayed
906 * there have been successfully filled out.
909 function activate($key)
912 $info = MEMBER::getActivationInfo($key);
920 switch ( $info->vtype )
926 // set canlogin value
928 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($CONF['NewMemberCanLogon']). ' WHERE mnumber=' . intval($info->vmember));
930 case 'addresschange':
931 // reset old 'canlogin' value
932 list($oldEmail, $oldCanLogin) = i18n::explode('/', $info->vextra);
933 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ' WHERE mnumber=' . intval($info->vmember));
937 // delete from activation table
938 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vkey=\'' . sql_real_escape_string($key) . '\'');
945 * Cleans up entries in the activation table. All entries older than 2 days are removed.
950 public function cleanupActivationTable()
953 if ( isset($CONF['ActivationDays']) && intval($CONF['ActivationDays']) > 0 )
955 $actdays = intval($CONF['ActivationDays']);
959 $CONF['ActivationDays'] = 2;
961 $boundary = time() - (60 * 60 * 24 * $actdays);
963 // 1. walk over all entries, and see if special actions need to be performed
964 $res = sql_query('SELECT * FROM ' . sql_table('activation') . ' WHERE vtime < \'' . date('Y-m-d H:i:s',$boundary) . '\'');
966 while ( $o = sql_fetch_object($res) )
971 // delete all information about this site member. registration is undone because there was
972 // no timely activation
973 include_once($DIR_LIBS . 'ADMIN.php');
974 ADMIN::deleteOneMember(intval($o->vmember));
976 case 'addresschange':
977 // revert the e-mail address of the member back to old address
978 list($oldEmail, $oldCanLogin) = i18n::explode('/', $o->vextra);
979 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ', memail=\'' . sql_real_escape_string($oldEmail). '\' WHERE mnumber=' . intval($o->vmember));
982 // delete the activation link and ignore. member can request a new password using the
983 // forgot password link
988 // 2. delete activation entries for real
989 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vtime < \'' . date('Y-m-d H:i:s',$boundary) . '\'');