<?php
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.8.12 by Ahmed Amer
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2017 vBulletin Solutions Inc. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| #        www.vbulletin.com | www.vbulletin.com/license.html        # ||
|| #################################################################### ||
\*======================================================================*/

if (!class_exists('vB_DataManager'))
{
	exit;
}

/**
* Class to do data save/delete operations for profile messages
*
* @package	vBulletin
* @version	$Revision: 92984 $
* @date		|| # Date: 2026-01-21 by Ahmed Amer $
*
*/
class vB_DataManager_VisitorMessage extends vB_DataManager
{
	/**
	* Array of recognised and required fields for visitormessage, and their types
	*
	* @var	array
	*/
	var $validfields = array(
		'vmid'           => array(TYPE_UINT,       REQ_INCR, VF_METHOD, 'verify_nonzero'),
		'userid'         => array(TYPE_UINT,       REQ_YES,  VF_METHOD, 'verify_profileuserid'),
		'postuserid'     => array(TYPE_UINT,       REQ_NO,   VF_METHOD, 'verify_userid'),
		'postusername'   => array(TYPE_NOHTMLCOND, REQ_NO,   VF_METHOD, 'verify_username'),
		'dateline'       => array(TYPE_UNIXTIME,   REQ_AUTO),
		'state'          => array(TYPE_STR,        REQ_NO),
		'title'          => array(TYPE_NOHTMLCOND, REQ_NO,   VF_METHOD),
		'pagetext'       => array(TYPE_STR,        REQ_YES,  VF_METHOD),
		'ipaddress'      => array(TYPE_STR,        REQ_AUTO, VF_METHOD),
		'allowsmilie'    => array(TYPE_UINT,       REQ_NO),
		'reportthreadid' => array(TYPE_UINT,       REQ_NO),
		'messageread'    => array(TYPE_BOOL,       REQ_NO),
	);

	/**
	* Condition for update query
	*
	* @var	array
	*/
	var $condition_construct = array('vmid = %1$s', 'vmid');

	/**
	* The main table this class deals with
	*
	* @var	string
	*/
	var $table = 'visitormessage';

	/**
	* Verifies the title is valid and sets up the title for saving (wordwrap, censor, etc).
	*
	* @param	string	Title text
	*
	* @param	bool	Whether the title is valid
	*/
	function verify_title(&$title)
	{
		// replace html-encoded spaces with actual spaces
		$title = preg_replace('/&#(0*32|x0*20);/', ' ', $title);

		$title = trim($title);

		if ($this->registry->options['titlemaxchars'] AND $title != $this->existing['title'])
		{
			if (!empty($this->info['show_title_error']))
			{
				if (($titlelen = vbstrlen($title)) > $this->registry->options['titlemaxchars'])
				{
					// title too long
					$this->error('title_toolong', $titlelen, $this->registry->options['titlemaxchars']);
					return false;
				}
			}
			else if (empty($this->info['is_automated']))
			{
				// not showing the title length error, just chop it
				$title = vbchop($title, $this->registry->options['titlemaxchars']);
			}
		}

		require_once(DIR . '/includes/functions_newpost.php');
		// censor, remove all caps subjects, and htmlspecialchars title
		$title = fetch_no_shouting_text(fetch_censored_text($title));

		// do word wrapping
		$title = fetch_word_wrapped_string($title);

		return true;
	}

	/**
	* Constructor - checks that the registry object has been passed correctly.
	*
	* @param	vB_Registry	Instance of the vBulletin data registry object - expected to have the database object as one of its $this->db member.
	* @param	integer		One of the ERRTYPE_x constants
	*/
	function __construct(&$registry, $errtype = ERRTYPE_STANDARD)
	{
		parent::__construct($registry, $errtype);

		($hook = vBulletinHook::fetch_hook('visitormessagedata_start')) ? eval($hook) : false;
	}


