<?php
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.8.4 Patch Level 5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2009 Jelsoft Enterprises Ltd. 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 # ||
|| #################################################################### ||
\*======================================================================*/

if (!isset($GLOBALS['vbulletin']->db))
{
	exit;
}

require_once(DIR . '/includes/class_bbcode.php');
require_once(DIR . '/includes/functions_user.php');

/**
 * Collection Factory
 * Gets the appropriate collection handler for a content type.
 *
 * @package		vBulletin
 * @copyright	http://www.vbulletin.com/license
 */
class vB_Collection_Factory
{
	/**
	 * A reference to the application registry.
	 *
	 * @access protected
	 * @var vB_Registry
	 */
	var $registry;

	/**
	 * Array of acceptable content types that the factory can produce a collection
	 * handler for.
	 *
	 * @access protected
	 * @var array string
	 */
	var $types = array('album', 'groupcategory');

	/**
	 * Class prefix for created collections
	 *
	 * @access protected
	 * @var string
	 */
	var $class_prefix = 'vB_Collection_';

	// #######################################################################

	/**
	 * Constructor
	 * Note: No validation is done here as we know registry is reliably
	 * passed to vb_Collection which should always validate them.
	 *
	 * @access public
	 *
	 * @param vB_Registry $registry
	 * @return vB_Collection_Factory
	 */
	function __construct(&$registry)
	{
		$this->registry =& $registry;
	}

	/**
	 * Instantiates and returns the appropriate collection handler for the given
	 * content type.
	 *
	 * @access protected
	 * @see vB_Collection
	 *
	 * @param string $collection_type			The name of the content type
	 * @param integer $parent_id				Optional id of outer item
	 * @param integer $page						Optional starting index
	 * @param integer $quantity					Optional quantity
	 * @param boolean $descending				Whether to get results in descending order
	 * @param boolean $no_limit					Whether to get all results or not
	 * @return vB_Group_Collection				The appropriate collection
	 */
	function &create($collection_type, $parent_id = false, $page = 1, $quantity = false, $descending = true, $no_limit = false)
	{
		// Hook to add custom classes
		($hook = vBulletinHook::fetch_hook('collection_factory_create')) ? eval($hook) : false;

		// Ensure the type is valid
		if (!in_array($collection_type, $this->types))
		{
			trigger_error("vB_Collection_Factory::create(): Invalid type ", E_USER_ERROR);
		}

		$class_name = $this->class_prefix . ucfirst($collection_type);

		// Create the collection handler
		if (class_exists($class_name))
		{
			return $this->instantiate($class_name, $parent_id, $page, $quantity, $descending, $no_limit);
		}
		else
		{
			trigger_error('vB_Collection_Factory::create(): Invalid type ' . htmlspecialchars_uni($class_name) . '.', E_USER_ERROR);
		}
	}

	/**
	 * Instantiates the required collection object.
	 *
	 * @access protected
	 * @see vB_Collection
	 *
	 * @param string $class_name				The resolved name of the class to instantate
	 * @param integer $parent_id				Optional id of outer item
	 * @param integer $page						Optional starting index
	 * @param integer $quantity					Optional quantity
	 * @param boolean $descending				Whether to get results in descending order
	 * @param boolean $no_limit					Whether to get all results or not
	 * @return vB_Collection				The appropriate collection
	 */
	function instantiate($class_name, $parent_id = false, $page = 1, $quantity = false, $descending = true, $no_limit = false)
	{
		return new $class_name($this->registry, $parent_id, $page, $quantity, $descending, $no_limit);
	}
}


/**
 * Collection
 * Fetches a collection of content items with the given parent id,
 * start item and quantity of items to fetch.
 *
 * Also contains information for pagination.
 * @see vB_Collection::fetch_counts()
 *
 * @package		vBulletin
 * @copyright	http://www.vbulletin.com/license.html
 *
 * @abstract
 */
class vB_Collection
{
	/**
	 * Type identifier.
	 * Given to items so it can be checked whenever they are processed.
	 *
	 * @access protected
	 * @var string
	 */
	var $type;

	/**
	 * A reference to the application registry.
	 *
	 * @access protected
	 * @var vB_Registry
	 */
	var $registry;

	/**
	 * The id of the parent item that encapsulates the collection.
	 * This is optional and depends on the context.
	 *
	 * @access protected
	 * @var integer
	 */
	var $parent_id;

	/**
	 * The index of the results page to fetch
	 *
	 * @access protected
	 * @var integer
	 */
	var $page;

	/**
	 * Amount of items to fetch.
	 * Note: The result may be less than this.
	 *
	 * @access protected
	 * @var integer
	 */
	var $quantity;

	/**
	 * If this is true, the collection will be fetched in descending order.
	 *
	 * @access protected
	 * @var bool
	 */
	var $descending;

	/**
	 * If this is true, the collection will fetch all applicable items.
	 *
	 * @access protected
	 * @var bool
	 */
	var $no_limit;

	/**
	 * Cached result
	 *
	 * @access protected
	 * @var array
	 */
	var $collection;

	/**
	 * Name of a view query hook to execute to add to the collection query.
	 *
	 * @access protected
	 * @var string | boolean
	 */
	var $view_query_hook = false;

	/**
	 * The resolved start result index in the collection
	 *
	 * @access protected
	 * @var integer
	 */
	var $start;

	/**
	 * The resolved end result index in the collection
	 *
	 * @access protected
	 * @var integer
	 */
	var $end;

	/**
	 * The resolved total size of the collection
	 *
	 * @access protected
	 * @var integer
	 */
	var $total;

	/**
	 * Reference to the first item of the resolved collection
	 *
	 * @access protected
	 * @var array
	 */
	var $firstitem;

	/**
	 * Reference to the last item of the resolved collection
	 *
	 * @access protected
	 * @var array
	 */
	var $lastitem;

	/**
	 * Whether ignor emarking is allowed in this class.
	 *
	 * @access protected
	 * @var boolean
	 */
	var $allow_ignore_marking = false;

	/**
	 * Whether mark ignored items as ignored.
	 *
	 * @access protected
	 * @var array
	 */
	var $ignore_marking = true;

	/**
	 * The name of the userid field to be used for ignore marking
	 *
	 * @access protected
	 * @var string
	 */
	var $userid_field = 'userid';

	/**
	 * Optional id to fetch a specific item
	 *
	 * @access protected
	 * @var mixed filter_id
	 */
	var $filter_id;

	/**
	 * Only show items created after this many days old
	 * @see filter_days_prune()
	 *
	 * @access protected
	 * @var array
	 */
	var $daysprune;

	/**
	 * Field to sort by
	 * @see filter_sort_field()
	 *
	 * @access protected
	 * @var array
	 */
	var $sortfield;

	/**
	 * Name of the sort field hook for the item type.
	 *
	 * @access protected
	 * @var array
	 */
	var $sort_field_hook;

	/**
	 * Cache of the compiled collection sql
	 *
	 * @access protected
	 * @var string
	 */
	var $collection_sql;

	/**
	 * Cache of resolved query conditions based on user permission and message state
	 *
	 * @access protected
	 * @var string
	 */
	var $state_sql;

	/**
	 * Cache of compiled condition sql
	 *
	 * @access protected
	 * @var string
	 */
	var $condition_sql;

	// #######################################################################

	/**
	 * Constructor.
	 * Assigns and normalizes the required property members.
	 *
	 * @access public
	 *
	 * @param vB_Registry $registry				Global registry
	 * @param integer $parent_id				Optional parent item id
	 * @param integer $page						Optional page index
	 * @param integer $quantity					Optional quantity
	 * @param boolean $descending				Whether to get results in descending order
	 * @param boolean $no_limit					Whether to get all results or not
	 * @return vB_Group_Collection
	 */
	function __construct(&$registry, $parent_id = false, $page = 1, $quantity = false, $descending = true, $no_limit = false)
	{
		// Check the registry is valid
		if (is_object($registry))
		{
			$this->registry =& $registry;
		}
		else
		{
			trigger_error("vB_Collection::Registry object is not an object", E_USER_ERROR);
		}

		$this->parent_id = intval($parent_id);
		$this->page = max(1, intval($page));
		$this->quantity = max(1, intval($quantity));
		$this->descending = $descending;
		$this->no_limit = $no_limit;
	}

	/**
	 * Finds and sets the results page based on a dateline.
	 * Must be extended by child classes to have any effect.
	 *
	 * @access public
	 *
	 * @var integer								The dateline of an item on the required page
	 * @return integer | boolean				The resolved page index or false
	 */
	function seek_item($dateline)
	{
		if (!is_numeric($dateline))
		{
			if (1 != $this->page)
			{
				$this->page = 1;
				$this->reset();
				return false;
			}
		}

		return true;
	}

	/**
	 * Fetch collection of items.
	 * Creates a multidimensional array of messages with all useful properties for
	 * each message.
	 *
	 * @access public
	 *
	 * @return array
	 */
	function fetch()
	{
		// check local cache
		if (isset($this->collection))
		{
			return $this->collection;
		}

		$this->ignore_marking = ($this->allow_ignore_marking AND $this->ignore_marking AND $this->userid_field);

		// create a new collection
		$collection = array();

		// while start is valid
		do
		{
			$this->start = ($this->page - 1) * $this->quantity;

			// get query
			if (!($sql = $this->collection_sql(true)))
			{
				return $collection;
			}

			// exec query
			$query = $this->registry->db->query_read($sql);

			// get total
			list($this->total) = $this->registry->db->query_first("SELECT FOUND_ROWS()", DBARRAY_NUM);

			// check start is valid
			if ($this->start >= $this->total)
			{
				$this->page = ceil($this->total / $this->quantity);
			}
		}
		while ($this->start >= $this->total AND $this->total);

		// save the pagination info for querying from the client code
		$this->start++;
		$this->end = min(($this->start-1) + $this->quantity, $this->total);

		if ($this->ignore_marking)
		{
			// get ignore list
			if ($this->registry->userinfo['userid'] AND !$this->registry->GPC['showignored'])
			{
				$ignorelist = preg_split('/( )+/', trim($this->registry->userinfo['ignorelist']), -1, PREG_SPLIT_NO_EMPTY);
			}
			else
			{
				$ignorelist = array();
			}
		}

		// build collection, get the first item and flag ignored messages
		$this->collection = array();
		while ($item = $this->registry->db->fetch_array($query))
		{
			if (!$this->firstitem)
			{
				$this->firstitem =& $item;
			}

			if ($this->ignore_marking AND $ignorelist AND in_array($item[$this->userid_field], $ignorelist))
			{
				$item['ignored'] = true;
			}

			// set the item type
			$item['type'] = $this->type;

			$this->collection[] = $item;
		}
		$this->registry->db->free_result($query);

		if (sizeof($this->collection))
		{
			$this->lastitem =& $this->collection[sizeof($this->collection)-1];
		}

		return $this->collection;
	}

