<?php if (!defined('VB_ENTRY')) die('Access denied.');
/*========================================================================*\
|| ###################################################################### ||
|| # vBulletin 5.1.9 Patch Level 4 - Licence Number LD125EAAF9
|| # ------------------------------------------------------------------ # ||
|| # Copyright 2000-2016 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_Vb4_activity
 *
 * @package vBApi
 * @access public
 */
class vB_Api_Vb4_activity extends vB_Api
{
	const FILTER_SORTBY_RECENT  = 'recent';
	const FILTER_SORTBY_POPULAR = 'popular';
	const FILTER_LASTDAY 		= 'today';
	const FILTER_LASTWEEK 		= 'week';
	const FILTER_LASTMONTH 		= 'month';
	//const FILTER_SHOW_ALL  		= 'all';
	const FILTER_SHOW_SOCIALGROUP = 'socialgroup';
	const FILTER_SHOW_BLOG 		= 'blog';
	const FILTER_SHOW_CMS		= 'cms';
	const FILTER_SHOW_FORUM		= 'forum';
	const FILTER_SHOW_PHOTOS	= 'photos';

	/**
	 *  Results per page
	 */
	protected $perpage = 15;

	// Resovling the result structure block name
	protected $section = array(
		// Commented out are not yet implemented in vb5
		'album' 	  => 'albuminfo',
		//'calendar'	  => 'calendarinfo',
		'forum' 	  => 'foruminfo',
		'socialgroup' => 'groupinfo',
		//'cms' 		  => 'cmsinfo',
		'blog' 		  => 'bloginfo',
	);

	// Top channels of the board
	protected $topChannels = array();

