<?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 EVENTS
*
*
* @package	vBulletin
* @version	$Revision: 92875 $
* @date		Date: 2026-01-21 by Ahmed Amer $
*/
class vB_DataManager_Event extends vB_DataManager
{
	/**
	* Array of recognized and required fields for events
	*
	* @var	array
	*/
	var $validfields = array(
		'eventid'       => array(TYPE_UINT,      REQ_INCR, 'return ($data > 0);'),
		'userid'        => array(TYPE_UINT,      REQ_YES,  VF_METHOD),
		'event'         => array(TYPE_STR,       REQ_YES,  VF_METHOD),
		'title'         => array(TYPE_STR,       REQ_YES,  VF_METHOD),
		'allowsmilies'  => array(TYPE_UINT,      REQ_YES),
		'recurring'     => array(TYPE_UINT,      REQ_YES),
		'recuroption'   => array(TYPE_STR,       REQ_YES),
		'calendarid'    => array(TYPE_UINT,      REQ_YES,  VF_METHOD),
		'customfields'  => array(TYPE_ARRAY_STR, REQ_NO,   VF_METHOD, 'verify_serialized'),
		'visible'       => array(TYPE_UINT,      REQ_NO),
		'utc'           => array(TYPE_NUM,       REQ_YES,  VF_METHOD),
		'dst'           => array(TYPE_UINT,      REQ_NO),
		'dateline'      => array(TYPE_UNIXTIME,  REQ_AUTO),
		'dateline_from' => array(TYPE_UINT,      REQ_YES),
		'dateline_to'   => array(TYPE_UINT,      REQ_YES),
	);

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

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