	/**
	 * Fetches a single item at a time.
	 *
	 * @access public
	 *
	 * @return array							Single content item
	 */
	function fetch_item()
	{
		if (!$this->collection)
		{
			$this->fetch();
		}

		if ($item = current($this->collection))
		{
			next($this->collection);
			return $item;
		}

		return false;
	}

	/**
	 * Fetches the counts of the fetched collection.
	 *
	 * @access public
	 *
	 * @return integer
	 */
	function fetch_counts()
	{
		$this->fetch();

		return array('start' => $this->start, 'end' => $this->end, 'shown' => sizeof($this->collection), 'total' => $this->total);
	}

	/**
	 * Only fetches the total size of the collection.
	 *
	 * @access public
	 *
	 * @return integer;
	 */
	function fetch_count()
	{
		$this->fetch();

		return sizeof($this->collection);
	}

	/**
	 * Fetches the first item of the collection or a property of the first item.
	 *
	 * @access public
	 *
	 * @param string $property					Optional property key
	 */
	function fetch_firstitem($property = false)
	{
		// Ensure the collection is fetched
		$this->fetch();

		if ($property AND isset($this->firstitem["$property"]))
		{
			return $this->firstitem["$property"];
		}
		else if ($this->firstitem)
		{
			return $this->firstitem;
		}

		return false;
	}

	/**
	 * Fetches the last item of the collection or a property of the last item.
	 *
	 * @access public
	 *
	 * @param string $property					Optional property key
	 */
	function fetch_lastitem($property)
	{
		// Ensure the collection is fetched
		$this->fetch();

		if ($property AND isset($this->lastitem["$property"]))
		{
			return $this->lastitem["$property"];
		}
		else if ($this->lastitem)
		{
			return $this->lastitem;
		}

		return false;
	}

	/**
	 * Gets the resolved results page
	 *
	 * @access public
	 *
	 * @return integer
	 */
	function fetch_pagenumber()
	{
		$this->fetch();

		return $this->page ? $this->page : 0;
	}

	/**
	 * Gets the resolved per page quanitity
	 *
	 * @access public
	 *
	 * @return integer
	 */
	function fetch_quantity()
	{
		$this->fetch();

		return $this->quantity;
	}

	/**
	 * Unsets the fetched collection and parsed collection sql.
	 *
	 * @access public
	 */
	function reset()
	{
		unset($this->collection, $this->collection_sql, $this->state_sql, $this->condition_sql);
	}

	// #######################################################################

	/**
	 * Sets the hook for modifying the fetch query.
	 * @see fetch()
	 *
	 * @access public
	 */
	function set_query_hook($name)
	{
		$this->view_query_hook = $name;
	}

	/**
	 * Builds the query to get the collection.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * @param bool $force_refresh				Whether to ignore the cache
	 * @return string							The built SQL
	 */
	function collection_sql($force_refresh = false)
	{
		if ($this->collection_sql AND !$force_refresh)
		{
			return $this->collection_sql;
		}

		return $this->collection_sql = false;
	}

	/**
	 * Builds conditions for the message query based on the user's permissions.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * @return string							The built SQL
	 */
	function state_sql()
	{
		if (isset($this->state_sql))
		{
			return $this->state_sql;
		}

		return $this->state_sql = false;
	}

	/**
	 * Allows child collection classes to add further conditions to the fetch sql.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * return string
	 */
	function condition_sql()
	{
		if (isset($this->condition_sql))
		{
			return $this->condition_sql;
		}

		return $this->condition_sql = false;
	}

	// ##Filters##############################################################

	/**
	 * Sets whether to use ignore marking.
	 *
	 * @access public
	 *
	 * @param boolean $ignore_marking
	 */
	function set_ignore_marking($ignore_marking = true)
	{
		$this->ignore_marking = $ignore_marking;
	}

	/**
	 * Sets amount of days old to prune items.
	 * Items older than $this->daysprune days old will not be fetched.
	 *
	 * @access public
	 * @param int $days
	 */
	function filter_days_prune($days=0)
	{
		if ($days != $this->daysprune)
		{
			$this->daysprune = intval($days);
			$this->reset();
		}
	}

	/**
	 * Sets the sort field.
	 * Child classes should validate the field and prefix.* the appropriate table alias.
	 *
	 * @access public
	 * @param string $field						- The field to sort by
	 */
	function filter_sort_field($field)
	{
		$this->filter_sort_field_hook($field);
	}

	/**
	 * Allows hooks to evaluate sort field.
	 *
	 * @access protected
	 * @param string $field
	 */
	function filter_sort_field_hook($field)
	{
		$resolved_table = false;
		$resolved_field = false;

		($hook = vBulletinHook::fetch_hook($this->sort_field_hook)) ? eval($hook) : false;

		if ($resolved_table AND $resolved_field)
		{
			$sortfield = $resolved_table . '.' . $resolved_field;

			if ($sortfield != $this->sortfield)
			{
				$this->sortfield = $sortfield;
				$this->reset();
			}
		}
	}

	/**
	 * Specifies an item id to fetch
	 *
	 * @access protected
	 * @param mixed $id
	 */
	function filter_id($id)
	{
		$this->filter_id = $id;
		$this->reset();
	}
}


/**
 * Group Collection Factory
 * Gets the appropriate collection handler for a social group content type.
 *
 * @package		vBulletin
 * @copyright	http://www.vbulletin.com/license
 */
class vB_Group_Collection_Factory extends vB_Collection_Factory
{
	/**
	 * Information about the social group.
	 *
	 * @access protected
	 * @var array
	 */
	var $group;

	/**
	 * Array of acceptable content types that the factory can produce a collection
	 * handler for.
	 *
	 * @access protected
	 * @var array string
	 */
	var $types = array('discussion', 'message', 'recentmessage');

	/**
	 * Class prefix for created collections
	 *
	 * @access protected
	 * @var string
	 */
	var $class_prefix = 'vB_Group_Collection_';

	// #######################################################################

	/**
	 * Constructor
	 * Note: No validation is done here as we know registry and group are reliably
	 * passed to vb_Group_Collection which should always validate them.
	 *
	 * @access public
	 *
	 * @param vB_Registry $registry
	 * @param array $group						Information about the social group
	 * @return vB_Group_Collection_Factory
	 */
	function __construct(&$registry, $group = false)
	{
		parent::__construct($registry);
		$this->group = $group;
	}

	/**
	 * Instantiates the required group collection object.
	 *
	 * @access protected
	 * @see vB_Group_Collection
	 *
	 * @param string $class_name				The resolved name of the class to instantate
	 * @param integer $parent_id				Optional id of outer item
	 * @param integer $page						Optional starting index
	 * @param integer $quantity					Optional quantity
	 * @param boolean $descending				Whether to get results in descending order
	 * @param boolean $no_limit					Whether to get all results or not
	 * @return vB_Group_Collection				The appropriate collection
	 */
	function instantiate($class_name, $parent_id = false, $page = 1, $quantity = false, $descending = true, $no_limit = false)
	{
		return new $class_name($this->registry, $this->group, $parent_id, $page, $quantity, $descending, $no_limit);
	}
}


/**
 * Group Collection
 * Fetches a collection of group content items with the given parent id,
 * start item and quantity of items to fetch.
 *
 * Note: All valid group items are expected to have the user fields of the author
 * as properties, and optionally a state('visible', 'moderation', 'deleted') and
 * a dateline.
 *
 * Also contains information for pagination.
 * @see vB_Group_Collection::fetch_counts()
 *
 * @package		vBulletin
 * @copyright	http://www.vbulletin.com/license.html
 *
 * @abstract
 */
class vB_Group_Collection extends vB_Collection
{
	/**
	 * Information about the social group.
	 *
	 * @access protected
	 * @var array
	 */
	var $group;

	/**
	 * Text to search
	 * @see filter_searchtext
	 */
	var $filter_searchtext;

	// #######################################################################

	/**
	 * Constructor.
	 * Assigns and normalizes the required property members.
	 *
	 * @access public
	 *
	 * @param vB_Registry $registry				Global registry
	 * @param array $group						Information about the social group
	 * @param integer $parent_id				Optional parent item id
	 * @param integer $page						Optional page index
	 * @param integer $quantity					Optional quantity
	 * @return vB_Group_Collection
	 */
	function __construct(&$registry, $group = false, $parent_id = false, $page = 1, $quantity = false, $descending = true, $no_limit = false)
	{
		parent::__construct($registry, $parent_id, $page, $quantity, $descending, $no_limit);

		$this->group = $group;
	}

	/**
	 * Sets a string for searching
	 *
	 * @access public
	 * @param string $searchtext
	 */
	function filter_searchtext($text)
	{
		require_once(DIR . '/includes/class_socialgroup_search.php');
		$text = vB_SGSearchGenerator::prepare_search_text($text, $errors);

		if (!$errors AND ($text != $this->filter_searchtext))
		{
			$this->filter_searchtext = $text;
			$this->reset();
		}
	}
}


/**
 * Group Message Collection.
 *
 * Queries a collection of social messages.
 *
 * @package		vBulletin
 * @copyright 	http://www.vbulletin.com/license.html
 */
class vB_Group_Collection_Message extends vB_Group_Collection
{
	/**
	 * Type identifier.
	 * Given to items so it can be checked whenever they are processed.
	 *
	 * @access protected
	 * @var string
	 */
	var $type = 'message';

	/**
	 * Name of a view query hook to execute to add to the collection query.
	 *
	 * @access protected
	 * @var string | boolean
	 */
	var $view_query_hook = 'group_view_message_query';

	/**
	 * Whether to show deleted items that the user has permission to.
	 *
	 * @access protected
	 * @var boolean
	 */
	var $show_deleted = true;

	/**
	 * Whether to show visible items that the user has permission to.
	 *
	 * @access protected
	 * @var boolean
	 */
	var $show_visible = true;

	/**
	 * Whether to show moderated items that the user has permission to.
	 *
	 * @access protected
	 * @var boolean
	 */
	var $show_moderated = true;

	/**
	 * Cache of resolved query join based on user's permission to view deleted items
	 *
	 * @access protected
	 * @var string
	 */
	var $deleted_sql;

	/**
	 * Name of the sort field hook for the item type.
	 *
	 * @access protected
	 * @var array
	 */
	var $sort_field_hook = 'group_message_sort_field';

