<<<<<<< HEAD
<?php

/* 
 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
 * Copyright (C) 2002-2009 The Nucleus Group
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * (see nucleus/documentation/index.html#license for more info)
 */
/**
 * A class representing site members
 * 
 * @license http://nucleuscms.org/license.txt GNU General Public License
 * @copyright Copyright (C) 2002-2009 The Nucleus Group
 * @version $Id: MEMBER.php 1616 2012-01-08 09:48:15Z sakamocchi $
 */
class Member
{
	// 1 when authenticated, 0 when not
	public $loggedin = 0;
	public $password;		// not the actual password, but rather a MD5 hash
	private $algorism = 'md5';
	
	public $cookiekey;		// value that should also be in the client cookie to allow authentication
	private $cookie_salt = FALSE;
	
	// member info
	public $id = -1;
	public $realname;
	public $displayname;
	public $email;
	public $url;
	public $admin = 0;			// (either 0 or 1)
	public $canlogin = 0;		// (either 0 or 1)
	public $notes;
	public $autosave = 1;		// if the member use the autosave draft function
	private $locale = '';
	
	/**
	 * Member::__construct()
	 * Constructor for a member object
	 * 
	 * @param	Void
	 * @return	Void
	 * 
	 */
	public function __construct()
	{
		return;
	}
	
	/**
	 * Member::createFromName()
	 * Create a member object for a given displayname
	 * 
	 * @static
	 * @param	String	$displayname	login name
	 * @return	Object	member object
	 * 
	 */
	public static function &createFromName($displayname)
	{
		$mem = new Member();
		$mem->readFromName($displayname);
		return $mem;
	}
	
	/**
	 * Member::createFromID()
	 * Create a member object for a given ID
	 * 
	 * @static
	 * @param	Integer	$id	id for member
	 */
	public static function &createFromID($id)
	{
		$mem = new Member();
		$mem->readFromID($id);
		return $mem;
	}
	
	/**
	 * Member::readFromName()
	 * Read member table in database
	 * 
	 * @param	String	$displayname	login name
	 * @return	Object	SQL resource
	 * 
	 */
	public function readFromName($displayname)
	{
		return $this->read('mname='.DB::quoteValue($displayname));
	}
	
	/**
	 * Member::readFromID()
	 * Read member table in database
	 * 
	 * @param	Integer	$id	id for member
	 * @return	Object	SQL resource
	 * 
	 */
	public function readFromID($id)
	{
		return $this->read("mnumber=" . intval($id));
	}
	
	/**
	 * Member::hash()
	 * hash the target string
	 * 
	 * @param	String	$string	target string
	 * @return	Void	hashed string
	 */
	public function hash($string)
	{
		switch ( $this->algorism )
		{
			case 'md5':
			default:
				$string = md5($string);
		}
		return $string;
	}
	
	/**
	 * Member::set_cookie_salt()
	 * 
	 * @param	integer	$key	secureCookieKey value
	 * @return	void
	 * 
	 */
	private function set_cookie_salt($key = 0)
	{
		if ( !$key )
		{
			$key = 24;
		}
		
		switch( $key )
		{
			case 8:
				$this->cookie_salt = preg_replace('/\.[0-9]+\.[0-9]+\.[0-9]+$/', '', serverVar('REMOTE_ADDR'));
				break;
			case 16:
				$this->cookie_salt = preg_replace('/\.[0-9]+\.[0-9]+$/', '', serverVar('REMOTE_ADDR'));
				break;
			case 24:
				$this->cookie_salt = preg_replace('/\.[0-9]+$/', '', serverVar('REMOTE_ADDR'));
				break;
			case 32:
				$this->cookie_salt = serverVar('REMOTE_ADDR');
				break;
			default:
				$this->cookie_salt = 'none';
		}
		return;
	}
	
	/**
	 * Member::login()
	 * Tries to login as a given user.
	 * Returns true when succeeded, returns false when failed
	 * 3.40 adds CustomLogin event
	 * 
	 * @param	String	$login	login name for member
	 * @param	String	$password	password for member
	 * @param	Integer	$shared	whether the user agent is shared or not
	 * 
	 */
	public function login($login, $password, $shared=1)
	{
		global $CONF, $errormessage, $manager;
		
		/* TODO: validation for $login, $password, $shared */
		if ( $login == '' || $password == '' )
		{
			return 0;
		}
		/* limiting the length of password to avoid hash collision */
		$password=i18n::substr($password, 0, 40);
		
		/* 
		 * generate cookie salt from secure cookie key settings
		* (either 'none', 0, 8, 16, 24, or 32)
		*/
		if ( !$this->cookie_salt )
		{
			$salt = 0;
			if ( array_key_exists('secureCookieKey', $CONF) )
			{
				$salt = $CONF['secureCookieKey'];
			}
			$this->set_cookie_salt($salt);
		}
		
		$success = 0;
		$allowlocal = 1;
		$manager->notify('CustomLogin', array('login' => &$login, 'password'=>&$password, 'success'=>&$success, 'allowlocal'=>&$allowlocal));
		
		$this->loggedin = 0;
		if ( $success )
		{
			$this->loggedin = ( $this->readFromName($login) );
		}
		elseif ( $allowlocal )
		{
			$this->loggedin = ( $this->readFromName($login) && $this->checkPassword($password) );
		}
		
		/* login failed */
		if ( !$this->loggedin )
		{
			$trimlogin = trim($login);
			if ( empty($trimlogin) )
			{
				$errormessage = "Please enter a username.";
			}
			else
			{
				$errormessage = 'Login failed for ' . $login;
			}
			$manager->notify('LoginFailed', array('username' => $login) );
			ActionLog::add(INFO, $errormessage);
		}
		/* login success */
		else
		{
			/* For lower compatibility */
			if ( strlen($this->password) === 32 )
			{
				$this->password = $this->hash($password);
			}
			
			$this->newCookieKey();
			$this->setCookies($shared);
			
			if ( $this->cookie_salt !== 'none' )
			{
				/* secure cookie key */
				$this->setCookieKey($this->hash($this->getCookieKey() . $this->cookie_salt));
				$this->write();
			}
			
			$errormessage = '';
			$manager->notify('LoginSuccess', array('member' => &$member, 'username' => $login) );
			ActionLog::add(INFO, "Login successful for $login (sharedpc=$shared)");
		}
		
		return $this->loggedin;
	}
	
	/**
	 * Member::cookielogin()
	 * Login using cookie key
	 * 
	 * @param	String	$login	not used
	 * @param	String	$cookiekey	not used
	 * @return	Boolean	login or not
	 */
	public function cookielogin($login='', $cookiekey='')
	{
		global $CONF, $manager;
		
		if ( !headers_sent() && cookieVar("{$CONF['CookiePrefix']}user") )
		{
			/* Cookie Authentication */
			$ck = cookieVar("{$CONF['CookiePrefix']}loginkey");
				
			/* TODO: validation for each cookie values */
			 	
			/* limiting the length of password to avoid hash collision */
			$ck = i18n::substr($ck,0,32);
				
			/* 
			 * generate cookie salt from secure cookie key settings
			* (either 'none', 0, 8, 16, 24, or 32)
			*/
			if ( !$this->cookie_salt )
			{
				$salt = 0;
				if ( array_key_exists('secureCookieKey', $CONF) )
				{
					$salt = $CONF['secureCookieKey'];
				}
				$this->set_cookie_salt($salt);
			}
			
			if ( $this->cookie_salt !== 'none' )
			{
				$ck = $this->hash($ck . $this->cookie_salt);
			}
			$this->loggedin = ( $this->readFromName(cookieVar("{$CONF['CookiePrefix']}user")) && $this->checkCookieKey($ck) );
			unset($ck);
				
			/* renew cookies when not on a shared computer */
			if ( $this->loggedin && (cookieVar($CONF['CookiePrefix'] . 'sharedpc') != 1) )
			{
				$this->setCookieKey(cookieVar("{$CONF['CookiePrefix']}loginkey"));
				$this->setCookies();
			}
		}
		return $this->loggedin;
	}
	