	/**
	 * Code to run before saving a Visitor Message
	 *
	 * @param	booleaan	Should we actually do the query?
	 *
	 */
	function pre_save($doquery = true)
	{
		if ($this->presave_called !== null)
		{
			return $this->presave_called;
		}

		$this->set('ipaddress', 0, false); // We set this properly later

		if (!$this->condition)
		{
			if ($this->fetch_field('state') === null)
			{
				$this->set('state', 'visible');
			}

			if ($this->fetch_field('dateline') === null)
			{
				$this->set('dateline', TIMENOW);
			}

			if ($this->fetch_field('ipaddress') === null)
			{
				$this->set('ipaddress', ($this->registry->options['logip'] ? IPADDRESS : ''));
			}

			if (!$this->info['preview'])
			{
				if (($this->registry->options['floodchecktime'] > 0 AND empty($this->info['is_automated']) AND $this->fetch_field('postuserid') AND $this->is_flooding()) OR $this->is_duplicate())
				{
					return false;
				}
			}

			// Posting to own profile, lets assume we've read it
			if ($this->fetch_field('userid') == $this->registry->userinfo['userid'])
			{
				$this->set('messageread', true);
			}
		}

		if (!$this->verify_image_count('pagetext', 'allowsmilie', 'socialmessage'))
		{
			return false;
		}

		// New posts that aren't automated and are visible should be scanned
		if (!$this->condition AND !empty($this->registry->options['vb_antispam_key']) AND empty($this->info['is_automated']) AND $this->fetch_field('state') == 'visible' AND (!$this->registry->options['vb_antispam_posts'] OR $this->info['user']['posts'] < $this->registry->options['vb_antispam_posts']) AND !can_moderate())
		{
			require_once(DIR . '/includes/class_akismet.php');
			$akismet = new vB_Akismet($this->registry);
			$akismet->akismet_board = $this->registry->options['bburl'];
			$akismet->akismet_key = $this->registry->options['vb_antispam_key'];
			if ($akismet->verify_text(array('user_ip' => IPADDRESS, 'user_agent' => USER_AGENT, 'comment_type' => 'post', 'comment_author' => ($this->info['user']['userid'] ? $this->info['user']['username'] : $this->fetch_field('postusername')), 'comment_author_email' => $this->info['user']['email'], 'comment_author_url' => $this->info['user']['homepage'], 'comment_content' => $this->fetch_field('title') . ' : ' . $this->fetch_field('pagetext'))) === 'spam')
			{
				$this->set('state', 'moderation');
				$this->spamlog_insert = true;
			}
		}

		if (in_coventry($this->fetch_field('postuserid'), true))
		{
			$this->set('messageread', true);
		}

		$return_value = true;
		($hook = vBulletinHook::fetch_hook('visitormessagedata_presave')) ? eval($hook) : false;

		$this->presave_called = $return_value;
		return $return_value;
	}