	/**
	 * Whether to fetch messages that are also the first post of a discussion.
	 *
	 * @access protected
	 * @var boolean
	 */
	var $show_discussions = true;

	/**
	 * The name of the userid field to be used for ignore marking
	 *
	 * @access protected
	 * @var string
	 */
	var $userid_field = 'postuserid';

	/**
	 * Whether ignor emarking is allowed in this class.
	 *
	 * @access protected
	 * @var boolean
	 */
	var $allow_ignore_marking = true;

	// #######################################################################

	/**
	 * Builds the query to get the collection.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * @param bool $force_refresh				Whether to ignore the cache
	 * @return string							The built SQL
	 */
	function collection_sql($force_refresh = false)
	{
		if ($this->collection_sql AND !$force_refresh)
		{
			return $this->collection_sql;
		}

		if (!$this->sortfield)
		{
			$this->filter_sort_field('dateline');
		}

		// hook to alter query.
		$group = $this->group;	$vbulletin =& $this->registry;
		$hook_query_fields = $hook_query_joins = $hook_query_where = '';
		($hook = vBulletinHook::fetch_hook($this->view_query_hook)) ? eval($hook) : false;

		// build query
		$sql = 		"SELECT SQL_CALC_FOUND_ROWS
						gm.*, user.*, gm.ipaddress AS itemipaddress";

		if ($deleted_sql = $this->deleted_sql())
		{
			$sql .= "	,deletionlog.userid AS del_userid, deletionlog.username AS del_username,
						deletionlog.reason AS del_reason";
		}

		if ($this->registry->options['avatarenabled'])
		{
			$sql .= "	,avatar.avatarpath, NOT ISNULL(customavatar.userid) AS hascustomavatar,
						customavatar.dateline AS avatardateline,customavatar.width AS avwidth,
						customavatar.height AS avheight, customavatar.width_thumb AS avwidth_thumb,
						customavatar.height_thumb AS avheight_thumb, filedata_thumb,
						NOT ISNULL(customavatar.userid) AS hascustom";
		}

		$sql .= "		$hook_query_fields
					FROM " . TABLE_PREFIX . "groupmessage AS gm
					LEFT JOIN " . TABLE_PREFIX . "user AS user ON (gm.postuserid = user.userid)";

		if ($this->registry->options['avatarenabled'])
		{
			$sql .= " LEFT JOIN " . TABLE_PREFIX . "avatar AS avatar
						ON (avatar.avatarid = user.avatarid)
					 LEFT JOIN " . TABLE_PREFIX . "customavatar AS customavatar
						ON (customavatar.userid = user.userid)";
		}

		if (!$this->show_discussions)
		{
			$sql .= " INNER JOIN " . TABLE_PREFIX . "discussion AS discussion
						ON (discussion.discussionid = gm.discussionid
							AND discussion.firstpostid <> gm.gmid)";
		}

		$sql .= "	 $deleted_sql
					 $hook_query_joins
					WHERE 1=1";

		if ($this->parent_id)
		{
			$sql .= " AND gm.discussionid = $this->parent_id";
		}

		if ($this->daysprune)
		{
			$sql .= (($this->daysprune != -1) ? " AND gm.dateline >= " . (TIMENOW - ($this->daysprune * 86400)) : '');
		}

		if ($this->filter_id)
		{
			$sql .= " AND gm.gmid = " . intval($this->filter_id);
		}

		$sql .= 	" "	. $this->state_sql() .
					" " . $this->condition_sql() .
					"	$hook_query_where " .
					($this->filter_id ? '' : "ORDER BY " . $this->sortfield . " " . ($this->descending ? "DESC" : "ASC") . " " .
					($this->no_limit ? "" : "LIMIT {$this->start}, {$this->quantity}"));

		return $this->collection_sql = $sql;
	}

	/**
	 * Builds conditions for the message query based on the user's permissions.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * @return string							The built SQL
	 */
	function state_sql()
	{
		if (isset($this->state_sql))
		{
			return $this->state_sql;
		}

		// Build state conditions for query
		$state = array();
		$state_or = array();

		if ($this->show_visible)
		{
			$state = array('visible');
		}

		if ($this->show_moderated)
		{
			if (fetch_socialgroup_modperm('canmoderategroupmessages', $this->group))
			{
				$state[] = 'moderation';
			}
			else if ($this->registry->userinfo['userid'])
			{
				$state_or[] = "(gm.postuserid = " . $this->registry->userinfo['userid'] . " AND state = 'moderation')";
			}
		}

		if ($this->show_deleted)
		{
			if (fetch_socialgroup_modperm('canviewdeleted', $this->group))
			{
				$state[] = 'deleted';
			}
		}

		if (sizeof($state))
		{
			$state_or[] = "gm.state IN ('" . implode("','", $state) . "')";
		}

		if (sizeof($state_or))
		{
			return $this->state_sql = "AND (" . implode(" OR ", $state_or) . ")";
		}

		return '';
	}

	/**
	 * Builds SQL for deleted items based on whether the current user can view them.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * @return string							The created SQL join.
	 */
	function deleted_sql()
	{
		if (isset($this->deleted_sql))
		{
			return $this->deleted_sql;
		}

		// Check if user can view deleted items
		if ($this->show_deleted AND fetch_socialgroup_modperm('canviewdeleted', $this->group))
		{
			$sql = "LEFT JOIN " . TABLE_PREFIX . "deletionlog AS deletionlog
									ON (gm.gmid = deletionlog.primaryid AND deletionlog.type = 'groupmessage')";
		}
		else
		{
			$sql = false;
		}

		return $this->deleted_sql = $sql;
	}

	// #######################################################################

	/**
	 * Finds the page index based on a given dateline
	 *
	 * @access public
	 *
	 * @return unknown
	 */
	function seek_item($dateline)
	{
		if (!parent::seek_item($dateline))
		{
			return false;
		}

		// Collection is stale
		$this->reset();

		// Find page
		$getpagenum = $this->registry->db->query_first("
			SELECT COUNT(*) AS comments
			FROM " . TABLE_PREFIX . "groupmessage AS gm
			WHERE discussionid = $this->parent_id " .
				$this->state_sql() . "
				AND dateline " . ($this->descending ? ">" : "<") . "= $dateline
		");

		// if page is full, adding 1 pushes to top of next page
		return $this->page = max(1, ceil(($getpagenum['comments']+1) / $this->quantity));
	}

	/**
	 * Unsets the fetched collection and parsed collection sql.
	 *
	 * @access public
	 */
	function reset()
	{
		parent::reset();
		unset($this->deleted_sql);
	}

	// #######################################################################

	/**
	 * Sets whether to show deleted items that the user has permission to.
	 *
	 * @access public
	 *
	 * @param boolean $show
	 */
	function filter_show_deleted($show = true)
	{
		if ($show != $this->show_deleted)
		{
			$this->show_deleted = $show;
			$this->reset();
		}
	}

	/**
	 * Sets whether to show visible items that the user has permission to.
	 *
	 * @access public
	 *
	 * @param boolean $show
	 */
	function filter_show_visible($show = true)
	{
		if ($show != $this->show_visible)
		{
			$this->show_visible = $show;
			$this->reset();
		}
	}

	/**
	 * Sets whether to show moderated items that the user has permission to.
	 *
	 * @access public
	 *
	 * @param boolean $show
	 */
	function filter_show_moderated($show = true)
	{
		if($show != $this->show_moderated)
		{
			$this->show_moderated = $show;
			$this->reset();
		}
	}

	/**
	 * Sets the sort field.
	 * Child classes should validate the field and prefix.* the appropriate table alias.
	 *
	 * @access public
	 *
	 * @param string $field						- The field to sort by
	 */
	function filter_sort_field($field)
	{
		$this->filter_sort_field_hook($field);

		if (!$this->sortfield)
		{
			$this->sortfield = 'gm.';
			$this->sortfield .= (('username' == $field) ? 'postusername' : 'dateline');
		}
	}

	/**
	 * Sets wheter to select discussion posts.
	 * If this is false then first posts will not be included.  Ideal for moderation.
	 * @see moderation.php
	 *
	 * @access public
	 *
	 * @param boolean $show
	 */
	function filter_show_discussions($show = true)
	{
		if ($show != $this->show_discussions)
		{
			$this->show_discussions = $show;
			$this->reset();
		}
	}
}


/**
 * Group Recent Message Collection
 * Fetches messages after a dateline including an optionally specified message.
 *
 * Note: This class is unable to provide an accurate total messages and essentially
 * ignores any specified page or per page quantity.
 *
 * @package		vBulletin
 * @copyright 	http://www.vbulletin.com/license.html
 *
 */
class vB_Group_Collection_RecentMessage extends vB_Group_Collection_Message
{
	/**
	 * The dateline to fetch messages since.
	 * Only messages after this dateline will be fetched.
	 *
	 * @access protected
	 * @var integer
	 */
	var $dateline;

	/**
	 * An optional gmid of a message to include.
	 * The messages will be fetched in the collection regardless of it's dateline.
	 *
	 * @access protected
	 * @var integer
	 */
	var $include_gmid;

	// #######################################################################

	/**
	 * Sets the dateline to show messages since.
	 *
	 * @access public
	 *
	 * @param integer $dateline					Dateline that all messages since will be fetched
	 * @param integer $gmid						An optional gmid to add to the collection
	 */
	function set_dateline($dateline, $include_gmid = false)
	{
		if (($dateline != $this->dateline) OR $include_gmid != $this->include_gmid)
		{
			$this->dateline = $dateline;
			$this->include_gmid = $include_gmid;
			$this->reset();
		}
	}

	/**
	 * Allows child collection classes to add further conditions to the fetch sql.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * return string
	 */
	function condition_sql()
	{
		if (isset($this->condition_sql))
		{
			return $this->condition_sql;
		}

		$this->dateline = intval($this->dateline);
		$this->include_gmid = intval($this->include_gmid);

		return $this->condition_sql = "AND ((gm.dateline > {$this->dateline}) OR gm.gmid = {$this->include_gmid})";
	}
}

/**
 * Group Discussion Collection.
 *
 * Queries a collection of social discussions.
 *
 * @package		vBulletin
 * @copyright 	http://www.vbulletin.com/license.html
 */
class vB_Group_Collection_Discussion extends vB_Group_Collection_Message
{
	/**
	 * Type identifier.
	 * Given to items so it can be checked whenever they are processed.
	 *
	 * @access protected
	 * @var string
	 */
	var $type = 'discussion';

	/**
	 * Name of a view query hook to execute to add to the collection query.
	 *
	 * @access protected
	 * @var string | boolean
	 */
	var $view_query_hook = 'group_view_discussion_query';

