<?php
if (!defined('VB_ENTRY')) die('Access denied.');
/*======================================================================*\
   || #################################################################### ||
   || # vBulletin 5.0.0 Beta 10
   || # ---------------------------------------------------------------- # ||
   || # Copyright ?2000-2012 vBulletin Solutions Inc. All Rights Reserved. ||
   || # This file may not be redistributed in whole or significant part. # ||
   || # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
   || # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
   || #################################################################### ||
   \*======================================================================*/


/**
 * vB_Api_Content_Text
 *
 * @package vBApi
 * @author ebrown
 * @copyright Copyright (c) 2011
 * @version $Id$
 * @access public
 */
class vB_Library_Content_Text extends vB_Library_Content
{
	//override in client- the text name
	protected $contenttype = 'vBForum_Text';

	//The table for the type-specific data.
	protected $tablename = 'text';

	//When we parse the page.
	protected $bbcode_parser = false;

	//Whether we change the parent's text count- 1 or zero
	protected $textCountChange = 1;

	//for spam checking
	protected $spamType = false;
	protected $spamKey = false;
	protected $akismet;

	//Does this content show author signature?
	protected $showSignature = true;

	protected function __construct()
	{
		parent::__construct();
		//see if we have spam checking set.
		if (isset($this->options['vb_antispam_type']) AND !empty($this->options['vb_antispam_key']))
		{
			$this->spamType = $this->options['vb_antispam_type'];
			$this->spamKey = $this->options['vb_antispam_key'];
		}
	}