	/**
	 * Code to delete a Visitor Message
	 *
	 * @return	Whether this code successfully completed
	 *
	 */
	function delete($doquery = true)
	{
		if ($vmid = $this->existing['vmid'])
		{
			$db =& $this->registry->db;

			if ($this->info['hard_delete'])
			{
				$db->query_write("
					DELETE FROM " . TABLE_PREFIX . "deletionlog WHERE primaryid = $vmid AND type = 'visitormessage'
				");

				$db->query_write("
					DELETE FROM " . TABLE_PREFIX . "visitormessage WHERE vmid = $vmid
				");

				$db->query_write("
					DELETE FROM " . TABLE_PREFIX . "moderation WHERE primaryid = $vmid AND type = 'visitormessage'
				");

				$db->query_write("
					DELETE FROM " . TABLE_PREFIX . "ipaddress WHERE contenttype = 'visitormessage' AND contentid = $vmid
				");
			}
			else
			{
				$this->set('state', 'deleted');
				$this->save();

				$deletionman = datamanager_init('Deletionlog_VisitorMessage', $this->registry, ERRTYPE_SILENT, 'deletionlog');
				$deletionman->set('primaryid', $vmid);
				$deletionman->set('type', 'visitormessage');
				$deletionman->set('userid', $this->registry->userinfo['userid']);
				$deletionman->set('username', $this->registry->userinfo['username']);
				$deletionman->set('reason', $this->info['reason']);
				$deletionman->save();
				unset($deletionman);
			}

			if (!$this->info['profileuser'])
			{
				$this->info['profileuser'] = fetch_userinfo($this->existing['userid']);
			}

			if ($this->info['profileuser'])
			{
				build_visitor_message_counters($this->info['profileuser']['userid']);
			}

			$db->query_write("
				DELETE FROM " . TABLE_PREFIX . "moderation WHERE primaryid = $vmid AND type = 'visitormessage'
			");

			return true;
		}

		return false;
	}

	/**
	* Code to run after deleting a Visitor Message
	*
	* @param	boolean	Do the query?
	*/
	function post_delete($doquery = true)
	{
		($hook = vBulletinHook::fetch_hook('visitormessagedata_delete')) ? eval($hook) : false;
	}


	/**
	* Code to run after Saving a Visitor Message
	*
	* @param	boolean	Do the query?
	*/
	function post_save_once($doquery = true)
	{
		$vmid = intval($this->fetch_field('vmid'));

		if (!$this->condition)
		{
			if ($this->fetch_field('userid'))
			{
				$this->insert_dupehash($this->fetch_field('userid'));
			}
		}

		if (!$this->info['profileuser'])
		{
			$this->info['profileuser'] = fetch_userinfo($this->fetch_field('userid'));
		}

		if ($this->info['profileuser'] AND !in_coventry($this->fetch_field('postuserid'), true))
		{
			$userdata = datamanager_init('User', $this->registry, ERRTYPE_SILENT);
			$userdata->set_existing($this->info['profileuser']);

			if ($this->fetch_field('state') == 'visible')
			{
				if (!$this->condition AND !$this->fetch_field('messageread'))
				{
					// new vm, not been read, visible -> increase unread count
					$userdata->set('vmunreadcount', 'vmunreadcount + 1', false);
				}
				else if ($this->condition AND $this->fetch_field('messageread') AND isset($this->existing['messageread']) AND !$this->existing['messageread'])
				{
					// existing vm going from unread to read -> decrease unread count
					// isset() check ensures that messageread info was explicitly passed in
					$userdata->set('vmunreadcount', 'vmunreadcount - 1', false);
				}
			}

			if ($this->fetch_field('state') == 'visible' AND $this->existing['state'] == 'moderation')
			{
				// moderated message made visible -> decrease moderated count
				$userdata->set('vmmoderatedcount', 'vmmoderatedcount - 1', false);
			}
			else if ($this->fetch_field('state') == 'moderation' AND $this->fetch_field('state') != $this->existing['state'])
			{
				// message is moderated and wasn't moderated before -> increase moderated count
				$userdata->set('vmmoderatedcount', 'vmmoderatedcount + 1', false);
			}

			$userdata->save();
		}

		if ($this->fetch_field('state') == 'moderation')
		{
			/*insert query*/
			$this->dbobject->query_write("
				INSERT IGNORE INTO " . TABLE_PREFIX . "moderation
					(primaryid, type, dateline)
				VALUES
					($vmid, 'visitormessage', " . TIMENOW . ")
			");
		}
		else if ($this->fetch_field('state') == 'visible' AND $this->existing['state'] == 'moderation')
		{
			// message was made visible, remove the moderation record
			$this->dbobject->query_write("
				DELETE FROM " . TABLE_PREFIX . "moderation
				WHERE primaryid = $vmid AND type = 'visitormessage'
			");
		}

		($hook = vBulletinHook::fetch_hook('visitormessagedata_postsave')) ? eval($hook) : false;
	}

	/**
	* Verifies that the specified user exists
	*
	* @param	integer	User ID
	*
	* @return 	boolean	Returns true if user exists
	*/
	function verify_userid(&$userid)
	{
		if ($userid == $this->registry->userinfo['userid'])
		{
			$this->info['user'] =& $this->registry->userinfo;
			$return = true;
		}
		else if ($userinfo = fetch_userinfo($userid))
		{	// This case should hit the cache most of the time
			$this->info['user'] =& $userinfo;
			$return = true;
		}
		else
		{
			$this->error('no_users_matched_your_query');
			$return = false;
		}

		if ($return)
		{
				$this->do_set('postusername', $this->info['user']['username']);
		}

		return $return;
	}

	/**
	* Verifies that the specified user exists for the profile
	*
	* @param	integer	User ID
	*
	* @return 	boolean	Returns true if user exists
	*/
	function verify_profileuserid(&$userid)
	{
		if ($userid == $this->registry->userinfo['userid'])
		{
			$this->info['profileuser'] =& $this->registry->userinfo;
			$return = true;
		}
		else if ($userinfo = fetch_userinfo($userid))
		{	// This case should hit the cache most of the time
			$this->info['profileuser'] =& $userinfo;
			$return = true;
		}
		else
		{
			$this->error('no_users_matched_your_query');
			$return = false;
		}

		return $return;
	}

	/**
	* Verifies the page text is valid and sets it up for saving.
	*
	* @param	string	Page text
	*
	* @param	bool	Whether the text is valid
	*/
	function verify_pagetext(&$pagetext, $noshouting = true)
	{
		if (empty($this->info['is_automated']))
		{
			if ($this->registry->options['vm_maxchars'] != 0 AND ($postlength = vbstrlen($pagetext)) > $this->registry->options['vm_maxchars'])
			{
				$this->error('toolong', $postlength, $this->registry->options['vm_maxchars']);
				return false;
			}

			$this->registry->options['postminchars'] = intval($this->registry->options['postminchars']);
			if ($this->registry->options['postminchars'] <= 0)
			{
				$this->registry->options['postminchars'] = 1;
			}
			if (vbstrlen(strip_bbcode($pagetext, $this->registry->options['ignorequotechars'])) < $this->registry->options['postminchars'])
			{
				$this->error('tooshort', $this->registry->options['postminchars']);
				return false;
			}
		}

		return parent::verify_pagetext($pagetext, $noshouting);

	}

	/**
	 * Determines whether the message being posted would constitute flooding
	 *
	 * @return      boolean Is this classed as flooding?
	 *
	 */
	function is_flooding()
	{
		$floodmintime = TIMENOW - $this->registry->options['floodchecktime'];
		if (!can_moderate() AND $this->fetch_field('dateline') > $floodmintime)
		{
			$flood = $this->registry->db->query_first("
				SELECT dateline
				FROM " . TABLE_PREFIX . "visitormessage_hash
				WHERE postuserid = " . $this->fetch_field('postuserid') . "
					AND dateline > " . $floodmintime . "
				ORDER BY dateline DESC
				LIMIT 1
			");
			if ($flood)
			{
				$this->error(
					'postfloodcheck',
					$this->registry->options['floodchecktime'],
					($flood['dateline'] - $floodmintime)
				);
				return true;
			}
		}

		return false;
	}

	/**
	 * Is this a duplicate post of a message posted with the last 5 minutes?
	 *
	 * @return      boolean whether this is a duplicate post or not
	 *
	 */
	function is_duplicate()
	{
		$dupemintime = TIMENOW - 300;
		if ($this->fetch_field('dateline') > $dupemintime)
		{
			// ### DUPE CHECK ###
			$dupehash = md5($this->fetch_field('userid') . $this->fetch_field('pagetext') . $this->fetch_field('postuserid'));
			if ($dupe = $this->registry->db->query_first("
				SELECT hash.userid, user.username
				FROM " . TABLE_PREFIX . "visitormessage_hash AS hash
				LEFT JOIN " . TABLE_PREFIX . "user AS user USING (userid)
				WHERE hash.postuserid = " . $this->fetch_field('postuserid') . " AND
					hash.dupehash = '" . $this->registry->db->escape_string($dupehash) . "' AND
					hash.dateline > " . $dupemintime . "
			"))
			{
				// Do we want to only check for the post for this same user, or for all users???
				if ($dupe['userid'] == $this->fetch_field('userid'))
				{
					$this->error('duplicate_post');
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Inserts a hash into the database for use with duplicate checking
	 *
	 * @param       integer Group ID (-1 for current group from DM)
	 *
	 */
	function insert_dupehash($userid = -1)
	{
		if ($userid == -1)
		{
			$userid = $this->fetch_field('userid');
		}

		$postuserid = $this->fetch_field('postuserid');

		$dupehash = md5($userid . $this->fetch_field('pagetext') . $postuserid);
		/*insert query*/
		$this->dbobject->query_write("
			INSERT INTO " . TABLE_PREFIX . "visitormessage_hash
			(postuserid, userid, dupehash, dateline)
			VALUES
			(" . intval($postuserid) . ", " . intval($userid) . ", '" . $dupehash . "', " . TIMENOW . ")
		");
	}

	/**
	* Additional data to update after a save call (such as denormalized values in other tables).
	* In batch updates, is executed for each record updated.
	*
	* @param	boolean	Do the query?
	*/
	function post_save_each($doquery = true)
	{
		$this->save_ipaddress();

		return true;
	}

	/**
	* Save the IP Address Data.
	*
	* @param	type : The contents type 
	* @param	fieldid : The content type column id
	*
	* @return	boolean	True on success; false if an error occurred
	*/
	function save_ipaddress($type = 'visitormessage', $fieldid = 'vmid')
	{
		$ipman = datamanager_init('IPAddress', $this->registry, ERRTYPE_STANDARD);
		$contentid = $this->{$type}[$fieldid] ? $this->{$type}[$fieldid] : $this->existing[$fieldid];

		$ipman->set('contenttype', $type);
		$ipman->set('contentid', $contentid);

		$ipid = $ipman->save();

		if (empty($ipid))
		{
			$return = false;
		}
		else
		{
			$return = $ipman->update_content($type, $fieldid, $ipid, $contentid);
		}

		unset($ipman);
		return $return;
	}
}
/*======================================================================*\
|| ####################################################################
|| # by Ahmed Amer
|| # || # Date: 2026-01-21 by Ahmed Amer $
|| ####################################################################
\*======================================================================*/
?>
