<?php
/******************************************************************************
* Calendar.php                                                                *
*******************************************************************************
* SMF: Simple Machines Forum                                                  *
* Open-Source Project Inspired by Zef Hemel (zef@zefhemel.com)                *
* =========================================================================== *
* Software Version:           SMF 1.0.3                                       *
* Software by:                Simple Machines (http://www.simplemachines.org) *
* Copyright 2001-2005 by:     Lewis Media (http://www.lewismedia.com)         *
* Support, News, Updates at:  http://www.simplemachines.org                   *
*******************************************************************************
* This program is free software; you may redistribute it and/or modify it     *
* under the terms of the provided license as published by Lewis Media.        *
*                                                                             *
* This program is distributed in the hope that it is and will be useful,      *
* but WITHOUT ANY WARRANTIES; without even any implied warranty of            *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                        *
*                                                                             *
* See the "license.txt" file for details of the Simple Machines license.      *
* The latest version can always be found at http://www.simplemachines.org.    *
******************************************************************************/
/* Original module by Aaron O'Neil - aaron@mud-master.com                     *
******************************************************************************/
if (!defined('SMF'))
	die('Hacking attempt...');

/*	This file has only one real task... showing the calendar.  Posting is done
	in Post.php - this just has the following functions:

	void CalendarMain()
		- loads the specified month's events, holidays, and birthdays.
		- requires the calendar_view permission.
		- depends on the cal_enabled setting, and many of the other cal_
		  settings.
		- uses the calendar_start_day theme option. (Monday/Sunday)
		- uses the main sub template in the Calendar template.
		- goes to the month and year passed in 'month' and 'year' by
		  get or post.
		- accessed through ?action=calendar.

	array calendarBirthdayArray(string earliest_date, string latest_date)
		- finds all the birthdays in the specified range of days.
		- earliest_date and latest_date are inclusive, and should both be in
		  the YYYY-MM-DD format.
		- works with birthdays set for no year, or any other year, and
		  respects month and year boundaries.
		- returns an array of days, each of which an array of birthday
		  information for the context.

	array calendarEventArray(string earliest_date, string latest_date,
			bool use_permissions = true)
		- finds all the posted calendar events within a date range.
		- both the earliest_date and latest_date should be in the standard
		  YYYY-MM-DD format.
		- censors the posted event titles.
		- uses the current user's permissions if use_permissions is true,
		  otherwise it does nothing "permission specific".
		- returns an array of contextual information if use_permissions is
		  true, and an array of the data needed to build that otherwise.

	array calendarHolidayArray(string earliest_date, string latest_date)
		- finds all the applicable holidays for the specified date range.
		- earliest_date and latest_date should be YYYY-MM-DD.
		- returns an array of days, which are all arrays of holiday names.

	void calendarInsertEvent(int id_board, int id_topic, string title,
			int id_member, int month, int day, int year, int span)
		- inserts the passed event information into the calendar table.
		- recaches the calendar information after doing so.
		- expects the passed title not to have html characters.
		- handles spanned events by inserting them multiple times.
		- does not check any permissions of any sort.

	bool calendarCanLink()
		- checks if the current user can link the current topic to the
		  calendar, permissions et al.
		- this requires the calendar_post permission, a forum moderator, or a
		  topic starter.
		- expects the $topic and $board variables to be set.
		- returns true or false corresponding to whether they can or cannot
		  link this topic to the calendar.
*/

