4 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
5 * Copyright (C) 2002-2012 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-2012 The Nucleus Group
19 * $NucleusJP: MEMBER.php,v 1.6 2006/07/17 20:03:44 kimitake Exp $
23 // 1 when authenticated, 0 when not
25 var $password; // not the actual password, but rather a MD5 hash
27 var $cookiekey; // value that should also be in the client cookie to allow authentication
35 var $language = ''; // name of the language file to use (e.g. 'english' -> english.php)
36 var $admin = 0; // (either 0 or 1)
37 var $canlogin = 0; // (either 0 or 1)
39 var $autosave = 1; // if the member use the autosave draft function
42 * Constructor for a member object
49 * Create a member object for a given displayname
53 function &createFromName($displayname) {
55 $mem->readFromName($displayname);
60 * Create a member object for a given ID
64 function &createFromID($id) {
66 $mem->readFromID($id);
70 function readFromName($displayname) {
71 return $this->read("mname='".sql_real_escape_string($displayname)."'");
74 function readFromID($id) {
75 return $this->read("mnumber=" . intval($id));
79 * Tries to login as a given user.
80 * Returns true when succeeded, returns false when failed
81 * 3.40 adds CustomLogin event
83 function login($login, $password) {
90 'password' =>&$password,
91 'success' =>&$success,
92 'allowlocal' =>&$allowlocal
94 $manager->notify('CustomLogin', $param);
95 if ($success && $this->readFromName($login)) {
97 return $this->isLoggedIn();
98 } elseif (!$success && $allowlocal) {
99 if (!$this->readFromName($login))
101 if (!$this->checkPassword($password))
104 return $this->isLoggedIn();
111 * Login using cookie key
113 function cookielogin($login, $cookiekey) {
115 if (!$this->readFromName($login))
117 if (!$this->checkCookieKey($cookiekey))
120 return $this->isLoggedIn();
127 function isLoggedIn() {
128 return $this->loggedin;
132 * Read member information from the database
134 function read($where) {
136 $query = 'SELECT * FROM '.sql_table('member') . ' WHERE ' . $where;
138 $res = sql_query($query);
139 $obj = sql_fetch_object($res);
141 $this->setRealName($obj->mrealname);
142 $this->setEmail($obj->memail);
143 $this->password = $obj->mpassword;
144 $this->setCookieKey($obj->mcookiekey);
145 $this->setURL($obj->murl);
146 $this->setDisplayName($obj->mname);
147 $this->setAdmin($obj->madmin);
148 $this->id = $obj->mnumber;
149 $this->setCanLogin($obj->mcanlogin);
150 $this->setNotes($obj->mnotes);
151 $this->setLanguage($obj->deflang);
152 $this->setAutosave($obj->mautosave);
154 return sql_num_rows($res);
159 * Returns true if member is an admin for the given blog
160 * (returns false if not a team member)
162 function isBlogAdmin($blogid) {
163 $query = 'SELECT tadmin FROM '.sql_table('team').' WHERE'
164 . ' tblog=' . intval($blogid)
165 . ' and tmember='. $this->getID();
166 $res = sql_query($query);
167 if (sql_num_rows($res) == 0)
170 return (sql_result($res,0,0) == 1) ;
173 function blogAdminRights($blogid) {
174 return ($this->isAdmin() || $this->isBlogAdmin($blogid));
178 function teamRights($blogid) {
179 return ($this->isAdmin() || $this->isTeamMember($blogid));
183 * Returns true if this member is a team member of the given blog
185 function isTeamMember($blogid) {
186 $query = 'SELECT * FROM '.sql_table('team').' WHERE'
187 . ' tblog=' . intval($blogid)
188 . ' and tmember='. $this->getID();
189 $res = sql_query($query);
190 return (sql_num_rows($res) != 0);
193 function canAddItem($catid) {
196 // if this is a 'newcat' style newcat
197 // no blog admin of destination blog -> NOK
198 // blog admin of destination blog -> OK
199 if (strstr($catid,'newcat')) {
201 list($blogid) = sscanf($catid,"newcat-%d");
202 return $this->blogAdminRights($blogid);
205 // category does not exist -> NOK
206 if (!$manager->existsCategory($catid)) return 0;
208 $blogid = getBlogIDFromCatID($catid);
210 // no team rights for blog -> NOK
211 if (!$this->teamRights($blogid)) return 0;
213 // all other cases: OK
218 * Returns true if this member can edit/delete a commentitem. This can be in the
220 * - member is a super-admin
221 * - member is the author of the comment
222 * - member is admin of the blog associated with the comment
223 * - member is author of the item associated with the comment
225 function canAlterComment($commentid) {
226 if ($this->isAdmin()) return 1;
228 $query = 'SELECT citem as itemid, iblog as blogid, cmember as cauthor, iauthor'
229 . ' FROM '.sql_table('comment') .', '.sql_table('item').', '.sql_table('blog')
230 . ' WHERE citem=inumber and iblog=bnumber and cnumber=' . intval($commentid);
231 $res = sql_query($query);
232 $obj = sql_fetch_object($res);
234 return ($obj->cauthor == $this->getID()) or $this->isBlogAdmin($obj->blogid) or ($obj->iauthor == $this->getID());
238 * Returns true if this member can edit/delete an item. This is true in the following
239 * cases: - member is a super-admin
240 * - member is the author of the item
241 * - member is admin of the the associated blog
243 function canAlterItem($itemid) {
244 if ($this->isAdmin()) return 1;
246 $query = 'SELECT iblog, iauthor FROM '.sql_table('item').' WHERE inumber=' . intval($itemid);
247 $res = sql_query($query);
248 $obj = sql_fetch_object($res);
249 return ($obj->iauthor == $this->getID()) or $this->isBlogAdmin($obj->iblog);
253 * Return true if member can be deleted. This means that there are no items
254 * posted by the member left
256 function canBeDeleted() {
257 $res = sql_query('SELECT * FROM '.sql_table('item').' WHERE iauthor=' . $this->getID());
258 return (sql_num_rows($res) == 0);
262 * returns true if this member can move/update an item to a given category,
263 * false if not (see comments fot the tests that are executed)
266 * @param newcat (can also be of form 'newcat-x' with x=blogid)
268 function canUpdateItem($itemid, $newcat) {
271 // item does not exists -> NOK
272 if (!$manager->existsItem($itemid,1,1)) return 0;
274 // cannot alter item -> NOK
275 if (!$this->canAlterItem($itemid)) return 0;
277 // if this is a 'newcat' style newcat
278 // no blog admin of destination blog -> NOK
279 // blog admin of destination blog -> OK
280 if (strstr($newcat,'newcat')) {
282 list($blogid) = sscanf($newcat,'newcat-%d');
283 return $this->blogAdminRights($blogid);
286 // category does not exist -> NOK
287 if (!$manager->existsCategory($newcat)) return 0;
291 $item =& $manager->getItem($itemid,1,1);
293 // old catid = new catid -> OK
294 if ($item['catid'] == $newcat) return 1;
296 // not a valid category -> NOK
297 $validCat = quickQuery('SELECT COUNT(*) AS result FROM '.sql_table('category').' WHERE catid='.intval($newcat));
298 if (!$validCat) return 0;
300 // get destination blog
301 $source_blogid = getBlogIDFromItemID($itemid);
302 $dest_blogid = getBlogIDFromCatID($newcat);
304 // not a team member of destination blog -> NOK
305 if (!$this->teamRights($dest_blogid)) return 0;
307 // if member is author of item -> OK
308 if ($item['authorid'] == $this->getID()) return 1;
310 // if member has admin rights on both blogs: OK
311 if (($this->blogAdminRights($dest_blogid)) && ($this->blogAdminRights($source_blogid))) return 1;
313 // all other cases: NOK
319 * Sets the cookies for the member
322 * set this to 1 when using a shared computer. Cookies will expire
323 * at the end of the session in this case.
325 function setCookies($shared = 0) {
328 if ($CONF['SessionCookie'] || $shared)
331 $lifetime = (time()+2592000);
333 setcookie($CONF['CookiePrefix'] .'user',$this->getDisplayName(),$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
334 setcookie($CONF['CookiePrefix'] .'loginkey', $this->getCookieKey(),$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
336 // make sure cookies on shared pcs don't get renewed
338 setcookie($CONF['CookiePrefix'] .'sharedpc', '1',$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
341 function sendActivationLink($type, $extra='')
345 if (!isset($CONF['ActivationDays'])) $CONF['ActivationDays'] = 2;
347 // generate key and URL
348 $key = $this->generateActivationEntry($type, $extra);
349 $url = $CONF['AdminURL'] . 'index.php?action=activate&key=' . $key;
351 // choose text to use in mail
355 $message = _ACTIVATE_REGISTER_MAIL;
356 $title = _ACTIVATE_REGISTER_MAILTITLE;
359 $message = _ACTIVATE_FORGOT_MAIL;
360 $title = _ACTIVATE_FORGOT_MAILTITLE;
362 case 'addresschange':
363 $message = _ACTIVATE_CHANGE_MAIL;
364 $title = _ACTIVATE_CHANGE_MAILTITLE;
369 // fill out variables in text
372 'siteName' => $CONF['SiteName'],
373 'siteUrl' => $CONF['IndexURL'],
374 'memberName' => $this->getDisplayName(),
375 'activationUrl' => $url,
376 'activationDays' => $CONF['ActivationDays']
379 $message = TEMPLATE::fill($message, $aVars);
380 $title = TEMPLATE::fill($title, $aVars);
385 mb_internal_encoding(_CHARSET);
386 @mb_send_mail($this->getEmail(), $title ,$message,'From: ' . $CONF['AdminEmail']);
388 ACTIONLOG::add(INFO, _ACTIONLOG_ACTIVATIONLINK . ' (' . $this->getDisplayName() . ' / type: ' . $type . ')');
394 * Returns an array of all blogids for which member has admin rights
396 function getAdminBlogs() {
399 if ($this->isAdmin())
400 $query = 'SELECT bnumber as blogid from '.sql_table('blog');
402 $query = 'SELECT tblog as blogid from '.sql_table('team').' where tadmin=1 and tmember=' . $this->getID();
404 $res = sql_query($query);
405 if (sql_num_rows($res) > 0) {
406 while ($obj = sql_fetch_object($res)) {
407 array_push($blogs, $obj->blogid);
415 * Returns an array of all blogids for which member has team rights
417 function getTeamBlogs($incAdmin = 1) {
418 $incAdmin = intval($incAdmin);
421 if ($this->isAdmin() && $incAdmin)
422 $query = 'SELECT bnumber as blogid from '.sql_table('blog');
424 $query = 'SELECT tblog as blogid from '.sql_table('team').' where tmember=' . $this->getID();
426 $res = sql_query($query);
427 if (sql_num_rows($res) > 0) {
428 while ($obj = sql_fetch_object($res)) {
429 array_push($blogs, $obj->blogid);
437 * Returns an email address from which notification of commenting/karma voting can
438 * be sent. A suggestion can be given for when the member is not logged in
440 function getNotifyFromMailAddress($suggest = "") {
442 if ($this->isLoggedIn()) {
443 return $this->getDisplayName() . " <" . $this->getEmail() . ">";
444 } else if (isValidMailAddress($suggest)) {
447 return $CONF['AdminEmail'];
452 * Write data to database
456 $query = 'UPDATE '.sql_table('member')
457 . " SET mname='" . sql_real_escape_string($this->getDisplayName()) . "',"
458 . " mrealname='". sql_real_escape_string($this->getRealName()) . "',"
459 . " mpassword='". sql_real_escape_string($this->getPassword()) . "',"
460 . " mcookiekey='". sql_real_escape_string($this->getCookieKey()) . "',"
461 . " murl='" . sql_real_escape_string($this->getURL()) . "',"
462 . " memail='" . sql_real_escape_string($this->getEmail()) . "',"
463 . " madmin=" . $this->isAdmin() . ","
464 . " mnotes='" . sql_real_escape_string($this->getNotes()) . "',"
465 . " mcanlogin=" . $this->canLogin() . ","
466 . " deflang='" . sql_real_escape_string($this->getLanguage()) . "',"
467 . " mautosave=" . intval($this->getAutosave()) . ""
468 . " WHERE mnumber=" . $this->getID();
472 function checkCookieKey($key) {
473 return (($key != '') && ($key == $this->getCookieKey()));
476 function checkPassword($pw) {
477 return (md5($pw) == $this->getPassword());
480 function getRealName() {
481 return $this->realname;
484 function setRealName($name) {
485 $this->realname = $name;
488 function getEmail() {
492 function setEmail($email) {
493 $this->email = $email;
496 function getPassword() {
497 return $this->password;
500 function setPassword($pwd) {
501 $this->password = md5($pwd);
504 function getCookieKey() {
505 return $this->cookiekey;
509 * Generate new cookiekey, save it, and return it
511 function newCookieKey() {
512 mt_srand( (double) microtime() * 1000000);
513 $this->cookiekey = md5(uniqid(mt_rand()));
515 return $this->cookiekey;
518 function setCookieKey($val) {
519 $this->cookiekey = $val;
526 function setURL($site) {
530 function getLanguage() {
531 return $this->language;
534 function setLanguage($lang) {
535 $this->language = $lang;
538 function setDisplayName($nick) {
539 $this->displayname = $nick;
542 function getDisplayName() {
543 return $this->displayname;
550 function setAdmin($val) {
554 function canLogin() {
555 return $this->canlogin;
558 function setCanLogin($val) {
559 $this->canlogin = $val;
562 function getNotes() {
566 function setNotes($val) {
570 function getAutosave() {
571 return $this->autosave;
574 function setAutosave($val) {
575 $this->autosave = $val;
583 * Returns true if there is a member with the given login name
587 function exists($name) {
588 $r = sql_query('select * FROM '.sql_table('member')." WHERE mname='".sql_real_escape_string($name)."'");
589 return (sql_num_rows($r) != 0);
593 * Returns true if there is a member with the given ID
597 function existsID($id) {
598 $r = sql_query('select * FROM '.sql_table('member')." WHERE mnumber='".intval($id)."'");
599 return (sql_num_rows($r) != 0);
603 * Checks if a username is protected.
604 * If so, it can not be used on anonymous comments
606 function isNameProtected($name) {
609 $name = strip_tags($name);
612 return MEMBER::exists($name);
620 function create($name, $realname, $password, $email, $url, $admin, $canlogin, $notes) {
621 if (!isValidMailAddress($email))
623 return _ERROR_BADMAILADDRESS;
625 if (!isValidDisplayName($name))
627 return _ERROR_BADNAME;
629 if (MEMBER::exists($name))
631 return _ERROR_NICKNAMEINUSE;
635 return _ERROR_REALNAMEMISSING;
639 return _ERROR_PASSWORDMISSING;
642 # replaced eregi() below with preg_match(). ereg* functions are deprecated in PHP 5.3.0
643 # original eregi: !eregi("^https?://", $url)
644 // begin if: sometimes user didn't prefix the URL with http:// or https://, this cause a malformed URL. Let's fix it.
645 if (!preg_match('#^https?://#', $url) )
647 $url = 'http://' . $url;
650 $name = sql_real_escape_string($name);
651 $realname = sql_real_escape_string($realname);
652 $password = sql_real_escape_string(md5($password));
653 $email = sql_real_escape_string($email);
654 $url = sql_real_escape_string($url);
655 $admin = intval($admin);
656 $canlogin = intval($canlogin);
657 $notes = sql_real_escape_string($notes);
659 if (($admin) && !($canlogin)) {
663 $query = 'INSERT INTO '.sql_table('member')." (MNAME,MREALNAME,MPASSWORD,MEMAIL,MURL, MADMIN, MCANLOGIN, MNOTES) "
664 . "VALUES ('$name','$realname','$password','$email','$url',$admin, $canlogin, '$notes')";
667 ACTIONLOG::add(INFO, _ACTIONLOG_NEWMEMBER . ' ' . $name);
673 * Returns activation info for a certain key (an object with properties vkey, vmember, ...)
678 function getActivationInfo($key)
680 $query = 'SELECT * FROM ' . sql_table('activation') . ' WHERE vkey=\'' . sql_real_escape_string($key). '\'';
681 $res = sql_query($query);
683 if (!$res || (sql_num_rows($res) == 0))
686 return sql_fetch_object($res);
690 * Creates an account activation key
692 * @param $type one of the following values (determines what to do when activation expires)
693 * 'register' (new member registration)
694 * 'forgot' (forgotton password)
695 * 'addresschange' (member address has changed)
696 * @param $extra extra info (needed when validation link expires)
697 * addresschange -> old email address
700 function generateActivationEntry($type, $extra = '')
702 // clean up old entries
703 $this->cleanupActivationTable();
705 // kill any existing entries for the current member (delete is ok)
706 // (only one outstanding activation key can be present for a member)
707 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vmember=' . intval($this->getID()));
709 $canLoginWhileActive = false; // indicates if the member can log in while the link is active
713 $canLoginWhileActive = true;
717 case 'addresschange':
718 $extra = $extra . '/' . ($this->canLogin() ? '1' : '0');
725 // generate a random key
726 srand((double)microtime()*1000000);
727 $key = md5(uniqid(rand(), true));
729 // attempt to add entry in database
730 // add in database as non-active
731 $query = 'INSERT INTO ' . sql_table('activation'). ' (vkey, vtime, vmember, vtype, vextra) ';
732 $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). '\')';
733 if (sql_query($query))
737 // mark member as not allowed to log in
738 if (!$canLoginWhileActive)
740 $this->setCanLogin(0);
749 * Inidicates that an activation link has been clicked and any forms displayed
750 * there have been successfully filled out.
753 function activate($key)
756 $info = MEMBER::getActivationInfo($key);
762 switch ($info->vtype)
768 // set canlogin value
770 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($CONF['NewMemberCanLogon']). ' WHERE mnumber=' . intval($info->vmember));
772 case 'addresschange':
773 // reset old 'canlogin' value
774 list($oldEmail, $oldCanLogin) = explode('/', $info->vextra);
775 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ' WHERE mnumber=' . intval($info->vmember));
779 // delete from activation table
780 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vkey=\'' . sql_real_escape_string($key) . '\'');
787 * Cleans up entries in the activation table. All entries older than 2 days are removed.
792 function cleanupActivationTable()
795 if (isset($CONF['ActivationDays']) && intval($CONF['ActivationDays']) > 0) {
796 $actdays = intval($CONF['ActivationDays']);
799 $CONF['ActivationDays'] = 2;
801 $boundary = time() - (60 * 60 * 24 * $actdays);
803 // 1. walk over all entries, and see if special actions need to be performed
804 $res = sql_query('SELECT * FROM ' . sql_table('activation') . ' WHERE vtime < \'' . date('Y-m-d H:i:s',$boundary) . '\'');
806 while ($o = sql_fetch_object($res))
811 // delete all information about this site member. registration is undone because there was
812 // no timely activation
813 include_once($DIR_LIBS . 'ADMIN.php');
814 ADMIN::deleteOneMember(intval($o->vmember));
816 case 'addresschange':
817 // revert the e-mail address of the member back to old address
818 list($oldEmail, $oldCanLogin) = explode('/', $o->vextra);
819 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ', memail=\'' . sql_real_escape_string($oldEmail). '\' WHERE mnumber=' . intval($o->vmember));
822 // delete the activation link and ignore. member can request a new password using the
823 // forgot password link
828 // 2. delete activation entries for real
829 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vtime < \'' . date('Y-m-d H:i:s',$boundary) . '\'');