	/**
	 * Name of the sort field hook for the item type.
	 *
	 * @access protected
	 * @var array
	 */
	var $sort_field_hook = 'group_discussion_sort_field';

	/**
	 * Whether to check read marking.
	 * @see check_read()
	 *
	 * @access protected
	 * var bool
	 */
	var $check_read = true;

	/**
	 * Whether to hide read discussions.
	 * @see filter_show_read()
	 *
	 * @access protected
	 * var bool
	 */
	var $show_read = true;

	/**
	 * Whether to show only subscribed discussions.
	 * @see filter_show_unsubscribed()
	 *
	 * @access protected
	 * var bool
	 */
	var $show_unsubscribed = true;

	// #######################################################################

	/**
	 * Builds the query to get the collection.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * @return string							The built SQL
	 */
	function collection_sql($force_refresh = false)
	{
		if ($this->collection_sql)
		{
			return $this->collection_sql;
		}

		if (!$this->sortfield AND $this->filter_searchtext)
		{
			 $this->filter_sort_field('relevance');
		}
		else if (!$this->sortfield
				 OR ('subscribediscussion.emailupdate' == $this->sortfield AND $this->show_unsubscribed)
				 OR ('relevance' == $this->sortfield AND !$this->filter_searchtext))
		{
			$this->filter_sort_field('lastpost');
		}

		// hook to alter query.
		$group = $this->group;	$vbulletin =& $this->registry;
		$hook_query_fields = $hook_query_joins = $hook_query_where = '';
		($hook = vBulletinHook::fetch_hook($this->view_query_hook)) ? eval($hook) : false;

		// build query
		$sql = 		"SELECT SQL_CALC_FOUND_ROWS
						d.*, gm.*, user.*,
						gm.ipaddress AS itemipaddress, d.lastpost AS lastpost, d.lastpostid AS lastpostid";

		if ($deleted_sql = $this->deleted_sql())
		{
			$sql .= "	,deletionlog.userid AS del_userid, deletionlog.username AS del_username,
						deletionlog.reason AS del_reason";
		}

		if ($this->check_read AND $this->show_read)
		{
			$sql .= " ,IF(d.lastpost <= discussionread.readtime,1,0) AS is_read, discussionread.readtime AS readtime";
		}

		if (!$this->show_unsubscribed)
		{
			$sql .= " ,subscribediscussion.emailupdate";
		}

		if ($this->filter_searchtext AND 'relevance' == $this->sortfield)
		{
			$sql .= " ,(MATCH(gm.title, gm.pagetext) AGAINST ('" . $this->registry->db->escape_string($this->filter_searchtext) . "' IN BOOLEAN MODE) ) AS relevance";
		}

		$sql .= "		$hook_query_fields
					FROM " . TABLE_PREFIX . "discussion AS d
					INNER JOIN " . TABLE_PREFIX . "groupmessage AS gm
						ON (gm.gmid = d.firstpostid)
					LEFT JOIN " . TABLE_PREFIX . "user AS user ON (gm.postuserid = user.userid)";

		if ($this->check_read OR !$this->show_read)
		{
			$sql .= " LEFT JOIN " . TABLE_PREFIX . "discussionread AS discussionread
						ON (discussionread.discussionid = d.discussionid
						AND discussionread.userid = " . $this->registry->userinfo['userid'] . ')';
		}

		// if not getting read posts we need to check the group
		if (!$this->show_read)
		{
			$sql .= " LEFT JOIN " . TABLE_PREFIX . "groupread AS groupread
					    ON (groupread.groupid = d.groupid
					    AND groupread.userid = " . $this->registry->userinfo['userid'] . ')';
		}

		// if only getting subscriptions
		if (!$this->show_unsubscribed)
		{
			$sql .= " INNER JOIN " . TABLE_PREFIX . "subscribediscussion AS subscribediscussion
						ON (subscribediscussion.discussionid = d.discussionid
						AND subscribediscussion.userid = " . $this->registry->userinfo['userid'] . ')';
		}

		$sql .= "	$deleted_sql
					$hook_query_joins
					WHERE 1=1";

		if ($this->parent_id)
		{
			$sql .= " AND d.groupid = $this->parent_id";
		}

		if ($this->daysprune)
		{
			$sql .= (($this->daysprune != -1) ? " AND d.lastpost >= " . (TIMENOW - ($this->daysprune * 86400)) : '');
		}

		if (!$this->show_read)
		{
			// posts older than markinglimit days won't be highlighted as new
			$oldtime = TIMENOW - ($this->registry->options['markinglimit'] * 24 * 60 * 60);
			$sql .= " AND d.lastpost > $oldtime AND d.lastpost > COALESCE(groupread.readtime, 0) AND d.lastpost > COALESCE(discussionread.readtime, 0)";
		}

		if ($this->filter_searchtext)
		{
			$sql .= " AND (MATCH(gm.title, gm.pagetext) AGAINST ('" . $this->registry->db->escape_string($this->filter_searchtext) . "' IN BOOLEAN MODE) )";
		}

		if ($this->filter_id)
		{
			$sql .= " AND d.discussionid = " . intval($this->filter_id);
		}

		$sql .= " " . 	$this->state_sql() . " " .
						$this->condition_sql() . "
						$hook_query_where " .
					($this->filter_id ? '' : "ORDER BY " . $this->sortfield . " " . ($this->descending ? "DESC" : "ASC") . " " .
					($this->no_limit ? "" : "LIMIT {$this->start}, {$this->quantity}"));

		return $this->collection_sql = $sql;
	}

	/**
	 * Builds conditions for the message query based on the user's permissions.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * @return string							The built SQL
	 */
	function state_sql()
	{
		if (isset($this->state_sql))
		{
			return $this->state_sql;
		}

		// Build state conditions for query
		$state = array();
		$state_or = array();

		if ($this->show_visible)
		{
			$state = array('visible');
		}

		if ($this->show_moderated)
		{
			if (fetch_socialgroup_modperm('canmoderatediscussions', $this->group))
			{
				$state[] = 'moderation';
			}
			else if ($this->registry->userinfo['userid'])
			{
				$state_or[] = "(gm.postuserid = " . $this->registry->userinfo['userid'] . " AND state = 'moderation')";
			}
		}

		if ($this->show_deleted)
		{
			if (fetch_socialgroup_modperm('canviewdeleted', $this->group))
			{
				$state[] = 'deleted';
			}
		}

		if (sizeof($state))
		{
			$state_or[] = "gm.state IN ('" . implode("','", $state) . "')";
		}

		if (sizeof($state_or))
		{
			return $this->state_sql = "AND (" . implode(" OR ", $state_or) . ")";
		}

		return $this->state_sql = '';
	}

	/**
	 * Finds the page index based on a given dateline
	 *
	 * @access public
	 *
	 * @return unknown
	 */
	function seek_item($dateline)
	{
		if (!parent::seek_item($dateline))
		{
			return false;
		}

		// Collection is stale
		$this->reset();

		// Find page
		$getpagenum = $db->query_first("
			SELECT COUNT(*) AS comments
			FROM " . TABLE_PREFIX . "discussion AS d
			INNER JOIN " . TABLE_PREFIX . "groupmessage AS gm
				ON gm.gmid = d.firstpostid
			WHERE d.discussionid = $this->parentid " .
				$this->state_sql() . "
				AND dateline " . ($this->descending ? ">" : "<") . "= $dateline
		");

		// if page is full, adding 1 pushes to top of next page
		return $this->page = max(1, ceil(($getpagenum['comments']+1) / $this->quantity));
	}

	// #######################################################################

	/**
	 * Whether to check read marking when fetching.
	 *
	 * @param boolean $check
	 */
	function check_read($check = true)
	{
		if ($check != $this->check_read)
		{
			$this->check_read = ($check AND $this->registry->userinfo['userid']);
			$this->reset();
		}
	}

	/**
	 * Sets the sort field.
	 * Sort by creation dateline, lastpost dateline or creator username
	 *
	 * @access public
	 *
	 * @param string $field						- The field to sort by
	 */
	function filter_sort_field($field)
	{
		$this->filter_sort_field_hook($field);

		if ($field != $this->sortfield)
		{
			if (!$this->sortfield)
			{
				if ('username' == $field)
				{
					$this->sortfield = 'gm.postusername';
				}
				else if ('dateline' == $field)
				{
					$this->sortfield = 'gm.dateline';
				}
				else if ('messages' == $field OR 'replies' == $field)
				{
					$this->sortfield = 'd.visible';
				}
				else if ('subscription' == $field)
				{
					$this->sortfield = 'subscribediscussion.emailupdate';
				}
				else if ('relevance' == $field)
				{
					$this->sortfield = 'relevance';
				}
				else
				{
					$this->sortfield = 'd.lastpost';
				}
			}

			$this->reset();
		}
	}

	/**
	 * Sets whether read discussions are selected.
	 *
	 * @access public
	 *
	 * @param bool $show						- Whether to select read discussions
	 */
	function filter_show_read($show = true)
	{
		if ($show != $this->show_read)
		{
			$this->show_read = ($show OR !$this->registry->userinfo['userid']);
			$this->reset();
		}
	}

	/**
	 * Sets whether to show discussions the user is not subscribed to.
	 *
	 * @access public
	 *
	 * @param bool $show
	 */
	function filter_show_unsubscribed($show = true)
	{
		if ($show != $this->show_unsubscribed)
		{
			$this->show_unsubscribed = ($show OR !$this->registry->userinfo['userid']);
			$this->reset();
		}
	}
}

/**
 * Album Collection.
 *
 * Queries a collection of albums.
 *
 * @package		vBulletin
 * @copyright 	http://www.vbulletin.com/license.html
 */
class vB_Collection_Album extends vB_Collection
{
	/**
	 * Type identifier.
	 * Given to items so it can be checked whenever they are processed.
	 *
	 * @access protected
	 * @var string
	 */
	var $type = 'album';

	/**
	 * Name of a view query hook to execute to add to the collection query.
	 *
	 * @access protected
	 * @var string | boolean
	 */
	var $view_query_hook = 'album_user_query';

	/**
	 * Whether to show moderated items that the user has permission to.
	 *
	 * @access protected
	 * @var boolean
	 */
	var $show_moderated = false;

	/**
	 * Name of the sort field hook for the item type.
	 *
	 * @access protected
	 * @var array
	 */
	var $sort_field_hook = 'album_sort_field';

	/**
	 * Whether to only get albums that have been auto approved.
	 * Albums are auto approved when a user updates and album and has permission
	 * to display albums at the time of the update.
	 * Updates are limited to a fixed number ($vbulletin->options['albumupdates'])
	 * and will eventually expire.
	 *
	 * @access protected
	 * @var boolean
	 */
	var $only_autoapproved = true;

	/**
	 * Whether ignor emarking is allowed in this class.
	 *
	 * @access protected
	 * @var boolean
	 */
	var $allow_ignore_marking = true;

	// #######################################################################

	/**
	 * Builds the query to get the collection.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * @param bool $force_refresh				Whether to ignore the cache
	 * @return string							The built SQL
	 */
	function collection_sql($force_refresh = false)
	{
		if ($this->collection_sql AND !$force_refresh)
		{
			return $this->collection_sql;
		}

		if (!$this->sortfield)
		{
			$this->filter_sort_field('lastpicturedate');
		}

		// hook to alter query.
		$vbulletin =& $this->registry;
		$hook_query_fields = $hook_query_joins = $hook_query_where = '';
		($hook = vBulletinHook::fetch_hook($this->view_query_hook)) ? eval($hook) : false;

		// build query
		$sql = 		"SELECT SQL_CALC_FOUND_ROWS
						album.*, user.*, album.lastpicturedate AS dateline,
						picture.pictureid, picture.extension, picture.idhash,
						picture.thumbnail_dateline, picture.thumbnail_width, picture.thumbnail_height
						$hook_query_fields
					FROM " . TABLE_PREFIX . "album AS album
					LEFT JOIN " . TABLE_PREFIX . "user AS user ON (user.userid = album.userid)
					LEFT JOIN " . TABLE_PREFIX . "profileblockprivacy AS profileblockprivacy ON
						(profileblockprivacy.userid = user.userid AND profileblockprivacy.blockid = 'albums')";

		if ($this->only_autoapproved)
		{
			$sql .= " INNER JOIN " . TABLE_PREFIX . "albumupdate AS albumupdate ON (albumupdate.albumid = album.albumid)";
		}

		$sql .= "	LEFT JOIN " . TABLE_PREFIX . "picture AS picture ON (album.coverpictureid = picture.pictureid AND picture.thumbnail_filesize > 0)
					$hook_query_joins
					WHERE 1=1";

		// get albums for a specific user
		if ($this->parent_id)
		{
			$sql .= " AND album.userid = $this->parent_id";
		}
		else
		{
			// get public ones only
			$sql .= " AND (profileblockprivacy.requirement = 0 OR profileblockprivacy.requirement IS NULL)";
		}

		if ($this->daysprune)
		{
			$sql .= (($this->daysprune != -1) ? " AND album.lastpicturedate >= " . (TIMENOW - ($this->daysprune * 86400)) : '');
		}

		if ($this->filter_id)
		{
			$sql .= " AND album.albumid = " . intval($this->filter_id);
		}

		$sql .= 	" "	. $this->state_sql() .
					" " . $this->condition_sql() .
					"	$hook_query_where " .
					($this->filter_id ? '' : "ORDER BY " . $this->sortfield . " " . ($this->descending ? "DESC" : "ASC") . " " .
					($this->no_limit ? "" : "LIMIT {$this->start}, {$this->quantity}"));

		return $this->collection_sql = $sql;
	}

	/**
	 * Builds conditions for the message query based on the user's permissions.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * @return string							The built SQL
	 */
	function state_sql()
	{
		if (isset($this->state_sql))
		{
			return $this->state_sql;
		}

		// Build state conditions for query
		$state = array('public');

		if ($this->parent_id)
		{
			if (can_view_private_albums($this->parent_id))
			{
				$state[] = 'private';
			}
			if (can_view_profile_albums($this->parent_id))
			{
				$state[] = 'profile';
			}
		}

		$this->state_sql = "AND (album.state IN ('" . implode("','", $state) . "')";

		if ($this->show_moderated AND can_moderate(0, 'canmoderatepictures'))
		{
			$this->state_sql .= 'AND (album.visible > 0 OR album.moderation > 0)';
		}
		else
		{
			$this->state_sql .= 'AND album.visible > 0';
		}

		$this->state_sql .= ')';

		require_once(DIR . '/includes/functions_user.php');
		$privacy_requirement = fetch_user_relationship($this->parent_id, $this->registry->userinfo['userid']);
		$this->state_sql .= " AND (profileblockprivacy.requirement <= " . intval($privacy_requirement) . " OR profileblockprivacy.requirement IS NULL)";

		return $this->state_sql;
	}

	// #######################################################################

	/**
	 * Finds the page index based on a given dateline
	 *
	 * @access public
	 *
	 * @return unknown
	 */
	function seek_item($dateline)
	{
		if (!parent::seek_item($dateline))
		{
			return false;
		}

		// Collection is stale
		$this->reset();

		$sql = "SELECT COUNT(*) AS total
			FROM " . TABLE_PREFIX . "album AS album";

		if ($this->only_autoapproved)
		{
			$sql .= "INNER JOIN " . TABLE_PREFIX . "albumupdate AS albumupdate";
		}

		$sql .= " WHERE 1=1 ";

		if ($this->parent_id)
		{
			$sql .= " AND userid = $this->parent_id ";
		}

		$sql .=	$this->state_sql() . "
				AND lastpicturedate " . ($this->descending ? ">" : "<") . " = $dateline
		";

		$getpagenum = $this->registry->db->query_first($sql);

		// if page is full, adding 1 pushes to top of next page
		return $this->page = max(1, ceil(($getpagenum['total']+1) / $this->quantity));
	}

	// #######################################################################

	/**
	 * Sets whether to show moderated items that the user has permission to.
	 *
	 * @access public
	 *
	 * @param boolean $show
	 */
	function filter_show_moderated($show = true)
	{
		if($show != $this->show_moderated)
		{
			$this->show_moderated = $show;
			$this->reset();
		}
	}

	/**
	 * Sets the sort field.
	 * Child classes should validate the field and prefix.* the appropriate table alias.
	 *
	 * @access public
	 *
	 * @param string $field						- The field to sort by
	 */
	function filter_sort_field($field)
	{
		$this->filter_sort_field_hook($field);

		if (!$this->sortfield)
		{
			$this->sortfield = ($this->only_autoapproved ? 'albumupdate.dateline' : 'album.lastpicturedate');
			$this->sortfield = (('username' == $field) ? 'user.username' : $this->sortfield);
		}
	}

	/**
	 * Whether albums must have been autoapproved.
	 *
	 * @param boolean $show
	 */
	function filter_auto_approved($show = true)
	{
		if ($show != $this->only_autoapproved)
		{
			$this->only_autoapproved = $show;
			$this->reset();
		}
	}
}

/**
 * Group Category Collection.
 *
 * Queries a collection of social group categories.
 *
 * @package		vBulletin
 * @copyright 	http://www.vbulletin.com/license.html
 */
class vB_Collection_GroupCategory extends vB_Collection
{
	/**
	 * Type identifier.
	 * Given to items so it can be checked whenever they are processed.
	 *
	 * @access protected
	 * @var string
	 */
	var $type = 'groupcategory';

	/**
	 * Name of a view query hook to execute to add to the collection query.
	 *
	 * @access protected
	 * @var string | boolean
	 */
	var $view_query_hook = 'group_view_categories_query';

	/**
	 * Name of the sort field hook for the item type.
	 *
	 * @access protected
	 * @var array
	 */
	var $sort_field_hook = 'group_category_sort_field';

	// #######################################################################

	/**
	 * Builds the query to get the collection.
	 * @see fetch()
	 *
	 * @access protected
	 *
	 * @param bool $force_refresh				Whether to ignore the cache
	 * @return string							The built SQL
	 */
	function collection_sql($force_refresh = false)
	{
		if ($this->collection_sql AND !$force_refresh)
		{
			return $this->collection_sql;
		}

		if (!$this->sortfield)
		{
			$this->filter_sort_field('title');
		}

		// hook to alter query.
		$vbulletin =& $this->registry;
		$hook_query_fields = $hook_query_joins = $hook_query_where = '';
		($hook = vBulletinHook::fetch_hook($this->view_query_hook)) ? eval($hook) : false;

		// build query
		$sql = "SELECT SQL_CALC_FOUND_ROWS
				cat.socialgroupcategoryid AS categoryid, cat.title, cat.description, COUNT(socialgroup.groupid) AS groups
				$hook_query_fields
			FROM " . TABLE_PREFIX . "socialgroupcategory AS cat
			LEFT JOIN " . TABLE_PREFIX . "socialgroup AS socialgroup
			 ON (socialgroup.socialgroupcategoryid = cat.socialgroupcategoryid)
			 $hook_query_joins
			WHERE socialgroup.groupid IS NOT NULL " .
			 ($this->filter_id ? " AND cat.socialgroupcategoryid = " . intval($this->filter_id) : '') .
			 $this->state_sql() .
			 $this->condition_sql() . "
			 $hook_query_where
			GROUP BY cat.socialgroupcategoryid " .
			($this->filter_id ? '' : "ORDER BY " . $this->sortfield . " " . ($this->descending ? "DESC" : "ASC") . " " .
			($this->no_limit ? "" : "LIMIT {$this->start}, {$this->quantity}"));

		return $this->collection_sql = $sql;
	}

	// #######################################################################

	/**
	 * Sets the sort field.
	 * Child classes should validate the field and prefix.* the appropriate table alias.
	 *
	 * @access public
	 *
	 * @param string $field						- The field to sort by
	 */
	function filter_sort_field($field)
	{
		$this->filter_sort_field_hook($field);

		if (!$this->sortfield)
		{
			$this->sortfield = ('groups' == $field ? 'groups' : 'cat.title');
		}
	}
}


// #######################################################################


/**
* Bit Factory.
* Fetches the appropriate class for rendering a content item's template bit.
*
* @package 		vBulletin
* @copyright 	http://www.vbulletin.com/license.html
*/
class vB_Bit_Factory
{
	/**
	* Registry object
	*
	* @access protected
	* @var	vB_Registry
	*/
	var $registry = null;

	/**
	* BB code parser object (if necessary)
	*
	* @access protected
	* @var	vB_BbCodeParser
	*/
	var $bbcode = null;

	/**
	* Permission cache for various users.
	*
	* @access protected
	* @var	array
	*/
	var $perm_cache = array();

	/**
	 * Item types that the factory supports.
	 *
	 * @access protected
	 * @var array
	 */
	var $types = array('album', 'groupcategory');

	/**
	 * Class prefix for created collections
	 *
	 * @access protected
	 * @var string
	 */
	var $class_prefix = 'vB_Bit_';

	// #######################################################################

	/**
	* Constructor, sets up the object.
	*
	* @access public
	*
	* @param	vB_Registry
	*/
	function __construct(&$registry)
	{
		if (is_object($registry))
		{
			$this->registry =& $registry;
		}
		else
		{
			trigger_error("vB_Bit_Factory::Registry object is not an object", E_USER_ERROR);
		}
	}

	/**
	* Create a bit renderer for the specified item.
	*
	* @access public
	*
	* @param	array							item information
	* @return	vB_Bit
	*/
	function &create($item)
	{
		// Hook to allow custom items
		($hook = vBulletinHook::fetch_hook('bit_factory_create')) ? eval($hook) : false;

		if (empty($item['type']))
		{
			trigger_error("vB_Bit_Factory::create(): Given item does not have a type set", E_USER_ERROR);
		}

		if (!in_array($item['type'], $this->types))
		{
			trigger_error("vB_Bit_Factory::create(): Item type not recognised", E_USER_ERROR);
		}

		$class_name = $this->get_class_name($item);

		// Create the collection handler
		if (class_exists($class_name))
		{
			return $this->instantiate($class_name, $item);
		}
		else
		{
			trigger_error('vB_Collection_Factory::create(): Invalid type ' . htmlspecialchars_uni($class_name) . '.', E_USER_ERROR);
		}
	}

	/**
	 * Resolves the appropriate class name for the required Bit object
	 *
	 * @param	mixed array $item				Item info array
	 * @return	string
	 */
	function get_class_name($item)
	{
		return 'vB_Bit_' . ucfirst($item['type']);
	}

	/**
	 * Instantiates the required bit object.
	 *
	 * @access protected
	 * @see vB_Bit
	 *
	 * @param string $class_name				The resolved name of the class to instantate
	 * @param array mixed $item					Information about the item being rendered
	 * @return vB_Bit							The appropriate bit handler
	 */
	function instantiate($class_name, $item)
	{
		return new $class_name($this->registry, $this, $item);
	}
}


/**
* Generic bit class.
* Bit classes render the template bit for the given content item.
* Use the bit factory to resolve and fetch the appropriate bit class for a
* content item.
* @see vB_Bit_Factory
*
* @package 		vBulletin
* @copyright 	http://www.vbulletin.com/license.html
*
* @abstract
*/
class vB_Bit
{
	/**
	* Registry object
	*
	* @access protected
	* @var	vB_Registry
	*/
	var $registry = null;

	/**
	* Factory object that created this object. Used for permission caching.
	*
	* @access protected
	* @var	vB_Bit_Factory
	*/
	var $factory = null;

	/**
	* BB code parser object (if necessary)
	*
	* @access protected
	* @var	vB_BbCodeParser
	*/
	var $bbcode = null;

	/**
	* Cached information from the BB code parser
	*
	* @access protected
	* @var	array
	*/
	var $parsed_cache = array();

	/**
	* Information about the content item
	*
	* @access protected
	* @var	array
	*/
	var $item = array();

	/**
	* The template that will be used for outputting
	*
	* @access protected
	* @var	string
	*/
	var $template = '';

	/**
	 * Hook run at start of template bit
	 *
	 * @access protected
	 * @var string
	 */
	var $hook_display_start;

	/**
	 * Hook run at end of template bit
	 *
	 * @access protected
	 * @var string
	 */
	var $hook_display_complete;

	/**
	 * Whether to include information about the item user
	 *
	 * @access protected
	 * @var boolean
	 */
	var $process_user = true;

	/**
	 * Whether to fetch avatar info for the item's author
	 *
	 * @access protected
	 * @var boolean
	 */
	var $use_avatar;

	/**
	 * Whether to show moderation details.
	 * If this is false, edit links and inlinemod will be hidden.
	 * @see show_moderation()
	 *
	 * @access protected
	 * @var boolean
	 */
	var $show_moderation_tools = true;

	/**
	 * The variable name used in templates for the item
	 */
	var $template_item_var;

	/**
	 * Whether to force inline tools.
	 * This is useful when checkboxes are required for something other than inline
	 * moderation, such as selecting items to change subscription settings on.
	 *
	 * @access protected
	 * @var bool
	 */
	var $force_inline_selection = false;

	// #######################################################################

	/**
	* Constructor, sets up the object.
	*
	* @access public
	*
	* @param	vB_Registry
	* @param	vB_BbCodeParser
	* @param	vB_Group_MessagFactory
	* @param	array							User info
	* @param	array							Message info
	*/
	function __construct(&$registry, &$factory, $item)
	{
		if (!is_subclass_of($this, 'vB_Bit'))
		{
			trigger_error('Direct instantiation of vB_Bit class prohibited. Use the vB_Bit_Factory class.', E_USER_ERROR);
		}

		if (is_object($registry))
		{
			$this->registry =& $registry;
		}
		else
		{
			trigger_error("vB_Bit::Registry object is not an object", E_USER_ERROR);
		}

		$this->factory =& $factory;
		$this->item = $item;

		$this->bbcode = new vB_BbCodeParser($registry, fetch_tag_list());
	}

	/**
	* Template method that does all the work to render the item, including processing the template
	*
	* @access public
	*
	* @return	string	Templated note output
	*/
	function construct()
	{
		if ($this->hook_display_start)
		{
			($hook = vBulletinHook::fetch_hook($this->hook_display_start)) ? eval($hook) : false;
		}

		// preparation for display...
		$this->prepare_start();

		if ($this->process_user)
		{
			if ($this->item['userid'])
			{
				$this->process_registered_user();
			}
			else
			{
				$this->process_unregistered_user();
			}
		}

		if ($this->use_avatar)
		{
			fetch_avatar_from_userinfo($this->item, true);
		}

		$this->process_date_status();
		$this->process_display();
		$this->process_text();
		$this->prepare_end();

		// actual display...
		if ($this->template_item_var)
		{
			${$this->template_item_var} = $this->item;
		}

		global $show, $vbphrase, $stylevar;
		global $spacer_open, $spacer_close;
		global $perpage, $pagenumber;

		global $bgclass, $altbgclass;
		exec_switch_bg();

		if ($this->hook_display_complete)
		{
			($hook = vBulletinHook::fetch_hook($this->hook_display_complete)) ? eval($hook) : false;
		}

		eval('$output = "' . fetch_template($this->template) . '";');

		return $output;
	}

	/**
	* Any startup work that needs to be done.
	*
	* @access protected
	*/
	function prepare_start()
	{
		$this->item = array_merge($this->item, convert_bits_to_array($this->item['options'], $this->registry->bf_misc_useroptions));
		$this->item = array_merge($this->item, convert_bits_to_array($this->item['adminoptions'], $this->registry->bf_misc_adminoptions));
	}

	/**
	* Process note as if a registered user posted
	*
	* @access protected
	*/
	function process_registered_user()
	{
		global $show, $vbphrase;

		fetch_musername($this->item);

		$this->item['onlinestatus'] = 0;
		// now decide if we can see the user or not
		if ($this->item['lastactivity'] > (TIMENOW - $this->registry->options['cookietimeout']) AND $this->item['lastvisit'] != $this->item['lastactivity'])
		{
			if ($this->item['invisible'])
			{
				if (($this->registry->userinfo['permissions']['genericpermissions'] & $this->registry->bf_ugp_genericpermissions['canseehidden']) OR $this->item['userid'] == $this->registry->userinfo['userid'])
				{
					// user is online and invisible BUT bbuser can see them
					$this->item['onlinestatus'] = 2;
				}
			}
			else
			{
				// user is online and visible
				$this->item['onlinestatus'] = 1;
			}
		}

		if (!isset($this->factory->perm_cache["{$this->item['userid']}"]))
		{
			$this->factory->perm_cache["{$this->item['userid']}"] = cache_permissions($this->item, false);
		}

		if (   // item doesn't use avatars
			!$this->use_avatar
			OR // no avatar defined for this user
			empty($this->item['avatarurl'])
			OR // visitor doesn't want to see avatars
			($this->registry->userinfo['userid'] > 0 AND !$this->registry->userinfo['showavatars'])
			OR // user has a custom avatar but no permission to display it
			(!$this->item['avatarid'] AND !($this->factory->perm_cache["{$this->item['userid']}"]['genericpermissions'] & $this->registry->bf_ugp_genericpermissions['canuseavatar']) AND !$this->item['adminavatar']) //
		)
		{
			$show['avatar'] = false;
		}
		else
		{
			$show['avatar'] = true;
		}

		$show['emaillink'] = (
			$this->item['showemail'] AND $this->registry->options['displayemails'] AND (
				!$this->registry->options['secureemail'] OR (
					$this->registry->options['secureemail'] AND $this->registry->options['enableemail']
				)
			) AND $this->registry->userinfo['permissions']['genericpermissions'] & $this->registry->bf_ugp_genericpermissions['canemailmember']
		);
		$show['homepage'] = ($this->item['homepage'] != '' AND $this->item['homepage'] != 'http://');
		$show['pmlink'] = ($this->registry->options['enablepms'] AND $this->registry->userinfo['permissions']['pmquota'] AND ($this->registry->userinfo['permissions']['adminpermissions'] & $this->registry->bf_ugp_adminpermissions['cancontrolpanel']
	 					OR ($this->item['receivepm'] AND $this->factory->perm_cache["{$this->userinfo['userid']}"]['pmquota'])
	 				)) ? true : false;
	}

	/**
	* Process note as if an unregistered user posted
	*
	* @access protected
	*/
	function process_unregistered_user()
	{
		$this->item['rank'] = '';
		$this->item['notesperday'] = 0;
		$this->item['displaygroupid'] = 1;
		$this->item['username'] = $this->item['postusername'];
		fetch_musername($this->item);
		$this->item['usertitle'] = $this->registry->usergroupcache['1']['usertitle'];
		$this->item['joindate'] = '';
		$this->item['notes'] = 'n/a';
		$this->item['avatar'] = '';
		$this->item['profile'] = '';
		$this->item['email'] = '';
		$this->item['useremail'] = '';
		$this->item['icqicon'] = '';
		$this->item['aimicon'] = '';
		$this->item['yahooicon'] = '';
		$this->item['msnicon'] = '';
		$this->item['skypeicon'] = '';
		$this->item['homepage'] = '';
		$this->item['findnotes'] = '';
		$this->item['signature'] = '';
		$this->item['reputationdisplay'] = '';
		$this->item['onlinestatus'] = '';
		$this->item['showemail'] = false;
	}

	/**
	* Prepare the text for display
	*
	* @access protected
	*/
	function process_text(){}

	/**
	* Any closing work to be done.
	*
	* @access protected
	*/
	function prepare_end(){}

	/**
	 * Create Human readable Dates and Times
	 *
	 * @access protected
	 */
	function process_date_status()
	{
		if (isset($this->item['dateline']))
		{
			$this->item['date'] = vbdate($this->registry->options['dateformat'], $this->item['dateline'], true);
			$this->item['time'] = vbdate($this->registry->options['timeformat'], $this->item['dateline']);
		}
	}

	/**
	 * Sets up different display variables for the Group Item
	 *
	 * @access protected
	 */
	function process_display(){}

	/**
	 * Sets whether to show moderation details.
	 *
	 * @access public
	 *
	 * @var boolean
	 */
	function show_moderation_tools($show = true)
	{
		$this->show_moderation_tools = $show;
	}

	/**
	 * Sets whether to force inline selection.
	 *
	 * @access public
	 *
	 * @param boolean $force
	 */
	function force_inline_selection($force = true)
	{
		$this->force_inline_selection = $force;
	}

	/**
	 * Sets an alternative template to use
	 *
	 * @param string $template
	 */
	function set_template($template)
	{
		$this->template = $template;
	}
}


/**
* Group Bit Factory.
* Fetches the appropriate class for rendering a group content item's template bit.
*
* @package 		vBulletin
* @copyright 	http://www.vbulletin.com/license.html
*
*/
class vB_Group_Bit_Factory extends vB_Bit_Factory
{
	/**
	 * Item types that the factory supports.
	 *
	 * @access protected
	 * @var array
	 */
	var $types = array('discussion', 'message');

	/**
	 * Class prefix for created collections
	 *
	 * @access protected
	 * @var string
	 */
	var $class_prefix = 'vB_Group_Bit_';

	/**
	 * Information about the social group
	 *
	 * @access protected
	 * @var array mixed
	 */
	var $group;

	// #######################################################################

	/**
	* Create a bit renderer for the specified item.
	*
	* @access public
	*
	* @param	array							item information
	* @return	vB_Group_Bit
	*/
	function &create($item)
	{
		$this->group = $group;

		return parent::create($item);
	}

	/**
	 * Resolves the appropriate class name for the required Bit object
	 *
	 * @param	mixed array $item				Item info array
	 * @return	string
	 */
	function get_class_name($item)
	{
		$class_name = $this->class_prefix . ucfirst($item['type']);

		switch ($item['state'])
		{
			case 'deleted':
				$class_name .= '_Deleted';
				break;

			case 'moderation':
			case 'visible':
			default:
				if (!empty($item['ignored']))
				{
					$class_name .= '_Ignored';
				}
		}

		return $class_name;
	}

	/**
	 * Instantiates the required bit object.
	 *
	 * @access protected
	 * @see vB_Bit
	 *
	 * @param string $class_name				The resolved name of the class to instantate
	 * @param array mixed $item					Information about the item being rendered
	 * @return vB_Bit							The appropriate bit handler
	 */
	function instantiate($class_name, $item)
	{
		return new $class_name($this->registry, $this, $item, $this->group);
	}
}


/**
 * Album bit class.
 *
 * @package 	vBulletin
 * @copyright	http://www.vbulletin.com/license.html
 */
class vB_Bit_Album extends vB_Bit
{
	/**
	* The template that will be used for outputting
	*
	* @access protected
	* @var	string
	*/
	var $template = 'albumbit';

	/**
	 * Hook run at start of template bit
	 *
	 * @access protected
	 * @var string
	 */
	var $hook_display_start = 'albumbit_display_start';

	/**
	 * Hook run at end of template bit
	 *
	 * @access protected
	 * @var string
	 */
	var $hook_display_complete = 'albumbit_display_complete';

	/**
	 * The variable name used in templates for the item
	 *
	 * @access protected
	 * @var string
	 */
	var $template_item_var = 'album';

	// #######################################################################

	/**
	 * Created Human readable Dates and Times
	 *
	 * @access protected
	 */
	function process_date_status()
	{
		$this->item['picturedate'] = vbdate($this->registry->options['dateformat'], $this->item['lastpicturedate'], true);
		$this->item['picturetime'] = vbdate($this->registry->options['timeformat'], $this->item['lastpicturedate']);
	}

	/**
	* Prepare the text for display
	*
	* @access protected
	*/
	function process_text()
	{
		parent::process_text();

		$this->item['description_html'] = nl2br(fetch_word_wrapped_string(fetch_censored_text($this->item['description'])));
		$this->item['title_html'] = fetch_word_wrapped_string(fetch_censored_text($this->item['title']));
	}

	/**
	 * Sets up different display variables for the Group Message
	 *
	 * @access protected
	 */
	function process_display()
	{
		global $show, $vbphrase, $stylevar;

		// Simplify moderation for templating
		$this->item['picturecount'] = vb_number_format($this->item['visible']);

		// Get cover image info
		$this->item['coverthumburl'] = ($this->item['pictureid'] ? fetch_picture_url($this->item, $this->item, true) : '');
		$this->item['coverdimensions'] = ($this->item['thumbnail_width'] ? "width=\"{$this->item['thumbnail_width']}px\" height=\"{$this->item['thumbnail_height']}px\"" : '');

		// Display album type
		if ('private' == $this->item['state'])
		{
			$show['personalalbum'] = true;
			$this->item['albumtype'] = $vbphrase['private_album_paren'];
		}
		else if ('profile' == $this->item['state'])
		{
			$show['personalalbum'] = true;
			$this->item['albumtype'] = $vbphrase['profile_album_paren'];
		}
		else
		{
			$show['personalalbum'] = false;
		}

		// Show moderation details
		if ($this->item['moderation'] AND (can_moderate(0, 'canmoderatepictures') OR $vbulletin->userinfo['userid'] == $this->item['userid']))
		{
			$show['moderated'] = true;
			$this->item['moderatedcount'] = vb_number_format($this->item['moderation']);
		}
		else
		{
			$show['moderated'] = false;
		}
	}
}


/**
 * Social Group Category bit class.
 *
 * @package 	vBulletin
 * @copyright	http://www.vbulletin.com/license.html
 */
class vB_Bit_GroupCategory extends vB_Bit
{
	/**
	* The template that will be used for outputting
	*
	* @access protected
	* @var	string
	*/
	var $template = 'socialgroups_categorylist_bit';

	/**
	 * Hook run at start of template bit
	 *
	 * @access protected
	 * @var string
	 */
	var $hook_display_start = 'categorylist_bit_display_start';

	/**
	 * Hook run at end of template bit
	 *
	 * @access protected
	 * @var string
	 */
	var $hook_display_complete = 'categorylist_bit_display_complete';

	/**
	 * The variable name used in templates for the item
	 *
	 * @access protected
	 * @var string
	 */
	var $template_item_var = 'category';

	/**
	 * Whether to include information about the item user
	 *
	 * @access protected
	 * @var boolean
	 */
	var $process_user = false;

	// #######################################################################

	/**
	* Prepare the text for display
	*
	* @access protected
	*/
	function process_text()
	{
		parent::process_text();

		$this->item['description'] = nl2br(fetch_word_wrapped_string(fetch_censored_text($this->item['description'])));
		$this->item['title'] = fetch_word_wrapped_string(fetch_censored_text($this->item['title']));
	}

	/**
	 * Sets up different display variables for the Group Message
	 *
	 * @access protected
	 */
	function process_display()
	{
		global $show, $vbphrase, $stylevar;

		// Simplify moderation for templating
		$this->item['groups'] = vb_number_format($this->item['groups']);
	}
}


/**
* Group bit class.
* Group bit classes render the template bit for the given group content item.
* Use the group bit factory to resolve and fetch the appropriate bit class for a
* group content item.
* @see vB_Group_Bit_Factory
*
* @package 		vBulletin
* @copyright 	http://www.vbulletin.com/license.html
*
* @abstract
*/
class vB_Group_Bit extends vB_Bit
{
	/**
	* Information about the group this message belongs to
	*
	* @access protected
	* @var	array
	*/
	var $group = array();

	// #######################################################################

	/**
	* Constructor, sets up the object.
	*
	* @access public
	*
	* @param	vB_Registry
	* @param	vB_Group_Bit_Factory
	* @param	array							Item info
	* @param	array							Group info
	*/
	function __construct(&$registry, &$factory, $item, $group)
	{
		parent::__construct($registry, $factory, $item);
		$this->group = $group;
	}

	/**
	* Template method that does all the work to render the item, including processing the template
	*
	* @access public
	*
	* @return	string	Templated note output
	*/
	function construct()
	{
		if (isset($this->group))
		{
			$group = $this->group;
		}

		return parent::construct();
	}

	/**
	* Any startup work that needs to be done.
	*
	* @access protected
	*/
	function prepare_start()
	{
		parent::prepare_start();

		$this->item['checkbox_value'] = 0;
		$this->item['checkbox_value'] += ($this->item['state'] == 'moderation') ? POST_FLAG_INVISIBLE : 0;
		$this->item['checkbox_value'] += ($this->item['state'] == 'deleted') ? POST_FLAG_DELETED : 0;
	}
}


/**
 * Group message bit class.
 *
 * @package 		vBulletin
 * @copyright 	http://www.vbulletin.com/license.html
 */
class vB_Group_Bit_Message extends vB_Group_Bit
{
	/**
	* The template that will be used for outputting
	*
	* @access protected
	* @var	string
	*/
	var $template = 'socialgroups_message';

	/**
	 * Hook run at start of template bit
	 *
	 * @access protected
	 * @var string
	 */
	var $hook_display_start = 'group_messagebit_display_start';

	/**
	 * Hook run at end of template bit
	 *
	 * @access protected
	 * @var string
	 */
	var $hook_display_complete = 'group_messagebit_display_complete';

	/**
	 * Whether to fetch avatar info for the item's author
	 *
	 * @access protected
	 * @var boolean
	 */
	var $use_avatar = true;

	/**
	 * The variable name used in templates for the item
	 */
	var $template_item_var = 'message';

	/**
	 * Sets up different display variables for the Group Message
	 *
	 * @access protected
	 */
	function process_display()
	{
		global $show;

		$this->discussion = fetch_socialdiscussioninfo($this->item['discussionid']);
		$this->group = fetch_socialgroupinfo($this->discussion['groupid']);

		$this->item['is_discussion'] =  ($this->item['gmid'] == $this->discussion['firstpostid']);

		$show['moderation'] = ($this->item['state'] == 'moderation');

		if ($this->show_moderation_tools AND !$this->force_inline_selection)
		{
			if ($this->item['is_discussion'])
			{
				$this->item['inlinemod'] = (
					(
						$this->item['state'] != 'moderation'
						OR fetch_socialgroup_modperm('canmoderatediscussions', $this->group)
					)
					AND
					(
						$this->item['state'] != 'deleted'
						OR fetch_socialgroup_modperm('canundeletediscussions', $this->group)
					)
					AND
					(
						fetch_socialgroup_modperm('canmoderatediscussions')
						OR fetch_socialgroup_modperm('candeletediscussions', $this->group)
						OR fetch_socialgroup_modperm('canremovediscussions', $this->group)
					)
				);
			}
			else
			{
				$this->item['inlinemod'] = (
					(
						$this->item['state'] != 'deleted'
						OR fetch_socialgroup_modperm('canundeletegroupmessages', $this->group)
					)
					AND
					(
						$this->item['state'] != 'moderated'
						OR fetch_socialgroup_modperm('canmoderategroupmessages', $this->group)
					)
					AND
					(
						fetch_socialgroup_modperm('canmoderategroupmessages', $this->group)
						OR fetch_socialgroup_modperm('canundeletegroupmessages', $this->group)
						OR fetch_socialgroup_modperm('canremovegroupmessages', $this->group)
					)
				);
			}
		}
		else
		{
			$this->item['inlinemod'] = $this->force_inline_selection;
		}

		if ($this->show_moderation_tools)
		{
			if ($this->item['is_discussion'])
			{
				$this->item['edit'] = (can_edit_group_discussion($discussion) OR can_edit_group_message($this->item, $this->info['group']));
			}
			else
			{
				$this->item['edit'] = can_edit_group_message($this->item, $this->info['group']);
			}
		}
		else
		{
			$show['edit'] = $this->item['edit'] = false;
		}

		// legacy
		$show['inlinemod'] = $this->item['inlinemod'];
		$show['edit'] = $this->item['edit'];
	}

	// #######################################################################

	/**
	* Prepare the text for display
	*
	* @access protected
	*/
	function process_text()
	{
		$this->item['message'] = $this->bbcode->parse(
			$this->item['pagetext'],
			'socialmessage',
			$this->item['allowsmilie']
		);
		$this->parsed_cache = $this->bbcode->cached;

		if (!empty($this->item['del_reason']))
		{
			$this->item['del_reason'] = fetch_censored_text($this->item['del_reason']);
		}

		$this->item['groupname'] = $this->group['name'];
		$this->item['discussiontitle'] = $this->discussion['title'];
	}

	/**
	* Any closing work to be done.
	*
	* @access protected
	*/
	function prepare_end()
	{
		global $show;

		global $onload, $itemid;

		if (can_moderate(0, 'canviewips'))
		{
			$this->item['itemipaddress'] = ($this->item['itemipaddress'] ? htmlspecialchars_uni(long2ip($this->item['itemipaddress'])) : '');
		}
		else
		{
			$this->item['itemipaddress'] = '';
		}

		$show['reportlink'] = (
			$this->registry->userinfo['userid']
			AND ($this->registry->options['rpforumid'] OR
				($this->registry->options['enableemail'] AND $this->registry->options['rpemail']))
		);
	}
}


/**
* Deleted message bit class.
*
* @package 		vBulletin
* @copyright 	http://www.vbulletin.com/license.html
*
*/
class vB_Group_Bit_Message_Deleted extends vB_Group_Bit_Message
{
	/**
	* The template that will be used for outputting
	*
	* @access protected
	* @var	string
	*/
	var $template = 'socialgroups_message_deleted';

	/**
	 * Whether to fetch avatar info for the item's author
	 *
	 * @access protected
	 * @var boolean
	 */
	var $use_avatar = false;
}


/**
* Ignored message bit class.
*
* @package 		vBulletin
* @copyright 	http://www.vbulletin.com/license.html
*
*/
class vB_Group_Bit_Message_Ignored extends vB_Group_Bit_Message
{
	/**
	* The template that will be used for outputting
	*
	* @access protected
	* @var	string
	*/
	var $template = 'socialgroups_message_ignored';

	/**
	 * Whether to fetch avatar info for the item's author
	 *
	 * @access protected
	 * @var boolean
	 */
	var $use_avatar = false;
}


/**
 * Group discussion bit class.
 *
 * @package 	vBulletin
 * @copyright	http://www.vbulletin.com/license.html
 */
class vB_Group_Bit_Discussion extends vB_Group_Bit
{
	/**
	* The template that will be used for outputting
	*
	* @access protected
	* @var	string
	*/
	var $template = 'socialgroups_discussion';

	/**
	 * Hook run at start of template bit
	 *
	 * @access protected
	 * @var string
	 */
	var $hook_display_start = 'group_discussionbit_display_start';

	/**
	 * Hook run at end of template bit
	 *
	 * @access protected
	 * @var string
	 */
	var $hook_display_complete = 'group_discussionbit_display_complete';

	/**
	 * Whether to check read status of the discussion
	 *
	 * @access protected
	 * @var boolean
	 */
	var $check_read = true;

	/**
	 * Whether to show subscription info
	 *
	 * @access protected
	 * @var boolean
	 */
	var $show_subscription = false;

	/**
	 * The variable name used in templates for the item
	 *
	 * @access protected
	 * @var string
	 */
	var $template_item_var = 'discussion';

	/**
	 * Whether to fetch avatar info for the item's author
	 *
	 * @access protected
	 * @var boolean
	 */
	var $use_avatar = false;

	// #######################################################################

	/**
	* Template method that does all the work to render the item, including processing the template
	*
	* @access public
	*
	* @return	string	Templated note output
	*/
	function construct()
	{
		if (isset($this->discussion))
		{
			$discussion = $this->discussion;
		}

		return parent::construct();
	}

	/**
	 * Created Human readable Dates and Times
	 *
	 * @access protected
	 */
	function process_date_status()
	{
		$this->item['lastpostdate'] = vbdate($this->registry->options['dateformat'], $this->item['lastpost'], true);
		$this->item['lastposttime'] = vbdate($this->registry->options['timeformat'], $this->item['lastpost']);
	}

	/**
	* Prepare the text for display
	*
	* @access protected
	*/
	function process_text()
	{
		parent::process_text();

		$this->item['title'] = fetch_censored_text($this->item['title']);
		$this->item['trimmessage'] = htmlspecialchars_uni(fetch_trimmed_title(fetch_censored_text(strip_bbcode($this->item['pagetext'],false,true)), 100));

		$this->item['groupname'] = $this->group['name'];
	}

	/**
	 * Sets up different display variables for the Group Message
	 *
	 * @access protected
	 */
	function process_display()
	{
		global $show, $vbphrase;

		$this->item['canview'] = ($this->item['state'] == 'visible'
							OR (
								($this->item['state'] == 'deleted')
								AND fetch_socialgroup_modperm('canundeletediscussions', $this->group)
								)
							OR (
								$this->item['state'] == 'moderation'
								AND fetch_socialgroup_modperm('canmoderatediscussions', $this->group)
								)
							);

		// Simplify moderation for templating
		if (fetch_socialgroup_modperm('canmoderategroupmessages', $this->group))
		{
			$this->item['moderated_replies'] = ($this->item['moderation'] > 1 OR ($this->item['state'] != 'moderation' AND $this->item['moderation'] == 1));
		}
		else
		{
			$this->item['moderated_replies'] = 0;
		}
		$this->item['moderated'] = ($this->item['state'] == 'moderation');

		// Show inline selection tools
		if ($this->show_moderation_tools AND !$this->force_inline_selection)
		{
			$this->item['inlinemod'] = (
				(
					$this->item['state'] != 'deleted'
					AND fetch_socialgroup_modperm('canmoderatediscussions', $this->group)
				)
				OR fetch_socialgroup_modperm('canundeletediscussions', $this->group)
				OR fetch_socialgroup_modperm('canremovediscussions', $this->group)
			);

			$show['inlinemod'] = ($show['inlinemod'] OR $this->item['inlinemod']);
		}
		else
		{
			$show['inlinemod'] = $this->item['inlinemod'] = $this->force_inline_selection;
		}

		// Show edit links
		$this->item['edit'] = ($this->show_moderation_tools AND can_edit_group_discussion($this->item, $this->group));
		$show['edit'] = $this->item['edit'];

		if ($this->check_read)
		{
			if (!$this->item['is_read'])
			{
				if (!$this->item['readtime'])
				{
					$this->item['readtime'] = 0;

					// no database marking, check cookie
					if (!$this->registry->options['threadmarking'] OR !$this->registry->userinfo['userid'])
					{
						$this->item['readtime'] = max(
							fetch_bbarray_cookie('discussion_marking', $this->item['discussionid']),
							$this->registry->userinfo['lastvisit']
						);
					}
				}

				// posts older than markinglimit days won't be highlighted as new
				$oldtime = (TIMENOW - ($this->registry->options['markinglimit'] * 24 * 60 * 60));

				$this->item['readtime'] = max($this->group['readtime'], $this->item['readtime'], $oldtime);
				$this->item['is_read'] = ($this->item['readtime'] > $this->item['lastpost']);
			}
		}
		else
		{
			$this->item['is_read'] = true;
		}

		$this->item['replies'] = max(0, ($this->item['visible']-1));

		if ($this->show_subscription)
		{
			$this->item['showsubsinfo'] = $this->show_subscription;
			$this->item['notification'] = ($this->item['emailupdate'] ? $vbphrase['instant'] : $vbphrase['none']);
		}
	}

	/**
	 * Sets whether to check if item is read
	 *
	 * @access public
	 *
	 * @param boolean $check
	 */
	function check_read($check = true)
	{
		$this->check_read = $check;
	}

	/**
	 * Sets whether to show subscription info.
	 *
	 * @access public
	 *
	 * @param boolean $show
	 */
	function show_subscription($show = true)
	{
		$this->show_subscription = $show;
	}
}


/**
* Deleted discussion bit class.
*
* @package 		vBulletin
* @copyright 	http://www.vbulletin.com/license.html
*
*/
class vB_Group_Bit_Discussion_Deleted extends vB_Group_Bit_Discussion
{
	/**
	* The template that will be used for outputting
	*
	* @access protected
	* @var	string
	*/
	var $template = 'socialgroups_discussion_deleted';
}


/**
* Ignored discussion bit class.
*
* @package 		vBulletin
* @copyright 	http://www.vbulletin.com/license.html
*
*/
class vB_Group_Bit_Discussion_Ignored extends vB_Group_Bit_Discussion
{
	/**
	* The template that will be used for outputting
	*
	* @access protected
	* @var	string
	*/
	var $template = 'socialgroups_discussion_ignored';
}

/*======================================================================*\
|| ####################################################################
|| # SVN: $Revision: 30506 $
|| ####################################################################
\*======================================================================*/
?>