// Show the calendar.
function CalendarMain()
{
	global $txt, $context, $modSettings, $scripturl, $months, $options;

	// This is gonna be needed...
	loadTemplate('Calendar');

	// Permissions, permissions, permissions.
	isAllowedTo('calendar_view');

	// You can't do anything if the calendar is off.
	if (empty($modSettings['cal_enabled']))
		fatal_lang_error('calendar_off', false);

	// Set the page title to mention the calendar ;).
	$context['page_title'] = $context['forum_name'] . ': ' . $txt['calendar24'];

	// Get the current day of month...
	$today = array(
		'day' => (int) strftime('%d', forum_time()),
		'month' => (int) strftime('%m', forum_time()),
		'year' => (int) strftime('%Y', forum_time())
	);

	// If the month and year are not passed in, use today's date as a starting point.
	$curPage = array(
		'month' => isset($_REQUEST['month']) ? (int) $_REQUEST['month'] : $today['month'],
		'year' => isset($_REQUEST['year']) ? (int) $_REQUEST['year'] : $today['year']
	);

	// Make sure the year and month are in valid ranges.
	if ($curPage['month'] < 1 || $curPage['month'] > 12)
		fatal_lang_error('calendar1', false);
	if ($curPage['year'] < $modSettings['cal_minyear'] || $curPage['year'] > $modSettings['cal_maxyear'])
		fatal_lang_error('calendar2', false);

	// Get information about the first day of this month.
	$firstDayOfMonth = array(
		'dayOfWeek' => (int) strftime('%w', mktime(0, 0, 0, $curPage['month'], 1, $curPage['year'])),
		'weekNum' => (int) strftime('%U', mktime(0, 0, 0, $curPage['month'], 1, $curPage['year']))
	);

	// Find the last day of the month.
	$nLastDay = (int) strftime('%d', mktime(0, 0, 0, $curPage['month'] == 12 ? 1 : $curPage['month'] + 1, 0, $curPage['month'] == 12 ? $curPage['year'] + 1 : $curPage['year']));

	// The number of days the first row is shifted to the right for the starting day.
	$nShift = $firstDayOfMonth['dayOfWeek'];

	// Start on Monday or Sunday?
	$bStartMonday = !empty($options['calendar_start_day']) && $options['calendar_start_day'] == 1;

	// Starting on monday?  Move the shift around.
	if ($bStartMonday)
		$nShift = ($nShift == 0 ? 6 : $nShift - 1);

	// Number of rows required to fit the month.
	$nRows = floor(($nLastDay + $nShift) / 7);
	if (($nLastDay + $nShift) % 7)
		$nRows++;

	// Get the lowest and highest days of this month, in YYYY-MM-DD format. ($nLastDay is always 2 digits.)
	$low = $curPage['year'] . '-' . str_pad($curPage['month'], 2, '0', STR_PAD_LEFT) . '-01';
	$high = $curPage['year'] . '-' . str_pad($curPage['month'], 2, '0', STR_PAD_LEFT) . '-' . $nLastDay;

	// Fetch the arrays for birthdays, posted events, and holidays.
	$bday = calendarBirthdayArray($low, $high);
	$events = calendarEventArray($low, $high);
	$holidays = calendarHolidayArray($low, $high);

	// Days of the week taking into consideration that they may want it to start on a monday.
	$context['week_days'] = $bStartMonday ? array(1, 2, 3, 4, 5, 6, 0) : array(0, 1, 2, 3, 4, 5, 6);

	// An adjustment value to apply to all calculated week numbers.
	if (!empty($modSettings['cal_showweeknum']))
	{
		// Need to know what day the first of the year was on.
		$foy = (int) strftime('%w', mktime(0, 0, 0, 1, 1, $curPage['year']));

		// If the first day of the year is on the start day of a week, then there is no adjustment
		// to be made. However, if the first day of the year is not a start day, then there is a partial
		// week at the start of the year that needs to be accounted for.
		$nWeekAdjust = $foy == 0 ? 0 : 1;
	}
	else
		$nWeekAdjust = 0;

	// Basic template stuff.
	$context['can_post'] = allowedTo('calendar_post');
	$context['last_day'] = $nLastDay;
	$context['current_month'] = $curPage['month'];
	$context['current_year'] = $curPage['year'];

	// Load up the linktree!
	$context['linktree'][] = array(
		'url' => $scripturl . '?action=calendar;year=' . $context['current_year'] . ';month=' . $context['current_month'],
		'name' => $months[$context['current_month']] . ' ' . $context['current_year']
	);

	// Iterate through each week.
	for ($nRow = 0; $nRow < $nRows; $nRow++)
	{
		// Start off the week - and don't let it go above 52, since that's the number of weeks in a year.
		$context['weeks'][$nRow] = array(
			'days' => array(),
			'number' => $firstDayOfMonth['weekNum'] + $nRow + $nWeekAdjust
		);
		if ($context['weeks'][$nRow]['number'] == 53)
			$context['weeks'][$nRow]['number'] = 1;

		// And figure out all the days.
		for ($nCol = 0; $nCol < 7; $nCol++)
		{
			$nDay = ($nRow * 7) + $nCol - $nShift + 1;

			if ($nDay < 1 || $nDay > $context['last_day'])
				$nDay = 0;

			$context['weeks'][$nRow]['days'][$nCol] = array(
				'day' => $nDay,
				'is_today' => $today['day'] == $nDay && $today['month'] == $curPage['month'] && $today['year'] == $curPage['year'],
				'is_first_day' => !empty($modSettings['cal_showweeknum']) && ((!$bStartMonday && ($firstDayOfMonth['dayOfWeek'] + $nDay - 1) % 7 == 0) || ($bStartMonday && ($firstDayOfMonth['dayOfWeek'] + $nDay - 1) % 7 == 1)),
				'holidays' => !empty($holidays[$nDay]) ? $holidays[$nDay] : array(),
				'events' => !empty($events[$nDay]) ? $events[$nDay] : array(),
				'birthdays' => !empty($bday[$nDay]) ? $bday[$nDay] : array()
			);
		}
	}

	// Find the previous month. (if we can go back that far.)
	if ($curPage['month'] > 1 || ($curPage['month'] == 1 && $curPage['year'] > $modSettings['cal_minyear']))
	{
		// Need to roll the year back one?
		$context['previous_calendar'] = array(
			'year' => $curPage['month'] == 1 ? $curPage['year'] - 1 : $curPage['year'],
			'month' => $curPage['month'] == 1 ? 12 : $curPage['month'] - 1,
		);
		$context['previous_calendar']['href'] = $scripturl . '?action=calendar;year=' . $context['previous_calendar']['year'] . ';month=' . $context['previous_calendar']['month'];
	}

	// The next month... (or can we go that far?)
	if ($curPage['month'] < 12 || ($curPage['month'] == 12 && $curPage['year'] < $modSettings['cal_maxyear']))
	{
		$context['next_calendar'] = array(
			'year' => $curPage['month'] == 12 ? $curPage['year'] + 1 : $curPage['year'],
			'month' => $curPage['month'] == 12 ? 1 : $curPage['month'] + 1
		);
		$context['next_calendar']['href'] = $scripturl . '?action=calendar;year=' . $context['next_calendar']['year'] . ';month=' . $context['next_calendar']['month'];
	}
}

