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);
158 $this->setLocale($obj->mlocale);
159 $this->setAutosave($obj->mautosave);
161 return sql_num_rows($res);
165 * Returns true if member is an admin for the given blog
166 * (returns false if not a team member)
168 public function isBlogAdmin($blogid)
170 $query = 'SELECT tadmin FROM '.sql_table('team').' WHERE'
171 . ' tblog=' . intval($blogid)
172 . ' and tmember='. $this->getID();
173 $res = sql_query($query);
174 if ( sql_num_rows($res) == 0 )
177 return ( sql_result($res,0,0) == 1 );
180 public function blogAdminRights($blogid)
182 return ($this->isAdmin() || $this->isBlogAdmin($blogid));
185 public function teamRights($blogid)
187 return ($this->isAdmin() || $this->isTeamMember($blogid));
191 * Returns true if this member is a team member of the given blog
193 function isTeamMember($blogid)
195 $query = 'SELECT * FROM '.sql_table('team').' WHERE'
196 . ' tblog=' . intval($blogid)
197 . ' and tmember='. $this->getID();
198 $res = sql_query($query);
199 return (sql_num_rows($res) != 0);
202 function canAddItem($catid)
206 // if this is a 'newcat' style newcat
207 // no blog admin of destination blog -> NOK
208 // blog admin of destination blog -> OK
209 if ( strstr($catid,'newcat') )
212 list($blogid) = sscanf($catid,"newcat-%d");
213 return $this->blogAdminRights($blogid);
216 // category does not exist -> NOK
217 if ( !$manager->existsCategory($catid) )
222 $blogid = getBlogIDFromCatID($catid);
224 // no team rights for blog -> NOK
225 if (!$this->teamRights($blogid))
230 // all other cases: OK
235 * Returns true if this member can edit/delete a commentitem. This can be in the
237 * - member is a super-admin
238 * - member is the author of the comment
239 * - member is admin of the blog associated with the comment
240 * - member is author of the item associated with the comment
242 public function canAlterComment($commentid)
244 if ( $this->isAdmin() )
249 $query = 'SELECT citem as itemid, iblog as blogid, cmember as cauthor, iauthor'
250 . ' FROM '.sql_table('comment') .', '.sql_table('item').', '.sql_table('blog')
251 . ' WHERE citem=inumber and iblog=bnumber and cnumber=' . intval($commentid);
252 $res = sql_query($query);
253 $obj = sql_fetch_object($res);
255 return ($obj->cauthor == $this->getID()) or $this->isBlogAdmin($obj->blogid) or ($obj->iauthor == $this->getID());
259 * Returns true if this member can edit/delete an item. This is true in the following
260 * cases: - member is a super-admin
261 * - member is the author of the item
262 * - member is admin of the the associated blog
264 public function canAlterItem($itemid)
266 if ($this->isAdmin()) return 1;
268 $query = 'SELECT iblog, iauthor FROM '.sql_table('item').' WHERE inumber=' . intval($itemid);
269 $res = sql_query($query);
270 $obj = sql_fetch_object($res);
271 return ($obj->iauthor == $this->getID()) or $this->isBlogAdmin($obj->iblog);
275 * Return true if member can be deleted. This means that there are no items
276 * posted by the member left
278 public function canBeDeleted()
280 $res = sql_query('SELECT * FROM '.sql_table('item').' WHERE iauthor=' . $this->getID());
281 return (sql_num_rows($res) == 0);
285 * returns true if this member can move/update an item to a given category,
286 * false if not (see comments fot the tests that are executed)
289 * @param newcat (can also be of form 'newcat-x' with x=blogid)
291 public function canUpdateItem($itemid, $newcat)
295 // item does not exists -> NOK
296 if ( !$manager->existsItem($itemid,1,1) )
301 // cannot alter item -> NOK
302 if (!$this->canAlterItem($itemid))
307 // if this is a 'newcat' style newcat
308 // no blog admin of destination blog -> NOK
309 // blog admin of destination blog -> OK
310 if (strstr($newcat,'newcat'))
313 list($blogid) = sscanf($newcat,'newcat-%d');
314 return $this->blogAdminRights($blogid);
317 // category does not exist -> NOK
318 if (!$manager->existsCategory($newcat))
324 $item =& $manager->getItem($itemid,1,1);
326 // old catid = new catid -> OK
327 if ($item['catid'] == $newcat)
332 // not a valid category -> NOK
333 $validCat = quickQuery('SELECT COUNT(*) AS result FROM '.sql_table('category').' WHERE catid='.intval($newcat));
339 // get destination blog
340 $source_blogid = getBlogIDFromItemID($itemid);
341 $dest_blogid = getBlogIDFromCatID($newcat);
343 // not a team member of destination blog -> NOK
344 if ( !$this->teamRights($dest_blogid) )
349 // if member is author of item -> OK
350 if ( $item['authorid'] == $this->getID() )
355 // if member has admin rights on both blogs: OK
356 if ( ($this->blogAdminRights($dest_blogid)) && ($this->blogAdminRights($source_blogid)) )
361 // all other cases: NOK
366 * Sets the cookies for the member
369 * set this to 1 when using a shared computer. Cookies will expire
370 * at the end of the session in this case.
372 public function setCookies($shared = 0)
376 if ( $CONF['SessionCookie'] || $shared )
382 $lifetime = time()+2592000;
385 setcookie($CONF['CookiePrefix'] .'user',$this->getDisplayName(),$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
386 setcookie($CONF['CookiePrefix'] .'loginkey', $this->getCookieKey(),$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
388 // make sure cookies on shared pcs don't get renewed
391 setcookie($CONF['CookiePrefix'] .'sharedpc', '1',$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
397 * MEMBER::sendActivationLink()
398 * Send activation mail
400 * @param String $type activation type
401 * @param String $extra extra info
404 public function sendActivationLink($type, $extra='')
408 if ( !isset($CONF['ActivationDays']) )
410 $CONF['ActivationDays'] = 2;
413 // generate key and URL
414 $key = $this->generateActivationEntry($type, $extra);
415 $url = $CONF['AdminURL'] . 'index.php?action=activate&key=' . $key;
417 // choose text to use in mail
421 $message = _ACTIVATE_REGISTER_MAIL;
422 $subject = _ACTIVATE_REGISTER_MAILTITLE;
425 $message = _ACTIVATE_FORGOT_MAIL;
426 $subject = _ACTIVATE_FORGOT_MAILTITLE;
428 case 'addresschange':
429 $message = _ACTIVATE_CHANGE_MAIL;
430 $subject = _ACTIVATE_CHANGE_MAILTITLE;
435 // fill out variables in text
437 'siteName' => $CONF['SiteName'],
438 'siteUrl' => $CONF['IndexURL'],
439 'memberName' => $this->getDisplayName(),
440 'activationUrl' => $url,
441 'activationDays' => $CONF['ActivationDays']
444 $message = TEMPLATE::fill($message, $aVars);
445 $subject = TEMPLATE::fill($subject, $aVars);
448 NOTIFICATION::mail($this->getEmail(), $subject ,$message, $CONF['AdminEmail'], i18n::get_current_charset());
450 ACTIONLOG::add(INFO, _ACTIONLOG_ACTIVATIONLINK . ' (' . $this->getDisplayName() . ' / type: ' . $type . ')');
455 * Returns an array of all blogids for which member has admin rights
457 public function getAdminBlogs()
461 if ($this->isAdmin())
463 $query = 'SELECT bnumber as blogid from '.sql_table('blog');
467 $query = 'SELECT tblog as blogid from '.sql_table('team').' where tadmin=1 and tmember=' . $this->getID();
470 $res = sql_query($query);
471 if ( sql_num_rows($res) > 0 )
473 while ( $obj = sql_fetch_object($res) )
475 array_push($blogs, $obj->blogid);
482 * Returns an array of all blogids for which member has team rights
484 public function getTeamBlogs($incAdmin = 1)
486 $incAdmin = intval($incAdmin);
489 if ( $this->isAdmin() && $incAdmin )
491 $query = 'SELECT bnumber as blogid from '.sql_table('blog');
495 $query = 'SELECT tblog as blogid from '.sql_table('team').' where tmember=' . $this->getID();
498 $res = sql_query($query);
499 if ( sql_num_rows($res) > 0 )
501 while ( $obj = sql_fetch_object($res) )
503 array_push($blogs, $obj->blogid);
510 * MEMBER::getNotifyFromMailAddress()
512 * Returns an email address from which notification of commenting/karma voting can
513 * be sent. A suggestion can be given for when the member is not logged in
515 * @param String $suggest
516 * @return String mail address or suggestion
518 public function getNotifyFromMailAddress($suggest = "")
521 if ( $this->isLoggedIn() )
523 return $this->getDisplayName() . " <" . $this->getEmail() . ">";
525 else if ( NOTIFICATION::address_validation($suggest) )
531 return $CONF['AdminEmail'];
536 * Write data to database
540 $query = 'UPDATE '.sql_table('member')
541 . " SET mname='" . sql_real_escape_string($this->getDisplayName()) . "',"
542 . " mrealname='". sql_real_escape_string($this->getRealName()) . "',"
543 . " mpassword='". sql_real_escape_string($this->getPassword()) . "',"
544 . " mcookiekey='". sql_real_escape_string($this->getCookieKey()) . "',"
545 . " murl='" . sql_real_escape_string($this->getURL()) . "',"
546 . " memail='" . sql_real_escape_string($this->getEmail()) . "',"
547 . " madmin=" . $this->isAdmin() . ","
548 . " mnotes='" . sql_real_escape_string($this->getNotes()) . "',"
549 . " mcanlogin=" . $this->canLogin() . ","
550 . " mlocale='" . sql_real_escape_string($this->getLocale()) . "',"
551 . " mautosave=" . intval($this->getAutosave()) . ""
552 . " WHERE mnumber=" . $this->getID();
557 public function checkCookieKey($key)
559 return ( ($key != '') && ( $key == $this->getCookieKey() ) );
562 public function checkPassword($pw)
564 return (md5($pw) == $this->getPassword());
567 public function getRealName()
569 return $this->realname;
572 public function setRealName($name)
574 $this->realname = $name;
577 public function getEmail()
582 public function setEmail($email)
584 $this->email = $email;
587 public function getPassword()
589 return $this->password;
592 public function setPassword($pwd)
594 $this->password = md5($pwd);
597 public function getCookieKey()
599 return $this->cookiekey;
603 * Generate new cookiekey, save it, and return it
605 public function newCookieKey()
607 mt_srand( (double) microtime() * 1000000);
608 $this->cookiekey = md5(uniqid(mt_rand()));
610 return $this->cookiekey;
613 public function setCookieKey($val)
615 $this->cookiekey = $val;
618 public function getURL()
623 public function setURL($site)
629 * FIXME: $this->locale is always correct or not?
630 * NOTE: Deprecated, this will be obsoleted soon.
632 public function getLanguage()
634 if ( ($language = i18n::convert_locale_to_old_language_file_name($this->locale)) === FALSE )
641 public function getLocale()
643 return $this->locale;
647 * FIXME: $locale value should obsolete $language value near future
649 public function setLocale($locale)
651 if ( !!preg_match('#^(.+)_(.+)_(.+)$#', $locale)
652 && ($locale = i18n::convert_old_language_file_name_to_locale($locale)) === FALSE )
656 $this->locale = $locale;
660 public function setDisplayName($nick)
662 $this->displayname = $nick;
665 public function getDisplayName()
667 return $this->displayname;
670 public function isAdmin()
675 public function setAdmin($val)
680 public function canLogin()
682 return $this->canlogin;
685 public function setCanLogin($val)
687 $this->canlogin = $val;
690 public function getNotes()
695 public function setNotes($val)
700 public function getAutosave()
702 return $this->autosave;
705 public function setAutosave($val)
707 $this->autosave = $val;
710 public function getID()
716 * Returns true if there is a member with the given login name
720 public static function exists($name)
722 $r = sql_query('select * FROM '.sql_table('member')." WHERE mname='".sql_real_escape_string($name)."'");
723 return ( sql_num_rows($r) != 0 );
727 * Returns true if there is a member with the given ID
731 public static function existsID($id)
733 $r = sql_query('select * FROM '.sql_table('member')." WHERE mnumber='".intval($id)."'");
734 return (sql_num_rows($r) != 0);
738 * Checks if a username is protected.
739 * If so, it can not be used on anonymous comments
741 function isNameProtected($name)
744 $name = strip_tags($name);
746 return MEMBER::exists($name);
755 * @param String $name
756 * @param String $realname
757 * @param String $password
758 * @param String $email
760 * @param String $admin
761 * @param String $canlogin
762 * @param String $notes
763 * @return String 1 if success, others if fail
765 public static function create($name, $realname, $password, $email, $url, $admin, $canlogin, $notes)
767 if ( !NOTIFICATION::address_validation($email) )
769 return _ERROR_BADMAILADDRESS;
772 if ( !isValidDisplayName($name) )
774 return _ERROR_BADNAME;
777 if ( MEMBER::exists($name) )
779 return _ERROR_NICKNAMEINUSE;
784 return _ERROR_REALNAMEMISSING;
789 return _ERROR_PASSWORDMISSING;
793 * begin if: sometimes user didn't prefix the URL with http:// or https://,
794 * this cause a malformed URL. Let's fix it.
797 if ( !preg_match('#^https?://#', $url) )
799 $url = 'http://' . $url;
802 $name = sql_real_escape_string($name);
803 $realname = sql_real_escape_string($realname);
804 $password = sql_real_escape_string(md5($password));
805 $email = sql_real_escape_string($email);
806 $url = sql_real_escape_string($url);
807 $admin = intval($admin);
808 $canlogin = intval($canlogin);
809 $notes = sql_real_escape_string($notes);
811 $query = 'INSERT INTO '.sql_table('member')." (MNAME,MREALNAME,MPASSWORD,MEMAIL,MURL, MADMIN, MCANLOGIN, MNOTES) "
812 . "VALUES ('$name','$realname','$password','$email','$url',$admin, $canlogin, '$notes')";
815 ACTIONLOG::add(INFO, _ACTIONLOG_NEWMEMBER . ' ' . $name);
821 * Returns activation info for a certain key (an object with properties vkey, vmember, ...)
826 public static function getActivationInfo($key)
828 $query = 'SELECT * FROM ' . sql_table('activation') . ' WHERE vkey=\'' . sql_real_escape_string($key). '\'';
829 $res = sql_query($query);
831 if ( !$res || (sql_num_rows($res) == 0) )
837 return sql_fetch_object($res);
842 * Creates an account activation key
844 * @param $type one of the following values (determines what to do when activation expires)
845 * 'register' (new member registration)
846 * 'forgot' (forgotton password)
847 * 'addresschange' (member address has changed)
848 * @param $extra extra info (needed when validation link expires)
849 * addresschange -> old email address
852 public function generateActivationEntry($type, $extra = '')
854 // clean up old entries
855 $this->cleanupActivationTable();
857 // kill any existing entries for the current member (delete is ok)
858 // (only one outstanding activation key can be present for a member)
859 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vmember=' . intval($this->getID()));
861 // indicates if the member can log in while the link is active
862 $canLoginWhileActive = false;
866 $canLoginWhileActive = true;
870 case 'addresschange':
871 $extra = $extra . '/' . ( $this->canLogin() ? '1' : '0' );
878 // generate a random key
879 srand((double)microtime()*1000000);
880 $key = md5(uniqid(rand(), true));
882 // attempt to add entry in database
883 // add in database as non-active
884 $query = 'INSERT INTO ' . sql_table('activation'). ' (vkey, vtime, vmember, vtype, vextra) ';
885 $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). '\')';
886 if ( sql_query($query) )
890 // mark member as not allowed to log in
891 if ( !$canLoginWhileActive )
893 $this->setCanLogin(0);
902 * Inidicates that an activation link has been clicked and any forms displayed
903 * there have been successfully filled out.
906 function activate($key)
909 $info = MEMBER::getActivationInfo($key);
917 switch ( $info->vtype )
923 // set canlogin value
925 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($CONF['NewMemberCanLogon']). ' WHERE mnumber=' . intval($info->vmember));
927 case 'addresschange':
928 // reset old 'canlogin' value
929 list($oldEmail, $oldCanLogin) = i18n::explode('/', $info->vextra);
930 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ' WHERE mnumber=' . intval($info->vmember));
934 // delete from activation table
935 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vkey=\'' . sql_real_escape_string($key) . '\'');
942 * Cleans up entries in the activation table. All entries older than 2 days are removed.
947 public function cleanupActivationTable()
950 if ( isset($CONF['ActivationDays']) && intval($CONF['ActivationDays']) > 0 )
952 $actdays = intval($CONF['ActivationDays']);
956 $CONF['ActivationDays'] = 2;
958 $boundary = time() - (60 * 60 * 24 * $actdays);
960 // 1. walk over all entries, and see if special actions need to be performed
961 $res = sql_query('SELECT * FROM ' . sql_table('activation') . ' WHERE vtime < \'' . date('Y-m-d H:i:s',$boundary) . '\'');
963 while ( $o = sql_fetch_object($res) )
968 // delete all information about this site member. registration is undone because there was
969 // no timely activation
970 include_once($DIR_LIBS . 'ADMIN.php');
971 ADMIN::deleteOneMember(intval($o->vmember));
973 case 'addresschange':
974 // revert the e-mail address of the member back to old address
975 list($oldEmail, $oldCanLogin) = i18n::explode('/', $o->vextra);
976 sql_query('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ', memail=\'' . sql_real_escape_string($oldEmail). '\' WHERE mnumber=' . intval($o->vmember));
979 // delete the activation link and ignore. member can request a new password using the
980 // forgot password link
985 // 2. delete activation entries for real
986 sql_query('DELETE FROM ' . sql_table('activation') . ' WHERE vtime < \'' . date('Y-m-d H:i:s',$boundary) . '\'');