	/**
	* Condition for verifying date. Set to false if you wish to update data without verifying if the date/time is valid
	*
	* @var bool
	*/
	var $verify_datetime = 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('eventdata_start')) ? eval($hook) : false;
	}

	/**
	* Verifies that the specified calendar exists
	*
	* @param	integer	Calendar ID
	*
	* @return 	boolean	Returns true if calendar exists
	*/
	function verify_calendarid(&$calendarid)
	{
		if ($this->dbobject->query_first("SELECT calendarid FROM " . TABLE_PREFIX . "calendar WHERE calendarid = $calendarid"))
		{
			return true;
		}
		else
		{
			$this->error('no_calendars_matched_your_query');
			return false;
		}

	}

	/**
	* Verifies that the title is valid
	*
	* @param	String	Title
	*
	* @return 	boolean	Returns true if title is valid
	*/
	function verify_title(&$title)
	{

		$title = fetch_censored_text($title);

		// replace html-encoded spaces with actual spaces
		$title = preg_replace('/&#(0*32|x0*20);/', ' ', $title);

		// do word wrapping
		if ($this->registry->options['wordwrap'] != 0)
		{
			$title = fetch_word_wrapped_string($title);
		}

		// remove all caps subjects
		require_once(DIR . '/includes/functions_newpost.php');
		$title = fetch_no_shouting_text($title);

		$title = trim($title);

		if (empty($title))
		{
			$this->error('invalid_title_specified');
			return false;
		}

		return true;
	}

	/**
	* Verifies that a valid timezoneoffset has been specified
	*
	* @param	float	Event timezoneoffset
	*
	* @return	boolean	Returns true if the timezone is valid
	*/
	function verify_utc(&$timezoneoffset)
	{
		require_once(DIR . '/includes/functions_misc.php');
		if (fetch_timezone($timezoneoffset))
		{
			return true;
		}
		else
		{
			$this->error('invalid_timezone_specified');
			return false;
		}
	}

	/**
	* 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_event(&$pagetext)
	{
		if ($this->registry->options['postmaxchars'] != 0 AND ($postlength = vbstrlen($pagetext)) > $this->registry->options['postmaxchars'])
		{
			$this->error('toolong', $postlength, $this->registry->options['postmaxchars']);
			return false;
		}

		return $this->verify_pagetext($pagetext);
	}

	/**
	* Selected values for custom fields defined for the calendar that contains this event
	*
	* @param	array	Customfield data from $_POST
	*/
	function set_userfields(&$userfields)
	{
		if (!($calendarid = $this->fetch_field('calendarid')))
		{
			trigger_error('Calendarid must be set before userfields.', E_USER_ERROR);
		}

		$customcalfields = $this->dbobject->query_read("
			SELECT *
			FROM " . TABLE_PREFIX . "calendarcustomfield
			WHERE calendarid = $calendarid
			ORDER BY calendarcustomfieldid
		");
		$customfields = array();

		while ($custom = $this->dbobject->fetch_array($customcalfields))
		{
			$customfield =& $userfields["f$custom[calendarcustomfieldid]"];
			$optional = vbchop($userfields["o$custom[calendarcustomfieldid]"], $custom['length'] ? $custom['length'] : 255);

			if ($custom['allowentry'] AND !empty($optional))
			{
				$option =& $optional;
			}
			else
			{
				$option =& $customfield;
			}

			if ($custom['required'] AND !$option)
			{
				$this->error('requiredfieldmissing', $custom['title']);
				return false;
			}

			$custom['options'] = vb_unserialize($custom['options']);
			unset($chosenoption);
			if (is_array($custom['options']))
			{
				foreach ($custom['options'] AS $index => $value)
				{
					if ($index == $option)
					{
						$chosenoption = $value;
						break;
					}
				}
			}
			if ($chosenoption == '' AND $custom['allowentry'])
			{
				$chosenoption = htmlspecialchars_uni($optional);
			}
			$customfields["{$custom['calendarcustomfieldid']}"] = $chosenoption;
		}

		$this->set('customfields', $customfields);
	}

	/**
	* Any checks to run immediately before saving. If returning false, the save will not take place.
	*
	* @param	boolean	Do the query?
	*
	* @return	boolean	True on success; false if an error occurred
	*/
	function pre_save($doquery = true)
	{
		if ($this->presave_called !== null)
		{
			return $this->presave_called;
		}

		if (!$this->verify_image_count('event', 'allowsmilies', 'calendar'))
		{
			return false;
		}

		if ($this->verify_datetime)
		{
			if (!checkdate($this->info['fromdate']['month'], $this->info['fromdate']['day'], $this->info['fromdate']['year'])
				OR ($this->info['type'] != 'single' AND !checkdate($this->info['todate']['month'], $this->info['todate']['day'], $this->info['todate']['year'])))
			{
				$this->error('calendarbaddate');
				return false;
			}

			if ($this->info['type'] != 'single')
			{
				// extract the relevant info from from_time and to_time
				if (!empty($this->info['fromtime']['user']) AND empty($this->info['fromtime']['defined']))
				{
					// match text in field for a valid time
					if (preg_match('#^(0?[1-9]|1[012])[:\.]([0-5]\d)(\s*[AP]M)|([01]\d|2[0-3])[:\.]([0-5]\d)$#i', $this->info['fromtime']['user'], $matches))
					{
						if (count($matches) == 4)
						{
							$from_hour = intval($matches[1]);
							$from_minute = intval($matches[2]);
							$from_ampm = strtoupper(trim($matches[3]));
						}
						else // 24hr time
						{
							$from_hour = intval($matches[4]);
							$from_minute = intval($matches[5]);
							$from_ampm = ($from_hour <= 11) ? 'AM' : 'PM';
						}
					}
					else
					{
						$this->error('calendarbadtime');
						return false;
					}
				}
				else if (!empty($this->info['fromtime']['defined']) AND empty($this->info['fromtime']['user']))
				{
					$ft = explode('_', $this->info['fromtime']['defined']);
					$from_hour = intval($ft[0]);
					$from_minute = intval($ft[1]);
					$from_ampm = $ft[2];
				}
				else
				{
					$this->error('calendarbadtime');
					return false;
				}

				if (!empty($this->info['totime']['user']) AND empty($this->info['totime']['defined']))
				{
					// preg match text in field for a valid time
					if (preg_match('#^(0?[1-9]|1[012])[:\.]([0-5]\d)(\s*[AP]M)|([01]\d|2[0-3])[:\.]([0-5]\d)$#i', $this->info['totime']['user'], $matches))
					{
						if (count($matches) == 4)
						{
							$to_hour = intval($matches[1]);
							$to_minute = intval($matches[2]);
							$to_ampm = strtoupper(trim($matches[3]));
						}
						else // 24hr time
						{
							$to_hour = intval($matches[4]);
							$to_minute = intval($matches[5]);
							$to_ampm = ($to_hour <= 11) ? 'AM' : 'PM';
						}
					}
					else
					{
						$this->error('calendarbadtime');
						return false;
					}
				}
				else if (!empty($this->info['totime']['defined']) AND empty($this->info['totime']['user']))
				{
					$tt = explode('_', $this->info['totime']['defined']);
					$to_hour = intval($tt[0]);
					$to_minute = intval($tt[1]);
					$to_ampm = $tt[2];
				}
				else
				{
					$this->error('calendarbadtime');
					return false;
				}

				if (($pos = strpos($this->registry->options['timeformat'], 'H')) === false)
				{
					if ($to_ampm == 'PM')
					{
						if ($to_hour >= 1 AND $to_hour <= 11)
						{
							$to_hour += 12;
						}
					}
					else
					{
						if ($to_hour == 12)
						{
							$to_hour = 0;
						}
					}

					if ($from_ampm == 'PM')
					{
						if ($from_hour >= 1 AND $from_hour <= 11)
						{
							$from_hour += 12;
						}
					}
					else
					{
						if ($from_hour == 12)
						{
							$from_hour = 0;
						}
					}
				}

				$min_offset = $this->fetch_field('utc') - intval($this->fetch_field('utc'));

				$from_hour   -= intval($this->fetch_field('utc'));
				$from_minute -= intval($min_offset * 60);

				$to_hour   -= intval($this->fetch_field('utc'));
				$to_minute -= intval($min_offset * 60);

				$dateline_to = gmmktime($to_hour, $to_minute, 0, $this->info['todate']['month'], $this->info['todate']['day'], $this->info['todate']['year']);
				$dateline_from = gmmktime($from_hour, $from_minute, 0, $this->info['fromdate']['month'], $this->info['fromdate']['day'], $this->info['fromdate']['year']);

				if ($dateline_to < $dateline_from)
				{
					$this->error('calendartodate');
					return false;
				}

				require_once(DIR . '/includes/functions_misc.php');
				$this->set_info('occurdate', vbgmdate('Y-n-j', $dateline_from + intval($this->registry->userinfo['timezoneoffset']) * 3600, false, false));
			}
			else // single day event
			{
				$dateline_to = 0;
				$dateline_from = gmmktime(0, 0, 0, $this->info['fromdate']['month'], $this->info['fromdate']['day'], $this->info['fromdate']['year']);

				require_once(DIR . '/includes/functions_misc.php');
				$this->set_info('occurdate', $occurdate = vbgmdate('Y-n-j', $dateline_from, false, false));
			}

			$this->set('dateline_to', $dateline_to);
			$this->set('dateline_from', $dateline_from);

			$recuroption = '';

			if ($this->info['type'] == 'recur')
			{
				$checkevent = array(
					'eventid'            => 1,
					'dateline_from'      => $dateline_from,
					'dateline_to'        => $dateline_to,
					'dateline_from_user' => $dateline_from + intval($this->registry->userinfo['timezoneoffset']) * 3600,
					'dateline_to_user'   => $dateline_to + intval($this->registry->userinfo['timezoneoffset']) * 3600,
					'recurring'          => $this->fetch_field('recurring'),
					'utc'                => $this->fetch_field('utc'),
				);

				$startday = gmmktime(0, 0, 0, gmdate('n', $checkevent['dateline_from_user']), gmdate('j', $checkevent['dateline_from_user']), gmdate('Y', $checkevent['dateline_from_user']));
				$endday = gmmktime(0, 0, 0, gmdate('n', $checkevent['dateline_to_user']), gmdate('j', $checkevent['dateline_to_user']), gmdate('Y', $checkevent['dateline_to_user']));

				if ($this->info['recur']['pattern'] == 1)
				{
					$recuroption = $this->info['recur']['dailybox'];
				}
				else if ($this->info['recur']['pattern'] == 3)
				{
					if ($this->info['recur']['weeklysun'])
					{
						$daybit = 1;
					}
					if ($this->info['recur']['weeklymon'])
					{
						$daybit += 2;
					}
					if ($this->info['recur']['weeklytue'])
					{
						$daybit += 4;
					}
					if ($this->info['recur']['weeklywed'])
					{
						$daybit += 8;
					}
					if ($this->info['recur']['weeklythu'])
					{
						$daybit += 16;
					}
					if ($this->info['recur']['weeklyfri'])
					{
						$daybit += 32;
					}
					if ($this->info['recur']['weeklysat'])
					{
						$daybit += 64;
					}
					$recuroption = $this->info['recur']['weeklybox'] . '|' . $daybit;
				}
				else if ($this->info['recur']['pattern'] == 4)
				{
					$recuroption = $this->info['recur']['monthly1'] . '|' . $this->info['recur']['monthlybox1'];
				}
				else if ($this->info['recur']['pattern'] == 5)
				{
					$recuroption = $this->info['recur']['monthly2'] . '|' . $this->info['recur']['monthly3'] . '|' . $this->info['recur']['monthlybox2'];
				}
				else if ($this->info['recur']['pattern'] == 6)
				{
					$recuroption = $this->info['recur']['yearly1'] . '|' . $this->info['recur']['yearly2'];
				}
				else if ($this->info['recur']['pattern'] == 7)
				{
					$recuroption = $this->info['recur']['yearly3'] . '|' . $this->info['recur']['yearly4'] . '|' . $this->info['recur']['yearly5'];
				}
				$checkevent['recuroption'] = $recuroption;
				$foundevent = false;
				while ($startday <= $endday)
				{
					$temp = explode('-', gmdate('n-j-Y', $startday));
					if (cache_event_info($checkevent, $temp[0], $temp[1], $temp[2], 0))
					{
						$foundevent = true;
						break;
					}
					$startday += 86400;
				}
				if (!$foundevent)
				{
					$this->error('calendarnorecur');
					return false;
				}
			}

			$this->set('recuroption', $recuroption);

			if ($this->condition === null) # Insert
			{
				if ($query = $this->dbobject->query_first("
					SELECT eventid
					FROM " . TABLE_PREFIX . "event
					WHERE userid = " . intval($this->fetch_field('userid')) . "
						AND dateline_from = " . intval($this->fetch_field('dateline_from')) . "
						AND dateline_to = " . intval($this->fetch_field('dateline_to')) . "
						AND event = '" . $this->dbobject->escape_string($this->fetch_field('event')) . "'
						AND title = '" . $this->dbobject->escape_string($this->fetch_field('title')) . "'
						AND calendarid = " . intval($this->fetch_field('calendarid')) . "
				"))
				{
					$this->error('calendareventexists');
					return false;
				}
				if (!$this->fetch_field('dateline'))
				{
					$this->set('dateline', TIMENOW);
				}
			}
		}

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

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

	/**
	* 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)
	{

		if ($this->condition AND ($this->fetch_field('dateline_from') - $this->existing['dateline_from']) >= 82800)
		{
			// Start date has been pushed up at least 23 hours (this is a bit arbitrary) so reset any reminders that have already been sent
			$this->dbobject->query_write("
				UPDATE " . TABLE_PREFIX . "subscribeevent
				SET lastreminder = 0
				WHERE eventid = " . intval($this->fetch_field('eventid')) . "
			");
		}

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

		return true;
	}

	/**
	* Additional data to update after a save call (such as denormalized values in other tables).
	* In batch updates, is executed once after all records are updated.
	*
	* @param	boolean	Do the query?
	*/
	function post_save_once($doquery = true)
	{
		require_once(DIR . '/includes/functions_calendar.php');
		build_events();

		return parent::post_save_once($doquery);
	}

	/**
	* Additional data to update after a delete call (such as denormalized values in other tables).
	*
	* @param	boolean	Do the query?
	*/
	function post_delete($doquery = true)
	{

		$this->dbobject->query_write("DELETE FROM " . TABLE_PREFIX . "subscribeevent WHERE eventid = " . intval($this->fetch_field('eventid')));

		require_once(DIR . '/includes/functions_calendar.php');
		build_events();

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

		return true;
	}
}

/*======================================================================*\
|| ####################################################################
|| # by Ahmed Amer
|| # Date: 2026-01-21 by Ahmed Amer $
|| ####################################################################
\*======================================================================*/
?>