	/** This function parses the raw text and stores the result in pagetext and previewtext
	*
	*	@param	mixed
	***/
	protected function parseContent(&$record)
	{
		require_once(DIR . '/includes/class_bbcode.php');
	//	require_once(DIR . '/packages/vbforum/bbcode/wysiwyg.php');
		if ($this->bbcode_parser == false)
		{
			//TODO: execute the line below to get all text tags.
			//$tags = vBForum_BBCode_Wysiwyg::fetchTextTags();
			$tags = fetch_tag_list();
			$registry = vB::get_registry();

			if (empty($registry->db) AND !empty(vB::$db))
			{
				$registry->db = vB::$db;
			}

			$this->bbcode_parser = new vB_BbCodeParser($registry, $tags);
		}

		// Articles will generally have an attachment but they should still keep a counter so that this query isn't always running
		require_once(DIR . '/packages/vbattach/attach.php');
		$attach = new vB_Attach_Display_Content(vB::$vbulletin, 'vBForum_Text');
		$attachments = $attach->fetch_postattach(0, $record['nodeid']);
		$this->bbcode_parser->attachments = $attachments;
		$this->bbcode_parser->unsetattach = true;

		require_once DIR . '/includes/functions.php';
		$this->bbcode_parser->attachments = $attachments;

		$authorContext = vB::getUserContext($record['userid']);

		$record['pagetext'] = fetch_censored_text($this->bbcode_parser->do_parse(
			$record['rawtext'],
			$authorContext->getChannelPermission('forumpermissions', 'canusehtml', $record['parentid']),
			true, true, $authorContext->getChannelPermission('forumpermissions', 'cangetattachment', $record['parentid']),
			true,true,$record->htmlstate
			));

		$attach->process_attachments($record, $this->bbcode_parser->attachments, false, false, true, false, true);

		$record['previewtext'] = $this->bbcode_parser->get_preview($record['rawtext'], 200,
			false, true, $record->htmlstate );
		$this->assertor->assertQuery('vBForum:text', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_UPDATE, 'nodeid' => $record['nodeid'],
			'pagetext' => $record['pagetext'], 'previewtext' => $record['previewtext']) );
	}

	/*** Returns the node content as an associative array with fullcontent
	 *	@param	mixed	integer or array of integers=The id in the primary table
	 *	@param array permissions
	 *
	 * 	 *	@param bool	appends to the content the channel routeid and title, and starter route and title the as an associative array
	 ***/
	public function getFullContent($nodes, $permissions = false, $full = true)
	{
		if (empty($nodes))
		{
			return array();
		}

		$results = parent::getFullContent($nodes, $permissions, $full);
		return $this->addContentInfo($results);
	}

	protected function addContentInfo($results)
	{
		//the key of for each node is the nodeid, fortunately
		$userids = array();
		$userContext = vB::getUserContext();
		//If pagetext and previewtext aren't populated let's do that now.
		foreach ($results as $key => $record)
		{
			//make sure we have the preview text
			if (empty($record['previewtext']))
			{
				$results[$key]['previewtext'] = vB_String::getPreviewText($record['rawtext']);
			}

			if (isset($record['pagetextimages']))
			{
				if (!$userContext->getChannelPermission('forumpermissions', 'cangetattachment', $record['parentid']))
				{
					$results[$key]['pagecontent'] = $record['pagetextimages'];
					unset($results[$key]['pagetextimages']);
				}
				else
				{
					unset($results[$key]['pagetextimages']);
				}
			}

			if (!empty($record['userid']) AND !in_array($record['userid'], $userids))
			{
				$userids[] = $record['userid'];
			}

			if (empty($record['starter']))
			{
				//The starter should never be empty or zero.  Let's fix this.
				$starter = $this->getStarter($record['nodeid']);
				$data = array(vB_dB_Query::TYPE_KEY =>vB_dB_Query::QUERY_UPDATE, 'nodeid' => $record['nodeid'],
					'starter' => $starter);
				$this->assertor->assertQuery('vBForum:node', $data);
				$results[$key]['starter'] = $starter;
			}
			$results[$key]['attach'] = array();
		}

		if (!empty($userids))
		{
			//First, see what we can get from the page cache.
			$users = array();
			$pageCache = vB_Cache::instance(vB_Cache::CACHE_FAST);
			foreach ($userids AS $key => $userid)
			{
				$userInfo = $pageCache->read('userDetails.' . $userid);

				if (!empty($userInfo))
				{
					$users[$userid] = $userInfo;
					//unset so we won't query.
					unset($userids[$key]);
				}
			}

			//Now query for whatever is left
			if (!empty($userids))
			{
				$userQry = $this->assertor->assertQuery('vBForum:getUserDetails', array(vB_dB_Query::TYPE_KEY =>vB_dB_Query::QUERY_STORED,
					'userid' => $userids));

				if ($userQry AND $userQry->valid())
				{
					$user = $userQry->current();
					while($userQry->valid())
					{

						//If this user has a signature we'll probably want it.
						/** TODO:
						 * This is currently broken because the cache_permissions function is broken.
						 * It's not worth fixing- it's broken because it relies on the $vbulletin object.
						 if (!empty($user['signature']))
						 {
						 require_once(DIR . '/includes/class_sigparser.php');
						 $userperms = cache_permissions($user['userid'], false);
						 $bbcode_parser = new vB_SignatureParser(vB::$vbulletin, fetch_tag_list(), $userperms, $user['userid'] );

						 $authorContext = vB::getUserContext($user['userid']);
						 $user['sigparsed'] = fetch_censored_text($bbcode_parser->do_parse(
						 $user['signature'],
						 $authorContext->getChannelPermission('forumpermissions', 'canusehtml', $record['parentid']),
						 true, true, $authorContext->getChannelPermission('forumpermissions', 'cangetattachment', $record['parentid']),
						 true,true,$record->htmlstate
						 ));
						 }
						 ****/
						$pageCache->write('userDetails.' . $user['userid'], $user, 1440, 'userChanged.' . $userid);
						$users[$user['userid']] = $user;
						$user = $userQry->next();
					}
				}
			}

			foreach ($users AS $userid => $user)
			{
				foreach ($results as $key => $record)
				{
					if (!empty($record['userid']) AND !empty($users[$record['userid']]))
					{
						$results[$key]['userinfo'] = $users[$record['userid']];

						if ($results[$key]['showsignature'] AND !empty($users[$record['userid']]['sigparsed']) )
						{
							$results[$key]['signature'] = $users[$record['userid']]['sigparsed'];
						}
					}
				}
			}
		}

		//let's get the attachment info.
		$attachments = vB_Api::instanceInternal('node')->getNodeAttachments(array_keys($results));
		foreach ($attachments as $attachment)
		{
			if (array_key_exists($attachment['parentid'], $results))
			{
				$results[$attachment['parentid']]['attach'][] = $attachment;
			}
		}

		foreach ($results as $key => $result)
		{
			if (empty($result))
			{
				continue;
			}
			if (is_array($result['attach']) AND !empty($result['attach']))
			{
				$results[$key]['photocount'] = count($result['attach']);

			}
			else
			{
				$results[$key]['photocount'] = 0;
			}
		}

		return $results;
	}


	/*** updates a record
	 *
	 *	@param	mixed		array of nodeid's
	 *	@param	mixed		array of permissions that should be checked.
	 *
	 * 	@return	boolean
	 ***/
	public function update($nodeid, $data)
	{
		$node = $this->assertor->getRow('node', array('nodeid' => $nodeid));
		//We may need to update the "last" counts.
		if (isset($data['publishdate']) OR isset($data['unpublishdate']) OR isset($data['showpublished']))
		{

			$updates = array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED,
				'lastauthorid' => $node['userid'], 'nodeid' => $nodeid);

			if (!isset($data['publishdate']))
			{
				$updates['lastcontent'] = $node['publishdate'];
			}
			else
			{
				$updates['lastcontent'] = $data['publishdate'];
			}

			if (empty($data['lastcontentauthor']))
			{
				$updates['lastcontentauthor'] = $node['authorname'];
			}
			else
			{
				$updates['lastcontentauthor'] = $data['authorname'];
			}

			if (empty($data['lastauthorid']))
			{
				$updates['lastauthorid'] = $node['userid'];
			}

		}

		if (isset($data['rawtext']))
		{
			$wysiwyg = new vB_WysiwygHtmlParser();
			$data['rawtext'] = $wysiwyg->parseWysiwygHtmlToBbcode($data['rawtext']);

			if (!isset($data['pagetext']))
			{
				$data['pagetext'] = '';
			}

			if (!isset($data['previewtext']))
			{
				$data['previewtext'] = '';
			}
			//Set the "hasvideo" value;
			$filter = '~\[video.*\[\/video~i';
			$matches = array();
			$count = preg_match_all($filter, $data['rawtext'], $matches);

			if ($count > 0 )
			{
				$data['hasvideo'] = 1;
		}
			else
			{
				$data['hasvideo'] = 0;
			}
		}

		$published = $this->isPublished($data);
		$result = parent::update($nodeid, $data, $published);

		if (isset($node) AND ($published <> $node['showpublished']))
		{
			//We don't need to update the counts- that gets done in the parent class.
			if ($published)
			{
				$updates['lastcontentid'] = $nodeid;
				$this->assertor->assertQuery('vBForum:setLastData', $updates);
			}
		}
		$this->nodeApi->clearCacheEvents(array($nodeid, $node['parentid']));
		return $result;
	}

	/*** Permanently deletes a node
	 *	@param	integer	The nodeid of the record to be deleted
	 *
	 *	@return	boolean
	 ***/
	public function delete($nodeid)
	{
		//We need to update the parent counts, but first we need to get the status
		try
		{
			$node = $this->nodeApi->getNode($nodeid);
		}
		catch (Exception $e)
		{
			// make sure the parent counts are updated
			$node['showpublished'] = true;
		}

		//We have to get this before we delete
		$parents = $this->getParents($nodeid);

		//do the delete
		$result = parent::delete($nodeid);
		$cachedNodes = array($nodeid);

		// reset last content for all parents that have the deleted node
		$this->assertor->update('node',
				array(
					'lastcontent' => 0,
					'lastcontentid' => 0,
					'lastcontentauthor' => '',
					'lastauthorid' => 0),
				array(
					'lastcontentid' => $nodeid
				)
		);

		//Update the parent counts.
		if ($node['showpublished'])
		{
			$this->updateParentCounts($nodeid, -1, 0, -1, 0, 1);
			//And we need to reset the lasttext fields.
			//We have to update them one at a time.
			foreach ($parents as $node)
			{
				$this->assertor->assertQuery('vBForum:updateLastData', array('parentid' => $node['nodeid'], 'timenow' => vB::getRequest()->getTimeNow()));
			}
		}
		else
		{
			$this->updateParentCounts($nodeid, 0, -1, 0, -1, 0);
		}

		try
		{
			foreach ($parents as $node)
			{
				$cachedNodes[] = $node['nodeid'];
			}
		}
		catch(exception $e)
		{
			//nothing to do- just proceed.
		}

		$this->nodeApi->clearCacheEvents($cachedNodes);
		return $result;
	}

	/*** Adds a new node.
	 *
	 *	@param	mixed		Array of field => value pairs which define the record.
	 *  @param	array		Array of options for the content being created.
	 *						Available options include:
	 *							- nl2br: if TRUE, all \n will be converted to <br /> so that it's not removed by the html parser (e.g. comments).
	 *
	 * 	@return	integer		the new nodeid
	 ***/
	public function add($data, array $options = array())
	{

		if (empty($data['parentid']))
		{
			throw new Exception('need_parent_node');
		}
		// convert to bbcode for saving
		if (isset($data['rawtext']) AND !empty($data['rawtext']))
		{
			if (isset($options['nl2br']) AND $options['nl2br'])
			{
				$data['rawtext'] = nl2br($data['rawtext']);
			}

			$wysiwyg = new vB_WysiwygHtmlParser();
			$data['rawtext'] = $wysiwyg->parseWysiwygHtmlToBbcode($data['rawtext']);
		}

		if (empty($data['userid']))
		{
			$user = vB::getCurrentSession()->fetch_userinfo();
			$data['authorname'] = $user['username'];
			$userid = $data['userid'] = $user['userid'];
		}
		else
		{
			$userid = $data['userid'];
			if (empty($data['authorname']))
			{
				$data['authorname'] = vB_Api::instanceInternal('user')->fetchUserName($userid);
			}
		}

		$isSpam = false;
		//run the spam check.
		if ($this->spamType !== false)
		{

			if (empty($this->akismet))
			{
				$this->akismet = vB_Akismet::instance();
			}

			$params = array('comment_type' => 'user_post', 'comment_content' => $data['rawtext']);

			$params['comment_author'] = $data['authorname'];
			$params['comment_author_email'] = $user['email'];
			$result = $this->akismet->verifyText($params);

			if ($result == 'spam')
			{
				$data['publishdate'] = 0;
				$data['showpublished'] = 0;
				$isSpam = true;
			}
		}


		//We need a copy of the data, maybe
		$updates = array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED);

		//Set the "hasvideo" value;
		if (!empty($data['rawtext']))
		{
			$filter = '~\[video.*\[\/video~i';
			$matches = array();
			$count = preg_match_all($filter, $data['rawtext'], $matches);

			if ($count > 0 )
			{
				$data['hasvideo'] = 1;
			}
			else
			{
				$data['hasvideo'] = 0;
			}
		}

		if (!isset($data['publishdate']))
		{
			$data['publishdate'] = vB::getRequest()->getTimeNow();
		}
		$updates['lastcontent'] = $data['publishdate'];

		if (isset($data['userid']))
		{
			$updates['lastauthorid'] = $data['userid'];
		}
		else
		{
			$updates['lastauthorid'] = $data['userid'] = vB::getCurrentSession()->get('userid');
		}

		if (isset($data['authorname']))
		{
			$updates['lastcontentauthor'] = $data['authorname'];
		}
		else
		{
			$author = $this->assertor->getRow('user',
				array(vB_Db_Query::TYPE_KEY =>vB_dB_Query::QUERY_SELECT, 'userid' => $data['userid']));
			$data['authorname'] = $author['username'];
			$updates['lastcontentauthor'] = $author['username'];
		}

		$published = $this->isPublished($data);
		$nodeid = parent::add($data, $options);

		// Obtain and set generic conversation route
		$conversation = $this->getConversationParent($nodeid);
		$routeid = vB_Api::instanceInternal('route')->getChannelConversationRoute($conversation['parentid']);
		$this->assertor->update('node', array('routeid' => $routeid), array('nodeid' => $nodeid));

		//set the last post and count data.

		if ($published)
		{
			$updates['nodeid'] = $nodeid;
			$updates['lastcontentid'] = $nodeid;
			$this->assertor->assertQuery('vBForum:setLastData', $updates);
		}

		if ($isSpam)
		{
			$this->assertor->assertQuery('spamlog', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_INSERT, 'postid' => $nodeid));
		}

		$cachedNodes = array($nodeid);
		$parents = $this->getParents($data['parentid']);
		foreach ($parents as $node)
		{
			$cachedNodes[] = $node['nodeid'];
		}

		$this->nodeApi->clearCacheEvents($cachedNodes);
		$this->nodeApi->clearCacheEvents(array($nodeid, $data['parentid']));

		// publish to facebook
		$this->publishToFacebook($nodeid, $options);

		return $nodeid;
	}

	/*** Processing after a move. In this case, update the starter field and set the text counts and "last" data.
	*
	* 	@param	int		the nodeid
	* 	@param	int		the old parentid
	* 	@param	int		the new parentid
	*/
	public function afterMove($nodeid, $oldparent, $newparent)
	{
		parent::afterMove($nodeid, $oldparent, $newparent);
		$newParentNode = $this->nodeApi->getNode($newparent);

		//if this IS the starter, then no change is necessary.
		if ($newParentNode['starter'] != $nodeid)
		{
			$starter = $this->getStarter($nodeid);
			$this->assertor->assertQuery('vBForum:setStarter', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED,
				'nodeid' => $nodeid, 'starter' => $starter));
		}

	}

	/** THis returns a string with quoted strings in bbcode format.
	*
	*	@param	mixed	array of integers
	*
	* 	@return	string
	***/

	public function getQuotes($nodeids)
	{
		if (is_array($nodeids)){
			$quotes = array();
			foreach ($nodeids as $nodeid)
			{
				$node = $this->getContent($nodeid);
				$node = $node[$nodeid];
				$quotes[$nodeid] = "[QUOTE=$node[authorname];$nodeid]$node[rawtext][/QUOTE]";
			}
			return quotes;
		}
		else
		{
			$node = $this->getContent($nodeids);
			$node = $node[$nodeids];
			$quote = "[QUOTE=$node[authorname];$nodeids]$node[rawtext][/QUOTE]";
			return $quote;
		}
	}

	public function getIndexableContent($nodeId, $include_attachments = true)
	{
		$all_content = parent::getIndexableContent($nodeId, $include_attachments = true);
		$content = $this->getContent($nodeId);
		if (!empty($content) AND !empty($content[$nodeId]) &&  !empty($content[$nodeId]['rawtext']))
		{
			array_unshift($all_content, $content[$nodeId]['rawtext']);
		}
		return $all_content;
	}

	/**
	 * Adds content info to $result so that merged content can be edited.
	 * @param array $result
	 * @param array $content
	 */
	public function mergeContentInfo(&$result, $content)
	{
		if (!isset($content['rawtext']))
		{
			throw new vB_Exception_Api('Invalid content info.');
		}

		if (!isset($result['rawtext']) || empty($result['rawtext']))
		{
			$result['rawtext'] = $content['rawtext'];
		}
		else
		{
			$result['rawtext'] .= "\n{$content['rawtext']}";
		}
	}

	/**
	 * Performs the merge of content and updates the node.
	 * @param type $data
	 * @return type
	 */
	public function mergeContent($data)
	{
		// if we are merging text, the contenttype must already be text, so just update rawtext, author
		$update = array(
			'userid' => $data['destauthorid'],
			'rawtext' => $data['text']
		);

		return $this->update($data['destnodeid'], $update);
	}
}