	/**
	 * Default activity call.
	 *
	 * @param  integer $userid      [userid]
	 * @param  integer $mindateline [The dateline of the min record currently shown]
	 * @param  integer $maxdateline [The dateline of the max record currently shown]
	 * @param  integer $minscore    []
	 * @param  mixed   $minid       [CSV of the ids of the items with mindateline]
	 * @param  string  $maxid       [CSV of the ids of the items with maxdateline]
	 * @param  string  $sortby      [Sorting the results. Possible values see constant with FILTER_SORT_BY_*]
	 * @param  string  $time        [Filtering the results. Possible values see constant with FILTER_*]
	 * @param  string  $show        [Filtering the results by section. Possible values see constant with FILTER_SHOW_*]
	 * @return array                [Result structure]
	 */
	public function call(
		$userid = 0,
		$mindateline = 0,
		$maxdateline = 0,
		$minscore = 0,
		$minid = '',
		$maxid = '',
		$sortby = '',
		$time = '',
		$show = '',
		$pagenumber = 0
	)
	{
		$cleaner = vB::getCleaner();

		$userid 	 = $cleaner->clean($userid, vB_Cleaner::TYPE_UINT);
		$mindateline = $cleaner->clean($mindateline, vB_Cleaner::TYPE_UINT);
		$maxdateline = $cleaner->clean($maxdateline, vB_Cleaner::TYPE_UINT);
		$minscore	 = $cleaner->clean($minscore, vB_Cleaner::TYPE_NUM);
		$minid 		 = $cleaner->clean($minid, vB_Cleaner::TYPE_STR);
		$maxid 		 = $cleaner->clean($maxid, vB_Cleaner::TYPE_STR);
		$sortby 	 = $cleaner->clean($sortby, vB_Cleaner::TYPE_STR);
		$time 		 = $cleaner->clean($time, vB_Cleaner::TYPE_STR);
		$show 		 = $cleaner->clean($show, vB_Cleaner::TYPE_STR);
		$pagenumber  = $cleaner->clean($pagenumber, vB_Cleaner::TYPE_UINT);

		$usedFilter = false;

		$searchJSON = array();

		$searchJSON['view'] = vB_Api_Search::FILTER_VIEW_ACTIVITY;

		if (!empty($userid))
		{
			$searchJSON['authorid'] = $userid;
		}

		// In vb4 when you send $mindateline You send the minid(s) (when you click on show more)
		// and with that info you get older set of data for the stream
		if (!empty($mindateline))
		{
			$searchJSON['date']['to'] = $mindateline;
			$usedFilter = true;
		}

		// In vb4 when you send $maxdateline You send the maxid(s)
		// and with that info you get any newer results that are there (if any)
		if (!empty($maxdateline))
		{
			$searchJSON['date']['from'] = $maxdateline;
			$usedFilter = true;
		}

		if (!empty($sortby))
		{
			if ($sortby == self::FILTER_SORTBY_RECENT)
			{
				$searchJSON['sort'] = array('dateline' => 'desc');
			}
			else
			{
				// Best we have in vB5 is replies, this could also be votes if desired
				$searchJSON['sort'] = array('replies' => 'desc');
				$usedFilter = true;
			}
		}

		if (!empty($time))
		{
			$usedFilter = true;
			if ($time == self::FILTER_LASTDAY)
			{
				$searchJSON['date']['from'] = vB_Api_Search::FILTER_LASTDAY;
			}
			elseif ($time == self::FILTER_LASTWEEK)
			{
				$searchJSON['date']['from'] = vB_Api_Search::FILTER_LASTWEEK;
			}
			elseif ($time == self::FILTER_LASTMONTH)
			{
				$searchJSON['date']['from'] = vB_Api_Search::FILTER_LASTMONTH;
			}
		}
		else
		{
			$searchJSON['date']['from'] = vB_Api_Search::FILTER_LASTYEAR;
		}

		if (!empty($show))
		{
			$usedFilter = true;
			$topChannels = $this->getTopChannels();

			if ($show == self::FILTER_SHOW_SOCIALGROUP)
			{
				//get the sg channel
				$searchJSON['channel'] = $topChannels['groups'];
			}
			elseif ($show == self::FILTER_SHOW_BLOG)
			{
				// get the blog channel
				$searchJSON['channel'] = $topChannels['blog'];
			}
			elseif ($show == self::FILTER_SHOW_CMS)
			{
				// This is not yet implemented in vb5
				//$searchJSON['channel'] = $topChannels['cms'];
			}
			elseif ($show == self::FILTER_SHOW_FORUM)
			{
				// get the forum channel
				$searchJSON['channel'] = $topChannels['forum'];
			}
			elseif ($show == self::FILTER_SHOW_PHOTOS)
			{
				// In vb4 this means getting the photos from the socialgroups and albums
				// NOTE: This has different result layout then the others
				$searchJSON['type'] = array('vBForum_Photo');
			}
		}

		// Exclude PMs. vB4 didn't seem to show these in the activities.
		$searchJSON['exclude_type'] = array('vBForum_PrivateMessage');

		$result = vB_Api::instance('search')->getInitialResults($searchJSON, $this->perpage, (empty($pagenumber) ? 1 : $pagenumber), true);

		if ($result === null || isset($result['errors']))
		{
			return vB_Library::instance('vb4_functions')->getErrorResponse($result);
		}

		$activitybits = $this->prepareSearchData($result, $show);

		$count = count($activitybits);

		// Where do I get the data for 'actdata' (perpage, refresh) and for 'show' -> Need to investigate how this is done in vb4 activity
		$response = array(
			'actdata' => array(
				'mindateline' => $mindateline,
				'maxdateline' => $maxdateline,
				'minscore' 	  => $minscore,
				'minid' 	  => $minid,
				'maxid' 	  => $maxid,
				'count' 	  => $count,
				'totalcount'  => $count,
				'perpage' 	  => $this->perpage,
				'time' 		  => $time,
				'sortby' 	  => $sortby,
				'refresh' 	  => 1,
			),
			'show' => array(
				'more_results' => ($count <= $this->perpage ? 0 : 1),
				'as_blog' => true,
				'as_cms' => false ,
				'as_socialgroup' => true,
				'filterbar' => $usedFilter,
			),
		);

		$response['activitybits'] = $activitybits;

		return array(
			'response' => $response,
		);
	}

