<?php if (!defined('VB_ENTRY')) die('Access denied.');
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 4.0.0 Alpha 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 # ||
|| #################################################################### ||
\*======================================================================*/

/**
 * CMS content class.
 * A node is a data instance of configured cms content.  Each node has a content
 * type and contentid, a layout, style and widgets, as well as permissions and any
 * content agnostic meta types that may be attached to the content, such as tags.
 *
 * @todo Get inherited permissions.
 *
 * @author vBulletin Development Team
 * @version 4.0.0 a5
 * @since 26th Nov, 2008
 * @copyright Jelsoft Enterprises Ltd.
 */
class vBCms_Item_Content extends vB_Item_Content
{
	/*Properties====================================================================*/

	/**
	 * A package identifier.
	 *
	 * @var string
	 */
	protected $package = 'vBCms';

	/**
	 * A class identifier.
	 *
	 * @var string
	 */
	protected $class = 'Content';

	/**
	 * The class name of the most appropriate DM for managing the item's data.
	 *
	 * @var string
	 */
	protected $dm_class = 'vBCms_DM_Node';

	/**
	 * Whether the model info can be cached.
	 *
	 * @var bool
	 */
	protected $cachable = false;



	/*InfoFlags=====================================================================*/

	/**
	 * Flags for required item info.
	 * These are used for $required_info and $loaded_info.
	 *
	 * Note: INFO_CONTENT is a placeholder for child implementations.
	 */
	const INFO_NODE = 2;
	const INFO_DEPTH = 4;
	const INFO_PARENTS = 8;
	const INFO_CONFIG = 16;
	const INFO_CONTENT = 32;
	const INFO_NAVIGATION = 64;

	/**
	 * The total flags for all info.
	 * This would be a constant if we had late static binding.
	 *
	 * @var int
	 */
	protected $INFO_ALL = 127;

	/**
	 * List of dependencies.
	 * If a particular info requires another info to be loaded then you can map them
	 * here.  The array should be in the form array(dependent => dependent on)
	 *
	 * @var array int
	 */
	protected $INFO_DEPENDENCIES = array(
		self::INFO_NAVIGATION => self::INFO_PARENTS
	);

	/**
	 * Query types.
	 */
	const QUERY_PARENTS = 2;
	const QUERY_CONFIG = 3;
	const QUERY_CONTENT = 4;
	const QUERY_NAVIGATION = 5;

	/**
	 * Map of query => info.
	 *
	 * @var array int => int
	 */
	protected $query_info = array(
		self::QUERY_BASIC => /* self::INFO_BASIC | self::INFO_NODE | self::INFO_DEPTH */ 7,
		self::QUERY_PARENTS => self::INFO_PARENTS,
		self::QUERY_CONFIG => self::INFO_CONFIG,
		self::QUERY_CONTENT => self::INFO_CONTENT,
		self::QUERY_NAVIGATION => self::INFO_NAVIGATION
	);



	/*ModelProperties===============================================================*/

	/**
	 * Extra item properties.
	 * These are merged with $item_properties on construction, providing a simple
	 * way for children to extend the model properties without duplicating or
	 * destroying the common properties.
	 *
	 * @var array
	 */
	protected $content_properties = array();

	/**
	 * Node model properties.
	 *
	 * @var array string
	 */
	protected $item_properties = array(
		/*INFO_BASIC==================*/
		'nodeid',			'isroot',			'contentid',
		'contenttypeid',	'url',				'userid',
		'parentnode',		'layoutid', 		'styleid',
		'publishdate',		'setpublish',		'issection',
		'permissionsfrom', 'parentpermissions',
		'publicpreview', 'comments_enabled', 'nodeleft',

		/*INFO_DEPTH==================*/
		'depth',

		/*INFO_NODE===================*/
		'username',			'title', 	'html_title',	'description',
		'creationdate',		'viewcount',			'workflowlevelid',
		'workflowstatus',	'workflowdate',			'workflowcheckedout', 'associatedthreadid',
		'replycount', 'keywords',
		/**  Comes from cms_sectionorder**/
		'displayorder',

		/*INFO_NAVIGATION=============*/
		'navigation_nodes'
	);

	/*INFO_BASIC==================*/

	/**
	 * The id of the node.
	 *
	 * @var int
	 */
	protected $nodeid;

	/**
	 * Whether the node is the root node.
	 *
	 * @var bool
	 */
	protected $isroot;

	/**
	 * The id of the resolved content.
	 * This is interpreted by the content type handler and may even be null.
	 *
	 * @var int
	 */
	protected $contentid;

	/**
	 * The id of the resolved contenttype.
	 *
	 * @var int
	 */
	protected $contenttypeid;

	/**
	 * The url segment name for the node.
	 *
	 * @var string
	 */
	protected $url;

	/**
	 * The id of the parent node
	 *
	 * @var int
	 */
	protected $parentnode;

	/**
	 * The layout id for the node.
	 * This may be resolved from a parent node.
	 *
	 * @var int
	 */
	protected $layoutid;

	/**
	 * The style id for the node.
	 * This may be resolved from a parent node.
	 *
	 * @var int
	 */
	protected $styleid;

	/**
	 * Whether the node is an aggregator.
	 *
	 * @var bool
	 */
	protected $issection;

	/**
	 * Username of the user who created the content.
	 *
	 * @var string
	 */
	protected $username;

	/**
	 * The public publish date of the content.
	 * This may not necessarily reflect the actual date that the content was first
	 * created; depending on how the content is handled.
	 *
	 * @var int
	 */
	protected $publishdate;

	/**
	 * Whether a publish date has been set.
	 * If this is false the the publishdate only represents the creation date of
	 * the content.
	 *
	 * @var bool
	 */
	protected $setpublish;

	/*INFO_DEPTH====================*/

	/**
	 * The depth of the content in the node tree.
	 *
	 * @var int
	 */
	protected $depth;

	/*INFO_NODE=====================*/

	/**
	 * The date the node was created.
	 *
	 * @var int
	 */
	protected $creationdate;

	/**
	 * The number of times the node has been viewed post publish.
	 *
	 * @var int
	 */
	protected $viewcount;

	/**
	 * The current workflow level.
	 * Only users with permission to this level for this node may modify the node.
	 *
	 * @var int
	 */
	protected $workflowlevelid;