// This is used by the board index to only find members of the current day. (month PLUS one!)
function calendarBirthdayArray($low_date, $high_date)
{
	global $db_prefix, $scripturl, $modSettings;

	// Birthdays people set without specifying a year (no age, see?) are the easiest ;).
	if (substr($low_date, 0, 4) != substr($high_date, 0, 4))
		$allyear_part = "birthdate BETWEEN '0000" . substr($low_date, 4) . "' AND '0000-12-31'
			OR birthdate BETWEEN '0000-01-01' AND '0000" . substr($high_date, 4) . "'";
	else
		$allyear_part = "birthdate BETWEEN '0000" . substr($low_date, 4) . "' AND '0000" . substr($high_date, 4) . "'";

	// We need to search for any birthday in this range, and whatever year that birthday is on.
	$year_low = (int) substr($low_date, 0, 4);
	$year_high = (int) substr($high_date, 0, 4);

	$result = db_query("
		SELECT DAYOFMONTH(birthdate) AS dom, ID_MEMBER, realName, YEAR(birthdate) AS birthYear, birthdate
		FROM {$db_prefix}members
		WHERE DAYOFYEAR(birthdate) IS NOT NULL
			AND	($allyear_part
				OR DATE_FORMAT(birthdate, '{$year_low}-%m-%d') BETWEEN '$low_date' AND '$high_date'" . ($year_low == $year_high ? '' : "
				OR DATE_FORMAT(birthdate, '{$year_high}-%m-%d') BETWEEN '$low_date' AND '$high_date'") . ")", __FILE__, __LINE__);
	$bday = array();
	while ($row = mysql_fetch_assoc($result))
	{
		if ($year_low != $year_high)
			$age_year = substr($row['birthdate'], 5) < substr($high_date, 5) ? $year_high : $year_low;
		else
			$age_year = $year_low;

		$bday[$row['dom']][] = array(
			'id' => $row['ID_MEMBER'],
			'name' => $row['realName'],
			'age' => $row['birthYear'] > 0 && $row['birthYear'] <= $age_year ? $age_year - $row['birthYear'] : null,
			'is_last' => false
		);
	}
	mysql_free_result($result);

	// Set is_last, so the themes know when to stop placing separators.
	foreach ($bday as $mday => $array)
		$bday[$mday][count($array) - 1]['is_last'] = true;

	return $bday;
}

// Create an array of events occurring in this day/month.
function calendarEventArray($low_date, $high_date, $use_permissions = true)
{
	global $db_prefix, $ID_MEMBER, $scripturl, $modSettings, $user_info, $sc;

	// Find all the calendar info...
	$result = db_query("
		SELECT
			cal.ID_EVENT, DAYOFMONTH(cal.eventDate) AS day, cal.title, cal.ID_MEMBER, cal.ID_TOPIC,
			cal.ID_BOARD, b.memberGroups, t.ID_FIRST_MSG
		FROM {$db_prefix}calendar AS cal, {$db_prefix}boards AS b, {$db_prefix}topics AS t
		WHERE cal.eventDate BETWEEN '$low_date' AND '$high_date'
			AND cal.ID_TOPIC = t.ID_TOPIC
			AND cal.ID_BOARD = b.ID_BOARD" . ($use_permissions ? "
			AND $user_info[query_see_board]" : ''), __FILE__, __LINE__);
	$events = array();
	while ($row = mysql_fetch_assoc($result))
	{
		// Censor the title.
		censorText($row['title']);

		// If we're using permissions (calendar pages?) then just ouput normal contextual style information.
		if ($use_permissions)
			$events[$row['day']][] = array(
				'id' => $row['ID_EVENT'],
				'title' => $row['title'],
				'can_edit' => allowedTo('calendar_edit_any') || ($row['ID_MEMBER'] == $ID_MEMBER && allowedTo('calendar_edit_own')),
				'modify_href' => $scripturl . '?action=post;msg=' . $row['ID_FIRST_MSG'] . ';topic=' . $row['ID_TOPIC'] . '.0;calendar;eventid=' . $row['ID_EVENT'] . ';sesc=' . $sc,
				'href' => $scripturl . '?topic=' . $row['ID_TOPIC'] . '.0',
				'link' => '<a href="' . $scripturl . '?topic=' . $row['ID_TOPIC'] . '.0">' . $row['title'] . '</a>',
				'is_last' => false
			);
		// Otherwise, this is going to be cached and the VIEWER'S permissions should apply... just put together some info.
		else
			$events[$row['day']][] = array(
				'id' => $row['ID_EVENT'],
				'title' => $row['title'],
				'topic' => $row['ID_TOPIC'],
				'msg' => $row['ID_FIRST_MSG'],
				'poster' => $row['ID_MEMBER'],
				'is_last' => false,
				'allowed_groups' => explode(',', $row['memberGroups'])
			);
	}
	mysql_free_result($result);

	// If we're doing normal contextual data, go through and make things clear to the templates ;).
	if ($use_permissions)
	{
		foreach ($events as $mday => $array)
			$events[$mday][count($array) - 1]['is_last'] = true;
	}

	return $events;
}

// Builds an array of holiday strings for a particular month.  Note... month PLUS 1 not just month.
function calendarHolidayArray($low_date, $high_date)
{
	global $db_prefix;

	// Get the lowest and highest dates for "all years".
	if (substr($low_date, 0, 4) != substr($high_date, 0, 4))
		$allyear_part = "eventDate BETWEEN '0000" . substr($low_date, 4) . "' AND '0000-12-31'
			OR eventDate BETWEEN '0000-01-01' AND '0000" . substr($high_date, 4) . "'";
	else
		$allyear_part = "eventDate BETWEEN '0000" . substr($low_date, 4) . "' AND '0000" . substr($high_date, 4) . "'";

	// Find some holidays... ;).
	$result = db_query("
		SELECT DAYOFMONTH(eventDate) AS day, YEAR(eventDate) AS year, title
		FROM {$db_prefix}calendar_holidays
		WHERE eventDate BETWEEN '$low_date' AND '$high_date'
			OR $allyear_part", __FILE__, __LINE__);
	$holidays = array();
	while ($row = mysql_fetch_assoc($result))
		$holidays[$row['day']][] = $row['title'];
	mysql_free_result($result);

	return $holidays;
}

// Consolidating the various INSERT statements into this function.
function calendarInsertEvent($id_board, $id_topic, $title, $id_member, $month, $day, $year, $span)
{
	global $db_prefix;

	// Add special chars to the title.
	$title = htmlspecialchars($title, ENT_QUOTES);

	// Span multiple days?
	if (empty($span) || trim($span) == '')
		$insertString = "
				($id_board, $id_topic, '$title', $id_member, '$year-$month-$day'),";
	else
	{
		// Calculate the time...
		$tVal = mktime(0, 0, 0, $month, $day, $year);

		// I went for the simplest way I could think of for making the events span multiple days.
		$insertString = '';

		// For each day a row is added to the calendar table.
		for ($i = 0; $i < $span; $i++)
		{
			$eventTime = array(
				'day' => (int) strftime('%d', $tVal),
				'month' => (int) strftime('%m', $tVal),
				'year' => (int) strftime('%Y', $tVal)
			);

			$insertString .= "
					($id_board, $id_topic, '$title', $id_member, '$eventTime[year]-$eventTime[month]-$eventTime[day]'),";

			// Add a day...
			$tVal = strtotime('tomorrow', $tVal);
		}
	}

	// Insert the day(s)!
	if (strlen($insertString) > 0)
	{
		$insertString = substr($insertString, 0, -1);

		db_query("
			INSERT INTO {$db_prefix}calendar
				(ID_BOARD, ID_TOPIC, title, ID_MEMBER, eventDate)
			VALUES $insertString", __FILE__, __LINE__);
	}

	updateStats('calendar');
}

// Returns true if this user is allowed to link the topic in question.
function calendarCanLink()
{
	global $ID_MEMBER, $db_prefix, $user_info, $topic, $board;

	// If you can't post, you can't link.
	isAllowedTo('calendar_post');

	// No board?  No topic?!?
	if (!isset($board))
		fatal_lang_error('calendar38', false);
	if (!isset($topic))
		fatal_lang_error('calendar39', false);

	// Administrator, Moderator, or owner.  Period.
	if (!allowedTo('admin_forum') && !allowedTo('moderate_board'))
	{
		// Not admin or a moderator of this board. You better be the owner - or else.
		$result = db_query("
			SELECT ID_MEMBER_STARTED
			FROM {$db_prefix}topics
			WHERE ID_TOPIC = $topic", __FILE__, __LINE__);
		if ($row = mysql_fetch_assoc($result))
		{
			// Not the owner of the topic.
			if ($row['ID_MEMBER_STARTED'] != $ID_MEMBER)
				fatal_lang_error('calendar41');
		}
		// Topic/Board doesn't exist.....
		else
			fatal_lang_error('calendar40');
		mysql_free_result($result);
	}

	// If you got this far, it's okay.
	return true;
}

?>