	/**
	 * Structuring the result data for output
	 *
	 * @param  [Array]  $result [Array with the search result]
	 * @param  [String] $show   [See constant FILTER_SHOW_*. Only when it has value 'photos' it changes the output structure]
	 *
	 * @return [array]			[Formatted structure for output]
	 */
	private function prepareSearchData($result, $show)
	{
		$bbcodeParserInstance = new vB_Library_BbCode(true, true);
		$activitybits = array();
		$result['results'] = vB_Api::instance('node')->mergeNodeviewsForTopics($result['results']);

		foreach ($result['results'] AS $node)
		{
			$activitybit = array();

			if (empty($node['content']))
			{
				$conversation = $node;
			}
			else
			{
				$conversation = $node['content'];
			}

			$itemSection  = $this->getSection($node);

			if (empty($itemSection))
			{
				// This might be some weird node under the special channel or some other node
				// that shouldn't have ended up here. Just skip it.
				continue;
			}

			// Socialgroup not handled in the apps
			// TODO: Need to look into this.
			if ($itemSection == 'socialgroup')
			{
				continue;
			}
			$activityType = $this->getActivityType($itemSection, $node);

			$activitybit['activity'] = array(
				'posttime' => vbdate('h:i A', $conversation['publishdate']),
				'postdate' => vbdate('m-d-Y', $conversation['publishdate']),
				'section'  => $itemSection,
				'type'	   => $activityType,
				'score'	   => 0.000,
			);

			if ($show == self::FILTER_SHOW_PHOTOS)
			{
				// TODO: TEST THIS
				$activitybit['attachmentinfo'] = array(
					// Hack to let attachment.php differentiate legacy vs vB5 attachments.
					'attachmentid'  => - intval($conversation['nodeid']),
					'dateline' 		=> $conversation['publishdate'],
					'thumbnail_width'  => '',
					'thumbnail_height' => '',
				);

				if ($itemSection == 'socialgroup')
				{
					$activitybit['groupinfo'] = array(
						'albumid' => $conversation['parentid'],
					);
				}
				else
				{
					$activitybit['albuminfo'] = array(
						'albumid' => $conversation['parentid'],
					);
				}
			}
			else
			{
				// Section specific info
				switch ($itemSection) {
				case 'forum':
					// @TODO: This is inside the special channel and not here, but at this moment the search api doesn't even return vm's.
					// Need to set up a filter for those or just a flag of ijf we should include or not.
					if (!empty($node['setfor']))
					{
						$activitybit['messageinfo'] = array(
							'vmid' 	  => $node['nodeid'],
							'preview' => $node['preview'],
						);

						$activitybit['userinfo2'] = array(
							'userid'   => $node['setfor'],
							'username' => vB_Library::instance('user')->fetchUserName($node['setfor']),
						);
					}
					else
					{
						$isPost = false;

						if ($node['nodeid'] == $node['starter'])
						{
							$forumInfo  = vB_Library::instance('node')->getNodeBare($node['parentid']);
							$threadInfo = $node;

							$forumid  =	(int)$node['parentid'];
							$threadid = $postid = (int)$node['nodeid'];
							$threadPreview = $postPreview = $node['content']['rawtext'];
						}
						else
						{
							$threadInfo = vB_Library::instance('node')->getNode($node['starter']);
							$forumInfo  = vB_Library::instance('node')->getNodeBare($threadInfo['parentid']);

							$forumid  =	(int)$forumInfo['nodeid'];
							$threadid = (int)$threadInfo['nodeid'];
							$postid   = (int)$node['nodeid'];
							$threadPreview = $threadInfo['content']['rawtext'];
							$postPreview   = $node['content']['rawtext'];
							$isPost = true;
						}

						$activitybit['foruminfo'] = array(
							'forumid' => $forumid,
							'title'   => vB_String::unHtmlSpecialChars($forumInfo['title']),
						);

						$activitybit['threadinfo'] = array(
							'title'		 => vB_String::unHtmlSpecialChars($threadInfo['title']),
							'forumid'	 => $forumid,
							'replycount' => $threadInfo['totalcount'],
							'views'		 => (isset($threadInfo['content']['views']) ? $threadInfo['content']['views'] : 0),
							'preview' => vB_Library::instance('vb4_functions')->getPreview($threadPreview),
						);

						if ($node['contenttypeid'] == vB_Types::instance()->getContentTypeID('vBForum_Poll'))
						{
							$activitybit['threadinfo']['pollid'] = $node['nodeid'];
						}
						else
						{
							$activitybit['threadinfo']['threadid'] = $threadid;
						}

						if ($isPost)
						{
							$activitybit['postinfo'] = array(
								'postid'   => $postid,
								'threadid' => $threadid,
								'preview' => vB_Library::instance('vb4_functions')->getPreview($postPreview),
							);
						}

						$activitybit['show'] = array(
							'threadcontent' => true,
						);
					}
					break;

				case 'blog':
					$topChannels = $this->getTopChannels();
					$blogComment = false;

					if ($node['parentid'] == $topChannels['blogs'])
					{
						// if it is a blog dont include it
						break;
					}
					elseif ($node['nodeid'] == $node['starter'])
					{
						// blog entry
						$blogentryInfo  = $node;
						$blogInfo  		= vB_Library::instance('node')->getNodeBare($node['parentid']);
					}
					else
					{
						// blog comment
						$blogentryInfo  = vB_Library::instance('node')->getNode($node['parentid']);
						$blogInfo  		= vB_Library::instance('node')->getNodeBare($blogentryInfo['parentid']);
						$blogComment = true;
					}

					$activitybit['bloginfo'] = array(
						'blogid' => $blogentryInfo['nodeid'],
						'title'  => vB_String::unHtmlSpecialChars($blogentryInfo['title']),
						'blog_title' => vB_String::unHtmlSpecialChars($blogInfo['title']),
						// TODO: This doesn't seem right but I'm not going to go search around
						// for this at this point. Should this be blogentryInfo['textcount'] instead?
						'comments_visible' => $blogInfo['textcount'],
						// Let's just go with the blog entry's views. We only keep track of starter views.
						'views' => (isset($blogentryInfo['content']['views']) ? $blogentryInfo['content']['views'] : 0 ),
						'preview' => vB_Library::instance('vb4_functions')->getPreview($blogentryInfo['content']['rawtext']),
					);

					if ($blogComment)
					{
						$activitybit['blogtextinfo'] = array(
							'blogtextid' => $node['nodeid'],
							'preview' => vB_Library::instance('vb4_functions')->getPreview($node['content']['rawtext']),
						);
					}
					break;

				case 'socialgroup':
					$topChannels = $this->getTopChannels();
					$discussionInfo = false;
					$messageinfo = false;

					if ($node['parentid'] == $topChannels['groups'])
					{
						// group
						$groupInfo = $node;
					}
					elseif ($node['nodeid'] == $node['starter'])
					{
						// group discussion
						$discussionInfo = $node;
						$groupInfo 		= vB_Library::instance('node')->getNodeBare($node['parentid']);
					}
					else
					{
						// group message
						$discussionInfo = vB_Library::instance('node')->getNodeBare($node['parentid']);
						$groupInfo 		= vB_Library::instance('node')->getNodeBare($discussionInfo['parentid']);
						$messageInfo 	= $node;
					}

					$activitybit['groupinfo'] = array(
						'groupid' => $groupInfo['nodeid'],
						'name'    => $groupInfo['title'],
					);

					if ($discussionInfo)
					{
						$activitybit['discussioninfo'] = array(
							'discussionid' => $discussionInfo['nodeid'],
							'title'		 => $discussionInfo['title'],
							'preview' => vB_Library::instance('vb4_functions')->getPreview($discussionInfo['content']['rawtext']),
							'visible'	 => 1,
						);
					}

					if ($messageInfo)
					{
						$activitybit['messageinfo'] = array(
							'gmid'     => $messageInfo['nodeid'],
							'preview' => vB_Library::instance('vb4_functions')->getPreview($discussionInfo['content']['rawtext']),
						);
					}

					break;

				case 'album':
					// Only show albums, not individual photos in albums.
					// For some reason, the search can return individual photos
					// added later to the album.
					if (!($node['nodeid'] == $node['starter']))
					{
						$album = vB_Library::instance('node')->getNodeFullContent($node['starter']);
						if (empty($album[$node['starter']]))
						{
							// This shouldn't happen AFAIK. Maybe if there's data corruption/incomplete cleanup?
							continue 2;
						}
						$album = vB_Api::instance('node')->mergeNodeviewsForTopics($album);
						$album = $album[$node['starter']];
					}
					else
					{
						$album = $node;
					}

					// There are some serious inconsistencies between getNodeFullContent() and
					// returned node array from search results...
					if (!isset($album['photo']) AND isset($album['content']['photo']))
					{
						$album['photo'] = $album['content']['photo'];
					}
					if (!isset($album['photopreview']) AND isset($album['content']['photopreview']))
					{
						$album['photopreview'] = $album['content']['photopreview'];
					}
					if (!isset($album['photocount']) AND isset($album['content']['photocount']))
					{
						$album['photocount'] = $album['content']['photocount'];
					}


					// Use actual thumbnail size if thumbnail is found.
					// TODO: include this as part of the regular 'photo' array info?
					$filedataids = array();
					foreach ($album['photopreview'] AS $key => $photo)
					{
						$filedataids[$photo['filedataid']] = $photo['filedataid'];
					}
					$thumbInfoQuery = vB::getDbAssertor()->getRows(
						'vBForum:filedataresize',
						array(
							vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
							vB_dB_Query::COLUMNS_KEY => array('filedataid', 'resize_width', 'resize_height'),
							vB_dB_Query::CONDITIONS_KEY => array(
								'resize_type' => vB_Api_Filedata::SIZE_THUMB,
								'filedataid' => $filedataids
							),
						)
					);
					$thumbInfo = array();
					foreach ($thumbInfoQuery AS $row)
					{
						$thumbInfo[$row['filedataid']] = $row;
					}

					/*
						Since this is an "album", let's just ignore any attachments that
						the user might've added to the post. Especially since clicking on
						an album doesn't show the post, just the photos in the gallery.
						vB4 didn't have a "gallery", so it didn't have this issue.
					*/

					$activitybit['albuminfo'] = array(
						'albumid' => $album['nodeid'],
						'title'		 => $album['title'],
						// Both of these are missing in the examples in wiki
						'picturecount'	 => $album['photocount'],
						'views'	 => (isset($album['content']['views']) ? $album['content']['views'] : 0),
					);
					$activitybit['photocount'] = $album['photocount'];
					$activitybit['attach'] = array();

					// Let's just use the photo preview for this view. It never shows more than 3 photos anyways.
					foreach ($album['photopreview'] as $photo) {
						$thumburl = $bbcodeParserInstance->getAttachmentLink($photo, vB_Api_Filedata::SIZE_THUMB);
						$filedataid = $photo['filedataid'];
						if (isset($thumbInfo[$filedataid]))
						{
							$thumbwidth = $thumbInfo[$filedataid]['resize_width'];
							$thumbheight = $thumbInfo[$filedataid]['resize_height'];
						}
						else
						{
							$thumbwidth = $photo['width'];
							$thumbheight = $photo['height'];
						}

						$activitybit['attach'][] = array(
							// Hacky hack to let attachment.php know that this negative attachmentid is a vB5 attachment
							// not legacy.
							'attachmentid'  => - intval($photo['nodeid']),
							'thumburl' => $thumburl,
							'dateline'		=> $photo['dateline'],
							'thumbnail_width'	 => $thumbwidth,
							'thumbnail_height'	 => $thumbheight,
						);
					}

					break;

				default:
					break;
				}
			}

			$userInfo = vB_Library::instance('user')->fetchUserinfo($node['userid'], array('avatar'));

			$activitybit['userinfo'] = array(
				'userid'   => $userInfo['userid'],
				'username' => $userInfo['username'],
				'avatarurl'  => vB_Library::instance('vb4_functions')->avatarUrl($userInfo['userid']),
				'showavatar' => $userInfo['showavatars'],
			);

			$activitybits[] = $activitybit;
		}

		return $activitybits;
	}