	/**
	 * The current workflow status.
	 *
	 * @var int
	 */
	protected $workflowstatus;

	/**
	 * The date of the last workflow status change.
	 *
	 * @var int
	 */
	protected $workflowdate;

	/**
	 * Whether the node is checked out for editing.
	 *
	 * @var int
	 */
	protected $workflowcheckedout;

	/**
	 * Whether the threadid for associated discussion.
	 *
	 * @var int
	 */
	protected $associatedthreadid;

	/* Is this publicly viewable */
	protected $publicCanView;

	/**
	 * Array of nodeids that make up the navigation menu.
	 *
	 * @var array int
	 */
	protected $navigation_nodes;



	/*ClassProperties===============================================================*/

	/**
	 * Unpublished parent.
	 * If any of the node's parent's are not published, this is the nearest
	 * unpublished parent.
	 */
	protected $pending_parent;

	/**
	 * The node's layout.
	 *
	 * @var vBCms_Item_Layout
	 */
	protected $layout;

	/**
	 * The layout setting for this specific node.
	 * This is the chosen layout before inheritance.  This is useful when
	 * configuring the node to get whether the layout is currently inherited or not.
	 *
	 * @var int
	 */
	protected $node_layout;

	/**
	 * The style setting for this specific node.
	 * This is the chosen style before inheritance.  This is useful when
	 * configuring the node to get whether the style is currently inherited or not.
	 *
	 * @var int
	 */
	protected $node_styleid;

	/**
	 * Info about the node's parent nodes.
	 *
	 * @var array mixed
	 */
	protected $parents;

	/**
	 * Config for the node.
	 *
	 * @var array cvar => value
	 */
	protected $config;

	protected $userid;

	protected $permissionsfrom;

	protected $parentpermissions;

	public $publicpreview;

	protected $replycount;

	protected $displayorder;

	protected $comments_enabled;

	protected $keywords;

	protected $pagetitle;

	protected $title;

	protected $html_title;

	protected $parenttitle;

	protected $nodeleft;

	/**
	 * Whether this node has it's own navigation menu.
	 *
	 * @var bool
	 */
	protected $navigation_ownmenu;

	/**
	 * The nearest parent with a navigation menu.
	 *
	 * @var int
	 */
	protected $navigation_parentid;

	/**
	 * The title of the nearest parent with a navigation menu.
	 *
	 * @var string
	 */
	protected $navigation_parenttitle;



	/*Initialisation================================================================*/


	/**
	 * Sets the itemid of the item to be loaded.
	 * The node id can be the integer id, the x_url path segment or an
	 * array('contenttypeid' => int, 'contentid' => int).
	 *
	 * @param mixed $itemid							- The id of the node or content
	 */
	public function setItemId($itemid = false)
	{
		// Allow the item id to be set as contenttypeid, contentid info
		if (is_array($itemid))
		{
			if (!isset($itemid['contenttypeid']) OR !isset($itemid['contentid']))
			{
				$this->is_valid = false;
				return;
			}

			$this->contenttypeid = $itemid['contenttypeid'];
			$this->contentid = $itemid['contentid'];
			$this->itemid = false;
		}
		else if (!intval($itemid))
		{
			$this->url = $itemid;
			$this->itemid = false;
		}
		else
		{
			$this->itemid = $this->nodeid = intval($itemid);
		}
	}



	/*Initialisation================================================================*/

	/**
	 * Constructs the content item.
	 * The id passed will usually be the primary key of the model data in the
	 * database but as this is model specific it can be interpreted in other ways.
	 *
	 * @param mixed $itemid					- The id of the item
	 * @param int $load_flags				- Any required info prenotification
	 */
	public function __construct($itemid = false, $load_flags = false)
	{
		$this->item_properties = array_merge($this->item_properties, $this->content_properties);

		parent::__construct($itemid, $load_flags);
	}

	/*LoadInfo======================================================================*/

	/**
	 * Applies the result of the load query.
	 *
	 * @param resource $result					- The db result resource
	 * @param int $load_query					- The query that the result is from
	 */
	protected function applyLoad($result, $load_query)
	{
		if (self::QUERY_PARENTS == $load_query)
		{
			$parents = array();
			if (vB::$db->num_rows($result))
			{
				while ($parent = vB::$db->fetch_array($result))
				{
					$parents[$parent['nodeid']] = $parent;
				}
			}

			$this->setParentsArray($parents);

			return true;
		}
		else if (self::QUERY_CONFIG == $load_query)
		{
			$config = array();

			while ($cvar = vB::$db->fetch_array($result))
			{
				$config[$cvar['name']] = $cvar['value'];
			}

			$this->config = $config;

			$this->loaded_info = ($this->loaded_info | self::INFO_CONFIG);

			return true;
		}
		else if (self::QUERY_NAVIGATION == $load_query)
		{
			while ($nodes = vB::$db->fetch_array($result))
			{
				if ($nodes['nodeid'] != $this->nodeid)
				{
					$this->navigation_parentid = $nodes['nodeid'];
					$this->navigation_parenttitle = $nodes['title'];
				}
				else
				{
					$this->navigation_ownmenu = true;
				}

				if (!$this->navigation_nodes)
				{
					$this->navigation_nodes = explode(',', $nodes['nodelist']);
				}

				foreach ($this->navigation_nodes AS $key => $nodeid)
				{
					if (!is_numeric($nodeid))
					{
						unset($this->navigation_nodes[$key]);
					}
				}
			}

			$this->loaded_info = ($this->loaded_info | self::INFO_NAVIGATION);

			return true;
		}

		$result = parent::applyLoad($result, $load_query);

		// Keep a copy of the actual node layout and style before inheritance
		if (self::QUERY_BASIC == $load_query)
		{
			$this->node_layout = $this->layoutid;
			$this->node_style = $this->styleid;
		}

		return $result;
	}


	/**
	 * Copies info from this object to another of the same type.
	 * This is usefull when using a generic collection class that used a parent type
	 * to fetch the items.
	 *
	 * @param vBCms_Item_Content $target
	 */
	public function castInfo(vBCms_Item_Content $target)
	{
		parent::castInfo($target);

		$target->setConfig($this->config);
		$target->setParents($this->parents);
	}