	/**
	 * Member::logout()
	 * logout and expire cookie
	 * 
	 * @param	Void
	 * @return	Void
	 */
	public function logout()
	{
		global $CONF, $manager;
		
		if ( !headers_sent() && cookieVar("{$CONF['CookiePrefix']}user") )
		{
			/* remove cookies on logout */
			setcookie("{$CONF['CookiePrefix']}user", '', (time() - 2592000), $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
			setcookie("{$CONF['CookiePrefix']}loginkey", '', (time() - 2592000), $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
			$manager->notify('Logout', array('username' => cookieVar("{$CONF['CookiePrefix']}user") ) );
		}
		
		$this->loggedin = 0;
		return;
	}
	
	/**
	 * Member::isLoggedIn()
	 * return member is loggedin or not
	 * 
	 * @param	Void
	 * @return	Void
	 */
	public function isLoggedIn()
	{
		return $this->loggedin;
	}
	
	/**
	 * MEMBER:read()
	 * Read member information from the database
	 * 
	 * @param	String	$where	where statement
	 * @return	Resource	SQL resource
	 * 
	 */
	public function read($where)
	{
		// read info
		$query =  'SELECT * FROM '.sql_table('member') . ' WHERE ' . $where;
		
		$row = DB::getRow($query);
		
		$this->setRealName($row['mrealname']);
		$this->setEmail($row['memail']);
		$this->password = $row['mpassword'];
		$this->setCookieKey($row['mcookiekey']);
		$this->setURL($row['murl']);
		$this->setDisplayName($row['mname']);
		$this->setAdmin($row['madmin']);
		$this->id = $row['mnumber'];
		$this->setCanLogin($row['mcanlogin']);
		$this->setNotes($row['mnotes']);
		$this->setLocale($row['mlocale']);
		$this->setAutosave($row['mautosave']);
		
		return $row ? TRUE : FALSE;
	}
	
	/**
	 * Member::isBlogAdmin()
	 * Returns true if member is an admin for the given blog
	 * (returns false if not a team member)
	 * 
	 * @param	Integer	$blogid	weblog id
	 * @return	Integer	weblog admin or not
	 * 
	 */
	public function isBlogAdmin($blogid)
	{
		$query = 'SELECT tadmin FROM '.sql_table('team').' WHERE'
		. ' tblog=' . intval($blogid)
		. ' and tmember='. $this->getID();
		$res = DB::getValue($query);
		if ( $res )
			return ($res == 1);
		else
			return 0;
	}
	
	/**
	 * Member::blogAdminRights()
	 * 
	 * @param	integer	$blogid	ID of target weblog
	 * @return	boolean	whether to have admin rights to the weblog or not
	 * 
	 */
	public function blogAdminRights($blogid)
	{
		return ($this->isAdmin() || $this->isBlogAdmin($blogid));
	}
	
	/**
	 * Member::teamRights()
	 * 
	 * @param	integer	$blogid	ID of target weblog
	 * @return	boolean	whether to have admin right to the weblog or not
	 * 
	 */
	public function teamRights($blogid)
	{
		return ($this->isAdmin() || $this->isTeamMember($blogid));
	}
	
	/**
	 * Member::isTeamMember()
	 * Returns true if this member is a team member of the given blog
	 * 
	 * @param	integer	$blogid	ID of target weblog
	 * @return	boolean	whether to join the weblog or not
	 * 
	 */
	public function isTeamMember($blogid)
	{
		$query = 'SELECT * FROM '.sql_table('team').' WHERE'
			   . ' tblog=' . intval($blogid)
			   . ' and tmember='. $this->getID();
		$res = DB::getResult($query);
		return ($res->rowCount() != 0);
	}
	
	/**
	 * Member::canAddItem()
	 * 
	 * @param	integer	$catid	ID of target category
	 * @return	boolean	whether to be able to add items to the category or not
	 * 
	 */
	public function canAddItem($catid)
	{
		global $manager;
		
		// if this is a 'newcat' style newcat
		// no blog admin of destination blog -> NOK
		// blog admin of destination blog -> OK
		if ( i18n::strpos($catid,'newcat') === 0 )
		{
			// get blogid
			list($blogid) = sscanf($catid,"newcat-%d");
			return $this->blogAdminRights($blogid);
		}
		
		// category does not exist -> NOK
		if ( !$manager->existsCategory($catid) )
		{
			return 0;
		}
		
		$blogid = getBlogIDFromCatID($catid);
		
		// no team rights for blog -> NOK
		if (!$this->teamRights($blogid))
		{
			return 0;
		}
		
		// all other cases: OK
		return 1;
	}
	
	/**
	 * Member::canAlterComment()
	 * Returns true if this member can edit/delete a commentitem. This can be in the
	 * following cases:
	 *	  - member is a super-admin
	 *   - member is the author of the comment
	 *   - member is admin of the blog associated with the comment
	 *   - member is author of the item associated with the comment
	 * 
	 * @param	integer	$commentid	ID of target comment
	 * @return	boolean	delete/edit the comment or not
	 * 
	 */
	public function canAlterComment($commentid)
	{
		if ( $this->isAdmin() )
		{
			return 1;
		}
		
		$query =  'SELECT citem as itemid, iblog as blogid, cmember as cauthor, iauthor'
			   . ' FROM '.sql_table('comment') .', '.sql_table('item').', '.sql_table('blog')
			   . ' WHERE citem=inumber and iblog=bnumber and cnumber=' . intval($commentid);
		$res = DB::getRow($query);
		
		return ($res['cauthor'] == $this->getID()) or $this->isBlogAdmin($res['blogid']) or ($res['iauthor'] == $this->getID());
	}
	
	/**
	 * Member::canAlterItem()
	 * Returns true if this member can edit/delete an item. This is true in the following
	 * cases: - member is a super-admin
	 *	       - member is the author of the item
	 *        - member is admin of the the associated blog
	 * 
	 * @param	integer	$itemid	ID of target item
	 * @return	boolean	delete/edit the item or not
	 * 
	 */
	public function canAlterItem($itemid)
	{
		if ($this->isAdmin()) return 1;
		
		$query =  'SELECT iblog, iauthor FROM '.sql_table('item').' WHERE inumber=' . intval($itemid);
		$res = DB::getRow($query);
		return ($res['iauthor'] == $this->getID()) or $this->isBlogAdmin($res['iblog']);
	}
	
	/**
	 * Member::canBeDeleted()
	 * Return true if member can be deleted. This means that there are no items posted by the member left
	 * 
	 * @param	void
	 * @return	boolean	whether there is no items or exists
	 * 
	 */
	public function canBeDeleted()
	{
		$res = DB::getResult('SELECT * FROM '.sql_table('item').' WHERE iauthor=' . $this->getID());
		return ( $res->rowCount() == 0 );
	}
	
	/**
	 * Member::canUpdateItem()
	 * returns true if this member can move/update an item to a given category,
	 * false if not (see comments fot the tests that are executed)
	 * 
	 * @param	integer	$itemid
	 * @param	string	$newcat (can also be of form 'newcat-x' with x=blogid)
	 * @return	boolean	whether being able to update the item or not
	 * 
	 */
	public function canUpdateItem($itemid, $newcat)
	{
		global $manager;
		
		// item does not exists -> NOK
		if ( !$manager->existsItem($itemid,1,1) )
		{
			return 0;
		}
		
		// cannot alter item -> NOK
		if (!$this->canAlterItem($itemid))
		{
			return 0;
		}
		
		// if this is a 'newcat' style newcat
		// no blog admin of destination blog -> NOK
		// blog admin of destination blog -> OK
		if ( i18n::strpos($newcat, 'newcat') === 0 )
		{
			// get blogid
			list($blogid) = sscanf($newcat, 'newcat-%d');
			return $this->blogAdminRights($blogid);
		}
		
		// category does not exist -> NOK
		if (!$manager->existsCategory($newcat))
		{
			return 0;
		}
		
		// get item
		$item =& $manager->getItem($itemid,1,1);
		
		// old catid = new catid -> OK
		if ($item['catid'] == $newcat)
		{
			return 1;
		}
		
		// not a valid category -> NOK
		$validCat = DB::getValue('SELECT COUNT(*) AS result FROM '.sql_table('category').' WHERE catid='.intval($newcat));
		if ( !$validCat )
		{
			return 0;
		}
		
		// get destination blog
		$source_blogid = getBlogIDFromItemID($itemid);
		$dest_blogid = getBlogIDFromCatID($newcat);
		
		// not a team member of destination blog -> NOK
		if ( !$this->teamRights($dest_blogid) )
		{
			return 0;
		}
		
		// if member is author of item -> OK
		if ( $item['authorid'] == $this->getID() )
		{
			return 1;
		}
		
		// if member has admin rights on both blogs: OK
		if ( ($this->blogAdminRights($dest_blogid)) && ($this->blogAdminRights($source_blogid)) )
		{
			return 1;
		}
		
		// all other cases: NOK
		return 0;
	}
	
	/**
	 * Member::setCookies()
	 * Sets the cookies for the member
	 * 
	 * @param boolean	$shared	set this to 1 when using a shared computer. Cookies will expire
	 *				at the end of the session in this case.
	 * @return	void
	 * 
	 */
	public function setCookies($shared = 0)
	{
		global $CONF;
		
		if ( $CONF['SessionCookie'] || $shared )
		{
			$lifetime = 0;
		}
		else
		{
			$lifetime = time()+2592000;
		}
		
		setcookie($CONF['CookiePrefix'] . 'user', $this->getDisplayName(), $lifetime, $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
		setcookie($CONF['CookiePrefix'] . 'loginkey', $this->getCookieKey(), $lifetime, $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
		
		// make sure cookies on shared pcs don't get renewed
		if ( $shared )
		{
			setcookie($CONF['CookiePrefix'] .'sharedpc', '1',$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
		}
		return;
	}
	
	/**
	 * Member::sendActivationLink()
	 * Send activation mail
	 * 
	 * @param	string	$type	activation type
	 * @param	string	$extra	extra info
	 * @return	void
	 */
	public function sendActivationLink($type, $extra='')
	{
		global $CONF;
		
		if ( !isset($CONF['ActivationDays']) )
		{
			$CONF['ActivationDays'] = 2;
		}
		
		// generate key and URL
		$key = $this->generateActivationEntry($type, $extra);
		$url = $CONF['AdminURL'] . 'index.php?action=activate&key=' . $key;
		
		// choose text to use in mail
		switch ( $type )
		{
			case 'register':
				$message = _ACTIVATE_REGISTER_MAIL;
				$subject = _ACTIVATE_REGISTER_MAILTITLE;
				break;
			case 'forgot':
				$message = _ACTIVATE_FORGOT_MAIL;
				$subject = _ACTIVATE_FORGOT_MAILTITLE;
				break;
			case 'addresschange':
				$message = _ACTIVATE_CHANGE_MAIL;
				$subject = _ACTIVATE_CHANGE_MAILTITLE;
				break;
			default;
		}
		
		// fill out variables in text
		$aVars = array(
			'siteName' => $CONF['SiteName'],
			'siteUrl' => $CONF['IndexURL'],
			'memberName' => $this->getDisplayName(),
			'activationUrl' => $url,
			'activationDays' => $CONF['ActivationDays']
		);
		
		$message = Template::fill($message, $aVars);
		$subject = Template::fill($subject, $aVars);
		
		// send mail
		NOTIFICATION::mail($this->getEmail(), $subject ,$message, $CONF['AdminEmail'], i18n::get_current_charset());
		
		ActionLog::add(INFO, _ACTIONLOG_ACTIVATIONLINK . ' (' . $this->getDisplayName() . ' / type: ' . $type . ')');
		return;
	}
	
	/**
	 * Member::getAdminBlogs()
	 * Returns an array of all blogids for which member has admin rights
	 * 
	 * @param	void
	 * @return	array	weblog IDs in which this member has admin rights
	 * 
	 */
	public function getAdminBlogs()
	{
		$blogs = array();
		
		if ($this->isAdmin())
		{
			$query = 'SELECT bnumber as blogid from '.sql_table('blog');
		}
		else
		{
			$query = 'SELECT tblog as blogid from '.sql_table('team').' where tadmin=1 and tmember=' . $this->getID();
		}
		
		$res = DB::getResult($query);
		if ( $res->rowCount() > 0 )
		{
			foreach ( $res as $row )
			{
				array_push($blogs, $row['blogid']);
			}
		}
		return $blogs;
	}
	
	/**
	 * Member::getTeamBlogs()
	 * Returns an array of all blogids for which member has team rights
	 * 
	 * @param	boolean	$incAdmin	whether checking weblog admin rights or not
	 * @return	array	weblog IDs in which this member join
	 * 
	 */
	public function getTeamBlogs($incAdmin = 1)
	{
		$incAdmin = intval($incAdmin);
		$blogs = array();
		
		if ( $this->isAdmin() && $incAdmin )
		{
			$query = 'SELECT bnumber as blogid from '.sql_table('blog');
		}
		else
		{
			$query = 'SELECT tblog as blogid from '.sql_table('team').' where tmember=' . $this->getID();
		}
		
		$res = DB::getResult($query);
		if ( $res->rowCount() > 0 )
		{
			foreach ( $res as $row )
			{
				array_push($blogs, $row['blogid']);
			}
		}
		return $blogs;
	}
	
	/**
	 * Member::getNotifyFromMailAddress()
	 * 
	 * Returns an email address from which notification of commenting/karma voting can
	 * be sent. A suggestion can be given for when the member is not logged in
	 * 
	 * @param	String	$suggest
	 * @return	String	mail address or suggestion
	 */
	public function getNotifyFromMailAddress($suggest = "")
	{
		global $CONF;
		if ( $this->isLoggedIn() )
		{
			return $this->getDisplayName() . " <" . $this->getEmail() . ">";
		}
		else if ( NOTIFICATION::address_validation($suggest) )
		{
			return $suggest;
		}
		return $CONF['AdminEmail'];
	}
	
	/**
	 * Member::write()
	 * Write data to database
	 * 
	 * @param	void
	 * @return	void
	 * 
	 */
	public function write()
	{
		$query =  'UPDATE '.sql_table('member')
		        . ' SET mname=' . DB::quoteValue($this->displayname) . ', '
		           . 'mrealname='. DB::quoteValue($this->realname) . ', '
		           . 'mpassword='. DB::quoteValue($this->password) . ', '
		           . 'mcookiekey='. DB::quoteValue($this->cookiekey) . ', '
		           . 'murl=' . DB::quoteValue($this->url) . ', '
		           . 'memail=' . DB::quoteValue($this->email) . ', '
		           . 'madmin=' . intval($this->admin) . ', '
		           . 'mnotes=' . DB::quoteValue($this->notes) . ', '
		           . 'mcanlogin=' . intval($this->canlogin) . ', '
		           . 'mlocale=' . DB::quoteValue($this->locale) . ', '
		           . 'mautosave=' . intval($this->autosave) . ' '
		        . 'WHERE mnumber=' . intval($this->id);
		DB::execute($query);
		return;
	}
	
	public function checkCookieKey($key)
	{
		return ( ($key != '') && ( $key == $this->getCookieKey() ) );
	}
	
	public function checkPassword($pw)
	{
		/* for lower compatibility (md5) */
		if ( strlen($this->password) === 32 )
		{
			return (md5($pw) == $this->password);
		}
		return ($this->hash($pw) == $this->password);
	}
	
	public function getRealName()
	{
		return $this->realname;
	}
	
	public function setRealName($name)
	{
		$this->realname = $name;
	}
	
	public function getEmail()
	{
		return $this->email;
	}
	
	public function setEmail($email)
	{
		$this->email = $email;
	}
	
	public function getPassword()
	{
		return $this->password;
	}
	
	public function setPassword($pwd)
	{
		$this->password = $this->hash($pwd);
	}
	
	public function getCookieKey()
	{
		return $this->cookiekey;
	}
	
	/**
	 * Member::newCookieKey()
	 * Generate new cookiekey, save it, and return it
	 * 
	 * @param	void
	 * @return	void
	 * 
	 */
	public function newCookieKey()
	{
		mt_srand( (double) microtime() * 1000000);
		$this->cookiekey = $this->hash(uniqid(mt_rand()));
		$this->write();
		return $this->cookiekey;
	}
	
	public function setCookieKey($val)
	{
		$this->cookiekey = $val;
	}
	
	public function getURL()
	{
		return $this->url;
	}
	
	public function setURL($site)
	{
		$this->url = $site;
	}
	
	public function getLocale()
	{
		return $this->locale;
	}
	
	public function setLocale($locale)
	{
		if ( !preg_match('#^(.+)_(.+)_(.+)$#', $locale)
		 && ($locale = i18n::convert_old_language_file_name_to_locale($locale)) === FALSE )
		{
			$locale = '';
		}
		$this->locale = $locale;
		return;
	}
	
	public function setDisplayName($nick)
	{
		$this->displayname = $nick;
	}
	
	public function getDisplayName()
	{
		return $this->displayname;
	}
	
	public function isAdmin()
	{
		return $this->admin;
	}
	
	public function setAdmin($val)
	{
		$this->admin = $val;
	}
	
	public function canLogin()
	{
		return $this->canlogin;
	}
	
	public function setCanLogin($val)
	{
		$this->canlogin = $val;
	}
	
	public function getNotes()
	{
		return $this->notes;
	}
	
	public function setNotes($val)
	{
		$this->notes = $val;
	}
	
	public function getAutosave()
	{
		return $this->autosave;
	}
	
	public function setAutosave($val)
	{
		$this->autosave = $val;
		return;
	}
	
	/**
	 * Member::getID()
	 * 
	 * @param	void
	 * @return	integer	id of this member object
	 * 
	 */
	public function getID()
	{
		return $this->id;
	}
	
	/**
	 * Member::exists()
	 * Returns true if there is a member with the given login name
	 * 
	 * @static
	 * @param	string	$name	target name
	 * @return	boolean	whether target name exists or not
	 */
	public static function exists($name)
	{
		$r = DB::getResult('SELECT * FROM ' . sql_table('member') . ' WHERE mname=' . DB::quoteValue($name));
		return ( $r->rowCount() != 0 );
	}
	
	/**
	 * Member::existsID()
	 * Returns true if there is a member with the given ID
	 * 
	 * @static
	 * @param	integer	$id	target id
	 * @return	boolean	whether target id exists or not
	 * 
	 */
	public static function existsID($id)
	{
		$r = DB::getResult('SELECT * FROM ' . sql_table('member') . ' WHERE mnumber=' . intval($id));
		return ( $r->rowCount() != 0 );
	}
	
	/**
	 * Member::isNameProtected()
	 *  Checks if a username is protected.
	 *  If so, it can not be used on anonymous comments
	 * 
	 * @param	string	$name	target name
	 * @return	boolean	whether the name exists or not
	 * 
	 */
	public function isNameProtected($name)
	{
		// extract name
		$name = strip_tags($name);
		$name = trim($name);
		return self::exists($name);
	}
	
	/**
	 * Member::create()
	 * Adds a new member
	 * 
	 * @static
	 * @param	String	$name
	 * @param	String	$realname
	 * @param	String	$password
	 * @param	String	$email
	 * @param	String	$url
	 * @param	String	$admin
	 * @param	String	$canlogin
	 * @param	String	$notes
	 * @return	String	1 if success, others if fail
	 */
	static public function create($name, $realname, $password, $email, $url, $admin, $canlogin, $notes)
	{
		if ( !NOTIFICATION::address_validation($email) )
		{
			return _ERROR_BADMAILADDRESS;
		}
		
		/* TODO: this method should be in MEMBER class, not globalfunctions */
		if ( !isValidDisplayName($name) )
		{
			return _ERROR_BADNAME;
		}
		
		if ( self::exists($name) )
		{
			return _ERROR_NICKNAMEINUSE;
		}
		
		if ( !$realname )
		{
			return _ERROR_REALNAMEMISSING;
		}
		
		/* TODO: check the number of characters */
		if ( !$password )
		{
			return _ERROR_PASSWORDMISSING;
		}
		
		/* 
		 *  begin if: sometimes user didn't prefix the URL with http:// or https://,
		 *  this cause a malformed URL. Let's fix it.
		 */
		
		if ( !preg_match('#^https?://#', $url) )
		{
			$url = 'http://' . $url;
		}
		
		$name		= DB::quoteValue($name);
		$realname	= DB::quoteValue($realname);
		/* NOTE: hashed password is automatically updated if the length is 32 bytes when logging in */
		$password	= DB::quoteValue(md5($password));
		$email		= DB::quoteValue($email);
		$url		= DB::quoteValue($url);
		$admin		= (integer) $admin;
		$canlogin	= (integer) $canlogin;
		$notes		= DB::quoteValue($notes);
		
		$query = "INSERT INTO %s"
		       . " (MNAME,MREALNAME,MPASSWORD,MEMAIL,MURL, MADMIN, MCANLOGIN, MNOTES)"
		       . " VALUES (%s, %s, %s, %s, %s, %d, %d, %s)";
		$query = sprintf($query, sql_table('member'), $name, $realname, $password, $email, $url, $admin, $canlogin, $notes);
		DB::execute($query);
		
		ActionLog::add(INFO, _ACTIONLOG_NEWMEMBER . ' ' . $name);
		
		return 1;
	}
	
	/**
	 * Member::getActivationInfo()
	 * Returns activation info for a certain key (an object with properties vkey, vmember, ...)
	 * 
	 * @static
	 * @param	string	$key	activation key
	 * @return	mixed	return 0 if failed, else return activation table object
	 * 
	 */
	public static function getActivationInfo($key)
	{
		$query = 'SELECT * FROM ' . sql_table('activation') . ' WHERE vkey=' . DB::quoteValue($key);
		$res = DB::getResult($query);
		
		if ( !$res || ($res->rowCount() == 0) )
		{
			return 0;
		}
		return $res->fetch();
	}
	
	/**
	 * Member::generateActivationEntry()
	 * Creates an account activation key
	 * addresschange -> old email address
	 * 
	 * @param	string	$type	one of the following values (determines what to do when activation expires)
	 * 				'register'	(new member registration)
	 * 				'forgot'	(forgotton password)
	 * 				'addresschange'	(member address has changed)
	 * @param	string	$extra	extra info (needed when validation link expires)
	 * @return	string	activation key
	 */
	public function generateActivationEntry($type, $extra = '')
	{
		// clean up old entries
		$this->cleanupActivationTable();
		
		// kill any existing entries for the current member (delete is ok)
		// (only one outstanding activation key can be present for a member)
		DB::execute('DELETE FROM ' . sql_table('activation') . ' WHERE vmember=' . intval($this->getID()));
		
		// indicates if the member can log in while the link is active
		$canLoginWhileActive = false;
		switch ( $type )
		{
			case 'forgot':
				$canLoginWhileActive = true;
				break;
			case 'register':
				break;
			case 'addresschange':
				$extra = $extra . '/' . ( $this->canLogin() ? '1' : '0' );
				break;
		}
		
		$ok = false;
		while ( !$ok )
		{
			// generate a random key
			srand((double)microtime()*1000000);
			$key = $this->hash(uniqid(rand(), true));
			
			// attempt to add entry in database
			// add in database as non-active
			$query = 'INSERT INTO %s (vkey, vtime, vmember, vtype, vextra) VALUES (%s, %s, %d, %s, %s)';
			$query = sprintf($query
				, sql_table('activation')
				, DB::quoteValue($key)
				, DB::formatDateTime()
				, intval($this->getID())
				, DB::quoteValue($type)
				, DB::quoteValue($extra)
			);
			if ( DB::execute($query) !== FALSE )
				$ok = true;
		}
		
		// mark member as not allowed to log in
		if ( !$canLoginWhileActive )
		{
			$this->setCanLogin(0);
			$this->write();
		}
		
		// return the key
		return $key;
	}
	
	/**
	 * Member::activate()
	 * Inidicates that an activation link has been clicked and any forms displayed
	 * there have been successfully filled out.
	 * 
	 * @param	string	$key	activation key
	 * @return	boolean
	 * 
	 */
	public function activate($key)
	{
		// get activate info
		$info = self::getActivationInfo($key);
		
		// no active key
		if ( !$info )
		{
			return false;
		}
		
		switch ( $info['vtype'] )
		{
			case 'forgot':
				// nothing to do
				break;
			case 'register':
				// set canlogin value
				global $CONF;
				DB::execute('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($CONF['NewMemberCanLogon']). ' WHERE mnumber=' . intval($info['vmember']));
				break;
			case 'addresschange':
				// reset old 'canlogin' value
				list($oldEmail, $oldCanLogin) = preg_split('#/#', $info['vextra']);
				DB::execute('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ' WHERE mnumber=' . intval($info['vmember']));
				break;
		}
		
		// delete from activation table
		DB::execute('DELETE FROM ' . sql_table('activation') . ' WHERE vkey=' . DB::quoteValue($key));
		
		// success!
		return true;
	}
	
	/**
	 * Member::cleanupActivationTable()
	 * Cleans up entries in the activation table. All entries older than 2 days are removed.
	 * (static)
	 * 
	 * @param	void
	 * @return	void
	 */
	public function cleanupActivationTable()
	{
		$actdays = 2;
		if ( isset($CONF['ActivationDays']) && intval($CONF['ActivationDays']) > 0 )
		{
			$actdays = intval($CONF['ActivationDays']);
		}
		else
		{
			$CONF['ActivationDays'] = 2;
		}
		$boundary = time() - (60 * 60 * 24 * $actdays);
		
		// 1. walk over all entries, and see if special actions need to be performed
		$query = sprintf('SELECT * FROM %s WHERE vtime < %s', sql_table('activation'), DB::formatDateTime($boundary));
		$res = DB::getResult($query);
		
		foreach ( $res as $row )
		{
			switch ( $row['vtype'] )
			{
				case 'register':
					// delete all information about this site member. registration is undone because there was
					// no timely activation
					include_once($DIR_LIBS . 'ADMIN.php');
					Admin::deleteOneMember(intval($row['vmember']));
					break;
				case 'addresschange':
					// revert the e-mail address of the member back to old address
					list($oldEmail, $oldCanLogin) = preg_split('#/#', $row['vextra']);
					DB::execute('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ', memail=' . DB::quoteValue($oldEmail). ' WHERE mnumber=' . intval($row['vmember']));
					break;
				case 'forgot':
					// delete the activation link and ignore. member can request a new password using the
					// forgot password link
					break;
			}
		}
		
		// 2. delete activation entries for real
		$query = sprintf('DELETE FROM %s WHERE vtime < %s', sql_table('activation'), DB::formatDateTime($boundary));
		DB::execute($query);
		return;
	}
	
	/**
	 * Member::$language
	 * 
	 * @obsolete
	 * @param	void
	 * @return	void
	 * 
	 */
	public $language = '';
	/**
	 * Member::getLanguage()
	 * 
	 * @obsolete
	 * @param	void
	 * @return	void
	 * 
	 */
	public function getLanguage()
	{
		if ( ($language = i18n::convert_locale_to_old_language_file_name($this->locale)) === FALSE )
		{
			$language = '';
		}
		return $language;
	}
}
=======
<?php

/* 
 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
 * Copyright (C) 2002-2009 The Nucleus Group
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * (see nucleus/documentation/index.html#license for more info)
 */
/**
 * A class representing site members
 * 
 * @license http://nucleuscms.org/license.txt GNU General Public License
 * @copyright Copyright (C) 2002-2009 The Nucleus Group
 * @version $Id: MEMBER.php 1616 2012-01-08 09:48:15Z sakamocchi $
 */
class Member
{
	// 1 when authenticated, 0 when not
	public $loggedin = 0;
	public $password;		// not the actual password, but rather a MD5 hash
	private $algorism = 'md5';
	
	public $cookiekey;		// value that should also be in the client cookie to allow authentication
	private $cookie_salt = FALSE;
	
	// member info
	public $id = -1;
	public $realname;
	public $displayname;
	public $email;
	public $url;
	public $admin = 0;			// (either 0 or 1)
	public $canlogin = 0;		// (either 0 or 1)
	public $notes;
	public $autosave = 1;		// if the member use the autosave draft function
	public $adminskin = 0;		// if the member use the autosave draft function
	public $bookmarklet = 0;		// if the member use the autosave draft function
	private $locale = '';
	
	/**
	 * Member::__construct()
	 * Constructor for a member object
	 * 
	 * @param	Void
	 * @return	Void
	 * 
	 */
	public function __construct()
	{
		return;
	}
	
	/**
	 * Member::createFromName()
	 * Create a member object for a given displayname
	 * 
	 * @static
	 * @param	String	$displayname	login name
	 * @return	Object	member object
	 * 
	 */
	public static function &createFromName($displayname)
	{
		$mem = new Member();
		$mem->readFromName($displayname);
		return $mem;
	}
	
	/**
	 * Member::createFromID()
	 * Create a member object for a given ID
	 * 
	 * @static
	 * @param	Integer	$id	id for member
	 */
	public static function &createFromID($id)
	{
		$mem = new Member();
		$mem->readFromID($id);
		return $mem;
	}
	
	/**
	 * Member::readFromName()
	 * Read member table in database
	 * 
	 * @param	String	$displayname	login name
	 * @return	Object	SQL resource
	 * 
	 */
	public function readFromName($displayname)
	{
		return $this->read('mname='.DB::quoteValue($displayname));
	}
	
	/**
	 * Member::readFromID()
	 * Read member table in database
	 * 
	 * @param	Integer	$id	id for member
	 * @return	Object	SQL resource
	 * 
	 */
	public function readFromID($id)
	{
		return $this->read("mnumber=" . intval($id));
	}
	
	/**
	 * Member::hash()
	 * hash the target string
	 * 
	 * @param	String	$string	target string
	 * @return	Void	hashed string
	 */
	public function hash($string)
	{
		switch ( $this->algorism )
		{
			case 'md5':
			default:
				$string = md5($string);
		}
		return $string;
	}
	
	/**
	 * Member::set_cookie_salt()
	 * 
	 * @param	integer	$key	secureCookieKey value
	 * @return	void
	 * 
	 */
	private function set_cookie_salt($key = 0)
	{
		if ( !$key )
		{
			$key = 24;
		}
		
		switch( $key )
		{
			case 8:
				$this->cookie_salt = preg_replace('/\.[0-9]+\.[0-9]+\.[0-9]+$/', '', serverVar('REMOTE_ADDR'));
				break;
			case 16:
				$this->cookie_salt = preg_replace('/\.[0-9]+\.[0-9]+$/', '', serverVar('REMOTE_ADDR'));
				break;
			case 24:
				$this->cookie_salt = preg_replace('/\.[0-9]+$/', '', serverVar('REMOTE_ADDR'));
				break;
			case 32:
				$this->cookie_salt = serverVar('REMOTE_ADDR');
				break;
			default:
				$this->cookie_salt = 'none';
		}
		return;
	}
	
	/**
	 * Member::login()
	 * Tries to login as a given user.
	 * Returns true when succeeded, returns false when failed
	 * 3.40 adds CustomLogin event
	 * 
	 * @param	String	$login	login name for member
	 * @param	String	$password	password for member
	 * @param	Integer	$shared	whether the user agent is shared or not
	 * 
	 */
	public function login($login, $password, $shared=1)
	{
		global $CONF, $errormessage, $manager;
		
		/* TODO: validation for $login, $password, $shared */
		if ( $login == '' || $password == '' )
		{
			return 0;
		}
		/* limiting the length of password to avoid hash collision */
		$password=i18n::substr($password, 0, 40);
		
		/* 
		 * generate cookie salt from secure cookie key settings
		* (either 'none', 0, 8, 16, 24, or 32)
		*/
		if ( !$this->cookie_salt )
		{
			$salt = 0;
			if ( array_key_exists('secureCookieKey', $CONF) )
			{
				$salt = $CONF['secureCookieKey'];
			}
			$this->set_cookie_salt($salt);
		}
		
		$success = 0;
		$allowlocal = 1;
		$data = array('login' => &$login, 'password'=>&$password, 'success'=>&$success, 'allowlocal'=>&$allowlocal);
		$manager->notify('CustomLogin', $data);
		
		$this->loggedin = 0;
		if ( $success )
		{
			$this->loggedin = ( $this->readFromName($login) );
		}
		elseif ( $allowlocal )
		{
			$this->loggedin = ( $this->readFromName($login) && $this->checkPassword($password) );
		}
		
		/* login failed */
		if ( !$this->loggedin )
		{
			$trimlogin = trim($login);
			if ( empty($trimlogin) )
			{
				$errormessage = "Please enter a username.";
			}
			else
			{
				$errormessage = 'Login failed for ' . $login;
			}
			$data = array('username' => $login);
			$manager->notify('LoginFailed', $data);
			ActionLog::add(INFO, $errormessage);
		}
		/* login success */
		else
		{
			/* For lower compatibility */
			if ( strlen($this->password) === 32 )
			{
				$this->password = $this->hash($password);
			}
			
			$this->newCookieKey();
			$this->setCookies($shared);
			
			if ( $this->cookie_salt !== 'none' )
			{
				/* secure cookie key */
				$this->setCookieKey($this->hash($this->getCookieKey() . $this->cookie_salt));
				$this->write();
			}
			
			$errormessage = '';
			$data = array('member' => &$this, 'username' => $login);
			$manager->notify('LoginSuccess', $data);
			ActionLog::add(INFO, "Login successful for $login (sharedpc=$shared)");
		}
		
		return $this->loggedin;
	}
	
	/**
	 * Member::cookielogin()
	 * Login using cookie key
	 * 
	 * @param	String	$login	not used
	 * @param	String	$cookiekey	not used
	 * @return	Boolean	login or not
	 */
	public function cookielogin($login='', $cookiekey='')
	{
		global $CONF, $manager;
		
		if ( !headers_sent() && cookieVar("{$CONF['CookiePrefix']}user") )
		{
			/* Cookie Authentication */
			$ck = cookieVar("{$CONF['CookiePrefix']}loginkey");
				
			/* TODO: validation for each cookie values */
			 	
			/* limiting the length of password to avoid hash collision */
			$ck = i18n::substr($ck,0,32);
				
			/* 
			 * generate cookie salt from secure cookie key settings
			* (either 'none', 0, 8, 16, 24, or 32)
			*/
			if ( !$this->cookie_salt )
			{
				$salt = 0;
				if ( array_key_exists('secureCookieKey', $CONF) )
				{
					$salt = $CONF['secureCookieKey'];
				}
				$this->set_cookie_salt($salt);
			}
			
			if ( $this->cookie_salt !== 'none' )
			{
				$ck = $this->hash($ck . $this->cookie_salt);
			}
			$this->loggedin = ( $this->readFromName(cookieVar("{$CONF['CookiePrefix']}user")) && $this->checkCookieKey($ck) );
			unset($ck);
				
			/* renew cookies when not on a shared computer */
			if ( $this->loggedin && (cookieVar($CONF['CookiePrefix'] . 'sharedpc') != 1) )
			{
				$this->setCookieKey(cookieVar("{$CONF['CookiePrefix']}loginkey"));
				$this->setCookies();
			}
		}
		return $this->loggedin;
	}
	
	/**
	 * Member::logout()
	 * logout and expire cookie
	 * 
	 * @param	Void
	 * @return	Void
	 */
	public function logout()
	{
		global $CONF, $manager;
		
		if ( !headers_sent() && cookieVar("{$CONF['CookiePrefix']}user") )
		{
			/* remove cookies on logout */
			setcookie("{$CONF['CookiePrefix']}user", '', (time() - 2592000), $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
			setcookie("{$CONF['CookiePrefix']}loginkey", '', (time() - 2592000), $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
			$data = array('username' => cookieVar("{$CONF['CookiePrefix']}user"));
			$manager->notify('Logout', $data);
		}
		
		$this->loggedin = 0;
		return;
	}
	
	/**
	 * Member::isLoggedIn()
	 * return member is loggedin or not
	 * 
	 * @param	Void
	 * @return	Void
	 */
	public function isLoggedIn()
	{
		return $this->loggedin;
	}
	
	/**
	 * MEMBER:read()
	 * Read member information from the database
	 * 
	 * @param	String	$where	where statement
	 * @return	Resource	SQL resource
	 * 
	 */
	public function read($where)
	{
		// read info
		$query =  'SELECT * FROM '.sql_table('member') . ' WHERE ' . $where;
		
		$row = DB::getRow($query);
		
		$this->setRealName($row['mrealname']);
		$this->setEmail($row['memail']);
		$this->password = $row['mpassword'];
		$this->setCookieKey($row['mcookiekey']);
		$this->setURL($row['murl']);
		$this->setDisplayName($row['mname']);
		$this->setAdmin($row['madmin']);
		$this->id = $row['mnumber'];
		$this->setCanLogin($row['mcanlogin']);
		$this->setNotes($row['mnotes']);
		$this->setLocale($row['mlocale']);
		$this->setAutosave($row['mautosave']);
		$this->setAdminSkin($row['madminskin']);
		$this->setBookmarklet($row['mbkmklt']);
		
		return $row ? TRUE : FALSE;
	}
	
	/**
	 * Member::isBlogAdmin()
	 * Returns true if member is an admin for the given blog
	 * (returns false if not a team member)
	 * 
	 * @param	Integer	$blogid	weblog id
	 * @return	Integer	weblog admin or not
	 * 
	 */
	public function isBlogAdmin($blogid)
	{
		$query = 'SELECT tadmin FROM '.sql_table('team').' WHERE'
		. ' tblog=' . intval($blogid)
		. ' and tmember='. $this->getID();
		$res = DB::getValue($query);
		if ( $res )
			return ($res == 1);
		else
			return 0;
	}
	
	/**
	 * Member::blogAdminRights()
	 * 
	 * @param	integer	$blogid	ID of target weblog
	 * @return	boolean	whether to have admin rights to the weblog or not
	 * 
	 */
	public function blogAdminRights($blogid)
	{
		return ($this->isAdmin() || $this->isBlogAdmin($blogid));
	}
	
	/**
	 * Member::teamRights()
	 * 
	 * @param	integer	$blogid	ID of target weblog
	 * @return	boolean	whether to have admin right to the weblog or not
	 * 
	 */
	public function teamRights($blogid)
	{
		return ($this->isAdmin() || $this->isTeamMember($blogid));
	}
	
	/**
	 * Member::isTeamMember()
	 * Returns true if this member is a team member of the given blog
	 * 
	 * @param	integer	$blogid	ID of target weblog
	 * @return	boolean	whether to join the weblog or not
	 * 
	 */
	public function isTeamMember($blogid)
	{
		$query = 'SELECT * FROM '.sql_table('team').' WHERE'
			   . ' tblog=' . intval($blogid)
			   . ' and tmember='. $this->getID();
		$res = DB::getResult($query);
		return ($res->rowCount() != 0);
	}
	
	/**
	 * Member::canAddItem()
	 * 
	 * @param	integer	$catid	ID of target category
	 * @return	boolean	whether to be able to add items to the category or not
	 * 
	 */
	public function canAddItem($catid)
	{
		global $manager;
		
		// if this is a 'newcat' style newcat
		// no blog admin of destination blog -> NOK
		// blog admin of destination blog -> OK
		if ( i18n::strpos($catid,'newcat') === 0 )
		{
			// get blogid
			list($blogid) = sscanf($catid,"newcat-%d");
			return $this->blogAdminRights($blogid);
		}
		
		// category does not exist -> NOK
		if ( !$manager->existsCategory($catid) )
		{
			return 0;
		}
		
		$blogid = getBlogIDFromCatID($catid);
		
		// no team rights for blog -> NOK
		if (!$this->teamRights($blogid))
		{
			return 0;
		}
		
		// all other cases: OK
		return 1;
	}
	
	/**
	 * Member::canAlterComment()
	 * Returns true if this member can edit/delete a commentitem. This can be in the
	 * following cases:
	 *	  - member is a super-admin
	 *   - member is the author of the comment
	 *   - member is admin of the blog associated with the comment
	 *   - member is author of the item associated with the comment
	 * 
	 * @param	integer	$commentid	ID of target comment
	 * @return	boolean	delete/edit the comment or not
	 * 
	 */
	public function canAlterComment($commentid)
	{
		if ( $this->isAdmin() )
		{
			return 1;
		}
		
		$query =  'SELECT citem as itemid, iblog as blogid, cmember as cauthor, iauthor'
			   . ' FROM '.sql_table('comment') .', '.sql_table('item').', '.sql_table('blog')
			   . ' WHERE citem=inumber and iblog=bnumber and cnumber=' . intval($commentid);
		$res = DB::getRow($query);
		
		return ($res['cauthor'] == $this->getID()) or $this->isBlogAdmin($res['blogid']) or ($res['iauthor'] == $this->getID());
	}
	
	/**
	 * Member::canAlterItem()
	 * Returns true if this member can edit/delete an item. This is true in the following
	 * cases: - member is a super-admin
	 *	       - member is the author of the item
	 *        - member is admin of the the associated blog
	 * 
	 * @param	integer	$itemid	ID of target item
	 * @return	boolean	delete/edit the item or not
	 * 
	 */
	public function canAlterItem($itemid)
	{
		if ($this->isAdmin()) return 1;
		
		$query =  'SELECT iblog, iauthor FROM '.sql_table('item').' WHERE inumber=' . intval($itemid);
		$res = DB::getRow($query);
		return ($res['iauthor'] == $this->getID()) or $this->isBlogAdmin($res['iblog']);
	}
	
	/**
	 * Member::canBeDeleted()
	 * Return true if member can be deleted. This means that there are no items posted by the member left
	 * 
	 * @param	void
	 * @return	boolean	whether there is no items or exists
	 * 
	 */
	public function canBeDeleted()
	{
		$res = DB::getResult('SELECT * FROM '.sql_table('item').' WHERE iauthor=' . $this->getID());
		return ( $res->rowCount() == 0 );
	}
	
	/**
	 * Member::canUpdateItem()
	 * returns true if this member can move/update an item to a given category,
	 * false if not (see comments fot the tests that are executed)
	 * 
	 * @param	integer	$itemid
	 * @param	string	$newcat (can also be of form 'newcat-x' with x=blogid)
	 * @return	boolean	whether being able to update the item or not
	 * 
	 */
	public function canUpdateItem($itemid, $newcat)
	{
		global $manager;
		
		// item does not exists -> NOK
		if ( !$manager->existsItem($itemid,1,1) )
		{
			return 0;
		}
		
		// cannot alter item -> NOK
		if (!$this->canAlterItem($itemid))
		{
			return 0;
		}
		
		// if this is a 'newcat' style newcat
		// no blog admin of destination blog -> NOK
		// blog admin of destination blog -> OK
		if ( i18n::strpos($newcat, 'newcat') === 0 )
		{
			// get blogid
			list($blogid) = sscanf($newcat, 'newcat-%d');
			return $this->blogAdminRights($blogid);
		}
		
		// category does not exist -> NOK
		if (!$manager->existsCategory($newcat))
		{
			return 0;
		}
		
		// get item
		$item =& $manager->getItem($itemid,1,1);
		
		// old catid = new catid -> OK
		if ($item['catid'] == $newcat)
		{
			return 1;
		}
		
		// not a valid category -> NOK
		$validCat = DB::getValue('SELECT COUNT(*) AS result FROM '.sql_table('category').' WHERE catid='.intval($newcat));
		if ( !$validCat )
		{
			return 0;
		}
		
		// get destination blog
		$item =& $manager->getItem($itemid, 1, 1);
		$source_blogid = $item['blogid'];
		$dest_blogid = getBlogIDFromCatID($newcat);
		
		// not a team member of destination blog -> NOK
		if ( !$this->teamRights($dest_blogid) )
		{
			return 0;
		}
		
		// if member is author of item -> OK
		if ( $item['authorid'] == $this->getID() )
		{
			return 1;
		}
		
		// if member has admin rights on both blogs: OK
		if ( ($this->blogAdminRights($dest_blogid)) && ($this->blogAdminRights($source_blogid)) )
		{
			return 1;
		}
		
		// all other cases: NOK
		return 0;
	}
	
	/**
	 * Member::setCookies()
	 * Sets the cookies for the member
	 * 
	 * @param boolean	$shared	set this to 1 when using a shared computer. Cookies will expire
	 *				at the end of the session in this case.
	 * @return	void
	 * 
	 */
	public function setCookies($shared = 0)
	{
		global $CONF;
		
		if ( $CONF['SessionCookie'] || $shared )
		{
			$lifetime = 0;
		}
		else
		{
			$lifetime = time()+2592000;
		}
		
		setcookie($CONF['CookiePrefix'] . 'user', $this->getDisplayName(), $lifetime, $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
		setcookie($CONF['CookiePrefix'] . 'loginkey', $this->getCookieKey(), $lifetime, $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
		
		// make sure cookies on shared pcs don't get renewed
		if ( $shared )
		{
			setcookie($CONF['CookiePrefix'] .'sharedpc', '1',$lifetime,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);
		}
		return;
	}
	
	/**
	 * Member::sendActivationLink()
	 * Send activation mail
	 * 
	 * @param	string	$type	activation type
	 * @param	string	$extra	extra info
	 * @return	void
	 */
	public function sendActivationLink($type, $extra='')
	{
		global $CONF;
		
		if ( !isset($CONF['ActivationDays']) )
		{
			$CONF['ActivationDays'] = 2;
		}
		
		// generate key and URL
		$key = $this->generateActivationEntry($type, $extra);
		$url = $CONF['AdminURL'] . 'index.php?action=activate&key=' . $key;
		
		// choose text to use in mail
		switch ( $type )
		{
			case 'register':
				$message = _ACTIVATE_REGISTER_MAIL;
				$subject = _ACTIVATE_REGISTER_MAILTITLE;
				break;
			case 'forgot':
				$message = _ACTIVATE_FORGOT_MAIL;
				$subject = _ACTIVATE_FORGOT_MAILTITLE;
				break;
			case 'addresschange':
				$message = _ACTIVATE_CHANGE_MAIL;
				$subject = _ACTIVATE_CHANGE_MAILTITLE;
				break;
			default;
		}
		
		// fill out variables in text
		$aVars = array(
			'siteName' => $CONF['SiteName'],
			'siteUrl' => $CONF['IndexURL'],
			'memberName' => $this->getDisplayName(),
			'activationUrl' => $url,
			'activationDays' => $CONF['ActivationDays']
		);
		
		$message = Template::fill($message, $aVars);
		$subject = Template::fill($subject, $aVars);
		
		// send mail
		NOTIFICATION::mail($this->getEmail(), $subject ,$message, $CONF['AdminEmail'], i18n::get_current_charset());
		
		ActionLog::add(INFO, _ACTIONLOG_ACTIVATIONLINK . ' (' . $this->getDisplayName() . ' / type: ' . $type . ')');
		return;
	}
	
	/**
	 * Member::getAdminBlogs()
	 * Returns an array of all blogids for which member has admin rights
	 * 
	 * @param	void
	 * @return	array	weblog IDs in which this member has admin rights
	 * 
	 */
	public function getAdminBlogs()
	{
		$blogs = array();
		
		if ($this->isAdmin())
		{
			$query = 'SELECT bnumber as blogid from '.sql_table('blog');
		}
		else
		{
			$query = 'SELECT tblog as blogid from '.sql_table('team').' where tadmin=1 and tmember=' . $this->getID();
		}
		
		$res = DB::getResult($query);
		if ( $res->rowCount() > 0 )
		{
			foreach ( $res as $row )
			{
				array_push($blogs, $row['blogid']);
			}
		}
		return $blogs;
	}
	
	/**
	 * Member::getTeamBlogs()
	 * Returns an array of all blogids for which member has team rights
	 * 
	 * @param	boolean	$incAdmin	whether checking weblog admin rights or not
	 * @return	array	weblog IDs in which this member join
	 * 
	 */
	public function getTeamBlogs($incAdmin = 1)
	{
		$incAdmin = intval($incAdmin);
		$blogs = array();
		
		if ( $this->isAdmin() && $incAdmin )
		{
			$query = 'SELECT bnumber as blogid from '.sql_table('blog');
		}
		else
		{
			$query = 'SELECT tblog as blogid from '.sql_table('team').' where tmember=' . $this->getID();
		}
		
		$res = DB::getResult($query);
		if ( $res->rowCount() > 0 )
		{
			foreach ( $res as $row )
			{
				array_push($blogs, $row['blogid']);
			}
		}
		return $blogs;
	}
	
	/**
	 * Member::getNotifyFromMailAddress()
	 * 
	 * Returns an email address from which notification of commenting/karma voting can
	 * be sent. A suggestion can be given for when the member is not logged in
	 * 
	 * @param	String	$suggest
	 * @return	String	mail address or suggestion
	 */
	public function getNotifyFromMailAddress($suggest = "")
	{
		global $CONF;
		if ( $this->isLoggedIn() )
		{
			return $this->getDisplayName() . " <" . $this->getEmail() . ">";
		}
		else if ( NOTIFICATION::address_validation($suggest) )
		{
			return $suggest;
		}
		return $CONF['AdminEmail'];
	}
	
	/**
	 * Member::write()
	 * Write data to database
	 * 
	 * @param	void
	 * @return	void
	 * 
	 */
	public function write()
	{
		$query =  'UPDATE '.sql_table('member')
		        . ' SET mname=' . DB::quoteValue($this->displayname) . ', '
		           . 'mrealname='. DB::quoteValue($this->realname) . ', '
		           . 'mpassword='. DB::quoteValue($this->password) . ', '
		           . 'mcookiekey='. DB::quoteValue($this->cookiekey) . ', '
		           . 'murl=' . DB::quoteValue($this->url) . ', '
		           . 'memail=' . DB::quoteValue($this->email) . ', '
		           . 'madmin=' . intval($this->admin) . ', '
		           . 'mnotes=' . DB::quoteValue($this->notes) . ', '
		           . 'mcanlogin=' . intval($this->canlogin) . ', '
		           . 'mlocale=' . DB::quoteValue($this->locale) . ', '
		           . 'madminskin=' . DB::quoteValue($this->adminskin) . ', '
		           . 'mbkmklt=' . DB::quoteValue($this->bookmarklet) . ', '
		           . 'mautosave=' . intval($this->autosave) . ' '
		        . 'WHERE mnumber=' . intval($this->id);
		DB::execute($query);
		return;
	}
	
	public function checkCookieKey($key)
	{
		return ( ($key != '') && ( $key == $this->getCookieKey() ) );
	}
	
	public function checkPassword($pw)
	{
		/* for lower compatibility (md5) */
		if ( strlen($this->password) === 32 )
		{
			return (md5($pw) == $this->password);
		}
		return ($this->hash($pw) == $this->password);
	}
	
	public function getRealName()
	{
		return $this->realname;
	}
	
	public function setRealName($name)
	{
		$this->realname = $name;
	}
	
	public function getEmail()
	{
		return $this->email;
	}
	
	public function setEmail($email)
	{
		$this->email = $email;
	}
	
	public function getPassword()
	{
		return $this->password;
	}
	
	public function setPassword($pwd)
	{
		$this->password = $this->hash($pwd);
	}
	
	public function getCookieKey()
	{
		return $this->cookiekey;
	}
	
	/**
	 * Member::newCookieKey()
	 * Generate new cookiekey, save it, and return it
	 * 
	 * @param	void
	 * @return	void
	 * 
	 */
	public function newCookieKey()
	{
		mt_srand( (double) microtime() * 1000000);
		$this->cookiekey = $this->hash(uniqid(mt_rand()));
		$this->write();
		return $this->cookiekey;
	}
	
	public function setCookieKey($val)
	{
		$this->cookiekey = $val;
	}
	
	public function getURL()
	{
		return $this->url;
	}
	
	public function setURL($site)
	{
		$this->url = $site;
	}
	
	public function setAdminSkin($skin)
	{
		$this->adminskin = $skin;
	}

	public function setBookmarklet($skin)
	{
		$this->bookmarklet = $skin;
	}
	
	public function getAdminSkin()
	{
		return $this->adminskin;
	}

	public function getBookmarklet()
	{
		return $this->bookmarklet;
	}

	public function getLocale()
	{
		return $this->locale;
	}
	
	public function setLocale($locale)
	{
		if ( !preg_match('#^(.+)_(.+)_(.+)$#', $locale)
		 && ($locale = i18n::convert_old_language_file_name_to_locale($locale)) === FALSE )
		{
			$locale = '';
		}
		$this->locale = $locale;
		return;
	}
	
	public function setDisplayName($nick)
	{
		$this->displayname = $nick;
	}
	
	public function getDisplayName()
	{
		return $this->displayname;
	}
	
	public function isAdmin()
	{
		return $this->admin;
	}
	
	public function setAdmin($val)
	{
		$this->admin = $val;
	}
	
	public function canLogin()
	{
		return $this->canlogin;
	}
	
	public function setCanLogin($val)
	{
		$this->canlogin = $val;
	}
	
	public function getNotes()
	{
		return $this->notes;
	}
	
	public function setNotes($val)
	{
		$this->notes = $val;
	}
	
	public function getAutosave()
	{
		return $this->autosave;
	}
	
	public function setAutosave($val)
	{
		$this->autosave = $val;
		return;
	}
	
	/**
	 * Member::getID()
	 * 
	 * @param	void
	 * @return	integer	id of this member object
	 * 
	 */
	public function getID()
	{
		return $this->id;
	}
	
	/**
	 * Member::exists()
	 * Returns true if there is a member with the given login name
	 * 
	 * @static
	 * @param	string	$name	target name
	 * @return	boolean	whether target name exists or not
	 */
	public static function exists($name)
	{
		$r = DB::getResult('SELECT * FROM ' . sql_table('member') . ' WHERE mname=' . DB::quoteValue($name));
		return ( $r->rowCount() != 0 );
	}
	
	/**
	 * Member::existsID()
	 * Returns true if there is a member with the given ID
	 * 
	 * @static
	 * @param	integer	$id	target id
	 * @return	boolean	whether target id exists or not
	 * 
	 */
	public static function existsID($id)
	{
		$r = DB::getResult('SELECT * FROM ' . sql_table('member') . ' WHERE mnumber=' . intval($id));
		return ( $r->rowCount() != 0 );
	}
	
	/**
	 * Member::isNameProtected()
	 *  Checks if a username is protected.
	 *  If so, it can not be used on anonymous comments
	 * 
	 * @param	string	$name	target name
	 * @return	boolean	whether the name exists or not
	 * 
	 */
	public function isNameProtected($name)
	{
		// extract name
		$name = strip_tags($name);
		$name = trim($name);
		return self::exists($name);
	}
	
	/**
	 * Member::create()
	 * Adds a new member
	 * 
	 * @static
	 * @param	String	$name
	 * @param	String	$realname
	 * @param	String	$password
	 * @param	String	$email
	 * @param	String	$url
	 * @param	String	$admin
	 * @param	String	$canlogin
	 * @param	String	$notes
	 * @return	String	1 if success, others if fail
	 */
	static public function create($name, $realname, $password, $email, $url, $admin, $canlogin, $notes)
	{
		if ( !NOTIFICATION::address_validation($email) )
		{
			return _ERROR_BADMAILADDRESS;
		}
		
		/* TODO: this method should be in MEMBER class, not globalfunctions */
		if ( !isValidDisplayName($name) )
		{
			return _ERROR_BADNAME;
		}
		
		if ( self::exists($name) )
		{
			return _ERROR_NICKNAMEINUSE;
		}
		
		if ( !$realname )
		{
			return _ERROR_REALNAMEMISSING;
		}
		
		/* TODO: check the number of characters */
		if ( !$password )
		{
			return _ERROR_PASSWORDMISSING;
		}
		
		/* 
		 *  begin if: sometimes user didn't prefix the URL with http:// or https://,
		 *  this cause a malformed URL. Let's fix it.
		 */
		
		if ( !preg_match('#^https?://#', $url) )
		{
			$url = 'http://' . $url;
		}
		
		$name		= DB::quoteValue($name);
		$realname	= DB::quoteValue($realname);
		/* NOTE: hashed password is automatically updated if the length is 32 bytes when logging in */
		$password	= DB::quoteValue(md5($password));
		$email		= DB::quoteValue($email);
		$url		= DB::quoteValue($url);
		$admin		= (integer) $admin;
		$canlogin	= (integer) $canlogin;
		$notes		= DB::quoteValue($notes);
		
		$query = "INSERT INTO %s"
		       . " (MNAME,MREALNAME,MPASSWORD,MEMAIL,MURL, MADMIN, MCANLOGIN, MNOTES)"
		       . " VALUES (%s, %s, %s, %s, %s, %d, %d, %s)";
		$query = sprintf($query, sql_table('member'), $name, $realname, $password, $email, $url, $admin, $canlogin, $notes);
		DB::execute($query);
		
		ActionLog::add(INFO, _ACTIONLOG_NEWMEMBER . ' ' . $name);
		
		return 1;
	}
	
	/**
	 * Member::getActivationInfo()
	 * Returns activation info for a certain key (an object with properties vkey, vmember, ...)
	 * 
	 * @static
	 * @param	string	$key	activation key
	 * @return	mixed	return 0 if failed, else return activation table object
	 * 
	 */
	public static function getActivationInfo($key)
	{
		$query = 'SELECT * FROM ' . sql_table('activation') . ' WHERE vkey=' . DB::quoteValue($key);
		$res = DB::getResult($query);
		
		if ( !$res || ($res->rowCount() == 0) )
		{
			return 0;
		}
		return $res->fetch();
	}
	
	/**
	 * Member::generateActivationEntry()
	 * Creates an account activation key
	 * addresschange -> old email address
	 * 
	 * @param	string	$type	one of the following values (determines what to do when activation expires)
	 * 				'register'	(new member registration)
	 * 				'forgot'	(forgotton password)
	 * 				'addresschange'	(member address has changed)
	 * @param	string	$extra	extra info (needed when validation link expires)
	 * @return	string	activation key
	 */
	public function generateActivationEntry($type, $extra = '')
	{
		// clean up old entries
		$this->cleanupActivationTable();
		
		// kill any existing entries for the current member (delete is ok)
		// (only one outstanding activation key can be present for a member)
		DB::execute('DELETE FROM ' . sql_table('activation') . ' WHERE vmember=' . intval($this->getID()));
		
		// indicates if the member can log in while the link is active
		$canLoginWhileActive = false;
		switch ( $type )
		{
			case 'forgot':
				$canLoginWhileActive = true;
				break;
			case 'register':
				break;
			case 'addresschange':
				$extra = $extra . '/' . ( $this->canLogin() ? '1' : '0' );
				break;
		}
		
		$ok = false;
		while ( !$ok )
		{
			// generate a random key
			srand((double)microtime()*1000000);
			$key = $this->hash(uniqid(rand(), true));
			
			// attempt to add entry in database
			// add in database as non-active
			$query = 'INSERT INTO %s (vkey, vtime, vmember, vtype, vextra) VALUES (%s, %s, %d, %s, %s)';
			$query = sprintf($query
				, sql_table('activation')
				, DB::quoteValue($key)
				, DB::formatDateTime()
				, intval($this->getID())
				, DB::quoteValue($type)
				, DB::quoteValue($extra)
			);
			if ( DB::execute($query) !== FALSE )
				$ok = true;
		}
		
		// mark member as not allowed to log in
		if ( !$canLoginWhileActive )
		{
			$this->setCanLogin(0);
			$this->write();
		}
		
		// return the key
		return $key;
	}
	
	/**
	 * Member::activate()
	 * Inidicates that an activation link has been clicked and any forms displayed
	 * there have been successfully filled out.
	 * 
	 * @param	string	$key	activation key
	 * @return	boolean
	 * 
	 */
	public function activate($key)
	{
		// get activate info
		$info = self::getActivationInfo($key);
		
		// no active key
		if ( !$info )
		{
			return false;
		}
		
		switch ( $info['vtype'] )
		{
			case 'forgot':
				// nothing to do
				break;
			case 'register':
				// set canlogin value
				global $CONF;
				DB::execute('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($CONF['NewMemberCanLogon']). ' WHERE mnumber=' . intval($info['vmember']));
				break;
			case 'addresschange':
				// reset old 'canlogin' value
				list($oldEmail, $oldCanLogin) = preg_split('#/#', $info['vextra']);
				DB::execute('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ' WHERE mnumber=' . intval($info['vmember']));
				break;
		}
		
		// delete from activation table
		DB::execute('DELETE FROM ' . sql_table('activation') . ' WHERE vkey=' . DB::quoteValue($key));
		
		// success!
		return true;
	}
	
	/**
	 * Member::cleanupActivationTable()
	 * Cleans up entries in the activation table. All entries older than 2 days are removed.
	 * (static)
	 * 
	 * @param	void
	 * @return	void
	 */
	public function cleanupActivationTable()
	{
		$actdays = 2;
		if ( isset($CONF['ActivationDays']) && intval($CONF['ActivationDays']) > 0 )
		{
			$actdays = intval($CONF['ActivationDays']);
		}
		else
		{
			$CONF['ActivationDays'] = 2;
		}
		$boundary = time() - (60 * 60 * 24 * $actdays);
		
		// 1. walk over all entries, and see if special actions need to be performed
		$query = sprintf('SELECT * FROM %s WHERE vtime < %s', sql_table('activation'), DB::formatDateTime($boundary));
		$res = DB::getResult($query);
		
		foreach ( $res as $row )
		{
			switch ( $row['vtype'] )
			{
				case 'register':
					// delete all information about this site member. registration is undone because there was
					// no timely activation
					include_once($DIR_LIBS . 'ADMIN.php');
					Admin::deleteOneMember(intval($row['vmember']));
					break;
				case 'addresschange':
					// revert the e-mail address of the member back to old address
					list($oldEmail, $oldCanLogin) = preg_split('#/#', $row['vextra']);
					DB::execute('UPDATE ' . sql_table('member') . ' SET mcanlogin=' . intval($oldCanLogin). ', memail=' . DB::quoteValue($oldEmail). ' WHERE mnumber=' . intval($row['vmember']));
					break;
				case 'forgot':
					// delete the activation link and ignore. member can request a new password using the
					// forgot password link
					break;
			}
		}
		
		// 2. delete activation entries for real
		$query = sprintf('DELETE FROM %s WHERE vtime < %s', sql_table('activation'), DB::formatDateTime($boundary));
		DB::execute($query);
		return;
	}
	
	/**
	 * Member::$language
	 * 
	 * @obsolete
	 * @param	void
	 * @return	void
	 * 
	 */
	public $language = '';
	/**
	 * Member::getLanguage()
	 * 
	 * @obsolete
	 * @param	void
	 * @return	void
	 * 
	 */
	public function getLanguage()
	{
		if ( ($language = i18n::convert_locale_to_old_language_file_name($this->locale)) === FALSE )
		{
			$language = '';
		}
		return $language;
	}
}
>>>>>>> skinnable-master
