<?php

/* ======================================================================*\
   || #################################################################### ||
   || # vBulletin 5.0.0 Beta 10
   || # ---------------------------------------------------------------- # ||
   || # Copyright �2000-2012 vBulletin Solutions Inc. All Rights Reserved. ||
   || # This file may not be redistributed in whole or significant part. # ||
   || # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
   || # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
   || #################################################################### ||
   \*====================================================================== */

/**
* vB_UserContext
*
* @package vBulletin
*/

class vB_UserContext
{
	const USERGROUP_GUESTS = 1;

	protected $userid;
	/**
	 *
	 * @var vB_dB_Assertor
	 */
	protected $assertor;
	/**
	 *
	 * @var vB_Datastore
	 */
	protected $datastore;
	/**
	 *
	 * @var array
	 */
	protected $config;
	/**
	 * @var vB_PermissionContext
	 */
	protected $permissionContext;
	protected $admin_info = null;
	protected $canRead = false;
	protected $cantRead = false;
	protected $usergroups;
	protected $permissions = false;
	protected $channelPermissions = array();
	protected $permissionsFrom = array();
	protected $superAdmins = array();
	protected $canModerate = false;
	protected $groupInTopic = array();
	protected $contentTypes = array();
	protected $canCreateTypes = array();
	protected $noComments = array();
	protected $moderatorPerms = array();
	protected $moderatorBitMasks = false;

	/**
	 * The following permissions should always be checked even if current user is a Superadmin
	 *
	 * @var array
	 */
	protected $superAdminsCheckPerms = array(
		'requirehvcheck',
		'showeditedby',
		'canbeusernoted',
		'allowhtml',
	);

	public function __construct($userid, $assertor, $datastore, $config)
	{
		$this->userid = $userid;
		$this->assertor = $assertor;
		$this->datastore = $datastore;
		$this->config = $config;
		//We can't call the user api function getGroupInTopic(), because that does a permission check. Which causes a recursion loop

		//We have to do a try-catch block here. During an upgrade we check permissions although the groupintopic table doesn't exist.
		try
		{
			$groupInTopic = vB::getDbAssertor()->assertQuery('vBForum:groupintopic', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
				'userid' => $userid ));
			foreach ($groupInTopic as $topic)
			{
				if (!isset($this->groupInTopic[$topic['nodeid']]))
				{
					$this->groupInTopic[$topic['nodeid']] = array();
				}
				$this->groupInTopic[$topic['nodeid']][] = $topic['groupid'];
			}

		}
		catch (exception $e)
		{
			//Nothing to do here. Just continue
		}
		// fetch superadmin list
		if (empty($this->superAdmins))
		{
			$this->superAdmins = explode(",", $this->config['SpecialUsers']['superadministrators']);
		}
		// fetch user groups
		//first see if we have a cached value
		$cache = vB_Cache::instance(vB_Cache::CACHE_FAST);
		$usergroupCached = $cache->read('userGroups.' . $this->userid);

		if ($usergroupCached)
		{
			$this->usergroups = $usergroupCached['usergroups'];
			$infraction_group_ids = $usergroupCached['infractiongroupids'];
			$primary_group_id = current($usergroupCached['usergroups']);
			$secondary_group_ids = array_slice($usergroupCached['usergroups'], 1);
		}
		else
		{
			//No, we need to query for it
			$result = $this->assertor->assertQuery('fetch_usergroups', array('userid' => $this->userid));
			if ($result->valid())
			{
				$user_info = $result->current();
				$primary_group_id = $user_info['usergroupid'];
				$secondary_group_ids = (!empty($user_info['membergroupids'])) ? explode(',', str_replace(' ', '', $user_info['membergroupids'])) : array();
				$infraction_group_ids = (!empty($user_info['infractiongroupids'])) ? explode(',', str_replace(' ', '', $user_info['infractiongroupids'])) : array();
			}
			else
			{
				$primary_group_id = self::USERGROUP_GUESTS;
				$secondary_group_ids = array();
				$infraction_group_ids = array();
			}

			$this->usergroups = array_merge(array($primary_group_id), $secondary_group_ids);
			$cache->write('userGroups.' . $this->userid, array('usergroups' => $this->usergroups, 'infractiongroupids' => $infraction_group_ids), 1440, 'perms_changed');
		}

		$this->permissionContext = new vB_PermissionContext($datastore, $primary_group_id, $secondary_group_ids, $infraction_group_ids);