	/**
	 * Fetches the SQL for loading.
	 * $required_query is used to identify which query to build for classes that
	 * have multiple queries for fetching info.
	 *
	 * This can safely be based on $this->required_info as long as a consistant
	 * flag is used for identifying the query.
	 *
	 * @param int $required_query				- The required query
	 *
	 * @return string
	 */
	protected function getLoadQuery($required_query)
	{
		// Hooks should check the required query before populating the hook vars
		$hook_query_fields = $hook_query_joins = $hook_query_where = '';
		($hook = vBulletinHook::fetch_hook($this->query_hook)) ? eval($hook) : false;

		// Internal hooks for loading content with QUERY_BASIC
		$content_query_fields = $content_query_joins = $content_query_where = '';
		if ($this->requireLoad(vBCms_Item_Content::INFO_CONTENT))
		{
			$content_query_fields = $this->getContentQueryFields();
			$content_query_joins = $this->getContentQueryJoins();
			$content_query_where = $this->getContentQueryWhere();
		}

		if (self::QUERY_BASIC == intval($required_query))
		{
			$sql =
				"SELECT node.nodeid " .
				($this->requireLoad(self::INFO_BASIC) ?
				", (node.nodeleft = 1) AS isroot, node.contenttypeid, node.contentid, node.url, node.parentnode,
				(CASE WHEN node.contenttypeid = " . vb_Types::instance()->getContentTypeID("vBCms_Section") .
				" THEN node.styleid ELSE parent.styleid end ) AS styleid, node.userid,
					(CASE WHEN node.contenttypeid = " . vb_Types::instance()->getContentTypeID("vBCms_Section") .
					" THEN node.layoutid ELSE parent.layoutid end ) AS layoutid,
					node.publishdate, node.setpublish, node.issection, node.permissionsfrom, node.nodeleft,
					node.userid, parent.permissionsfrom as parentpermissions, node.publicpreview, node.comments_enabled " : '') .
				($this->requireLoad(self::INFO_NODE) ?
					", info.description, info.title, info.html_title, info.viewcount, info.creationdate, info.workflowdate, info.keywords,
					info.workflowstatus, info.workflowcheckedout, info.workflowlevelid, info.associatedthreadid,
					user.username, thread.replycount, sectionorder.displayorder " : '') .
				($this->requireLoad(self::INFO_DEPTH) ?
					", (COUNT(pdepth.nodeid) - 1) AS depth" : '') . "
					$hook_query_fields
				FROM " . TABLE_PREFIX . "cms_node AS node" .
				($this->requireLoad(self::INFO_NODE) ? "
				INNER JOIN " . TABLE_PREFIX . "cms_nodeinfo AS info ON info.nodeid = node.nodeid
				LEFT JOIN " . TABLE_PREFIX . "user AS user ON user.userid = node.userid
				LEFT JOIN " . TABLE_PREFIX . "thread AS thread ON thread.threadid = info.associatedthreadid
				LEFT JOIN " . TABLE_PREFIX . "cms_sectionorder AS sectionorder ON sectionorder.sectionid = node.parentnode
					AND sectionorder.nodeid = node.nodeid"
				: '')
				. ($this->requireLoad(self::INFO_BASIC) ? "
				LEFT JOIN " . TABLE_PREFIX . "cms_node AS parent ON parent.nodeid = node.parentnode " : '') .
				($this->requireLoad(self::INFO_DEPTH) ?
				" LEFT JOIN " . TABLE_PREFIX . "cms_node AS pdepth ON node.nodeleft BETWEEN pdepth.nodeleft AND pdepth.noderight " : '') . "
				$hook_query_joins
				WHERE ";

				if (is_numeric($this->itemid))
				{
					$sql .= 'node.nodeid = ' . intval($this->itemid);
				}
				else if (is_numeric($this->nodeid))
				{
					$sql .= 'node.nodeid = ' . intval($this->nodeid);
				}
				else if ($this->contenttypeid AND $this->contentid)
				{
					$sql .= 'node.contenttypeid = ' . intval($this->contenttypeid) . ' AND node.contentid = ' . intval($this->contentid);
				}

				$sql .=
					' ' . $hook_query_where .
					($this->requireLoad(self::INFO_DEPTH) ?
						" GROUP BY node.nodeid" : '');

				//If we don't have some actual content, return an empty string;
				if (strlen($sql) < 100)
				{
					return false;
				}

				return $sql;
		}
		else if (self::QUERY_PARENTS == $required_query)
		{
			return
				"SELECT parent.nodeid, parent.url, parent.publishdate, parent.setpublish, parent.issection,
						info.title, info.html_title, info.description, node.nodeleft
				$hook_query_fields
				FROM " . TABLE_PREFIX . "cms_node AS node
				INNER JOIN " . TABLE_PREFIX . "cms_node AS parent ON node.nodeleft BETWEEN parent.nodeleft AND parent.noderight
				INNER JOIN " . TABLE_PREFIX . "cms_nodeinfo AS info ON info.nodeid = parent.nodeid" .
				$hook_query_joins . "
				WHERE node.nodeid = " . intval($this->itemid) . "
				AND parent.nodeid != node.nodeid
				$hook_query_where
				ORDER BY parent.nodeleft"
			;
		}
		else if (self::QUERY_CONFIG == $required_query)
		{
			return
				"SELECT nodeconfig.name, nodeconfig.value, nodeconfig.serialized
				$hook_query_fields
				FROM " . TABLE_PREFIX . "cms_nodeconfig AS nodeconfig
				$hook_query_joins
				WHERE nodeid = " . intval($this->nodeid) . "
				$hook_query_where
			";
		}
		else if (self::QUERY_NAVIGATION == $required_query)
		{
			$source_nodes = intval($this->itemid) . (!empty($this->parents) ? ',' . implode(',', array_keys($this->parents)) : '');

			return
				"SELECT navigation.nodeid, navigation.nodelist, nodeinfo.title
				$hook_query_fields
				FROM " . TABLE_PREFIX . "cms_navigation AS navigation
				LEFT JOIN " . TABLE_PREFIX . "cms_nodeinfo AS nodeinfo ON nodeinfo.nodeid = navigation.nodeid
				$hook_query_joins
				WHERE navigation.nodeid IN ($source_nodes)
				ORDER BY navigation.nodeid DESC
				LIMIT 0, 2";
		}

		throw (new vB_Exception_Model('Invalid query id \'' . htmlspecialchars($required_query) . '\' specified for node item: ' . htmlspecialchars($query)));
	}


	/**
	 * Sets parent info from an array of assoc arrays.
	 * The assoc arrays should have the following keys:
	 * 	nodeid, url, styleid, layoutid, publishdate, setpublish, title, html_title description
	 *
	 * @param array mixed $parents
	 */
	protected function setParentsArray($parents)
	{
		if (sizeof($parents))
		{
			ksort($parents);

			foreach ($parents AS $parent)
			{
				// get nearest parent
				$this->parent = $parent['nodeid'];

				// get nearest unpublished parent
				if (!$parent['publishdate'] OR !$parent['setpublish'] OR ($parent['publishdate'] > TIMENOW))
				{
					$this->pending_parent = $parent['nodeid'];
				}

			}
		}

		// TODO: use a collection?
		$this->parents = $parents;

		$this->loaded_info = ($this->loaded_info | self::INFO_PARENTS);
	}


	/**
	 * Fetches additional fields for querying INFO_CONTENT in QUERY_BASIC.
	 * Note: Child classes may provide a seperate query for INFO_CONTENT.  In that
	 * case, this does not need to be redefined.
	 *
	 * @return string
	 */
	protected function getContentQueryFields()
	{
		return '';
	}


	/**
	 * Fetches additional join for querying INFO_CONTENT in QUERY_BASIC.
	 * Note: Child classes may provide a seperate query for INFO_CONTENT.  In that
	 * case, this does not need to be redefined.
	 *
	 * @return string
	 */
	protected function getContentQueryJoins()
	{
		return '';
	}


	/**
	 * Fetches additional conditions for querying INFO_CONTENT in QUERY_BASIC.
	 * Note: Child classes may provide a seperate query for INFO_CONTENT.  In that
	 * case, this does not need to be redefined.
	 *
	 * @return string
	 */
	protected function getContentQueryWhere()
	{
		return '';
	}



	/*Cache=========================================================================*/

	/**
	 * Saves non item properties as cachable info.
	 *
	 * @return array mixed $info				- The modified info array to cache
	 */
	protected function saveCacheInfo()
	{
		$info = parent::saveCacheInfo();

		if ($this->pending_parent)
		{
			$info['pending_parent'] = $this->pending_parent;
		}

		if ($this->node_layout)
		{
			$info['node_layout'] = $this->node_layout;
		}

		if ($this->node_styleid)
		{
			$info['node_styleid'] = $this->node_styleid;
		}

		if ($this->parents)
		{
			$info['parents'] = serialize($this->parents);
		}

		if ($this->config)
		{
			$info['config'] = serialize($this->config);
		}

		return $info;
	}


	/**
	 * Loads non item properties from a cache hit.
	 *
	 * @param mixed $info						- The info loaded from the cache
	 */
	protected function loadCacheInfo($info)
	{
		parent::loadCacheInfo($info);

		if (isset($info['pending_parent']))
		{
			$this->pending_parent = $info['pending_parent'];
		}

		if (isset($info['node_layout']))
		{
			$this->node_layout = $info['node_layout'];
		}

		if (isset($info['node_styleid']))
		{
			$this->node_styleid = $info['node_styleid'];
		}

		if (isset($info['parents']))
		{
			$this->parents = unserialize($info['parents']);
		}

		if (isset($info['config']))
		{
			$this->config = unserialize($info['config']);
		}

		return true;
	}



	/*Accessors=====================================================================*/

	/**
	 * Returns the content id
	 *
	 * @return int
	 */
	public function getId()
	{
		$this->Load();

		return $this->contentid;
	}

	/**
	 * Returns the keywords
	 *
	 * @return int
	 */
	public function getKeywords()
	{
		$this->Load();

		return $this->keywords;
	}


	/**
	 * Returns whether this node is the root.
	 *
	 * @return bool
	 */
	public function isRoot()
	{
		$this->Load();

		return $this->isroot;
	}


	/**
	 * Returns the node id
	 *
	 * @return int
	 */
	public function getNodeId()
	{
		$this->Load();

		return $this->nodeid;
	}

	/**
	 * Returns the node id
	 *
	 * @return int
	 */
	public function getNodeLeft()
	{
		$this->Load();

		return $this->nodeleft;
	}

	/**
	 * Returns the username of the user that created the content.
	 *
	 * @return string
	 */
	public function getUsername()
	{
		$this->Load(self::INFO_NODE);

		return $this->username;
	}

	/**
	 * Returns the id of the user that created the content.
	 *
	 * @return string
	 */
	public function getUserId()
	{
		$this->Load(self::INFO_NODE);

		return $this->userid;
	}


	/**
	 * Returns the description for the node.
	 *
	 * @return string
	 */
	public function getDescription()
	{
		$this->Load(self::INFO_NODE);
		return $this->description;
	}


	/**
	 * Returns whether the node is a section or not.
	 *
	 * @return bool
	 */
	public function isSection()
	{
		return $this->issection;
	}


	/**
	 * Fetches breadcrumb info.
	 *
	 * @return array mixed
	 */
	public function getBreadcrumbInfo()
	{
		// Ensure parent info is loaded
		$this->Load(self::INFO_PARENTS);

		$breadcrumbs = array();

		if ($this->parents)
		{
			$nodes = $this->parents;

			// don't include the root node in the breadcrumb
			array_shift($nodes);

			foreach ($nodes AS $node)
			{
				// TODO: use breadcrumb item?
				$breadcrumb = array();
				$breadcrumb['title'] = $node['title'];
				$breadcrumb['description'] = $node['description'];
				$breadcrumb['nodeid'] = $node['nodeid'];
				$breadcrumb['url'] = $node['url'];
				$breadcrumb['link'] = vBCms_Route_Content::getURL(array('node' => $node['nodeid'] . '-' . $node['url']));
				$breadcrumbs[] = $breadcrumb;
			}
		}
		if ($this->contenttypeid == vb_Types::instance()->getContentTypeID("vBCms_Section") AND !$this->isRoot())
		{
			$breadcrumb = array();
			$breadcrumb['title'] = $this->title;
			$breadcrumb['description'] = $this->description;
			$breadcrumb['nodeid'] = $this->nodeid;
			$breadcrumb['url'] = $this->url;
			$breadcrumb['link'] = vBCms_Route_Content::getURL(array('node' => $this->nodeid . '-' . $this->url));
			$breadcrumbs[] = $breadcrumb;
		}

		return $breadcrumbs;
	}


	/**
	 * Returns the nearest parent.
	 *
	 * @return array mixed
	 */
	public function getParentId()
	{
		return $this->parentnode;
	}


	/**
	 * Returns the title of the parent node.
	 *
	 * @return string
	 */
	public function getParentTitle()
	{
		if ($this->parentnode)
		{
			$this->Load(self::INFO_PARENTS);

			return $this->parents[$this->parentnode]['title'];
		}

		return false;
	}


	/**
	 * Returns the url segment of the parent node.
	 *
	 * @return string
	 */
	public function getParentURLSegment()
	{
		if ($this->parentnode)
		{
			$this->Load(self::INFO_PARENTS);

			return $this->parents[$this->parentnode]['url'] ? ($this->parentnode . '-' . $this->parents[$this->parentnode]['url']) : '';
		}
	}


	/**
	 * Returns the id of the section node.
	 * This may be this node if it is a section, or it's parent.
	 *
	 * @return int
	 */
	public function getSectionId()
	{
		return (1 == $this->itemid OR $this->isSection()) ? $this->itemid : $this->parentnode;
	}


	/**
	 * Returns the name of the nearest section that the node is in.
	 *
	 * @return string
	 */
	public function getSectionTitle()
	{
		return new vB_Phrase('vbcms', 'creating_page_in_x', ((1 == $this->itemid OR $this->isSection()) ?
															$this->getTitle() : $this->getParentTitle()));
	}

	/**
	 * Gets the url segment of the section that the content belongs to.
	 *
	 * @return string
	 */
	public function getSectionSegment()
	{
		if ($this->parentnode)
		{
			$this->Load(self::INFO_PARENTS);

			return $this->parents[$this->parentnode]['url'];
		}

		return $this->url;
	}


	/**
	 * Fetches the layout.
	 *
	 * @return vBCms_Item_Layout
	 */
	public function getLayout()
	{
		//Layouts are assigned for sections only.
		if (!$this->layout)
		{
			// ensure parent info is loaded
			$this->Load(self::INFO_PARENTS);

			//See if we have a layoutid.

			$this->layout = new vBCms_Item_Layout($this->getLayoutId());
			$this->layout->requireInfo(vBCms_Item_Layout::INFO_CONFIG | vBCms_Item_Layout::INFO_WIDGETS);

			if (!$this->layout->isValid())
			{
				throw (new vB_Exception_Model('Layout item object not valid for node item'));
			}
		}

		return $this->layout;
	}
	/** fetches the layout id **/
	public function getLayoutId()
	{
		$this->Load();
		if (!$this->layoutid)
		{
			//If our parent doesn't have the style defined, we need to go up the chain until we find one.
			if (! ($record = vB::$vbulletin->db->query_first("SELECT layoutid FROM " . TABLE_PREFIX . "cms_node AS node
					WHERE (" .
				$this->nodeleft . " BETWEEN node.nodeleft AND node.noderight) AND layoutid > 0 ORDER BY nodeleft DESC LIMIT 1" ))
				or
				(! intval($record['layoutid'])))
			{
				//There appears to be nothing defined. All we can do is pull the first record.
				$record = vB::$vbulletin->db->query_first("SELECT layoutid FROM " . TABLE_PREFIX . "cms_layout LIMIT 1" );
			}

			$this->layoutid = $record['layoutid'];
		}
		return $this->layoutid;
	}

	/** fetches the replycount  **/
	public function getReplyCount()
	{
		$this->Load();
		return $this->replycount;
	}



	/**
	 * Fetches the specific layout setting for this node.
	 * If this node is inheriting the layout then this should return false. To get
	 * the actual layout that will be used by this node, @see vBCms_Item_Content::getLayout()
	 *
	 * @return int | false
	 */
	public function getLayoutSetting()
	{
		$this->Load();

		return $this->node_layout ? $this->node_layout : false;
	}


	/**
	 * Fetches the styleid
	 *
	 * @return int
	 */
	public function getStyleId()
	{
		// Ensure parent info is loaded
		$this->Load(self::INFO_PARENTS);
		if (!$this->styleid)
		{
			//If our parent doesn't have the style defined, we need to go up the chain until we find one.
			if (! ($record = vB::$vbulletin->db->query_first("SELECT styleid FROM " . TABLE_PREFIX . "cms_node AS node
					WHERE (" .
				$this->nodeleft . " BETWEEN node.nodeleft AND node.noderight) AND styleid > 0 ORDER BY nodeleft DESC LIMIT 1" ))
				or
				(! intval($record['styleid'])))
			{
				//There appears to be nothing defined. All we can do is pull the first record.
				$record = vB::$vbulletin->db->query_first("SELECT styleid FROM " . TABLE_PREFIX . "style LIMIT 1" );
			}

			$this->styleid = $record['styleid'];
		}

		return $this->styleid;
	}


	/**
	 * Fetches the specific style setting for this node.
	 * If this node is inheriting the style then this should return false. To get
	 * the actual layout that will be used by this node, @see vBCms_Item_Content::getLayout()
	 *
	 * @return int | false
	 */
	public function getStyleSetting()
	{
		$this->Load();

		return $this->node_style ? $this->node_style : false;
	}


	/**
	 * Returns the publish dateline of the content.
	 *
	 * @return int
	 */
	public function getPublishDate()
	{
		$this->Load();

		return $this->setpublish ? $this->publishdate : false;
	}

	/**
	 * Returns the 'setpublish' status.
	 *
	 * @return int
	 */
	public function getSetPublish()
	{
		$this->Load();

		return $this->setpublish ? $this->publishdate : false;
	}


	/**
	 * Returns the 'setpublish' status.
	 *
	 * @return int
	 */
	public function getDisplayOrder($sectionid)
	{
		$this->Load();

		if (isset($this->displayorder) AND ($sectionid == $this->parentnode))
		{
			return $this->displayorder ;
		}
		$record = vB::$vbulletin->db->query_first($sql = "SELECT displayorder FROM " . TABLE_PREFIX .
			"cms_sectionorder WHERE nodeid = " . $this->nodeid . " AND sectionid = $sectionid");

		return $record['displayorder'];
	}


	/**
	 * Returns the page title- the HTML page header info
	 *
	 * @return int
	 */
	public function getTitle()
	{
		$this->Load(self::INFO_NODE);

		return $this->title ;
	}

	/**
	 * Returns the page title- the HTML page header info
	 *
	 * @return int
	 */
	public function getHtmlTitle()
	{
		$this->Load();

		return $this->html_title ;
	}

	/**
	 * Returns whether the content is published.
	 *
	 * @return bool
	 */
	public function isPublished()
	{
		$this->Load(self::INFO_PARENTS);

		return (!$this->pending_parent AND ($this->setpublish AND $this->publishdate AND ($this->publishdate <= TIMENOW)));
	}

	/**
	 * Returns the public preview flag of the content.
	 *
	 * @return int
	 */
	public function getPublicPreview()
	{
		$this->Load();

		return $this->publicpreview;
	}

	/**
	 * Returns the publish dateline of the content.
	 *
	 * @return int
	 */
	public function getViewcount()
	{
		$this->Load();

		return $this->viewcount;
	}


	/**
	 * Returns the id of the nearest unpublished parent.
	 *
	 * @return bool
	 */
	public function getPendingParentId()
	{
		$this->Load(self::INFO_PARENTS);

		return $this->pending_parent;
	}


	/**
	 * Returns the title of the nearest unpublished parent.
	 *
	 * @return string
	 */
	public function getPendingParentTitle()
	{
		$this->Load(self::INFO_PARENTS);

		if ($pending_id = $this->getPendingParentId())
		{
			return $this->parents[$pending_id]['title'];
		}

		return false;
	}


	/**
	 * Returns the navigation menu nodes.
	 *
	 * @return array int
	 */
	public function getNavigationNodes()
	{
		$this->Load(self::INFO_NAVIGATION);

		return $this->navigation_nodes;
	}


	/**
	 * Gets whether this node has it's own menu.
	 *
	 * @return bool
	 */
	public function hasNavigation()
	{
		$this->Load(self::INFO_NAVIGATION);

		return $this->navigation_ownmenu;
	}


	/**
	 * Returns the nodeid of the node that the navigation is loaded from.
	 *
	 * @return int
	 */
	public function getNavigationParentId()
	{
		$this->Load(self::INFO_NAVIGATION);

		return $this->navigation_parentid;
	}


	/**
	 * Returns the title of the node that the navigation is loaded from.
	 *
	 * @return string
	 */
	public function getNavigationParentTitle()
	{
		$this->Load(self::INFO_NAVIGATION);

		return $this->navigation_parenttitle;
	}


	/**
	 * Fetches the contentid.
	 * How this is interpreted is up to the content handler for the contenttype.
	 *
	 * @return int
	 */
	public function getContentId()
	{
		//For sections, and other types in the future, we have no separate contentid, just a nodeid
		$this->Load();
		return ($this->contentid >0 ? $this->contentid : $this->nodeid) ;
	}


	/**
	 * Fetches the id of the content.
	 *
	 * @return int
	 */
	public function getContentTypeId()
	{
		$this->Load();

		return $this->contenttypeid;
	}

	/**
	 * Fetches the depth of the content in the node tree.
	 *
	 * @return int
	 */
	public function getDepth()
	{
		$this->Load(self::INFO_DEPTH);

		return $this->depth;
	}


	/******
	* Can this user view this item?
	* return boolean
	******/
	public function canView()
	{
		//This user can view if either they are the creator,
		// or they have view rights for this content and this is published,
		// or they have edit rights or publish rights;

		$this->loadInfo();
		if (vB::$vbulletin->userinfo['userid'] == $this->userid)
		{
			return true;
		}

		if (! isset(vB::$vbulletin->userinfo['permissions']['cms']))
		{
			require_once DIR . '/packages/vbcms/permissions.php';
			vBCMS_Permissions::getUserPerms();
		}
		//If it's a section, we check here. If not, we get permissions from its parent;
		$permsForNode = (vb_Types::instance()->getContentTypeID("vBCms_Section") == $this->getContentTypeId() ?
			$this->permissionsfrom : $this->parentpermissions);

		//Is this published and viewable by this user?
		if ($this->setpublish AND $this->publishdate <= TIMENOW
			and in_array($permsForNode, vB::$vbulletin->userinfo['permissions']['cms']['canview']))
		{
			return true;
		}
		//does this user have edit rights?
		return (	in_array($permsForNode, vB::$vbulletin->userinfo['permissions']['cms']['canedit']) or
			in_array($permsForNode, vB::$vbulletin->userinfo['permissions']['cms']['canpublish']) );

	}

	/******
	 * Can a non-logged-in view this item?
	 * return boolean
	 ******/
	public function publicCanView()
	{
		//The public, i.e. non-logged-in, group is usergroupid 1
		$this->loadInfo();

		if (!isset($this->publicCanView))
		{
			//If it's a section, we check here. If not, we get permissions from its parent;
			$permsForNode = (vb_Types::instance()->getContentTypeID("vBCms_Section") == $this->contenttypeid ?
				$this->permissionsfrom : $this->parentpermissions);

			//We need to do a query here
			if ($record = vB::$vbulletin->db->query_first("SELECT permissionid FROM " .TABLE_PREFIX .
				"cms_permissions WHERE nodeid = " . $this->nodeid . " AND usergroupid =1"));
			{
				$this->publicCanView = (isset($record) AND intval($record['permissionsid']));
			}

		}

		return $this->publicCanView;
	}

	/******
	 * Can this user edit this item?
	 * return boolean
	 ******/
	public function canEdit()
	{
		//This user can edit if either they are the creator,
		// or they have edit rights for this content;
		$this->loadInfo();

		if (vB::$vbulletin->userinfo['userid'] == $this->userid)
		{
			return true;
		}

		if (! isset(vB::$vbulletin->userinfo['permissions']['cms']))
		{
			require_once DIR . '/packages/vbcms/permissions.php';
			vBCMS_Permissions::getUserPerms();
		}
		return (vb_Types::instance()->getContentTypeID("vBCms_Section") == $this->getContentTypeId() ?
			in_array($this->permissionsfrom, vB::$vbulletin->userinfo['permissions']['cms']['alledit']) :
			in_array($this->parentpermissions, vB::$vbulletin->userinfo['permissions']['cms']['alledit'])
		 );
	}

	/******
	 * Can this user create an item here?
	 * return boolean
	 ******/
	public function canCreate()
	{
		//This user can create content if this is a section
		// and they have create rights here;
		$this->loadInfo();
		if (!vb_Types::instance()->getContentTypeID("vBCms_Section") == $this->contenttypeid)
		{
			return false;
		}
		if (! isset(vB::$vbulletin->userinfo['permissions']['cms']))
		{
			require_once DIR . '/packages/vbcms/permissions.php';
			vBCMS_Permissions::getUserPerms();
		}

		return in_array($this->permissionsfrom, vB::$vbulletin->userinfo['permissions']['cms']['cancreate']);
	}

	/******
	 * Can this user publish in this Section?
	 * return boolean
	 ******/
	public function canPublish()
	{
		$this->loadInfo();
		//This user can view if they have view rights for this content;
		if (! isset(vB::$vbulletin->userinfo['permissions']['cms']))
		{
			require_once DIR . '/packages/vbcms/permissions.php';
			vBCMS_Permissions::getUserPerms();
		}

		return (vb_Types::instance()->getContentTypeID("vBCms_Section") == $this->contenttypeid ?
			in_array($this->permissionsfrom, vB::$vbulletin->userinfo['permissions']['cms']['canpublish']) :
			in_array($this->parentpermissions, vB::$vbulletin->userinfo['permissions']['cms']['canpublish'])
			) OR
			(vb_Types::instance()->getContentTypeID("vBCms_Section") == $this->contenttypeid ?
			in_array($this->permissionsfrom, vB::$vbulletin->userinfo['permissions']['cms']['canedit']) :
			in_array($this->parentpermissions, vB::$vbulletin->userinfo['permissions']['cms']['canedit'])
			);
	}

	/**
	 * Fetches the class identifier of the contenttype.
	 * Note: This is only a segment of the class name.  It should be combined with
	 * the package class identifier and the required class type.
	 *
	 * Usually class names can be resolved with the vB_Content for a specific
	 * content type.
	 *
	 * @return string
	 */
	public function getClass()
	{
		$this->Load();

		if ($this->contenttypeid)
		{
			return vBCms_Types::instance()->getContentTypeClass($this->contenttypeid);
		}

		return $this->class;
	}


	/**
	 * Fetches the package class identifier of the contenttype.
	 * Note: This is only a segment of the class name.  It should be combined with
	 * the content class identifier and the required class type.
	 *
	 * Usually class names can be resolved with the vB_Content for a specific
	 * content type.
	 *
	 * @return string
	 */
	public function getPackage()
	{
		$this->Load();

		if ($this->contenttypeid)
		{
			return vBCms_Types::instance()->getContentTypePackage($this->contenttypeid);
		}

		return $this->package;
	}


	/**
	 * Fetches the node config of the content.
	 *
	 * @return array mixed
	 */
	public function getConfig($cvar = false)
	{
		$this->Load(self::INFO_CONFIG);

		if ($cvar)
		{
			return (isset($this->config[$cvar]) ? $this->config[$cvar] : null);
		}

		return $this->config;
	}


	/**
	 * Sets the config.
	 * TODO: Only allow items of same type to set the config?
	 *
	 * @param array mixed $config
	 */
	public function setConfig($config)
	{
		$this->config = $config;

		$this->loaded_info |= self::INFO_CONFIG;
	}


	/**
	 * Allows parents to be set by client code.
	 *
	 * @param $parentlist						- The list of parents
	 */
	public function setParents($parentlist)
	{
		$this->parents = $parentlist;

		$this->loaded_info |= self::INFO_PARENTS;
	}


	/**
	 * Fetches the node url title only.
	 * The nodeid prefix is not included.  This is useful for display purposes.
	 *
	 * @return string
	 */
	public function getUrlTitle()
	{
		$this->Load();

		return $this->url;
	}


	/**
	 * Returns the resolved url segment for the node.
	 *
	 * @return string
	 */
	public function getUrlSegment()
	{
		$this->Load();

		return $this->nodeid . ($this->url ? '-' . $this->url : '');
	}

	/**
	 * Returns the url segment defined for the node.
	 *
	 * @return string
	 */
	public function getUrl()
	{
		$this->Load();

		return $this->url;
	}


	/*** returns the current style ******/
	public function getStyle()
	{
		return $this->getStyleId();
	}

	/*** returns the flag for comments enabled ******/
	public function getComments_Enabled()
	{
		return $this->comments_enabled;
	}

	public function setComments_Enabled($enabled = true)
	{
		$this->comments_enabled = $enabled;
	}

	/** returns a list of user groups that can read this ***/
	public function getReaderGroups()
	{
		//If this node is a section, we get its rights directly. Otherwise it comes
		// from the parent;
		$this_permissionsfrom = (vB_Types::instance()->getContentTypeId('vBCms_Section') == $this->contenttypeid) ?
			$this->permissionsfrom : $this->parentpermissions;
		$groups = array();

		if ($rst = vB::$vbulletin->db->query_read($sql = "SELECT u.usergroupid, u.title FROM " .
			TABLE_PREFIX . "usergroup AS u INNER JOIN " . TABLE_PREFIX . "cms_permissions AS perm
			ON perm.usergroupid = u.usergroupid WHERE perm.nodeid = $this_permissionsfrom AND perm.permissions > 0
			ORDER BY u.title"))
		{
			while($row = vB::$vbulletin->db->fetch_array($rst))
			{
				$groups[] = array('usergroupid' => $row['usergroupid'] , 'title' => $row['title']);
			}

		}
		return $groups;
	}

	/** returns a list of categories where this is used ***/
	public function getThisCategories()
	{
		if ($rst = vB::$vbulletin->db->query_read($sql = "SELECT
			cat.category, cat.categoryid, cat.catleft, cat.catright, nodec.nodeid
			FROM " . TABLE_PREFIX . "cms_category cat
			LEFT JOIN " . TABLE_PREFIX . "cms_nodecategory nodec ON cat.categoryid = nodec.categoryid
			  AND  nodec.nodeid = " . $this->nodeid . "
			ORDER BY cat.catleft;"))
		{
			$ancestry = array('0' => array());
			$array_index = 0;
			$categories = array();
			while($row = vB::$vbulletin->db->fetch_array($rst))
			{
				//Let's get the ancestry tree;
				// Make sure we have the index to an array element that's a parent of the current node.
				while(($array_index > 0) AND (intval($row['catright']) > intval($ancestry[$array_index]['catright']) ) )
				{
					$array_index--;
				}
				$parents = array();
				if (0 < $array_index )
				{
					for($i = 1;$i <= $array_index; $i++)
					{
						$parents[] = $ancestry[$i]['category'];
					}
				}
				$parents[] = $row['category'];
				$row['text'] = implode('>', $parents);
				$row['checked'] = (isset($row['nodeid']) ? 'checked="checked"' : '');
				$array_index++;
				$ancestry[$array_index] = $row;
				$categories[] = $row;
			}
		}
		return $categories;
	}

	/** returns a string representing the hierarchy of parentage for this node **/
	public function getParentage()
	{

		if (! $this->parentnode)
		{
			return '';
		}

		if ($rst = vB::$vbulletin->db->query_read("SELECT node.nodeleft, info.title FROM " .
			TABLE_PREFIX . "cms_node AS node INNER JOIN " . TABLE_PREFIX . "cms_nodeinfo AS info
			ON node.nodeid = info.nodeid INNER JOIN " . TABLE_PREFIX .
			"cms_node AS myself ON myself.nodeleft
			 BETWEEN node.nodeleft AND node.noderight WHERE myself.nodeid =" . $this->parentnode . "
			ORDER BY node.nodeleft"))
		{
			$parents = array();
			while($row = vB::$vbulletin->db->fetch_array($rst))
			{
				$parents[] = $row['title'];
			}

		}
		return implode('>' , $parents);

	}

	/** Creates the publish editor at the top right of the edit section **/
	public function getPublishEditor($submit_url, $formid)
	{
		if ($this->canPublish())
		{
			$pub_view = new vB_View('vbcms_edit_publisher');
			$pub_view->formid = $formid;
			$pub_view->setpublish = $this->setpublish;

			if (intval($this->publishdate))
			{
					$pub_view->publishdate = $this->publishdate;
			}
			else
			{
				$pub_view->publishdate = TIMENOW;
			}
			$pub_view->title = $this->title;
			$pub_view->html_title = $this->html_title;
			$pub_view->username = $this->username;
			$pub_view->dateformat = vB::$vbulletin->options['dateformat'];

			$pub_view->groups = $this->getReaderGroups();
			$pub_view->parents = $this->getParentage();
			$pub_view->submit_url = $submit_url;
			$pub_view->sectiontypeid = vb_Types::instance()->getContentTypeID("vBCms_Section");

			if ($this->contenttypeid == $pub_view->sectiontypeid)
			{
				$pub_view->show_categories = 0;
			}
			else
			{
				$pub_view->show_categories = 1;
				$pub_view->categories = $this->getThisCategories();
			}
			$pub_view->show_categories = ($this->contenttypeid == $pub_view->sectiontypeid ? 0 : 1);
			$pub_view->nodelist = vBCms_ContentManager::getNodes(1,
				array('contenttypeid' => 'node2.contenttypeid = ' . vb_Types::instance()->getContentTypeID("vBCms_Section"),
					'privileges' => "node2.permissionsfrom in (" . implode(',', vB::$vbulletin->userinfo['permissions']['cms']['cancreate']) .")"));
			return $pub_view;
		}
	}

	/** Creates the publish editor at the lower right of the edit section **/
	public function getMetadataEditor()
	{
		require_once DIR . '/includes/functions_databuild.php';

		fetch_phrase_group('cpglobal');

		if ($this->canEdit())
		{
			$meta_view = new vB_View('vbcms_edit_metadataeditor');
			$meta_view->html_title = $this->html_title;
			$meta_view->description = $this->description;
			$meta_view->keywords = $this->keywords;
			return $meta_view;
		}
	}

	/** Creates the publish editor across the top of the edit section **/
	public function getEditBar($submit_url, $view_url, $formid, $action = 'edit', $candelete = true)
	{
		global $vbphrase;
		if ($this->canEdit())
		{
			require_once DIR . '/includes/functions_databuild.php';

			fetch_phrase_group('cpcms');

			$new_view = new vB_View('vbcms_content_edit_editbar');
			$new_view->submit_url = $submit_url;
			$new_view->view_url = $view_url;
			$new_view->formid = $formid;
			$new_view->header_phrase = $header_phrase;
			$new_view->adding = construct_phrase($vbphrase['addoredit_x'], $vbphrase[$action], $this->class);
			$new_view->confirm_message = $vbphrase['delete_page_confirmation_message'];
			$new_view->candelete = $candelete;
			$new_view->is_section = ($this->contenttypeid == vb_Types::instance()->getContentTypeID("vBCms_Section"));

			return $new_view;
		}
	}

	/**
	 * Returns associated thread used for comments on this content
	 *
	 * @return string
	 */
	public function getAssociatedThreadId()
	{
		$this->Load();

		return $this->associatedthreadid;
	}

	/**** for non-section nodes, we return the category list-
	* array (id => title)
	******/
	public function getCategories()
	{
		if (vb_Types::instance()->getContentTypeID("vBCms_Section") == $this->contenttypeid )
		{
			return array();
		}
		$categories = array();

		if ($rst = vB::$vbulletin->db->query_read("SELECT category, cat.categoryid FROM "
			. TABLE_PREFIX . "cms_nodecategory nc INNER JOIN "
			. TABLE_PREFIX . "cms_category cat ON cat.categoryid = nc.categoryid
			WHERE nc.nodeid = " . $this->nodeid ))
		{
			while($record = vB::$vbulletin->db->fetch_array($rst))
			{
				$categories[] = $record;
			}
		}
		return $categories;
	}

}