	/**
	 * Determins the section of the node. Map it to the vb4 sections
	 *
	 * @param  [array]  $node    [The node that needs to be mapped]
	 * @return [string] $section [The section]
	 */
	private function getSection($node)
	{
		$section = '';

		$topChannels = $this->getTopChannels();

		if (in_array($topChannels['forum'], $node['parents']))
		{
			$section = 'forum';
		}
		else if (in_array($topChannels['blog'], $node['parents']))
		{
			$section = 'blog';
		}
		else if (in_array($topChannels['groups'], $node['parents']))
		{
			$section = 'socialgroup';
		}
		else if (in_array($topChannels['special'], $node['parents']))
		{
			// There are more than just "albums" in the special channel. For ex. PMs, reports, infractions...
			if (isset($node['content']['channeltype']) AND $node['content']['channeltype'] == 'album')
			{
				$section = 'album';
			}
			else if (!isset($node['content']['channeltype']))
			{
				$channelAPI = vB_Api::instance('content_channel');
				$albumChannelId = $channelAPI->fetchChannelIdByGUID(vB_Channel::ALBUM_CHANNEL);
				if (in_array($albumChannelId, $node['parents']))
				{
					$section = 'album';
				}
			}
		}

		if (empty($section))
		{
			// @TODO: This must not happen. Or this should throw an error.
			// UPDATE: this can happen because we don't exclude the special channel
			// which might have more than just "albums" in it.
			// Let's change the behavior to have the parser ignore anything with
			// an empty section.
			// We might be able to narrow down the result set if the 'channel' filter
			// allows for multiples and we specify all the highest level channels
			// (ex. Forum, Article, Blog, Social Group?, Album)
		}

		return $section;
	}