		//Add any permissions from the moderator table.
		$this->loadModeratorPermissions();
	}

	/**
	 * Loads moderator permissions from the moderator table. Respects inherited permissions
	 */
	protected function loadModeratorPermissions()
	{
		$this->moderatorPerms = array();

		$parentnodeids = array();
		$modPermQuery = $this->assertor->assertQuery('vBForum:moderator', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
			'userid' => $this->userid));
		foreach ($modPermQuery AS $modPerm)
		{
			if ($modPerm['nodeid'] >= 1)
			{
				$parentnodeids[] = $modPerm['nodeid'];
			}
			$this->moderatorPerms[$modPerm['nodeid']] = $modPerm;
		}

		if (!empty($parentnodeids))
		{
			$channels = $this->assertor->getRows('channel');
			$channelnodeids = array();
			foreach ($channels as $channel)
			{
				$channelnodeids[] = $channel['nodeid'];
			}

			foreach ($parentnodeids as $parentnodeid)
			{
				if ($parentnodeid < 1)
				{
					continue;
				}

				$closurerecords = $this->assertor->assertQuery('vBForum:getDescendantChannelNodeIds', array(
					'parentnodeid' => $parentnodeid,
					'channelnodeids' => $channelnodeids,
				));
				foreach ($closurerecords as $closurerecord)
				{
					$childnodeid = $closurerecord['child'];
					if (!isset($this->moderatorPerms[$childnodeid]) AND isset($this->moderatorPerms[$parentnodeid]))
					{
						// descendant channels inherit moderator permissions from parent channels
						// so we copy the parent channel's permissions and change the nodeid in it
						$this->moderatorPerms[$childnodeid] = $this->moderatorPerms[$parentnodeid];
						$this->moderatorPerms[$childnodeid]['nodeid'] = $childnodeid;
					}
				}
			}
		}
	}

	/** Clears all existing permissions. Needed primarily in test.
	 *
	 * 	@param	int		optional usergroup
	 *
	 **/
	public function clearChannelPermissions($usergroupid = false)
	{
		$this->permissionContext->clearChannelPermissions($usergroupid);
		$this->permissionsFrom = array();
		$this->canModerate = false;
		$this->canRead = false;
		$this->cantRead = false;
		$this->reloadGroupInTopic();
		$this->noComments = array();
		$this->canCreateTypes = array();

		//Add any permissions from the moderator table.
		$this->loadModeratorPermissions();
	}

	/** Reloads the groupInTopic data. Needed primarily in test.
	 *
	 *
	 **/
	public function reloadGroupInTopic()
	{
		$this->groupInTopic = array();
		$groupInTopic = vB::getDbAssertor()->assertQuery('vBForum:groupintopic', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
			'userid' => $this->userid ));
		foreach ($groupInTopic as $topic)
		{
			if (!isset($this->groupInTopic[$topic['nodeid']]))
			{
				$this->groupInTopic[$topic['nodeid']] = array();
			}
			$this->groupInTopic[$topic['nodeid']][] = $topic['groupid'];
		}
	}


	/*** Checks for a specific permission
	 *
	 * 	@param string	the name of the permission group
	 *	@param string	the name of the permission
	 *
	 *	@return	boolean
	 *
	 ***/
	public function hasPermission($group, $permission)
	{
		// if user is super admin no need to check
		return ((in_array($this->userid, $this->superAdmins) AND !in_array($permission, $this->superAdminsCheckPerms)) ? true : $this->permissionContext->hasPermission($group, $permission));
	}

	/** Get the userid
	 *
	 *	@return		integer
	 ***/
	public function fetchUserId()
	{
		return $this->userid;
	}

	/** Return the usergroups for this user
	*
	*	@return 	mixed	array of usergroupid's
	***/
	public function fetchUserGroups()
	{
		return $this->usergroups;
	}

	/*** Checks for a specific permission in a specific channel
	 *
	 *	@param	string	the name of the permission group
	 *	@param	string	the name of the permission
	 *	@param	int		the channel to check. Note that we may inherit from a parent.
	 * 	@param	mixed	optional array of either closure table records, node table records, or integer representing the ancestor nodeid's
	 *	@param	int		optional immediate parent nodeid
	 *
	 *	@return int		permission numeric value
	 *
	 ***/
	public function getChannelLimitPermission($permissiongroup, $permission, $nodeid, $parents = false, $parentid = false)
	{
		/* One of the rules is that permissions are additive. I.e. if someone belongs to multiple groups
		   and at least one of them has a permission, the user has that permission.
		   Also, permissions inherit. So:
		   So we start with all this user's usergroups. We look at the
		   current channel to see if that user has the requested permission. If not we look
		   up to tree to the top. At each level, if permissions are assigned for a usergroup
		   and the user DOESN'T have that permission then we remove that usergroup for further
		   checks.
		*/
		$usergroups = $this->usergroups;
		//If we're lucky they'll be asking for a channel in the record. Let's try

		if (!empty($this->moderatorPerms[$nodeid]) AND $this->checkModPerm($nodeid, $permissiongroup, $permission))
		{
			return true;
		}

		// comments disabled check
		if ($permissiongroup == 'createpermissions')
		{
			$createPerms = $this->getCanCreate($nodeid);
			$permission = vB_String::vBStrToLower($permission);
			if (!intval($createPerms[$permission]))
			{
				return false;
			}
		}

		foreach ($usergroups as $key => $usergroupid)
		{
			//we need a place to cache the ancestor where permissions inherit from.
			if (!isset($this->permissionsFrom[$usergroupid]))
			{
				$this->permissionsFrom[$usergroupid] = array();
			}

			if ($this->permissionContext->getChannelPermSet($usergroupid, $nodeid))
			{
				//Note that if we don't have permission, we can't necessarily return. We may have permission at a higher level.
				$thisPermission = $this->permissionContext->getChannelPerm($usergroupid, $permissiongroup, $permission, $nodeid);

				if ($thisPermission > 0)
				{
					return $thisPermission;
				}
				unset($usergroups[$usergroupid]);
			}

			if ($parentid AND ($this->permissionContext->getChannelPermSet($usergroupid, $parentid)))
			{
				$thisPermission = $this->permissionContext->getChannelPerm($usergroupid, $permissiongroup, $permission, $parentid);

				if ($thisPermission > 0)
				{
					return $thisPermission;
				}
				unset($usergroups[$usergroupid]);
			}

			//Let's see if we already know where this node inherits permissions.
			if ($parentid)
			{
				$checkFor = $parentid;
			}
			else
			{
				$checkFor = $nodeid;
			}

			if (isset($this->permissionsFrom[$usergroupid][$checkFor]))
			{
				$thisPermission = $this->permissionContext->getChannelPerm($usergroupid, $permissiongroup, $permission, $this->permissionsFrom[$usergroupid][$checkFor]);

				if ($thisPermission > 0)
				{
					return $thisPermission;
				}
				unset($usergroups[$key]);
			}
		}

		//So we need to seek for a parent.
		//If we already have ancestry we can use it.
		if ($parents AND !empty($usergroups))
		{
			foreach ($parents as $parent)
			{
				if (is_array($parent))
				{
					//can come from either closure or a join with nodes. So check either.
					if (!empty($parent['parent']) AND intval($parent['parent']))
					{
						$ancestorId = intval($parent['parent']);
					}
					else if (!empty($parent['parentid']) AND intval($parent['parentid']))
					{
						$ancestorId = intval($parent['parentid']);
					}
					else
					{
						continue;
					}
				}
				else if (is_numeric($parent))
				{
					$ancestorId = intval($parent);
				}
				else
				{
					continue;
				}

				if (!empty($this->moderatorPerms[$ancestorId]) AND $this->checkModPerm($ancestorId, $permissiongroup, $permission))
				{
					return true;
				}

				foreach ($usergroups as $key => $usergroupid)
				{
					if ($this->permissionContext->getChannelPermSet($usergroupid, $ancestorId))
					{
						//Note that if we don't have permission, we can't necessarily return. We may have permission at a higher level.
						$thisPermission = $this->permissionContext->getChannelPerm($usergroupid, $permissiongroup, $permission, $ancestorId);

						if ($thisPermission > 0)
						{
							return $thisPermission;
						}
						unset($usergroups[$key]);
					}
				}

				if (empty($usergroups))
				{
					return false;
				}
			}
			//If we got here, they don't have permission through usergroups but still might as moderator.
			if (empty($this->moderatorPerms))
			{
				return false;
			}
		}

		if (empty($usergroups) AND (empty($this->moderatorPerms) OR (($permissiongroup <> 'moderatorpermissions')
			AND ($permissiongroup <> 'moderatorpermissions2'))))
		{
			return false;
		}
		//We need to pull the ancestry from the database.
		$parentage = vB_Library::instance('node')-> fetchClosureParent($nodeid);

		foreach ($parentage AS $parent )
		{
			if (!empty($this->moderatorPerms[$parent['parent']]) AND $this->checkModPerm($parent['parent'], $permissiongroup, $permission))
			{
				return true;
			}

			foreach ($usergroups as $key => $usergroupid)
			{
				if ($this->permissionContext->getChannelPermSet($usergroupid, $parent['parent']))
				{
					$this->permissionsFrom[$usergroupid][$nodeid] = $parent['parent'];
					//Note that if we don't have permission, we can't necessarily return. We may have permission at a higher level.
					$thisPermission = $this->permissionContext->getChannelPerm($usergroupid, $permissiongroup, $permission, $parent['parent']);

					if ($thisPermission > 0)
					{
						return $thisPermission;
					}
					unset($usergroups[$key]);
				}
			}

			if (empty($usergroups) AND (empty($this->moderatorPerms) OR (($permissiongroup <> 'moderatorpermissions')
				AND ($permissiongroup <> 'moderatorpermissions2'))))
			{
				return false;
			}
		}
		//If we got here, we don't have any permission.
		return false;
	}

	/** This checks to see if the user has the appropriate moderator permission from the moderators table
	 * @param 	int		$nodeid
	 * @param 	string	$permissiongroup
	 * @param 	string	$permission
	 *
	 * @return 	bool
	 */
	protected function checkModPerm($nodeid, $permissiongroup, $permission)
	{
		if (!intval($nodeid) OR empty($this->moderatorPerms[$nodeid]) OR
		(($permissiongroup != 'moderatorpermissions') AND ($permissiongroup != 'moderatorpermissions2')))
		{
			return false;
		}

		//We don't loat the moderator bitmasks unless we need them.
		if (!$this->moderatorBitMasks)
		{
			$datastore = vB::getDatastore();
			$this->moderatorBitMasks['moderatorpermissions'] = $datastore->getValue('bf_misc_moderatorpermissions');
			$this->moderatorBitMasks['moderatorpermissions2'] = $datastore->getValue('bf_misc_moderatorpermissions2');
		}

		//the field name is just permissions or permissions2
		$field = substr($permissiongroup, 9);

		if (!empty($this->moderatorBitMasks[$permissiongroup]) AND !empty($this->moderatorBitMasks[$permissiongroup][$permission]))
		{
			return (bool) $this->moderatorBitMasks[$permissiongroup][$permission] & $this->moderatorPerms[$nodeid][$field];
		}
		return false;
	}

	/*** Checks for a specific permission in a specific channel
	 *
	 * 	@param	string	the name of the permission group
	 *	@param	string	the name of the permission
	 * 	@param	int		the channel to check. Note that we may inherit from a parent.
	 * 	@param	mixed	optional array of either closure table records, node table records, or integer representing the ancestor nodeid's
	 *	@param	int		optional immediate parent nodeid
	 *
	 *	@return	boolean|integer
	 *
	 ***/
	public function getChannelPermission($permissiongroup, $permission, $nodeid, $parents = false, $parentid = false)
	{
		// check if we're accessing a boolean or integer permission
		$permFields = vB_ChannelPermission::fetchPermFields();
		$isIntPermission = (isset($permFields[$permission]) && ($permFields[$permission] == vB_ChannelPermission::TYPE_HOURS || $permFields[$permission] == vB_ChannelPermission::TYPE_COUNT));

		// if user is super admin and not requesting an integer permission, no need to check
		if (!$isIntPermission && in_array($this->userid, $this->superAdmins))
		{
			return true;
		}

		// comments disabled check
		if ($permissiongroup == 'createpermissions')
		{
			$createPerms = $this->getCanCreate($nodeid);
			$permission = vB_String::vBStrToLower($permission);
			return (bool)($createPerms[$permission]);
		}

		//If we have channel-group permissions we need to check them first.
		if (!empty($this->groupInTopic))
		{
			//see if we have at the node level
			if (array_key_exists($nodeid, $this->groupInTopic))
			{
				foreach ($this->groupInTopic[$nodeid] as $usergroupid)
				{
					//we have a match. Let's see if we have permissions.
					if (($this->permissionContext->getChannelPermSet($usergroupid, $nodeid))
						AND ((bool)$this->permissionContext->getChannelPerm($usergroupid, $permissiongroup, $permission, $nodeid)))
					{
						return true;
					}
					else if (!empty($this->moderatorPerms[$nodeid]) AND $this->checkModPerm($nodeid, $permissiongroup, $permission))
					{
						return true;
					}
				}
			}

			//If we have the parentid we should check that.
			if ($parentid)
			{
				if (array_key_exists($parentid, $this->groupInTopic))
				{
					foreach ($this->groupInTopic[$parentid] as $usergroupid)
					{
						//we have a match. Let's see if we have permissions.
						if (($this->permissionContext->getChannelPermSet($usergroupid, $parentid))
							AND ((bool)$this->permissionContext->getChannelPerm($usergroupid, $permissiongroup, $permission, $parentid)))
						{
							return true;
						}
					}
				}

				if (!empty($this->moderatorPerms[$parentid]) AND $this->checkModPerm($parentid, $permissiongroup, $permission))
				{
					return true;
				}
			}

			//If we don't have the parent list we need to run a query.
			if (empty($parents))
			{
				$parentage = vB_Library::instance('node')-> fetchClosureParent($nodeid);
				$parents = array();
				foreach ($parentage as $parent)
				{
					$parents[] = $parent['parent'];
				}

			}
			//with the parents we can skip the query.
			$depth = 0;
			foreach ($parents as $parent)
			{
				if (array_key_exists($parent, $this->groupInTopic))
				{
					foreach ($this->groupInTopic[$parent] as $usergroupid)
					{
						//we have a match. Let's see if we have permissions.
						$myParents = array_slice($parents, $depth);
						foreach ($myParents as $myParent)
						{
							if (($this->permissionContext->getChannelPermSet($usergroupid, $myParent))
								AND ((bool)$this->permissionContext->getChannelPerm($usergroupid, $permissiongroup, $permission, $myParent)))
							{
								return true;
							}
						}
					}
				}

				if (!empty($this->moderatorPerms[$parent]) AND $this->checkModPerm($parent, $permissiongroup, $permission))
				{
					return true;
				}
				$depth ++;
			}
		}

		// do not cast this to a boolean value, since we may be accessing an integer permission (max attachments, max tags, etc)
		return $this->getChannelLimitPermission($permissiongroup, $permission, $nodeid, $parents, $parentid);
	}

	/** This returns the 5 limit-type settings at the channel level- edit_time, require_moderate, maxstartertags, maxothertags and maxtags
	 *
	 *	@param	integer
	 *	@param	parents		array of ancestry
	 *
	 *	@return	mixed		2 element array
	 ***/
	public function getChannelLimits($nodeid, $permission = '', $parents = false)
	{
		// If there's a permission
		if ($permission)
		{
			return $this->getChannelLimitPermission('forumpermissions' , $permission, $nodeid);
		}

		// If perm wasn't specified
		$perms = array('edit_time', 'require_moderate', 'maxstartertags', 'maxothertags', 'maxtags', 'maxattachments');

		$permValues = array();
		foreach ($perms as $perm)
		{
			// We must ensure we get the int value
			$permValues["$perm"] = (int) $this->getChannelLimitPermission('forumpermissions', $perm, $nodeid);
		}

		return $permValues;
	}

	/*** Checks for a limit-type permission- e.g. maximum number of images.
	 *
	 * @param string	the name of the permission group
	 *	@param string	the name of the permission
	 * @param int		the channel to check. Note that we may inherit from a parent.
	 *
	 *	@return	mixed
	 *
	 ***/
	public function getLimit($permission)
	{
		return $this->permissionContext->getLimit($permission);
	}

	/**
	 * Returns whether the user is administrator
	 * Adapted from adminfunctions.php::can_administer
	 * Returns 1 if it has full admin privileges, -1 if it doesn't, or 0 if further controls are required.
	 * @return int
	 */
	private function basicAdminControl()
	{
		// check if user is guest
		if ($this->userid < 1)
		{
			return -1;
		}

		// check if user has access to controlpanel
		$bf_ugp_adminpermissions = $this->datastore->get_value('bf_ugp_adminpermissions');
		$admin_permissions = $this->permissionContext->getPermission('adminpermissions');
		if (!($admin_permissions & $bf_ugp_adminpermissions['cancontrolpanel']))
		{
			return -1;
		}

		// check if user is superadmin (defined in config.php)
		$superadmins = preg_split('#\s*,\s*#s', $this->config['SpecialUsers']['superadministrators'], -1, PREG_SPLIT_NO_EMPTY);
		if (in_array($this->userid, $superadmins))
		{
			return 1;
		}

		// it is not superadmin but still can have admin privileges
		return 0;
	}

	/**
	 * Returns whether the user is administrator
	 * Adapted from adminfunctions.php::can_administer
	 * @return bool
	 */
	public function isAdministrator()
	{
		$full_admin = $this->basicAdminControl();

		return ($full_admin >= 0);
	}

	/*** Checks for a specific administrator permission
	 *
	 *	@param string	the name of the permission
	 *
	 *	@return	boolean
	 *
	 ***/
	public function hasAdminPermission($permission)
	{
		// if user is super admin
		if (in_array($this->userid, $this->superAdmins))
		{
			return true;
		}

		$full_admin = $this->basicAdminControl();
		if ($full_admin !== 0)
		{
			return $full_admin > 0;
		}

		if (!isset($this->admin_info))
		{
			$result = $this->assertor->assertQuery('administrator', array(vB_dB_Query::TYPE_KEY =>  vB_dB_Query::QUERY_SELECT, 'userid' => $this->userid));

			if ($result->valid())
			{
				$this->admin_info = $result->current();
			}
			else
			{
				return false;
			}
		}

		$bf_ugp_adminpermissions = $this->datastore->get_value('bf_ugp_adminpermissions');
		return (bool)($this->admin_info['adminpermissions'] & $bf_ugp_adminpermissions[$permission]);
	}

	/*** This returns a list of channels where this user can read, and an array where they can't.
	 *
	 *	@param	integer	limit to this channel
	 *
	 *	@return	mixed		array of ('can'=> array of integers, 'cannot' => array of integers)
	 *
	 ***/
	public function getReadChannels($nodeid = false)
	{
		// If we don't have a channel id and we've already generated the list don't do it again.

		if (($nodeid == false) AND ($this->canRead !== false) )
		{
			return array('canRead' =>$this->canRead, 'cantRead' => $this->cantRead);
		}

		$forumPerms = array('canRead' => array(), 'cantRead' => array());
		//We need to generate. First add everything we can see.

		foreach ($this->usergroups as $usergroupid)
		{
			$perms = $this->permissionContext->getForumAccess($usergroupid);

			if (!empty($perms))
			{
				foreach ($perms['canRead'] as $key => $values)
				{
					$forumPerms['canRead'][$key] = $values;
				}
				foreach ($perms['cantRead'] as $key => $values)
				{
					$forumPerms['cantRead'][$key] = $values;
				}
			}
		}

		//Now we check. If any channel is in both canRead and cantRead, cantRead wins.

		foreach ($forumPerms['cantRead'] as $nodeid)
		{
			if (array_key_exists($nodeid, $forumPerms['canRead'] ))
			{
				unset($forumPerms['canRead'][$nodeid]);
			}
		}


		//If this is the general case, store it.
		if (!$nodeid)
		{
			$this->canRead = $forumPerms['canRead'];
			$this->cantRead = $forumPerms['cantRead'];
		}

		return $forumPerms;
	}

	/*** This returns a list of channels where this user can moderate, and an array where they can't.
	 *
	 *	@param	integer	limit to this channel
	 *
	 *	@return	mixed		array of ('can'=> array of integers, 'cannot' => array of integers)
	 *
	 ***/
	public function getCanModerate($nodeid = false)
	{
		//We could access this several times in a page load. No sense composing it more than once.
		if ($this->canModerate !== false)
		{
			return $this->canModerate;
		}
		$forumPerms = array('can' => array(), 'cannot' => array());
		//We need to generate. First add everything we can see.
		foreach ($this->usergroups as $usergroupid)
		{
			$perms = $this->permissionContext->getForumAccess($usergroupid);
			if (!empty($perms))
			{
				foreach ($perms['canModerate'] as $key => $values)
				{
					$forumPerms['can'][$values] = $values;
				}
				foreach ($perms['cantModerate'] as $key => $values)
				{
					$forumPerms['cannot'][$values] = $values;
				}
			}
		}

		//We add any channels where this user is an owner.
		foreach ($this->groupInTopic as $nodeid => $usergroups)
		{
			foreach ($usergroups as $usergroupid)
			{
				$channelPerm = $this->permissionContext->getSingleForumAccess($usergroupid, $nodeid);
				//now we need to merge the permissions...
				if (!in_array('cantModerate', $channelPerm[$nodeid]))
				{
					$forumPerms['can'][$nodeid] = $nodeid;
				}
				else
				{
					$forumPerms['cannot'][$nodeid] = $nodeid;
				}
			}
		}

		//Now add any moderator channels
		// this includes child channels
		foreach($this->moderatorPerms AS $modPerm)
		{
			if ((($modPerm['permissions'] > 0) OR ($modPerm['permissions2'] > 0)) AND ($modPerm['nodeid'] > 0))
			{
				$forumPerms['can'][$modPerm['nodeid']] = $modPerm['nodeid'];
			}
		}

		//Now we check. If any channel is in both can and cannot, can wins.
		if (!empty($forumPerms['can']) and !empty($forumPerms['cannot']))
		{
			foreach ($forumPerms['cannot'] as $nodeid => $details)
			{
				if (array_key_exists($nodeid, $forumPerms['can'] ))
				{
					unset($forumPerms['cannot'][$nodeid]);
				}
			}
		}
		$this->canModerate = $forumPerms;
		return $forumPerms;
	}

	/** Checks to see if a user can upload a file for attachment
	*
	* 	@param 	int		size of the file
	* 	@param	string	file extension
	* 	@param	int		channelid to which this will be attached. Optional
	***/
	public function canUpload($size, $extension, $nodeid = false)
	{
		//TODO	This needs to actually check. This is fairly complex, but it needs to check with
		//	permissionContext, which needs to
		//	- build the file type array similar to vB4 cache_permissions in functions.php
		//	- see if this type is allowed for this user
		//	- check the user's existing total space used against allowed space
		//	- if a nodeid was passed, which usually won't be- can this user attach files in this location?
		return true;
	}

	/** check whether current user is super admin. Don't allow request for a different user.
	 *
	 *	@return bool
	 **/
	public function isSuperAdmin()
	{
		$userInfo = vB::getCurrentSession()->fetch_userinfo();

		if (($userInfo['userid'] > 0) AND in_array($userInfo['userid'], $this->superAdmins))
		{
			return true;
		}

		return false;
	}


	/** Lists the content types the current user can create at a node.
	 *
	 *	@param	int		the nodeid to be checked
	 *
	 *	@return	mixed	array of contenttypes
	 ***/
	public function getCanCreate($nodeid)
	{
		//if we've already gotten for this node, we're done.
		if (isset($this->canCreateTypes[$nodeid]))
		{
			return $this->canCreateTypes[$nodeid];
		}

		$bf_ugp = vB::getDatastore()->get_value('bf_ugp_createpermissions');
		$options = vB::getDatastore()->get_value('options');

		$createPerms = array();

		//If this user is superadmin, just grant them everything.
		if (in_array($this->userid, $this->superAdmins))
		{
			if (isset($this->canCreateTypes[1]))
			{
				return $this->canCreateTypes[1];
			}

			if (is_array($bf_ugp))
			{
				foreach ($bf_ugp AS $type => $bitfield)
				{
					$createPerms[$type] = 1;
				}
			}

			$this->canCreateTypes[1] = $createPerms;
			return $createPerms;
		}

		if (is_array($bf_ugp))
		{
			//This user isn't superadmin and we don't already have this, so we need to calculate it
			foreach ($bf_ugp AS $type => $bitfield)
			{
				$createPerms[$type] = 0;
			}
		}

		$nodeLib = vB_Library::instance('node');

		// get the node record
		$node = $nodeLib->getNode($nodeid);

		//if this or a parent has nodeoptions[OPTION_ALLOW_POST] = 0, we are done.
		if (in_array( $nodeid, $this->noComments))
		{
			$this->canCreateTypes[$nodeid] = $createPerms;
			return $createPerms;
		}

		// if comments are disabled
		if (!$options['postcommentthreads'])
		{
			if (($node['contenttypeid'] != vB_Types::instance()->getContentTypeId('vBForum_Channel')) AND ($node['nodeid'] != $node['starter']))
			{
				$this->canCreateTypes[$nodeid] = $createPerms;
				return $createPerms;
			}
		}

		if (in_array($node['parentid'], $this->noComments) OR in_array($node['starter'], $this->noComments))
		{
			$this->canCreateTypes[$nodeid] = $createPerms;
			return $createPerms;
		}

		if (($node['nodeoptions'] & vB_Api_Node::OPTION_ALLOW_POST) == 0)
		{
			//If we have admin or moderator permissions,  here we can continue.
			if (!$this->getChannelPermission('moderatorpermissions', 'canmoderateposts', $nodeid))
			{
				$this->canCreateTypes[$nodeid] = $createPerms;
				$this->noComments[] = $node['starter'];

				if ($node['nodeid'] != $node['starter'])
				{
					$this->noComments[] = $node['nodeid'];
				}

				if ($node['parentid'] != $node['starter'])
				{
					$this->noComments[] = $node['parentid'];
				}
				$this->canCreateTypes[$nodeid]  = $createPerms;
				return $createPerms;
			}
		}

		//if this is NOT a channel we need to test the channel.
		if ($node['contenttypeid'] != vB_Types::instance()->getContentTypeID('vBForum_Channel'))
		{
			// try to get the channel node
			// the try/catch block is to keep the widget from crashing and burning
			// if it encounters orphaned nodes; see VBV-4198
			try
			{
				if ($node['nodeid'] == $node['starter'])
				{
					$channel = $nodeLib->getNode($node['parentid']);
				}
				else
				{
					$parent = $nodeLib->getNode($node['parentid']);
					$channel = $nodeLib->getNode($parent['parentid']);
				}
			}
			catch (vB_Exception_Api $e)
			{
				$errors = $e->get_errors();
				foreach ($errors as $error)
				{
					if (array_pop($error) == 'invalid_node_id')
					{
						// we are likely checking permissions for the parent of an
						// orphaned post (no parent exists) see VBV-4198
						$this->canCreateTypes[$nodeid] = $createPerms;
						return $createPerms;
					}
				}

				throw $e; // re-throw anything else that's not invalid_node_id
			}

			if ((($channel['nodeoptions'] & vB_Api_Node::OPTION_ALLOW_POST) == 0))
			{
				if (!$this->getChannelPermission('moderatorpermissions', 'canmoderateposts', $nodeid))
				{
					$this->canCreateTypes[$nodeid] = $createPerms;
					$this->noComments[] = $node['nodeid'];
					$this->noComments[] = $channel['nodeid'];

					if ($node['nodeid'] != $node['starter'])
					{
						$this->noComments[] = $node['starter'];
					}

					if (($node['parentid'] != $node['starter']) AND ($node['parentid'] != $channel['nodeid']))
					{
						$this->noComments[] = $node['parentid'];
					}

					$this->canCreateTypes[$nodeid]  = $createPerms;
					return $createPerms;
				}
			}
		}

		$usergroups = $this->usergroups;
		//If we're lucky they'll be asking for a channel in the record. Let's try
		foreach ($usergroups as $key => $usergroupid)
		{
			//we need a place to cache the ancestor where permissions inherit from.
			$permissionsFrom = false;

			if (!isset($this->permissionsFrom[$usergroupid]))
			{
				$this->permissionsFrom[$usergroupid] = array();
			}


			if (isset($this->permissionsFrom[$usergroupid][$nodeid]))
			{
				$permissionsFrom = $this->permissionsFrom[$usergroupid][$nodeid];
			}
			else if ($this->permissionContext->getChannelPermSet($usergroupid, $nodeid))
			{
				//Note that if we don't have permission, we can't necessarily return. We may have permission at a higher level.
				$permissionsFrom = $nodeid;
			}
			/** @todo figure out $parentid */
			else if (!empty($parentid) AND ($this->permissionContext->getChannelPermSet($usergroupid, $parentid)))
			{
				$permissionsFrom = $parentid;
			}
			/** @todo figure out $parentid */
			else if (!empty($parentid) AND isset($this->permissionsFrom[$usergroupid][$parentid]))
			{
				$permissionsFrom = $this->permissionsFrom[$usergroupid][$parentid];
			}
			else
			{
				//So we need to seek for a parent.
				//If we already have ancestry we can use it.
				//We need to pull the ancestry from the database.
				if (!isset($parentage))
				{
					$parentage = vB_Library::instance('node')-> fetchClosureParent($nodeid);;
				}

				foreach ($parentage AS $parent)
				{
					if ($this->permissionContext->getChannelPermSet($usergroupid, $parent['parent']))
					{
						$permissionsFrom = $this->permissionsFrom[$usergroupid][$nodeid] = $parent['parent'];
						break;
					}
				}
			}

			if ($permissionsFrom AND is_array($bf_ugp)) //This gets called during installation and upgrade, before the bitfields have been loaded. hence the second check.
			{
				$haveAll = true;
				foreach ($bf_ugp AS $type => $bitfield)
				{
					if ($this->permissionContext->getChannelPerm($usergroupid, 'createpermissions', $type, $permissionsFrom))
					{
						$createPerms[$type] = 1;
					}
					else
					{
						$haveAll = false;
					}
				}

				//If the current user already has all create permissions, no reason to continue to other usergroups.
				if ($haveAll)
				{
					break;
				}
			}
		}
		//we now have all the create permissions info. Let's cache it for the next check.
		$this->canCreateTypes[$nodeid]  = $createPerms;
		return $createPerms;

	}

}



