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)
99 $manager->notify('CustomLogin', array('login' => &$login, 'password'=>&$password, 'success'=>&$success, 'allowlocal'=>&$allowlocal));
104 $this->loggedin = ( $this->readFromName($login) );
106 elseif ( $allowlocal )
108 $this->loggedin = ( $this->readFromName($login) && $this->checkPassword($password) );
110 return $this->loggedin;
114 * Login using cookie key
116 public function cookielogin($login, $cookiekey)
118 $this->loggedin = ( $this->readFromName($login) && $this->checkCookieKey($cookiekey) );
119 return $this->loggedin;
122 public function logout()
128 public function isLoggedIn()
130 return $this->loggedin;
134 * Read member information from the database
136 public function read($where) {
138 $query = 'SELECT * FROM '.sql_table('member') . ' WHERE ' . $where;
140 $res = sql_query($query);
141 $obj = sql_fetch_object($res);
143 $this->setRealName($obj->mrealname);
144 $this->setEmail($obj->memail);
145 $this->password = $obj->mpassword;
146 $this->setCookieKey($obj->mcookiekey);
147 $this->setURL($obj->murl);
148 $this->setDisplayName($obj->mname);
149 $this->setAdmin($obj->madmin);
150 $this->id = $obj->mnumber;
151 $this->setCanLogin($obj->mcanlogin);
152 $this->setNotes($obj->mnotes);
154 * FIXME: the name of this field should be 'mlocale', not deflang.
156 $this->setLocale($obj->deflang);
157 $this->setAutosave($obj->mautosave);
159 return sql_num_rows($res);
163 * Returns true if member is an admin for the given blog
164 * (returns false if not a team member)
166 public function isBlogAdmin($blogid)
168 $query = 'SELECT tadmin FROM '.sql_table('team').' WHERE'
169 . ' tblog=' . intval($blogid)
170 . ' and tmember='. $this->getID();
171 $res = sql_query($query);
172 if ( sql_num_rows($res) == 0 )
175 return ( sql_result($res,0,0) == 1 );
178 public function blogAdminRights($blogid)
180 return ($this->isAdmin() || $this->isBlogAdmin($blogid));
183 public function teamRights($blogid)
185 return ($this->isAdmin() || $this->isTeamMember($blogid));
189 * Returns true if this member is a team member of the given blog
191 function isTeamMember($blogid)
193 $query = 'SELECT * FROM '.sql_table('team').' WHERE'
194 . ' tblog=' . intval($blogid)
195 . ' and tmember='. $this->getID();
196 $res = sql_query($query);
197 return (sql_num_rows($res) != 0);
200 function canAddItem($catid)
204 // if this is a 'newcat' style newcat
205 // no blog admin of destination blog -> NOK
206 // blog admin of destination blog -> OK
207 if ( strstr($catid,'newcat') )
210 list($blogid) = sscanf($catid,"newcat-%d");
211 return $this->blogAdminRights($blogid);
214 // category does not exist -> NOK
215 if ( !$manager->existsCategory($catid) )
220 $blogid = getBlogIDFromCatID($catid);
222 // no team rights for blog -> NOK
223 if (!$this->teamRights($blogid))
228 // all other cases: OK
233 * Returns true if this member can edit/delete a commentitem. This can be in the
235 * - member is a super-admin
236 * - member is the author of the comment
237 * - member is admin of the blog associated with the comment
238 * - member is author of the item associated with the comment
240 public function canAlterComment($commentid)
242 if ( $this->isAdmin() )
247 $query = 'SELECT citem as itemid, iblog as blogid, cmember as cauthor, iauthor'
248 . ' FROM '.sql_table('comment') .', '.sql_table('item').', '.sql_table('blog')
249 . ' WHERE citem=inumber and iblog=bnumber and cnumber=' . intval($commentid);
250 $res = sql_query($query);
251 $obj = sql_fetch_object($res);
253 return ($obj->cauthor == $this->getID()) or $this->isBlogAdmin($obj->blogid) or ($obj->iauthor == $this->getID());
257 * Returns true if this member can edit/delete an item. This is true in the following
258 * cases: - member is a super-admin
259 * - member is the author of the item
260 * - member is admin of the the associated blog
262 public function canAlterItem($itemid)
264 if ($this->isAdmin()) return 1;
266 $query = 'SELECT iblog, iauthor FROM '.sql_table('item').' WHERE inumber=' . intval($itemid);
267 $res = sql_query($query);
268 $obj = sql_fetch_object($res);
269 return ($obj->iauthor == $this->getID()) or $this->isBlogAdmin($obj->iblog);
273 * Return true if member can be deleted. This means that there are no items
274 * posted by the member left
276 public function canBeDeleted()
278 $res = sql_query('SELECT * FROM '.sql_table('item').' WHERE iauthor=' . $this->getID());
279 return (sql_num_rows($res) == 0);
283 * returns true if this member can move/update an item to a given category,
284 * false if not (see comments fot the tests that are executed)
287 * @param newcat (can also be of form 'newcat-x' with x=blogid)
289 public function canUpdateItem($itemid, $newcat)
293 // item does not exists -> NOK
294 if ( !$manager->existsItem($itemid,1,1) )
299 // cannot alter item -> NOK
300 if (!$this->canAlterItem($itemid))
305 // if this is a 'newcat' style newcat
306 // no blog admin of destination blog -> NOK
307 // blog admin of destination blog -> OK
308 if (strstr($newcat,'newcat'))
311 list($blogid) = sscanf($newcat,'newcat-%d');
312 return $this->blogAdminRights($blogid);
315 // category does not exist -> NOK
316 if (!$manager->existsCategory($newcat))
322 $item =& $manager->getItem($itemid,1,1);
324 // old catid = new catid -> OK
325 if ($item['catid'] == $newcat)
330 // not a valid category -> NOK
331 $validCat = quickQuery('SELECT COUNT(*) AS result FROM '.sql_table('category').' WHERE catid='.intval($newcat));
337 // get destination blog
338 $source_blogid = getBlogIDFromItemID($itemid);
339 $dest_blogid = getBlogIDFromCatID($newcat);
341 // not a team member of destination blog -> NOK
342 if ( !$this->teamRights($dest_blogid) )
347 // if member is author of item -> OK
348 if ( $item['authorid'] == $this->getID() )
353 // if member has admin rights on both blogs: OK
354 if ( ($this->blogAdminRights($dest_blogid)) && ($this->blogAdminRights($source_blogid)) )
359 // all other cases: NOK
364 * Sets the cookies for the member
367 * set this to 1 when using a shared computer. Cookies will expire
368 * at the end of the session in this case.
370 public function setCookies($shared = 0)
374 if ( $CONF['SessionCookie'] || $shared )
380 $lifetime = time()+2592000;
383 setcookie($CONF['CookiePrefix'] .'user',$this->getDisplayName(),$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
384 setcookie($CONF['CookiePrefix'] .'loginkey', $this->getCookieKey(),$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
386 // make sure cookies on shared pcs don't get renewed
389 setcookie($CONF['CookiePrefix'] .'sharedpc', '1',$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
394 public function sendActivationLink($type, $extra='')
398 if (!isset($CONF['ActivationDays']))
400 $CONF['ActivationDays'] = 2;
403 // generate key and URL
404 $key = $this->generateActivationEntry($type, $extra);
405 $url = $CONF['AdminURL'] . 'index.php?action=activate&key=' . $key;
407 // choose text to use in mail
411 $message = _ACTIVATE_REGISTER_MAIL;
412 $title = _ACTIVATE_REGISTER_MAILTITLE;
415 $message = _ACTIVATE_FORGOT_MAIL;
416 $title = _ACTIVATE_FORGOT_MAILTITLE;
418 case 'addresschange':
419 $message = _ACTIVATE_CHANGE_MAIL;
420 $title = _ACTIVATE_CHANGE_MAILTITLE;
425 // fill out variables in text
427 'siteName' => $CONF['SiteName'],
428 'siteUrl' => $CONF['IndexURL'],
429 'memberName' => $this->getDisplayName(),
430 'activationUrl' => $url,
431 'activationDays' => $CONF['ActivationDays']
434 $message = TEMPLATE::fill($message, $aVars);
435 $title = TEMPLATE::fill($title, $aVars);
438 i18n::mail($this->getEmail(), $title ,$message, $CONF['AdminEmail']);
440 ACTIONLOG::add(INFO, _ACTIONLOG_ACTIVATIONLINK . ' (' . $this->getDisplayName() . ' / type: ' . $type . ')');
445 * Returns an array of all blogids for which member has admin rights
447 public function getAdminBlogs()
451 if ($this->isAdmin())
453 $query = 'SELECT bnumber as blogid from '.sql_table('blog');
457 $query = 'SELECT tblog as blogid from '.sql_table('team').' where tadmin=1 and tmember=' . $this->getID();
460 $res = sql_query($query);
461 if ( sql_num_rows($res) > 0 )
463 while ( $obj = sql_fetch_object($res) )
465 array_push($blogs, $obj->blogid);
472 * Returns an array of all blogids for which member has team rights
474 public function getTeamBlogs($incAdmin = 1)
476 $incAdmin = intval($incAdmin);
479 if ( $this->isAdmin() && $incAdmin )
481 $query = 'SELECT bnumber as blogid from '.sql_table('blog');
485 $query = 'SELECT tblog as blogid from '.sql_table('team').' where tmember=' . $this->getID();
488 $res = sql_query($query);
489 if ( sql_num_rows($res) > 0 )
491 while ( $obj = sql_fetch_object($res) )
493 array_push($blogs, $obj->blogid);
500 * Returns an email address from which notification of commenting/karma voting can
501 * be sent. A suggestion can be given for when the member is not logged in
503 public function getNotifyFromMailAddress($suggest = "")
506 if ( $this->isLoggedIn() )
508 return $this->getDisplayName() . " <" . $this->getEmail() . ">";
510 else if ( isValidMailAddress($suggest) )
516 return $CONF['AdminEmail'];
521 * Write data to database
525 $query = 'UPDATE '.sql_table('member')
526 . " SET mname='" . sql_real_escape_string($this->getDisplayName()) . "',"
527 . " mrealname='". sql_real_escape_string($this->getRealName()) . "',"
528 . " mpassword='". sql_real_escape_string($this->getPassword()) . "',"
529 . " mcookiekey='". sql_real_escape_string($this->getCookieKey()) . "',"
530 . " murl='" . sql_real_escape_string($this->getURL()) . "',"
531 . " memail='" . sql_real_escape_string($this->getEmail()) . "',"
532 . " madmin=" . $this->isAdmin() . ","
533 . " mnotes='" . sql_real_escape_string($this->getNotes()) . "',"
534 . " mcanlogin=" . $this->canLogin() . ","
535 . " deflang='" . sql_real_escape_string($this->getLocale()) . "',"
536 . " mautosave=" . intval($this->getAutosave()) . ""
537 . " WHERE mnumber=" . $this->getID();
542 public function checkCookieKey($key)
544 return ( ($key != '') && ( $key == $this->getCookieKey() ) );
547 public function checkPassword($pw)
549 return (md5($pw) == $this->getPassword());
552 public function getRealName()
554 return $this->realname;
557 public function setRealName($name)
559 $this->realname = $name;
562 public function getEmail()
567 public function setEmail($email)
569 $this->email = $email;
572 public function getPassword()
574 return $this->password;
577 public function setPassword($pwd)
579 $this->password = md5($pwd);
582 public function getCookieKey()
584 return $this->cookiekey;
588 * Generate new cookiekey, save it, and return it
590 public function newCookieKey()
592 mt_srand( (double) microtime() * 1000000);
593 $this->cookiekey = md5(uniqid(mt_rand()));
595 return $this->cookiekey;
598 public function setCookieKey($val)
600 $this->cookiekey = $val;
603 public function getURL()
608 public function setURL($site)
614 * FIXME: $this->locale is always correct or not?
615 * NOTE: Deprecated, this will be obsoleted soon.
617 public function getLanguage()
619 if ( ($language = i18n::convert_locale_to_old_language_file_name($this->locale)) === FALSE )
626 public function getLocale()
628 return $this->locale;
632 * FIXME: $locale value should obsolete $language value near future
634 public function setLocale($locale)
636 if ( !!preg_match('#^(.+)_(.+)_(.+)$#', $locale)
637 && ($locale = i18n::convert_old_language_file_name_to_locale($locale)) === FALSE )
641 $this->locale = $locale;
645 public function setDisplayName($nick)
647 $this->displayname = $nick;
650 public function getDisplayName()
652 return $this->displayname;
655 public function isAdmin()
660 public function setAdmin($val)
665 public function canLogin()
667 return $this->canlogin;
670 public function setCanLogin($val)
672 $this->canlogin = $val;
675 public function getNotes()
680 public function setNotes($val)
685 public function getAutosave()
687 return $this->autosave;
690 public function setAutosave($val)
692 $this->autosave = $val;
695 public function getID()
701 * Returns true if there is a member with the given login name
705 public static function exists($name)
707 $r = sql_query('select * FROM '.sql_table('member')." WHERE mname='".sql_real_escape_string($name)."'");
708 return ( sql_num_rows($r) != 0 );
712 * Returns true if there is a member with the given ID
716 public static function existsID($id)
718 $r = sql_query('select * FROM '.sql_table('member')." WHERE mnumber='".intval($id)."'");
719 return (sql_num_rows($r) != 0);
723 * Checks if a username is protected.
724 * If so, it can not be used on anonymous comments
726 function isNameProtected($name)
729 $name = strip_tags($name);
731 return MEMBER::exists($name);
739 public static function create($name, $realname, $password, $email, $url, $admin, $canlogin, $notes)
741 if ( !isValidMailAddress($email) )
743 return _ERROR_BADMAILADDRESS;
746 if ( !isValidDisplayName($name) )
748 return _ERROR_BADNAME;
751 if ( MEMBER::exists($name) )
753 return _ERROR_NICKNAMEINUSE;
758 return _ERROR_REALNAMEMISSING;
763 return _ERROR_PASSWORDMISSING;
766 // begin if: sometimes user didn't prefix the URL with http:// or https://, this cause a malformed URL. Let's fix it.
767 if (!preg_match('#^https?://#', $url) )
769 $url = 'http://' . $url;
772 $name = sql_real_escape_string($name);
773 $realname = sql_real_escape_string($realname);
774 $password = sql_real_escape_string(md5($password));
775 $email = sql_real_escape_string($email);
776 $url = sql_real_escape_string($url);
777 $admin = intval($admin);
778 $canlogin = intval($canlogin);
779 $notes = sql_real_escape_string($notes);
781 $query = 'INSERT INTO '.sql_table('member')." (MNAME,MREALNAME,MPASSWORD,MEMAIL,MURL, MADMIN, MCANLOGIN, MNOTES) "
782 . "VALUES ('$name','$realname','$password','$email','$url',$admin, $canlogin, '$notes')";
785 ACTIONLOG::add(INFO, _ACTIONLOG_NEWMEMBER . ' ' . $name);
791 * Returns activation info for a certain key (an object with properties vkey, vmember, ...)
796 public static function getActivationInfo($key)
798 $query = 'SELECT * FROM ' . sql_table('activation') . ' WHERE vkey=\'' . sql_real_escape_string($key). '\'';
799 $res = sql_query($query);
801 if ( !$res || (sql_num_rows($res) == 0) )
807 return sql_fetch_object($res);
812 * Creates an account activation key
814 * @param $type one of the following values (determines what to do when activation expires)
815 * 'register' (new member registration)
816 * 'forgot' (forgotton password)
817 * 'addresschange' (member address has changed)
818 * @param $extra extra info (needed when validation link expires)
819 * addresschange -> old email address
822 public function generateActivationEntry($type, $extra = '')
824 // clean up old entries
825 $this->cleanupActivationTable();
827 // kill any existing entries for the current member (delete is ok)
828 // (only one outstanding activation key can be present for a member)
829 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vmember=' . intval($this->getID()));
831 // indicates if the member can log in while the link is active
832 $canLoginWhileActive = false;
836 $canLoginWhileActive = true;
840 case 'addresschange':
841 $extra = $extra . '/' . ( $this->canLogin() ? '1' : '0' );
848 // generate a random key
849 srand((double)microtime()*1000000);
850 $key = md5(uniqid(rand(), true));
852 // attempt to add entry in database
853 // add in database as non-active
854 $query = 'INSERT INTO ' . sql_table('activation'). ' (vkey, vtime, vmember, vtype, vextra) ';
855 $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). '\')';
856 if ( sql_query($query) )
860 // mark member as not allowed to log in
861 if ( !$canLoginWhileActive )
863 $this->setCanLogin(0);
872 * Inidicates that an activation link has been clicked and any forms displayed
873 * there have been successfully filled out.
876 function activate($key)
879 $info = MEMBER::getActivationInfo($key);
887 switch ( $info->vtype )
893 // set canlogin value
895 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($CONF['NewMemberCanLogon']). ' WHERE mnumber=' . intval($info->vmember));
897 case 'addresschange':
898 // reset old 'canlogin' value
899 list($oldEmail, $oldCanLogin) = i18n::explode('/', $info->vextra);
900 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ' WHERE mnumber=' . intval($info->vmember));
904 // delete from activation table
905 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vkey=\'' . sql_real_escape_string($key) . '\'');
912 * Cleans up entries in the activation table. All entries older than 2 days are removed.
917 public function cleanupActivationTable()
920 if ( isset($CONF['ActivationDays']) && intval($CONF['ActivationDays']) > 0 )
922 $actdays = intval($CONF['ActivationDays']);
926 $CONF['ActivationDays'] = 2;
928 $boundary = time() - (60 * 60 * 24 * $actdays);
930 // 1. walk over all entries, and see if special actions need to be performed
931 $res = sql_query('SELECT * FROM ' . sql_table('activation') . ' WHERE vtime < \'' . date('Y-m-d H:i:s',$boundary) . '\'');
933 while ( $o = sql_fetch_object($res) )
938 // delete all information about this site member. registration is undone because there was
939 // no timely activation
940 include_once($DIR_LIBS . 'ADMIN.php');
941 ADMIN::deleteOneMember(intval($o->vmember));
943 case 'addresschange':
944 // revert the e-mail address of the member back to old address
945 list($oldEmail, $oldCanLogin) = i18n::explode('/', $o->vextra);
946 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ', memail=\'' . sql_real_escape_string($oldEmail). '\' WHERE mnumber=' . intval($o->vmember));
949 // delete the activation link and ignore. member can request a new password using the
950 // forgot password link
955 // 2. delete activation entries for real
956 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vtime < \'' . date('Y-m-d H:i:s',$boundary) . '\'');