	/**
	 * Gets the activity type for that section
	 *
	 * @param  [string] $section
	 * @param  [array]  $node
	 * @return [string]
	 */
	private function getActivityType($section, $node)
	{
		$type = '';

		switch ($section) {
		case 'forum':
			if (!empty($node['setfor']))
			{
				// @TODO: This will not happen. In vb5 in order to get visitor messages you need to pass a flag,
				// so we probably want to set this as filter
				$type = 'visitormessage';
			}
			elseif ($node['nodeid'] == $node['starter'])
			{
				$type = 'thread';
			}
			else
			{
				$type = 'post';
			}
			break;
		case 'blog':
			if ($node['nodeid'] == $node['starter'])
			{
				$type = 'entry';
			}
			else
			{
				$type = 'comment';
			}
			break;
		case 'socialgroup':
			$topChannels = $this->getTopChannels();

			if ($topChannels['groups'] == $node['parentid'])
			{
				$type = 'group';
			}
			elseif ($node['nodeid'] == $node['starter'])
			{
				$type = 'discussion';
			}
			else
			{
				$type = 'groupmessage';
			}
			break;
		case 'album':
			// The other options (comment and album) are not applicable
			$type = 'photo';
			break;
		default:
			// @TODO: This should not happen or it should throw an error
			break;
		}

		return $type;
	}

	/**
	 * Returns the top channels
	 *
	 * @return [array] [Top channels array]
	 */
	private function getTopChannels()
	{
		if (empty($this->topChannels))
		{
			$this->topChannels = vB_Api::instance('content_channel')->fetchTopLevelChannelIds();
		}

		return $this->topChannels;
	}
}

/*=========================================================================*\
|| #######################################################################
|| # Downloaded: 05:43, Thu May 26th 2016
|| # CVS: $RCSfile$ - $Revision: 84898 $
|| #######################################################################
\*=========================================================================*/
