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
19 * $NucleusJP: MEMBER.php,v 1.6 2006/07/17 20:03:44 kimitake Exp $
23 // 1 when authenticated, 0 when not
\r
25 var $password; // not the actual password, but rather a MD5 hash
\r
27 var $cookiekey; // value that should also be in the client cookie to allow authentication
\r
35 var $language = ''; // name of the language file to use (e.g. 'english' -> english.php)
\r
36 var $admin = 0; // (either 0 or 1)
\r
37 var $canlogin = 0; // (either 0 or 1)
\r
39 var $autosave = 1; // if the member use the autosave draft function
\r
42 * Constructor for a member object
\r
49 * Create a member object for a given displayname
\r
53 function &createFromName($displayname) {
\r
54 $mem =& new MEMBER();
\r
55 $mem->readFromName($displayname);
\r
60 * Create a member object for a given ID
\r
64 function &createFromID($id) {
\r
65 $mem =& new MEMBER();
\r
66 $mem->readFromID($id);
\r
70 function readFromName($displayname) {
\r
71 return $this->read("mname='".addslashes($displayname)."'");
\r
74 function readFromID($id) {
\r
75 return $this->read("mnumber=" . intval($id));
\r
79 * Tries to login as a given user.
\r
80 * Returns true when succeeded, returns false when failed
\r
81 * 3.40 adds CustomLogin event
\r
83 function login($login, $password) {
\r
85 $this->loggedin = 0;
\r
88 $manager->notify('CustomLogin', array('login' => &$login, 'password'=>&$password, 'success'=>&$success, 'allowlocal'=>&$allowlocal) );
\r
89 if ($success && $this->readFromName($login)) {
\r
90 $this->loggedin = 1;
\r
91 return $this->isLoggedIn();
\r
92 } elseif (!$success && $allowlocal) {
\r
93 if (!$this->readFromName($login))
\r
95 if (!$this->checkPassword($password))
\r
97 $this->loggedin = 1;
\r
98 return $this->isLoggedIn();
\r
105 * Login using cookie key
\r
107 function cookielogin($login, $cookiekey) {
\r
108 $this->loggedin = 0;
\r
109 if (!$this->readFromName($login))
\r
111 if (!$this->checkCookieKey($cookiekey))
\r
113 $this->loggedin = 1;
\r
114 return $this->isLoggedIn();
\r
117 function logout() {
\r
121 function isLoggedIn() {
\r
122 return $this->loggedin;
\r
126 * Read member information from the database
\r
128 function read($where) {
\r
130 $query = 'SELECT * FROM '.sql_table('member') . ' WHERE ' . $where;
\r
132 $res = sql_query($query);
\r
133 $obj = mysql_fetch_object($res);
\r
135 $this->setRealName($obj->mrealname);
\r
136 $this->setEmail($obj->memail);
\r
137 $this->password = $obj->mpassword;
\r
138 $this->setCookieKey($obj->mcookiekey);
\r
139 $this->setURL($obj->murl);
\r
140 $this->setDisplayName($obj->mname);
\r
141 $this->setAdmin($obj->madmin);
\r
142 $this->id = $obj->mnumber;
\r
143 $this->setCanLogin($obj->mcanlogin);
\r
144 $this->setNotes($obj->mnotes);
\r
145 $this->setLanguage($obj->deflang);
\r
146 $this->setAutosave($obj->mautosave);
\r
148 return mysql_num_rows($res);
\r
153 * Returns true if member is an admin for the given blog
\r
154 * (returns false if not a team member)
\r
156 function isBlogAdmin($blogid) {
\r
157 $query = 'SELECT tadmin FROM '.sql_table('team').' WHERE'
\r
158 . ' tblog=' . intval($blogid)
\r
159 . ' and tmember='. $this->getID();
\r
160 $res = sql_query($query);
\r
161 if (mysql_num_rows($res) == 0)
\r
164 return (mysql_result($res,0,0) == 1) ;
\r
167 function blogAdminRights($blogid) {
\r
168 return ($this->isAdmin() || $this->isBlogAdmin($blogid));
\r
172 function teamRights($blogid) {
\r
173 return ($this->isAdmin() || $this->isTeamMember($blogid));
\r
177 * Returns true if this member is a team member of the given blog
\r
179 function isTeamMember($blogid) {
\r
180 $query = 'SELECT * FROM '.sql_table('team').' WHERE'
\r
181 . ' tblog=' . intval($blogid)
\r
182 . ' and tmember='. $this->getID();
\r
183 $res = sql_query($query);
\r
184 return (mysql_num_rows($res) != 0);
\r
187 function canAddItem($catid) {
\r
190 // if this is a 'newcat' style newcat
\r
191 // no blog admin of destination blog -> NOK
\r
192 // blog admin of destination blog -> OK
\r
193 if (strstr($catid,'newcat')) {
\r
195 list($blogid) = sscanf($catid,"newcat-%d");
\r
196 return $this->blogAdminRights($blogid);
\r
199 // category does not exist -> NOK
\r
200 if (!$manager->existsCategory($catid)) return 0;
\r
202 $blogid = getBlogIDFromCatID($catid);
\r
204 // no team rights for blog -> NOK
\r
205 if (!$this->teamRights($blogid)) return 0;
\r
207 // all other cases: OK
\r
212 * Returns true if this member can edit/delete a commentitem. This can be in the
\r
214 * - member is a super-admin
\r
215 * - member is the author of the comment
\r
216 * - member is admin of the blog associated with the comment
\r
217 * - member is author of the item associated with the comment
\r
219 function canAlterComment($commentid) {
\r
220 if ($this->isAdmin()) return 1;
\r
222 $query = 'SELECT citem as itemid, iblog as blogid, cmember as cauthor, iauthor'
\r
223 . ' FROM '.sql_table('comment') .', '.sql_table('item').', '.sql_table('blog')
\r
224 . ' WHERE citem=inumber and iblog=bnumber and cnumber=' . intval($commentid);
\r
225 $res = sql_query($query);
\r
226 $obj = mysql_fetch_object($res);
\r
228 return ($obj->cauthor == $this->getID()) or $this->isBlogAdmin($obj->blogid) or ($obj->iauthor == $this->getID());
\r
232 * Returns true if this member can edit/delete an item. This is true in the following
\r
233 * cases: - member is a super-admin
\r
234 * - member is the author of the item
\r
235 * - member is admin of the the associated blog
\r
237 function canAlterItem($itemid) {
\r
238 if ($this->isAdmin()) return 1;
\r
240 $query = 'SELECT iblog, iauthor FROM '.sql_table('item').' WHERE inumber=' . intval($itemid);
\r
241 $res = sql_query($query);
\r
242 $obj = mysql_fetch_object($res);
\r
243 return ($obj->iauthor == $this->getID()) or $this->isBlogAdmin($obj->iblog);
\r
247 * Return true if member can be deleted. This means that there are no items
\r
248 * posted by the member left
\r
250 function canBeDeleted() {
\r
251 $res = sql_query('SELECT * FROM '.sql_table('item').' WHERE iauthor=' . $this->getID());
\r
252 return (mysql_num_rows($res) == 0);
\r
256 * returns true if this member can move/update an item to a given category,
\r
257 * false if not (see comments fot the tests that are executed)
\r
260 * @param newcat (can also be of form 'newcat-x' with x=blogid)
\r
262 function canUpdateItem($itemid, $newcat) {
\r
265 // item does not exists -> NOK
\r
266 if (!$manager->existsItem($itemid,1,1)) return 0;
\r
268 // cannot alter item -> NOK
\r
269 if (!$this->canAlterItem($itemid)) return 0;
\r
271 // if this is a 'newcat' style newcat
\r
272 // no blog admin of destination blog -> NOK
\r
273 // blog admin of destination blog -> OK
\r
274 if (strstr($newcat,'newcat')) {
\r
276 list($blogid) = sscanf($newcat,'newcat-%d');
\r
277 return $this->blogAdminRights($blogid);
\r
280 // category does not exist -> NOK
\r
281 if (!$manager->existsCategory($newcat)) return 0;
\r
285 $item =& $manager->getItem($itemid,1,1);
\r
287 // old catid = new catid -> OK
\r
288 if ($item['catid'] == $newcat) return 1;
\r
290 // not a valid category -> NOK
\r
291 $validCat = quickQuery('SELECT COUNT(*) AS result FROM '.sql_table('category').' WHERE catid='.intval($newcat));
\r
292 if (!$validCat) return 0;
\r
294 // get destination blog
\r
295 $source_blogid = getBlogIDFromItemID($itemid);
\r
296 $dest_blogid = getBlogIDFromCatID($newcat);
\r
298 // not a team member of destination blog -> NOK
\r
299 if (!$this->teamRights($dest_blogid)) return 0;
\r
301 // if member is author of item -> OK
\r
302 if ($item['authorid'] == $this->getID()) return 1;
\r
304 // if member has admin rights on both blogs: OK
\r
305 if (($this->blogAdminRights($dest_blogid)) && ($this->blogAdminRights($source_blogid))) return 1;
\r
307 // all other cases: NOK
\r
313 * Sets the cookies for the member
\r
316 * set this to 1 when using a shared computer. Cookies will expire
\r
317 * at the end of the session in this case.
\r
319 function setCookies($shared = 0) {
\r
322 if ($CONF['SessionCookie'] || $shared)
\r
325 $lifetime = (time()+2592000);
\r
327 setcookie($CONF['CookiePrefix'] .'user',$this->getDisplayName(),$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
\r
328 setcookie($CONF['CookiePrefix'] .'loginkey', $this->getCookieKey(),$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
\r
330 // make sure cookies on shared pcs don't get renewed
\r
332 setcookie($CONF['CookiePrefix'] .'sharedpc', '1',$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
\r
335 function sendActivationLink($type, $extra='')
\r
339 // generate key and URL
\r
340 $key = $this->generateActivationEntry($type, $extra);
\r
341 $url = $CONF['AdminURL'] . 'index.php?action=activate&key=' . $key;
\r
343 // choose text to use in mail
\r
347 $message = _ACTIVATE_REGISTER_MAIL;
\r
348 $title = _ACTIVATE_REGISTER_MAILTITLE;
\r
351 $message = _ACTIVATE_FORGOT_MAIL;
\r
352 $title = _ACTIVATE_FORGOT_MAILTITLE;
\r
354 case 'addresschange':
\r
355 $message = _ACTIVATE_CHANGE_MAIL;
\r
356 $title = _ACTIVATE_CHANGE_MAILTITLE;
\r
361 // fill out variables in text
\r
364 'siteName' => $CONF['SiteName'],
\r
365 'siteUrl' => $CONF['IndexURL'],
\r
366 'memberName' => $this->getDisplayName(),
\r
367 'activationUrl' => $url
\r
370 $message = TEMPLATE::fill($message, $aVars);
\r
371 $title = TEMPLATE::fill($title, $aVars);
\r
376 mb_internal_encoding(_CHARSET);
\r
377 @mb_send_mail($this->getEmail(), $title ,$message,'From: ' . $CONF['AdminEmail']);
\r
379 ACTIONLOG::add(INFO, _ACTIONLOG_ACTIVATIONLINK . ' (' . $this->getDisplayName() . ' / type: ' . $type . ')');
\r
385 * Returns an array of all blogids for which member has admin rights
\r
387 function getAdminBlogs() {
\r
390 if ($this->isAdmin())
\r
391 $query = 'SELECT bnumber as blogid from '.sql_table('blog');
\r
393 $query = 'SELECT tblog as blogid from '.sql_table('team').' where tadmin=1 and tmember=' . $this->getID();
\r
395 $res = sql_query($query);
\r
396 if (mysql_num_rows($res) > 0) {
\r
397 while ($obj = mysql_fetch_object($res)) {
\r
398 array_push($blogs, $obj->blogid);
\r
406 * Returns an email address from which notification of commenting/karma voting can
\r
407 * be sent. A suggestion can be given for when the member is not logged in
\r
409 function getNotifyFromMailAddress($suggest = "") {
\r
411 if ($this->isLoggedIn()) {
\r
412 return $this->getDisplayName() . " <" . $this->getEmail() . ">";
\r
413 } else if (isValidMailAddress($suggest)) {
\r
416 return $CONF['AdminEmail'];
\r
421 * Write data to database
\r
425 $query = 'UPDATE '.sql_table('member')
\r
426 . " SET mname='" . addslashes($this->getDisplayName()) . "',"
\r
427 . " mrealname='". addslashes($this->getRealName()) . "',"
\r
428 . " mpassword='". addslashes($this->getPassword()) . "',"
\r
429 . " mcookiekey='". addslashes($this->getCookieKey()) . "',"
\r
430 . " murl='" . addslashes($this->getURL()) . "',"
\r
431 . " memail='" . addslashes($this->getEmail()) . "',"
\r
432 . " madmin=" . $this->isAdmin() . ","
\r
433 . " mnotes='" . addslashes($this->getNotes()) . "',"
\r
434 . " mcanlogin=" . $this->canLogin() . ","
\r
435 . " deflang='" . addslashes($this->getLanguage()) . "',"
\r
436 . " mautosave=" . addslashes($this->getAutosave()) . ""
\r
437 . " WHERE mnumber=" . $this->getID();
\r
441 function checkCookieKey($key) {
\r
442 return (($key != '') && ($key == $this->getCookieKey()));
\r
445 function checkPassword($pw) {
\r
446 return (md5($pw) == $this->getPassword());
\r
449 function getRealName() {
\r
450 return $this->realname;
\r
453 function setRealName($name) {
\r
454 $this->realname = $name;
\r
457 function getEmail() {
\r
458 return $this->email;
\r
461 function setEmail($email) {
\r
462 $this->email = $email;
\r
465 function getPassword() {
\r
466 return $this->password;
\r
469 function setPassword($pwd) {
\r
470 $this->password = md5($pwd);
\r
473 function getCookieKey() {
\r
474 return $this->cookiekey;
\r
478 * Generate new cookiekey, save it, and return it
\r
480 function newCookieKey() {
\r
481 mt_srand( (double) microtime() * 1000000);
\r
482 $this->cookiekey = md5(uniqid(mt_rand()));
\r
484 return $this->cookiekey;
\r
487 function setCookieKey($val) {
\r
488 $this->cookiekey = $val;
\r
491 function getURL() {
\r
495 function setURL($site) {
\r
496 $this->url = $site;
\r
499 function getLanguage() {
\r
500 return $this->language;
\r
503 function setLanguage($lang) {
\r
504 $this->language = $lang;
\r
507 function setDisplayName($nick) {
\r
508 $this->displayname = $nick;
\r
511 function getDisplayName() {
\r
512 return $this->displayname;
\r
515 function isAdmin() {
\r
516 return $this->admin;
\r
519 function setAdmin($val) {
\r
520 $this->admin = $val;
\r
523 function canLogin() {
\r
524 return $this->canlogin;
\r
527 function setCanLogin($val) {
\r
528 $this->canlogin = $val;
\r
531 function getNotes() {
\r
532 return $this->notes;
\r
535 function setNotes($val) {
\r
536 $this->notes = $val;
\r
539 function getAutosave() {
\r
540 return $this->autosave;
\r
543 function setAutosave($val) {
\r
544 $this->autosave = $val;
\r
552 * Returns true if there is a member with the given login name
\r
556 function exists($name) {
\r
557 $r = sql_query('select * FROM '.sql_table('member')." WHERE mname='".addslashes($name)."'");
\r
558 return (mysql_num_rows($r) != 0);
\r
562 * Returns true if there is a member with the given ID
\r
566 function existsID($id) {
\r
567 $r = sql_query('select * FROM '.sql_table('member')." WHERE mnumber='".intval($id)."'");
\r
568 return (mysql_num_rows($r) != 0);
\r
572 * Checks if a username is protected.
\r
573 * If so, it can not be used on anonymous comments
\r
575 function isNameProtected($name) {
\r
578 $name = strip_tags($name);
\r
579 $name = trim($name);
\r
581 return MEMBER::exists($name);
\r
585 * Adds a new member
\r
589 function create($name, $realname, $password, $email, $url, $admin, $canlogin, $notes) {
\r
590 if (!isValidMailAddress($email))
\r
591 return _ERROR_BADMAILADDRESS;
\r
593 if (!isValidDisplayName($name))
\r
594 return _ERROR_BADNAME;
\r
596 if (MEMBER::exists($name))
\r
597 return _ERROR_NICKNAMEINUSE;
\r
600 return _ERROR_REALNAMEMISSING;
\r
603 return _ERROR_PASSWORDMISSING;
\r
605 // Sometimes user didn't prefix the URL with http://, this cause a malformed URL. Let's fix it.
\r
606 if (!eregi("^https?://", $url))
\r
607 $url = "http://".$url;
\r
609 $name = addslashes($name);
\r
610 $realname = addslashes($realname);
\r
611 $password = addslashes(md5($password));
\r
612 $email = addslashes($email);
\r
613 $url = addslashes($url);
\r
614 $admin = intval($admin);
\r
615 $canlogin = intval($canlogin);
\r
616 $notes = addslashes($notes);
\r
618 $query = 'INSERT INTO '.sql_table('member')." (MNAME,MREALNAME,MPASSWORD,MEMAIL,MURL, MADMIN, MCANLOGIN, MNOTES) "
\r
619 . "VALUES ('$name','$realname','$password','$email','$url',$admin, $canlogin, '$notes')";
\r
622 ACTIONLOG::add(INFO, _ACTIONLOG_NEWMEMBER . ' ' . $name);
\r
628 * Returns activation info for a certain key (an object with properties vkey, vmember, ...)
\r
633 function getActivationInfo($key)
\r
635 $query = 'SELECT * FROM ' . sql_table('activation') . ' WHERE vkey=\'' . addslashes($key). '\'';
\r
636 $res = sql_query($query);
\r
638 if (!$res || (mysql_num_rows($res) == 0))
\r
641 return mysql_fetch_object($res);
\r
645 * Creates an account activation key
\r
647 * @param $type one of the following values (determines what to do when activation expires)
\r
648 * 'register' (new member registration)
\r
649 * 'forgot' (forgotton password)
\r
650 * 'addresschange' (member address has changed)
\r
651 * @param $extra extra info (needed when validation link expires)
\r
652 * addresschange -> old email address
\r
655 function generateActivationEntry($type, $extra = '')
\r
657 // clean up old entries
\r
658 $this->cleanupActivationTable();
\r
660 // kill any existing entries for the current member (delete is ok)
\r
661 // (only one outstanding activation key can be present for a member)
\r
662 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vmember=' . intval($this->getID()));
\r
664 $canLoginWhileActive = false; // indicates if the member can log in while the link is active
\r
668 $canLoginWhileActive = true;
\r
672 case 'addresschange':
\r
673 $extra = $extra . '/' . ($this->canLogin() ? '1' : '0');
\r
680 // generate a random key
\r
681 srand((double)microtime()*1000000);
\r
682 $key = md5(uniqid(rand(), true));
\r
684 // attempt to add entry in database
\r
685 // add in database as non-active
\r
686 $query = 'INSERT INTO ' . sql_table('activation'). ' (vkey, vtime, vmember, vtype, vextra) ';
\r
687 $query .= 'VALUES (\'' . addslashes($key). '\', \'' . date('Y-m-d H:i:s',time()) . '\', \'' . intval($this->getID()). '\', \'' . addslashes($type). '\', \'' . addslashes($extra). '\')';
\r
688 if (sql_query($query))
\r
692 // mark member as not allowed to log in
\r
693 if (!$canLoginWhileActive)
\r
695 $this->setCanLogin(0);
\r
704 * Inidicates that an activation link has been clicked and any forms displayed
\r
705 * there have been successfully filled out.
\r
708 function activate($key)
\r
710 // get activate info
\r
711 $info = MEMBER::getActivationInfo($key);
\r
717 switch ($info->vtype)
\r
723 // set canlogin value
\r
725 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($CONF['NewMemberCanLogon']). ' WHERE mnumber=' . intval($info->vmember));
\r
727 case 'addresschange':
\r
728 // reset old 'canlogin' value
\r
729 list($oldEmail, $oldCanLogin) = explode('/', $info->vextra);
\r
730 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ' WHERE mnumber=' . intval($info->vmember));
\r
734 // delete from activation table
\r
735 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vkey=\'' . addslashes($key) . '\'');
\r
742 * Cleans up entries in the activation table. All entries older than 2 days are removed.
\r
747 function cleanupActivationTable()
\r
749 $boundary = time() - (60 * 60 * 24 * 2);
\r
751 // 1. walk over all entries, and see if special actions need to be performed
\r
752 $res = sql_query('SELECT * FROM ' . sql_table('activation') . ' WHERE vtime < \'' . date('Y-m-d H:i:s',$boundary) . '\'');
\r
754 while ($o = mysql_fetch_object($res))
\r
759 // delete all information about this site member. registration is undone because there was
\r
760 // no timely activation
\r
761 include_once($DIR_LIBS . 'ADMIN.php');
\r
762 ADMIN::deleteOneMember(intval($o->vmember));
\r
764 case 'addresschange':
\r
765 // revert the e-mail address of the member back to old address
\r
766 list($oldEmail, $oldCanLogin) = explode('/', $o->vextra);
\r
767 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ', memail=\'' . addslashes($oldEmail). '\' WHERE mnumber=' . intval($o->vmember));
\r
770 // delete the activation link and ignore. member can request a new password using the
\r
771 // forgot password link
\r
776 // 2. delete activation entries for real
\r
777 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vtime < \'' . date('Y-m-d H:i:s',$boundary) . '\'');
\r