<?php /*

 ocPortal
 Copyright (c) ocProducts, 2004-2012

 See text/EN/licence.txt for full licencing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license		http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright	ocProducts Ltd
 * @package		calendar
 */

/*
Note that all this code operates in "user time". That is, the timestamps being banded around are not as the server would recognise them, because they encode offsets to match the users timezone settings.
time() should never be called here, either explicitly, or implictly by not giving the date function a second argument. Instead, utctime_to_usertime() should be used to get the timestamp.
Any times that go-in-to or come-in-from the calendar backend API are also in "user time".
To complicate matters further "user time" is not the timestamp that would exist on a user's PC, as all timestamps are meant to be stored in GMT. "user time" is offsetted to compensate, a virtual construct.
*/

/**
 * Module page class.
 */
class Module_calendar
{

	/**
	 * Standard modular info function.
	 *
	 * @return ?array	Map of module info (NULL: module is disabled).
	 */
	function info()
	{
		$info=array();
		$info['author']='Chris Graham';
		$info['organisation']='ocProducts';
		$info['hacked_by']=NULL;
		$info['hack_version']=NULL;
		$info['version']=7;
		$info['locked']=false;
		$info['update_require_upgrade']=1;
		return $info;
	}

	/**
	 * Standard modular uninstall function.
	 */
	function uninstall()
	{
		$GLOBALS['SITE_DB']->drop_if_exists('calendar_events');
		$GLOBALS['SITE_DB']->drop_if_exists('calendar_types');
		$GLOBALS['SITE_DB']->drop_if_exists('calendar_reminders');
		$GLOBALS['SITE_DB']->drop_if_exists('calendar_interests');
		$GLOBALS['SITE_DB']->drop_if_exists('calendar_jobs');

		$perms=array('set_reminders','view_event_subscriptions','view_calendar','add_public_events','edit_viewable_events','edit_owned_events','view_personal_events','sense_personal_conflicts');
		foreach ($perms as $perm)
		{
			delete_specific_permission($perm);
		}

		$GLOBALS['SITE_DB']->query_delete('group_category_access',array('module_the_name'=>'calendar'));

		delete_menu_item_simple('_SEARCH:calendar:type=misc:member_id={$USER_OVERIDE}');

		delete_config_option('calendar_show_stats_count_events');
		delete_config_option('calendar_show_stats_count_events_this_week');
		delete_config_option('calendar_show_stats_count_events_this_month');
		delete_config_option('calendar_show_stats_count_events_this_year');
	}

	/**
	 * Standard modular install function.
	 *
	 * @param  ?integer	What version we're upgrading from (NULL: new install)
	 * @param  ?integer	What hack version we're upgrading from (NULL: new-install/not-upgrading-from-a-hacked-version)
	 */
	function install($upgrade_from=NULL,$upgrade_from_hack=NULL)
	{
		require_lang('calendar');

		if (is_null($upgrade_from))
		{
			add_specific_permission('CALENDAR','view_calendar',true);
			add_specific_permission('CALENDAR','add_public_events',true);
			add_specific_permission('CALENDAR','view_personal_events',false,true);
			add_specific_permission('CALENDAR','sense_personal_conflicts',false);
			add_specific_permission('CALENDAR','view_event_subscriptions',false);

			$GLOBALS['SITE_DB']->create_table('calendar_events',array(
				'id'=>'*AUTO',
				'e_submitter'=>'USER',
				'e_views'=>'INTEGER',
				'e_title'=>'SHORT_TRANS',
				'e_content'=>'LONG_TRANS',
				'e_add_date'=>'TIME',
				'e_edit_date'=>'?TIME',
				'e_recurrence'=>'ID_TEXT', // [none, daily, weekly, monthly, yearly, xth_dotw_of_monthly] X [fractional-occurrence]. e.g. "daily yyyyynn" for weekdays
				'e_recurrences'=>'?INTEGER', // NULL means none/infinite
				'e_seg_recurrences'=>'BINARY',
				'e_start_year'=>'INTEGER',
				'e_start_month'=>'INTEGER',
				'e_start_day'=>'INTEGER',
				'e_start_monthly_spec_type'=>'ID_TEXT', // day_of_month|day_of_month_backwards|dow_of_month|dow_of_month_backwards
				'e_start_hour'=>'?INTEGER',
				'e_start_minute'=>'?INTEGER',
				'e_end_year'=>'?INTEGER',
				'e_end_month'=>'?INTEGER',
				'e_end_day'=>'?INTEGER',
				'e_end_monthly_spec_type'=>'ID_TEXT', // day_of_month|day_of_month_backwards|dow_of_month|dow_of_month_backwards
				'e_end_hour'=>'?INTEGER',
				'e_end_minute'=>'?INTEGER',
				'e_timezone'=>'ID_TEXT', // The settings above are stored in GMT, were converted from this timezone, and back to this timezone if e_do_timezone_conv==1
				'e_do_timezone_conv'=>'BINARY',
				'e_is_public'=>'BINARY',
				'e_priority'=>'INTEGER', // 1-5, like e-mail
				'allow_rating'=>'BINARY',
				'allow_comments'=>'SHORT_INTEGER',
				'allow_trackbacks'=>'BINARY',
				'notes'=>'LONG_TEXT',
				'e_type'=>'AUTO_LINK',
				'validated'=>'BINARY',
			));

			$GLOBALS['SITE_DB']->create_index('calendar_events','e_views',array('e_views'));
			$GLOBALS['SITE_DB']->create_index('calendar_events','ces',array('e_submitter'));
			$GLOBALS['SITE_DB']->create_index('calendar_events','publicevents',array('e_is_public'));
			$GLOBALS['SITE_DB']->create_index('calendar_events','e_type',array('e_type'));
			$GLOBALS['SITE_DB']->create_index('calendar_events','eventat',array('e_start_year','e_start_month','e_start_day','e_start_hour','e_start_minute'));
			$GLOBALS['SITE_DB']->create_index('calendar_events','e_add_date',array('e_add_date'));
			$GLOBALS['SITE_DB']->create_index('calendar_events','validated',array('validated'));

			$GLOBALS['SITE_DB']->create_table('calendar_types',array(
				'id'=>'*AUTO',
				't_title'=>'SHORT_TRANS',
				't_logo'=>'SHORT_TEXT',
				't_external_feed'=>'URLPATH',
			));
			$default_types=array('system_command','general','birthday','public_holiday','vacation','appointment','commitment','anniversary');
			foreach ($default_types as $type)
			{
				$GLOBALS['SITE_DB']->query_insert('calendar_types',array('t_external_feed'=>'','t_title'=>lang_code_to_default_content('DEFAULT_CALENDAR_TYPE__'.$type),'t_logo'=>'calendar/'.$type));
			}

			$GLOBALS['SITE_DB']->create_table('calendar_reminders',array(
				'id'=>'*AUTO',
				'e_id'=>'AUTO_LINK',
				'n_member_id'=>'USER',
				'n_seconds_before'=>'INTEGER'
			));

			$GLOBALS['SITE_DB']->create_table('calendar_interests',array(
				'i_member_id'=>'*USER',
				't_type'=>'*AUTO_LINK'
			));

			add_menu_item_simple('main_content',NULL,'CALENDAR','_SEARCH:calendar:type=misc',0,0,true,'',0,'',3);
		}

		if ((is_null($upgrade_from)) || ($upgrade_from<3))
		{
			$GLOBALS['SITE_DB']->create_table('calendar_jobs',array(
				'id'=>'*AUTO',
				'j_time'=>'TIME',
				'j_reminder_id'=>'?AUTO_LINK',
				'j_member_id'=>'?USER',
				'j_event_id'=>'AUTO_LINK'
			));

			$GLOBALS['SITE_DB']->create_index('calendar_jobs','applicablejobs',array('j_time'));
		}

		if ((!is_null($upgrade_from)) && ($upgrade_from<3))
		{
			$GLOBALS['SITE_DB']->alter_table_field('calendar_events','e_edit_date','?TIME');
			$GLOBALS['SITE_DB']->alter_table_field('calendar_events','e_end_minute','?INTEGER');
			$GLOBALS['SITE_DB']->alter_table_field('calendar_events','e_end_hour','?INTEGER');
			$GLOBALS['SITE_DB']->alter_table_field('calendar_events','e_end_day','?INTEGER');
			$GLOBALS['SITE_DB']->alter_table_field('calendar_events','e_end_month','?INTEGER');
			$GLOBALS['SITE_DB']->alter_table_field('calendar_events','e_end_year','?INTEGER');
		}

		if ((!is_null($upgrade_from)) && ($upgrade_from<4))
		{
			$GLOBALS['SITE_DB']->add_table_field('calendar_events','allow_rating','BINARY',0);
			$GLOBALS['SITE_DB']->add_table_field('calendar_events','allow_comments','SHORT_INTEGER',0);
			$GLOBALS['SITE_DB']->add_table_field('calendar_events','allow_trackbacks','BINARY',0);
			$GLOBALS['SITE_DB']->add_table_field('calendar_events','notes','LONG_TEXT');
			$GLOBALS['SITE_DB']->add_table_field('calendar_events','validated','BINARY',1);
			delete_specific_permission('edit_owned_events');
			delete_specific_permission('edit_viewable_events');
			add_config_option('ADD_CALENDAR_EVENT','points_ADD_CALENDAR_EVENT','integer','return addon_installed(\'points\')?\'2\':NULL;','POINTS','COUNT_POINTS_GIVEN');
			$GLOBALS['SITE_DB']->rename_table('calendar_declarations_of_interest','calendar_interests');
		}

		if ((!is_null($upgrade_from)) && ($upgrade_from<5))
		{
			$GLOBALS['SITE_DB']->add_table_field('calendar_events','e_seg_recurrences','BINARY',0);
		}

		if ((is_null($upgrade_from)) || ($upgrade_from<5))
		{
			add_config_option('EVENTS','calendar_show_stats_count_events','tick','return addon_installed(\'stats_block\')?\'0\':NULL;','BLOCKS','STATISTICS');
			add_config_option('_EVENTS_THIS_WEEK','calendar_show_stats_count_events_this_week','tick','return addon_installed(\'stats_block\')?\'0\':NULL;','BLOCKS','STATISTICS');
			add_config_option('_EVENTS_THIS_MONTH','calendar_show_stats_count_events_this_month','tick','return addon_installed(\'stats_block\')?\'0\':NULL;','BLOCKS','STATISTICS');
			add_config_option('_EVENTS_THIS_YEAR','calendar_show_stats_count_events_this_year','tick','return addon_installed(\'stats_block\')?\'0\':NULL;','BLOCKS','STATISTICS');
			$GLOBALS['SITE_DB']->create_index('calendar_events','ftjoin_etitle',array('e_title'));
			$GLOBALS['SITE_DB']->create_index('calendar_events','ftjoin_econtent',array('e_content'));
		}

		if ((!is_null($upgrade_from)) && ($upgrade_from<6))
		{
			$GLOBALS['SITE_DB']->delete_table_field('calendar_events','e_geo_position');
			$GLOBALS['SITE_DB']->delete_table_field('calendar_events','e_groups_access');
			$GLOBALS['SITE_DB']->delete_table_field('calendar_events','e_groups_modify');
			$GLOBALS['SITE_DB']->add_table_field('calendar_events','e_timezone','ID_TEXT');
			$GLOBALS['SITE_DB']->add_table_field('calendar_events','e_do_timezone_conv','BINARY');
			$GLOBALS['SITE_DB']->alter_table_field('calendar_events','e_start_hour','?INTEGER');
			$GLOBALS['SITE_DB']->alter_table_field('calendar_events','e_start_minute','?INTEGER');

			$GLOBALS['SITE_DB']->add_table_field('calendar_types','t_external_feed','URLPATH');
		}

		if ((is_null($upgrade_from)) || ($upgrade_from<6))
		{
			$admin_groups=$GLOBALS['FORUM_DRIVER']->get_super_admin_groups();
			$groups=$GLOBALS['FORUM_DRIVER']->get_usergroup_list(false,true);

			// Save in permissions for event type
			$types=$GLOBALS['SITE_DB']->query_select('calendar_types');
			foreach ($types as $type)
			{
				foreach (array_keys($groups) as $group_id)
				{
					if (in_array($group_id,$admin_groups)) continue;

					$GLOBALS['SITE_DB']->query_insert('group_category_access',array('module_the_name'=>'calendar','category_name'=>strval($type['id']),'group_id'=>$group_id));
				}
			}
		}

		if ((!is_null($upgrade_from)) && ($upgrade_from<7))
		{
			$GLOBALS['SITE_DB']->add_table_field('calendar_events','e_start_monthly_spec_type','ID_TEXT','day_of_month');
			$GLOBALS['SITE_DB']->add_table_field('calendar_events','e_end_monthly_spec_type','ID_TEXT','day_of_month');
		}

		if ((!is_null($upgrade_from)) && ($upgrade_from<7))
		{
			add_specific_permission('CALENDAR','set_reminders',false);
		}
	}

	/**
	 * Standard modular entry-point finder function.
	 *
	 * @return ?array	A map of entry points (type-code=>language-code) (NULL: disabled).
	 */
	function get_entry_points()
	{
		return array('misc'=>'CALENDAR');
	}

	/**
	 * Standard modular page-link finder function (does not return the main entry-points that are not inside the tree).
	 *
	 * @param  ?integer  The number of tree levels to computer (NULL: no limit)
	 * @param  boolean	Whether to not return stuff that does not support permissions (unless it is underneath something that does).
	 * @param  ?string	Position to start at in the tree. Does not need to be respected. (NULL: from root)
	 * @param  boolean	Whether to avoid returning categories.
	 * @return ?array	 	A tuple: 1) full tree structure [made up of (pagelink, permission-module, permissions-id, title, children, ?entry point for the children, ?children permission module, ?whether there are children) OR a list of maps from a get_* function] 2) permissions-page 3) optional base entry-point for the tree 4) optional permission-module 5) optional permissions-id (NULL: disabled).
	 */
	function get_page_links($max_depth=NULL,$require_permission_support=false,$start_at=NULL,$dont_care_about_categories=false)
	{
		$permission_page='cms_calendar';

		$tree=array();
		if (!$require_permission_support)
		{
			$tree=array();
			$rows=$dont_care_about_categories?array():$GLOBALS['SITE_DB']->query_select('calendar_types c LEFT JOIN '.$GLOBALS['SITE_DB']->get_table_prefix().'translate t ON '.db_string_equal_to('language',user_lang()).' AND c.t_title=t.id',array('c.id','text_original','c.t_title'),NULL,'ORDER BY text_original');
			if (($max_depth>0) || (is_null($max_depth)))
			{
				foreach ($rows as $row)
				{
					if (!has_category_access($GLOBALS['FORUM_DRIVER']->get_guest_id(),'calendar',strval($row['id']))) continue;

					if (is_null($row['text_original'])) $row['text_original']=get_translated_text($row['t_title']);

					if (($row['id']!=db_get_first_id()) || (($GLOBALS['FORUM_DRIVER']->is_super_admin()) && (cron_installed()))) // Filters system commands
						$tree[]=array('_SELF:_SELF:type=misc:int_'.strval($row['id']).'=1','calendar',$row['id'],$row['text_original'],array());
				}
			}
		}

		return array($tree,$permission_page);
	}

	/**
	 * Standard modular new-style deep page-link finder function (does not return the main entry-points).
	 *
	 * @param  string  	Callback function to send discovered page-links to.
	 * @param  MEMBER		The member we are finding stuff for (we only find what the member can view).
	 * @param  integer	Code for how deep we are tunnelling down, in terms of whether we are getting entries as well as categories.
	 * @param  string		Stub used to create page-links. This is passed in because we don't want to assume a zone or page name within this function.
	 * @param  ?string	Where we're looking under (NULL: root of tree). We typically will NOT show a root node as there's often already an entry-point representing it.
	 * @param  integer	Our recursion depth (used to calculate importance of page-link, used for instance by Google sitemap). Deeper is typically less important.
	 * @param  ?array		Non-standard for API [extra parameter tacked on] (NULL: yet unknown). Contents of database table for performance.
	 * @param  ?array		Non-standard for API [extra parameter tacked on] (NULL: yet unknown). Contents of database table for performance.
	 */
	function get_sitemap_pagelinks($callback,$member_id,$depth,$pagelink_stub,$parent_pagelink=NULL,$recurse_level=0,$category_data=NULL,$entry_data=NULL)
	{
		// We read in all data for efficiency
		if (is_null($category_data))
			$category_data=$GLOBALS['SITE_DB']->query_select('calendar_types c LEFT JOIN '.get_table_prefix().'translate t ON '.db_string_equal_to('language',user_lang()).' AND t.id=c.t_title',array('c.t_title','c.id','t.text_original AS title'));

		// This is where we start
		if (is_null($parent_pagelink))
		{
			$parent_pagelink=$pagelink_stub.':misc'; // This is the entry-point we're under

			// Subcategories
			foreach ($category_data as $row)
			{
				if ($row['id']==db_get_first_id()) continue;

				if (!has_category_access($GLOBALS['FORUM_DRIVER']->get_guest_id(),'calendar',strval($row['id']))) continue;

				if (is_null($row['title'])) $row['title']=get_translated_text($row['t_title']);

				$pagelink=$pagelink_stub.'misc:int_'.strval($row['id']).'=1';
				if (__CLASS__!='')
				{
					$this->get_sitemap_pagelinks($callback,$member_id,$depth,$pagelink_stub,$pagelink,$recurse_level+1,$category_data,$entry_data); // Recurse
				} else
				{
					call_user_func_array(__FUNCTION__,array($callback,$member_id,$depth,$pagelink_stub,$pagelink,$recurse_level+1,$category_data,$entry_data)); // Recurse
				}
				call_user_func_array($callback,array($pagelink,$parent_pagelink,NULL,NULL,max(0.7-$recurse_level*0.1,0.3),$row['title'])); // Callback
			}
		} else
		{
			list(,$parent_attributes,)=page_link_decode($parent_pagelink);

			// Entries
			if ($depth>=DEPTH__ENTRIES)
			{
				$start=0;
				do
				{
					$parent_id=db_get_first_id();
					foreach ($parent_attributes as $key=>$val)
					{
						if (substr($key,0,4)=='int_') $parent_id=intval(substr($key,4));
					}
					$entry_data=$GLOBALS['SITE_DB']->query_select('calendar_events d LEFT JOIN '.get_table_prefix().'translate t ON '.db_string_equal_to('language',user_lang()).' AND t.id=d.e_title',array('d.e_title','d.id','t.text_original AS title','e_type AS category_id','e_add_date AS add_date','e_edit_date AS edit_date'),array('e_type'=>intval($parent_id)),'',500,$start);

					foreach ($entry_data as $row)
					{
						if (!has_category_access($GLOBALS['FORUM_DRIVER']->get_guest_id(),'calendar',strval($row['category_id']))) continue;

						if (is_null($row['title'])) $row['title']=get_translated_text($row['e_title']);

						$pagelink=$pagelink_stub.'view:'.strval($row['id']);
						call_user_func_array($callback,array($pagelink,$parent_pagelink,$row['add_date'],$row['edit_date'],0.2,$row['title'])); // Callback
					}

					$start+=500;
				}
				while (array_key_exists(0,$entry_data));
			}
		}
	}

	/**
	 * Standard modular run function.
	 *
	 * @return tempcode	The result of execution.
	 */
	function run()
	{
		require_lang('calendar');
		require_lang('dates');
		require_code('calendar');
		require_css('calendar');
		require_code('feedback');

		$type=get_param('type',post_param('type','misc'));

		// Decide what to do
		if ($type=='declare_interest') return $this->declare_interest();
		if ($type=='undeclare_interest') return $this->undeclare_interest();
		if ($type=='unsubscribe_event') return $this->unsubscribe_event();
		if ($type=='interests') return $this->interests();
		if ($type=='subscribe_event') return $this->subscribe_event();
		if ($type=='_subscribe_event') return $this->_subscribe_event();
		if ($type=='misc') return $this->view_calendar();
		if ($type=='view') return $this->view_event();

		return new ocp_tempcode();
	}

	/**
	 * Gets the type filter, if there is one.
	 *
	 * @return array			The filter
	 */
	function get_filter()
	{
		$filter=array();
		$some_pos=false;
		$types=$GLOBALS['SITE_DB']->query_select('calendar_types',array('id'));
		foreach ($types as $type)
		{
			$t=$type['id'];
			$filter['int_'.strval($t)]=get_param_integer('int_'.strval($t),0);

			global $NON_CANONICAL_PARAMS;
			$NON_CANONICAL_PARAMS[]='int_'.strval($t);

			if (post_param_integer('id',NULL)===$type['id']) $filter['int_'.strval($t)]=1;
			if ($filter['int_'.strval($t)]==1) $some_pos=true;
		}
		return $some_pos?$filter:array();
	}

	/**
	 * Gets the type filter in a simple list form.
	 *
	 * @return array			The filter
	 */
	function get_and_filter()
	{
		$and_filter=array();
		$filter=$this->get_filter();
		foreach ($filter as $key=>$val)
		{
			if ($val==1)
				$and_filter[]=intval(substr($key,4));
		}
		return $and_filter;
	}

	/**
	 * View the main calendar screen, with certain filter allowances.
	 *
	 * @return tempcode		The UI
	 */
	function view_calendar()
	{
		check_specific_permission('view_calendar');

		$view=get_param('view','day');
		$member_id=get_param_integer('member_id',get_member());
		$filter=$this->get_filter();
		$GLOBALS['FEED_URL']=find_script('backend').'?mode=calendar&filter='.implode(',',$this->get_and_filter());
		if ($member_id!=get_member()) enforce_personal_access($member_id);
		$back_url=NULL;

		switch ($view)
		{
			case 'day': // Like a diary
				$id=get_param('id',date('Y-m-d',utctime_to_usertime()));
				if (strpos($id,'-')===false) $id=date('Y-m-d',utctime_to_usertime()); // The id was actually a filter, will need to use default date/time
				$self_encompassing=($id==date('Y-m-d',utctime_to_usertime()));
				$date=$id;
				$explode=explode('-',$id);
				if (count($explode)!=3) warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
				$main=$this->view_calendar_view_day($id,$date,$explode,$member_id,$filter);
				$timestamp=mktime(0,0,0,intval($explode[1]),intval($explode[2]),intval($explode[0]));
				$back=get_week_number_for(utctime_to_usertime($timestamp));
				$back_view='week';
				$previous_timestamp=mktime(0,0,0,intval($explode[1]),intval($explode[2]),intval($explode[0]))-60*60*24;
				$previous=date('Y-m-d',$previous_timestamp);
				$next_timestamp=mktime(0,0,0,intval($explode[1]),intval($explode[2]),intval($explode[0]))+60*60*24;
				$next=date('Y-m-d',$next_timestamp);
				$title=get_screen_title('CALENDAR_SPECIFIC',true,array(escape_html(locale_filter(date(do_lang('calendar_date_verbose'),$timestamp)))));
				break;
			case 'week': // Like a compressed diary
				$id=get_param('id',get_week_number_for(utctime_to_usertime()));
				$self_encompassing=($id==get_week_number_for(utctime_to_usertime()));
				$explode=explode('-',$id);
				if (count($explode)!=2) warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
				list($start_month,$start_day,$start_year)=date_from_week_of_year(intval($explode[0]),intval($explode[1]));
				$date=strval($start_year).'-'.strval($start_month).'-01';
				$main=$this->view_calendar_view_week($id,$date,$explode,$member_id,$filter);
				$timestamp=mktime(0,0,0,$start_month,$start_day,$start_year);
				$back=$explode[0].'-'.strval($start_month);
				$back_view='month';
				$previous_timestamp=mktime(0,0,0,$start_month,$start_day,$start_year)-60*60*24*7;
				$previous=get_week_number_for($previous_timestamp);
				$next_timestamp=mktime(0,0,0,$start_month,$start_day,$start_year)+60*60*24*7;
				$next=get_week_number_for($next_timestamp);
				$title=get_screen_title('CALENDAR_SPECIFIC_WEEK',true,array(escape_html($explode[0]),escape_html($explode[1]),escape_html(locale_filter(date('M',$timestamp)))));
				break;
			case 'month': // Like a main calendar page
				$id=get_param('id',date('Y-m',utctime_to_usertime()));
				$self_encompassing=($id==date('Y-m',utctime_to_usertime()));
				$date=$id.'-01';
				$explode=explode('-',$id);
				if (count($explode)!=2) warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
				$main=$this->view_calendar_view_month($id,$date,$explode,$member_id,$filter);
				$timestamp=mktime(0,0,0,intval($explode[1]),1,intval($explode[0]));
				$back=$explode[0];
				$back_view='year';
				$previous_month=intval($explode[1])-1;
				$previous_year=intval($explode[0]);
				if ($previous_month==0)
				{
					$previous_month=12;
					$previous_year=$previous_year-1;
				}
				$next_month=intval($explode[1])+1;
				$next_year=intval($explode[0]);
				if ($next_month==13)
				{
					$next_month=1;
					$next_year=$next_year+1;
				}
				$previous_timestamp=mktime(0,0,0,$previous_month,1,$previous_year);
				$previous=date('Y-m',$previous_timestamp);
				$next_timestamp=mktime(0,0,0,$next_month,1,$next_year);
				$next=date('Y-m',$next_timestamp);
				$title=get_screen_title('CALENDAR_SPECIFIC',true,array(escape_html(locale_filter(my_strftime(do_lang('calendar_month_in_year_verbose'),$timestamp)))));
				break;
			case 'year': // Like front of a calendar
				$id=get_param('id',date('Y'));
				$self_encompassing=($id==date('Y'));
				$date=$id.'-01-01';
				$explode=explode('-',$id);
				if (count($explode)!=1) warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
				$main=$this->view_calendar_view_year($id,$date,$explode,$member_id,$filter);
				$timestamp=mktime(0,0,0,1,1,intval($id));
				$back_url=$GLOBALS['FORUM_DRIVER']->member_profile_url($member_id);
				$previous_timestamp=mktime(0,0,0,1,1,intval($explode[0])-1);
				$previous=date('Y',$previous_timestamp);
				$next_timestamp=mktime(0,0,0,1,1,intval($explode[0])+1);
				$next=date('Y',$next_timestamp);
				$title=get_screen_title('CALENDAR_SPECIFIC',true,array(escape_html($id)));
				break;
			default:
				warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
		}

		// Nofollow stuff
		$previous_no_follow=($previous_timestamp<filectime(get_file_base().'/info.php'));
		$next_no_follow=($next_timestamp>time());

		$map=array_merge($filter,array('page'=>'_SELF','view'=>$view,'id'=>$previous));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$previous_url=build_url($map,'_SELF');
		$map=array_merge($filter,array('page'=>'_SELF','view'=>$view,'id'=>$next));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$next_url=build_url($map,'_SELF');
		if (is_null($back_url))
		{
			$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>$back_view,'id'=>$back));
			if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
			$back_url=build_url($map,'_SELF');
		}

		$interests_url=build_url(array('page'=>'_SELF','type'=>'interests','view'=>$view,'id'=>$id),'_SELF');
		$event_types_1=new ocp_tempcode();
		$types=$GLOBALS['SITE_DB']->query_select('calendar_types',array('id','t_title'));
		foreach ($types as $type)
		{
			if ($type['id']==db_get_first_id()) continue;
			if (!has_category_access(get_member(),'calendar',strval($type['id']))) continue;

			if (is_guest())
			{
				$interested='';
			} else
			{
				$test=$GLOBALS['SITE_DB']->query_value_null_ok('calendar_interests','i_member_id',array('i_member_id'=>get_member(),'t_type'=>$type['id']));
				$interested=(!is_null($test))?'not_interested':'interested';
			}
			$event_types_1->attach(do_template('CALENDAR_EVENT_TYPE',array('_GUID'=>'104b723d5211f400267345f616c4a677','S'=>'I','INTERESTED'=>$interested,'TYPE'=>get_translated_text($type['t_title']),'TYPE_ID'=>strval($type['id']))));
		}
		$filter_url=build_url(array('page'=>'_SELF','type'=>'misc','view'=>$view,'id'=>$id),'_SELF',NULL,false,true);
		$event_types_2=new ocp_tempcode();
		foreach ($types as $type)
		{
			if ($type['id']==db_get_first_id()) continue;
			if (!has_category_access(get_member(),'calendar',strval($type['id']))) continue;

			$interested=((count($filter)==0) || ($filter['int_'.strval($type['id'])]==1))?'not_interested':'interested';
			$event_types_2->attach(do_template('CALENDAR_EVENT_TYPE',array('_GUID'=>'7511d60148835b7f4fea68a246af424e','S'=>'F','INTERESTED'=>$interested,'TYPE'=>get_translated_text($type['t_title']),'TYPE_ID'=>strval($type['id']))));
		}

		if ((has_actual_page_access(NULL,'cms_calendar',NULL,NULL)) && (has_submit_permission('low',get_member(),get_ip_address(),'cms_calendar')))
		{
			$and_filter=$this->get_and_filter();
			$add_url=build_url(array('page'=>'cms_calendar','type'=>'ad','date'=>$self_encompassing?NULL:$date,'e_type'=>(count($and_filter)==1)?$and_filter[0]:NULL),get_module_zone('cms_calendar'));
		} else
		{
			$add_url=new ocp_tempcode();
		}

		// Allow jumping between views
		if ($self_encompassing) $timestamp=time();
		$day=date('Y-m-d',$timestamp);
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$day));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$day_url=($view=='day')?new ocp_tempcode():build_url($map,'_SELF');
		$week=get_week_number_for($timestamp);
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'week','id'=>$week));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$week_url=($view=='week')?new ocp_tempcode():build_url($map,'_SELF');
		$month=date('Y-m',$timestamp);
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'month','id'=>$month));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$month_url=($view=='month')?new ocp_tempcode():build_url($map,'_SELF');
		$year=date('Y',$timestamp);
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'year','id'=>$year));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$year_url=($view=='year')?new ocp_tempcode():build_url($map,'_SELF');

		// RSS
		$fields=new ocp_tempcode();
		require_code('form_templates');
		for ($i=0;$i<10;$i++)
		{
			$fields->attach(form_input_line(do_lang_tempcode('FEED',integer_format($i+1)),'','feed_'.strval($i),ocp_admirecookie('feed_'.strval($i)),false));
		}
		$rss_form=do_template('FORM',array('HIDDEN'=>'','TEXT'=>do_lang_tempcode('DESCRIPTION_FEEDS_TO_OVERLAY'),'URL'=>get_self_url(),'FIELDS'=>$fields,'SUBMIT_NAME'=>do_lang_tempcode('PROCEED')));

		return do_template('CALENDAR_MAIN_SCREEN',array('_GUID'=>'147a58dbe05366ac37698a8cdd501d12','RSS_FORM'=>$rss_form,'PREVIOUS_NO_FOLLOW'=>$previous_no_follow,'NEXT_NO_FOLLOW'=>$next_no_follow,'DAY_URL'=>$day_url,'WEEK_URL'=>$week_url,'MONTH_URL'=>$month_url,'YEAR_URL'=>$year_url,'PREVIOUS_URL'=>$previous_url,'NEXT_URL'=>$next_url,'ADD_URL'=>$add_url,'TITLE'=>$title,'BACK_URL'=>$back_url,'MAIN'=>$main,'FILTER_URL'=>$filter_url,'EVENT_TYPES_1'=>$event_types_1,'INTERESTS_URL'=>$interests_url,'EVENT_TYPES_2'=>$event_types_2));
	}

	/**
	 * The calendar area view for viewing a single day.
	 *
	 * @param  string			The day we are viewing
	 * @param  string			The day (Y-m-d) we are viewing
	 * @param  array			List of components of our viewed ID
	 * @param  MEMBER			The member ID we are viewing as
	 * @param  ?array			The type filter (NULL: none)
	 * @return tempcode		The UI
	 */
	function view_calendar_view_day($view_id,$day,$explode,$member_id,$filter)
	{
		$start_year=intval($explode[0]);
		$start_month=intval($explode[1]);
		$start_day=intval($explode[2]);

		$period_start=mktime(0,0,0,$start_month,$start_day,$start_year);

		$period_end=mktime(0,0,0,$start_month,$start_day+1,$start_year);
		$happenings=calendar_matches($member_id,true,$period_start,$period_end,$filter);

		global $M_SORT_KEY;
		$M_SORT_KEY=0;
		usort($happenings,'multi_sort');

		$hours=new ocp_tempcode();
		$streams=array(array());
		foreach ($happenings as $happening)
		{
			list($e_id,$event,$from,$to,$real_from,$real_to,$utc_real_from)=$happening;
			$date=is_null($event['e_start_hour'])?'':locale_filter(my_strftime(do_lang('calendar_minute'),$from));
			if (is_numeric($e_id))
			{
				$map=array_merge($filter,array('page'=>'_SELF','type'=>'view','id'=>$event['e_id'],'day'=>date('Y-m-d',$utc_real_from),'date'=>$view_id));
				if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
				$url=build_url($map,'_SELF');
			} else
			{
				$url=$e_id;
			}
			$icon=$event['t_logo'];
			$from_h=intval(date('H',$from));
			if (!is_null($to))
			{
				$date=date_range($real_from,$real_to,!is_null($event['e_start_hour']));
				//$date2=date('H:i',$to);
				if ($to>=$period_end-60/*1 minute gap we use to stop stuff spanning to start of next day*/)
				{
					$to_h=24;
				} else
				{
					$to_h=intval(date('H',$to));
					if (date('i',$to)!='0') $to_h++;
				}
			} else $to_h=$from_h;

			// Try and find us a stream that can fit us
			$found_stream=NULL;
			foreach ($streams as $i=>$stream)
			{
				for ($h=$from_h;$h<=$to_h;$h++)
				{
					if (array_key_exists($h,$stream)) break;
				}
				if (($h==$to_h+1) && (!array_key_exists($h-1,$stream)))
				{
					$found_stream=$i;
					break;
				}
			}
			if (is_null($found_stream))
			{
				$streams[]=array();
				$found_stream=count($streams)-1;
			}

			// Fill in stream gaps as appropriate
			$title=is_integer($event['e_title'])?get_translated_text($event['e_title']):$event['e_title'];
			$down=strval($to_h-$from_h);
			$description=(intval($down)<3)?new ocp_tempcode():(is_numeric($event['e_content'])?get_translated_tempcode($event['e_content']):$event['e_content']);
			$priority_lang=do_lang_tempcode('PRIORITY_'.strval($event['e_priority']));
			$priority_icon='calendar/priority_'.strval($event['e_priority']);
			$streams[$found_stream][$from_h]=array('TPL'=>'CALENDAR_DAY_ENTRY','DESCRIPTION'=>$description,'DOWN'=>$down,'ID'=>strval($event['e_id']),'T_TITLE'=>array_key_exists('t_title',$event)?(is_string($event['t_title'])?$event['t_title']:get_translated_text($event['t_title'])):'RSS','PRIORITY'=>strval($event['e_priority']),'ICON'=>$icon,'TIME'=>$date,'TITLE'=>$title,'URL'=>$url,'PRIORITY_LANG'=>$priority_lang,'PRIORITY_ICON'=>$priority_icon,'RECURRING'=>$event['e_recurrence']!='none');
			for ($h=$from_h+1;$h<$to_h;$h++)
			{
				$streams[$found_stream][$h]=array('TPL'=>'-1');
			}
		}

		for ($i=0;$i<24;$i++)
		{
			// Work out the hour
			$hour=locale_filter(date(do_lang('time_hour_and_mins'),$i*60*60));

			// The streams need rendering for each hour
			$_streams=new ocp_tempcode();
			foreach ($streams as $stream)
			{
				$down='1';
				$priority='free_time';
				$entry=mixed();

				if (!array_key_exists($i,$stream))
				{
					$class='free_time_hourly';
					$text='&nbsp;';
					$entry=static_evaluate_tempcode(do_template('CALENDAR_DAY_ENTRY_FREE',array('_GUID'=>'0091fbb877164ac797cb88b4571b5d35','CLASS'=>$class,'TEXT'=>$text))); /*HTMLHTML*/
				} else
				{
					if ($stream[$i]['TPL']!='-1')
					{
						$down=$stream[$i]['DOWN'];
						$priority=$stream[$i]['PRIORITY'];
						$entry=static_evaluate_tempcode(do_template($stream[$i]['TPL'],$stream[$i])); /*HTMLHTML*/
					}
					else $entry='';
				}

				if ($down=='0') $down='1';
				if /*((is_object($entry)) && (!$entry->is_empty()))*/($entry!='')
				{
					$timestamp=$period_start+$i*60*60;
					if ((has_actual_page_access(NULL,'cms_calendar',NULL,NULL)) && (has_submit_permission('low',get_member(),get_ip_address(),'cms_calendar')))
					{
						$and_filter=$this->get_and_filter();
						$add_url=build_url(array('page'=>'cms_calendar','type'=>'ad','date'=>date('Y-m-d H:i:s',$timestamp),'e_type'=>(count($and_filter)==1)?$and_filter[0]:NULL),get_module_zone('cms_calendar'));
					} else $add_url=new ocp_tempcode();
					$_streams->attach(/*HTMLHTML*/static_evaluate_tempcode(do_template('CALENDAR_DAY_STREAM_HOUR',array('_GUID'=>'93a8fb53183a4225ec3bf7f2ea07cfc5','CURRENT'=>date('Y-m-d H',utctime_to_usertime())==date('Y-m-d H',$timestamp),'ADD_URL'=>$add_url,'PRIORITY'=>$priority,'DOWN'=>$down,'ENTRY'=>$entry))));
				}
			}

			$hours->attach(do_template('CALENDAR_DAY_HOUR',array('_GUID'=>'d967ce3f793942f78104c53b105f9f74','_HOUR'=>strval($i),'HOUR'=>$hour,'STREAMS'=>$_streams)));
		}

		return do_template('CALENDAR_DAY',array('_GUID'=>'60e102b38025c1b1618ac36070564065','HOURS'=>$hours,'PERIOD_START'=>strval($period_start),'PERIOD_END'=>strval($period_end)));
	}

	/**
	 * The calendar area view for viewing a single week.
	 *
	 * @param  string			The week we are viewing
	 * @param  string			The day (Y-m-d) we are viewing
	 * @param  array			List of components of our viewed ID
	 * @param  MEMBER			The member ID we are viewing as
	 * @param  ?array			The type filter (NULL: none)
	 * @return tempcode		The UI
	 */
	function view_calendar_view_week($view_id,$day,$explode,$member_id,$filter)
	{
		$start_year=intval($explode[0]);
		$start_week=intval($explode[1]);
		list($start_month,$start_day,$start_year)=date_from_week_of_year($start_year,$start_week);

		$period_start=mktime(0,0,0,$start_month,$start_day,$start_year);
		$period_end=$period_start+60*60*24*7;
		$happenings=calendar_matches($member_id,true,$period_start,$period_end,$filter);

		global $M_SORT_KEY;
		$M_SORT_KEY=0;
		usort($happenings,'multi_sort');

		if (get_option('ssw')=='0')
		{
			$day_remap=array('Mon'=>0,'Tue'=>1,'Wed'=>2,'Thu'=>3,'Fri'=>4,'Sat'=>5,'Sun'=>6);
		} else
		{
			$day_remap=array('Sun'=>6,'Mon'=>0,'Tue'=>1,'Wed'=>2,'Thu'=>3,'Fri'=>4,'Sat'=>5);
		}

		// We start with our 24x7 stream array, assuming all is free time
		$streams=array();  // hour X day
		for ($i=0;$i<24;$i++)
		{
			$streams[$i]=array();
			for ($j=0;$j<7;$j++)
			{
				$streams[$i][$j]=array('free_time','',$i);
			}
		}
		for ($j=0;$j<7;$j++)
		{
			for ($i=0;$i<24;$i++)
			{
				$entries=new ocp_tempcode();

				$class='';
				$worst_priority=6;
				$continuation=$i;

				// Happenings in a single day
				$cnt=count($happenings);
				for ($hap_i=0;$hap_i<$cnt;$hap_i++)
				{
					$happening=$happenings[$hap_i];
					list($e_id,$event,$from,$to,$real_from,$real_to,$utc_real_from)=$happening;
					$date=date('D:H',$from);
					$explode2=explode(':',$date);
					if ((intval($explode2[1])==$i) && ($day_remap[$explode2[0]]==$j))
					{
						$date=is_null($event['e_start_hour'])?'':locale_filter(my_strftime(do_lang('calendar_minute'),$real_from));
						if (is_numeric($e_id))
						{
							$map=array_merge($filter,array('page'=>'_SELF','type'=>'view','id'=>$event['e_id'],'day'=>date('Y-m-d',$utc_real_from),'date'=>$view_id,'back'=>'week'));
							if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
							$url=build_url($map,'_SELF');
						} else
						{
							$url=$e_id;
						}
						$icon=$event['t_logo'];
						if (!is_null($to))
						{
							$date=date_range($real_from,$real_to,!is_null($event['e_start_hour']));
							if ((!is_null($to)) && ($to>=mktime(0,0,0,$start_month,$start_day+$j+1,$start_year)))
							{
								$continuation=24;
								$ntime=mktime(0,0,0,$start_month,$start_day+$j+1,$start_year);
								if ($ntime<$period_end)
									$happenings[]=array($e_id,$event,$ntime,$to,$real_from,$real_to,$utc_real_from);
							}
							elseif (intval(date('H',$to))>$i+1)
							{
								$continuation=max($continuation,intval(date('H',$to)));
							}
						}
						$title=is_integer($event['e_title'])?get_translated_text($event['e_title']):$event['e_title'];
						$entries->attach(do_template('CALENDAR_WEEK_ENTRY',array('_GUID'=>'a5577fb634ecc5480789d1cd21f686fb','ID'=>strval($event['e_id']),'T_TITLE'=>array_key_exists('t_title',$event)?(is_string($event['t_title'])?$event['t_title']:get_translated_text($event['t_title'])):'RSS','PRIORITY'=>strval($event['e_priority']),'ICON'=>$icon,'TIME'=>$date,'TITLE'=>$title,'URL'=>$url,'RECURRING'=>$event['e_recurrence']!='none')));

						if ($event['e_priority']<$worst_priority)
						{
							$worst_priority=$event['e_priority'];
							$class='priority_'.strval($event['e_priority']);
						}
					}
				}

				// Not free time anymore
				if ($class!='')
				{
					$streams[$i][$j]=array($class,$entries,$continuation);

					// Now work backwards to cut back any continuation onto this spot
					for ($k=$i-1;$k>=0;$k--)
					{
						if ($streams[$k][$j][2]>=$i) $streams[$k][$j][2]=$i-1;
					}
				}
			}
		}

		// Now mark continuations ahead
		for ($i=0;$i<24;$i++)
		{
			for ($j=0;$j<7;$j++)
			{
				if ($streams[$i][$j][0]!='continuation')
				{
					$continuation=$streams[$i][$j][2];
					for ($k=$i+1;$k<=$continuation;$k++)
					{
						$streams[$k][$j][0]='continuation';
					}
				}
			}
		}

		// Now render
		$hours=new ocp_tempcode();
		for ($i=0;$i<24;$i++)
		{
			$hour=locale_filter(date(do_lang('time_hour_and_mins'),$i*60*60));

			$days=new ocp_tempcode();
			for ($j=0;$j<7;$j++)
			{
				$class=$streams[$i][$j][0];
				$entries=$streams[$i][$j][1];
				$continuation=$streams[$i][$j][2];

				if (((!is_object($entries)) || ($entries->is_empty())) && ($class!='continuation'))
				{
					$entries=do_template('CALENDAR_WEEK_ENTRY_FREE',array('_GUID'=>'4a16cdd5a2a0e5a444fd61a6aafa2ffa','CLASS'=>$class,'TEXT'=>''));
				}

				$down=$continuation-$i;

				if ($class!='continuation')
				{
					$timestamp=$period_start+($i+24*$j)*60*60;
					if ((has_actual_page_access(NULL,'cms_calendar',NULL,NULL)) && (has_submit_permission('low',get_member(),get_ip_address(),'cms_calendar')))
					{
						$and_filter=$this->get_and_filter();
						$add_url=build_url(array('page'=>'cms_calendar','type'=>'ad','date'=>date('Y-m-d H:i:s',$timestamp),'e_type'=>(count($and_filter)==1)?$and_filter[0]:NULL),get_module_zone('cms_calendar'));
					} else $add_url=new ocp_tempcode();
					$days->attach(do_template('CALENDAR_WEEK_HOUR_DAY',array('_GUID'=>'e001b4b2ea1995760ef0d4460d93b2e1','CURRENT'=>date('Y-m-d H',utctime_to_usertime())==date('Y-m-d H',$timestamp),'ADD_URL'=>$add_url,'DOWN'=>strval($down+1),'DAY'=>$day,'HOUR'=>$hour,'CLASS'=>$class,'ENTRIES'=>$entries)));
				}
			}

			$hours->attach(do_template('CALENDAR_WEEK_HOUR',array('_GUID'=>'a57d0d6a683d30fc0a48168b43299607','_HOUR'=>strval($i),'HOUR'=>$hour,'DAYS'=>$days)));
		}

		$offset=0;
		if (get_option('ssw')=='1')
		{
			$datex=date('Y-m-d',mktime(0,0,0,$start_month,$start_day+$offset,intval($explode[0])));
			$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$datex));
			if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
			$sunday_url=build_url($map,'_SELF');
			$sunday_date=locale_filter(date(do_lang('calendar_day_of_month_verbose'),$period_start+$offset*60*60*24));
			$offset++;
		}
		$datex=date('Y-m-d',mktime(0,0,0,$start_month,$start_day+$offset,intval($explode[0])));
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$datex));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$monday_url=build_url($map,'_SELF');
		$monday_date=locale_filter(date(do_lang('calendar_day_of_month_verbose'),$period_start+$offset*60*60*24));
		$offset++;
		$datex=date('Y-m-d',mktime(0,0,0,$start_month,$start_day+$offset,intval($explode[0])));
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$datex));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$tuesday_url=build_url($map,'_SELF');
		$tuesday_date=locale_filter(date(do_lang('calendar_day_of_month_verbose'),$period_start+$offset*60*60*24));
		$offset++;
		$datex=date('Y-m-d',mktime(0,0,0,$start_month,$start_day+$offset,intval($explode[0])));
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$datex));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$wednesday_url=build_url($map,'_SELF');
		$wednesday_date=locale_filter(date(do_lang('calendar_day_of_month_verbose'),$period_start+$offset*60*60*24));
		$offset++;
		$datex=date('Y-m-d',mktime(0,0,0,$start_month,$start_day+$offset,intval($explode[0])));
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$datex));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$thursday_url=build_url($map,'_SELF');
		$thursday_date=locale_filter(date(do_lang('calendar_day_of_month_verbose'),$period_start+$offset*60*60*24));
		$offset++;
		$datex=date('Y-m-d',mktime(0,0,0,$start_month,$start_day+$offset,intval($explode[0])));
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$datex));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$friday_url=build_url($map,'_SELF');
		$friday_date=locale_filter(date(do_lang('calendar_day_of_month_verbose'),$period_start+$offset*60*60*24));
		$offset++;
		$datex=date('Y-m-d',mktime(0,0,0,$start_month,$start_day+$offset,intval($explode[0])));
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$datex));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$saturday_url=build_url($map,'_SELF');
		$saturday_date=locale_filter(date(do_lang('calendar_day_of_month_verbose'),$period_start+$offset*60*60*24));
		$offset++;
		if (get_option('ssw')=='0')
		{
			$datex=date('Y-m-d',mktime(0,0,0,$start_month,$start_day+$offset,intval($explode[0])));
			$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$datex));
			if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
			$sunday_url=build_url($map,'_SELF');
			$sunday_date=locale_filter(date(do_lang('calendar_day_of_month_verbose'),$period_start+$offset*60*60*24));
			$offset++;
		}

		return do_template('CALENDAR_WEEK',array('_GUID'=>'8dd9ad4e874d2f320a48551e0c9bde57','MONDAY_DATE'=>$monday_date,'TUESDAY_DATE'=>$tuesday_date,'WEDNESDAY_DATE'=>$wednesday_date,'THURSDAY_DATE'=>$thursday_date,'FRIDAY_DATE'=>$friday_date,'SATURDAY_DATE'=>$saturday_date,'SUNDAY_DATE'=>$sunday_date,'MONDAY_URL'=>$monday_url,'TUESDAY_URL'=>$tuesday_url,'WEDNESDAY_URL'=>$wednesday_url,'THURSDAY_URL'=>$thursday_url,'FRIDAY_URL'=>$friday_url,'SATURDAY_URL'=>$saturday_url,'SUNDAY_URL'=>$sunday_url,'HOURS'=>$hours,'PERIOD_START'=>strval($period_start),'PERIOD_END'=>strval($period_end)));
	}

	/**
	 * The calendar area view for viewing a single month.
	 *
	 * @param  string			The month we are viewing
	 * @param  string			The day (Y-m-d) we are viewing
	 * @param  array			List of components of our viewed ID
	 * @param  MEMBER			The member ID we are viewing as
	 * @param  ?array			The type filter (NULL: none)
	 * @return tempcode		The UI
	 */
	function view_calendar_view_month($view_id,$day,$explode,$member_id,$filter)
	{
		$period_start=mktime(0,0,0,intval($explode[1]),1,intval($explode[0]));
		$period_end=mktime(0,0,0,intval($explode[1])+1,1,intval($explode[0]));
		$_days=intval(round(floatval($period_end-$period_start)/floatval(60*60*24)));
		if ($_days==0) warn_exit(do_lang_tempcode('INTERNAL_ERROR'));

		$happenings=calendar_matches($member_id,true,$period_start,$period_end,$filter);

		global $M_SORT_KEY;
		$M_SORT_KEY=0;
		usort($happenings,'multi_sort');

		if (get_option('ssw')=='0')
		{
			$ex_array=array('Mon'=>0,'Tue'=>1,'Wed'=>2,'Thu'=>3,'Fri'=>4,'Sat'=>5,'Sun'=>6);
		} else
		{
			$ex_array=array('Sun'=>0,'Mon'=>1,'Tue'=>2,'Wed'=>3,'Thu'=>4,'Fri'=>5,'Sat'=>6);
		}
		$empty_entry=do_template('CALENDAR_MONTH_ENTRY_FREE',array('_GUID'=>'c8eba771bd5f9a58d4822f4a2dac57a2','CLASS'=>'spacer','TEXT'=>''));

		$weeks=new ocp_tempcode();
		$days=new ocp_tempcode();
		$week_date=locale_filter(date(do_lang('calendar_day_of_month'),$period_start));
		$week_count=intval(get_week_number_for($period_start,true));
		$day_of_week=date('D',$period_start);
		for ($x=0;$x<$ex_array[$day_of_week];$x++)
			$days->attach(do_template('CALENDAR_MONTH_DAY',array('_GUID'=>'783ff6377292cc0400638c8857446a16','CURRENT'=>false,'DAY_URL'=>'','CLASS'=>'','DAY'=>'','ENTRIES'=>$empty_entry)));
		$dotw=$ex_array[$day_of_week]-1;
		for ($i=1;$i<=$_days;$i++)
		{
			$entries=new ocp_tempcode();

			$timestamp=$period_start+($i-1)*60*60*24+60*60;
			$day_of_week=date('D',$timestamp);
			if ($dotw==6)
			{
				$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'week','id'=>$explode[0].'-'.strval($week_count)));
				if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
				$week_url=build_url($map,'_SELF');
				$weeks->attach(do_template('CALENDAR_MONTH_WEEK',array('_GUID'=>'1010b12d8677bc577f30a013e9a838ce','WEEK_URL'=>$week_url,'WEEK_DATE'=>$week_date,'DAYS'=>$days)));
				$days=new ocp_tempcode();
				$week_date=locale_filter(date(do_lang('calendar_day_of_month'),$period_start+($i-1)*60*60*24));
				$week_count++;
				$dotw=0;
			} else $dotw++;

			$class='';
			$worst_priority=6;

			$cnt=count($happenings);
			for ($hap_i=0;$hap_i<$cnt;$hap_i++)
			{
				$happening=$happenings[$hap_i];

				list($e_id,$event,$from,$to,$real_from,$real_to,$utc_real_from)=$happening;
				$date=date('d',$from);
				if (intval($date)==$i)
				{
					$date=is_null($event['e_start_hour'])?'':locale_filter(my_strftime(do_lang('calendar_minute'),$real_from));

					if (is_numeric($e_id))
					{
						$map=array_merge($filter,array('page'=>'_SELF','type'=>'view','id'=>$event['e_id'],'day'=>date('Y-m-d',$utc_real_from),'date'=>$view_id,'back'=>'month'));
						if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
						$url=build_url($map,'_SELF');
					} else
					{
						$url=$e_id;
					}
					$icon=$event['t_logo'];
					if (!is_null($to))
					{
						$date=date_range($real_from,$real_to,!is_null($event['e_start_hour']));
					}
					$title=is_integer($event['e_title'])?get_translated_text($event['e_title']):$event['e_title'];
					$entries->attach(do_template('CALENDAR_MONTH_ENTRY',array('_GUID'=>'58353fc64595f981d41da303cfe40855','ID'=>strval($event['e_id']),'T_TITLE'=>array_key_exists('t_title',$event)?(is_string($event['t_title'])?$event['t_title']:get_translated_text($event['t_title'])):'RSS','PRIORITY'=>strval($event['e_priority']),'ICON'=>$icon,'TIME'=>$date,'TITLE'=>$title,'URL'=>$url,'RECURRING'=>$event['e_recurrence']!='none')));

					if ($event['e_priority']<$worst_priority)
					{
						$worst_priority=$event['e_priority'];
						$class='priority_'.strval($event['e_priority']);
					}

					if (!is_null($to))
					{
						$test=date('d',$to);
						$test2=date('d',$from);
						if (((intval($test)>intval($test2)) || (intval(date('m',$to))!=intval(date('m',$from))) || (intval(date('Y',$to))!=intval(date('Y',$from)))))
						{
							$ntime=mktime(0,0,0,intval(date('m',$from)),intval($test2)+1,intval(date('Y',$from)));
							if ($ntime<$period_end)
								$happenings[]=array($e_id,$event,$ntime,$to,$real_from,$real_to,$utc_real_from);
						}
					}
				}
			}

			if ($entries->is_empty())
			{
				$class='free_time';
				$text='&nbsp;';
				$entries=do_template('CALENDAR_MONTH_ENTRY_FREE',array('_GUID'=>'a5e193c8c14deb17ac629e0de74458a2','CLASS'=>$class,'TEXT'=>$text));
			}

			$day_url=build_url(array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$explode[0].'-'.$explode[1].'-'.str_pad(strval($i),2,'0',STR_PAD_LEFT)),'_SELF');
			$days->attach(do_template('CALENDAR_MONTH_DAY',array('_GUID'=>'44162c09e0647888cf079c0ac78c1912','CURRENT'=>date('Y-m-d')==date('Y-m-d',$timestamp),'DAY_URL'=>$day_url,'CLASS'=>$class,'DAY'=>strval($i),'ENTRIES'=>$entries)));
		}
		$ex_array=array_flip(array_reverse(array_flip($ex_array)));
		for ($x=0;$x<$ex_array[$day_of_week];$x++)
			$days->attach(do_template('CALENDAR_MONTH_DAY',array('_GUID'=>'3a34ca72f31d9244b9eb642814836736','CURRENT'=>false,'DAY_URL'=>'','CLASS'=>'','DAY'=>'','ENTRIES'=>$empty_entry)));
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'week','id'=>$explode[0].'-'.strval($week_count)));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$week_url=build_url($map,'_SELF');
		$weeks->attach(do_template('CALENDAR_MONTH_WEEK',array('_GUID'=>'0fc7a1691b3cb081d80519a2fe666849','WEEK_URL'=>$week_url,'WEEK_DATE'=>$week_date,'DAYS'=>$days)));

		return do_template('CALENDAR_MONTH',array('_GUID'=>'2ada774f9eb9524db1bed9bea440e5d1','WEEKS'=>$weeks,'PERIOD_START'=>strval($period_start),'PERIOD_END'=>strval($period_end)));
	}

	/**
	 * The calendar area view for viewing a single year.
	 *
	 * @param  string			The year we are viewing
	 * @param  string			The day (Y-m-d) we are viewing
	 * @param  array			List of components of our viewed ID
	 * @param  MEMBER			The member ID we are viewing as
	 * @param  ?array			The type filter (NULL: none)
	 * @return tempcode		The UI
	 */
	function view_calendar_view_year($view_id,$day,$explode,$member_id,$filter)
	{
		$period_start=mktime(0,0,0,1,1,intval($explode[0]));
		$period_end=mktime(0,0,0,1,0,intval($explode[0])+1);

		$happenings=calendar_matches($member_id,true,$period_start,$period_end,$filter);

		global $M_SORT_KEY;
		$M_SORT_KEY=0;
		usort($happenings,'multi_sort');

		$months='';
		$month_rows=new ocp_tempcode();
		for ($i=1;$i<=12;$i++)
		{
			$entries=array();
			$priorities=array();

			if ((($i-1)%3==0) && ($i!=1))
			{
				if ($i==4) list($month_a,$month_b,$month_c)=array(do_lang_tempcode('JANUARY'),do_lang_tempcode('FEBRUARY'),do_lang_tempcode('MARCH'));
				if ($i==7) list($month_a,$month_b,$month_c)=array(do_lang_tempcode('APRIL'),do_lang_tempcode('MAY'),do_lang_tempcode('JUNE'));
				if ($i==10) list($month_a,$month_b,$month_c)=array(do_lang_tempcode('JULY'),do_lang_tempcode('AUGUST'),do_lang_tempcode('SEPTEMBER'));
				$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'month','id'=>$explode[0].'-'.strval($i-3)));
				if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
				$month_a_url=build_url($map,'_SELF');
				$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'month','id'=>$explode[0].'-'.strval($i-2)));
				if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
				$month_b_url=build_url($map,'_SELF');
				$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'month','id'=>$explode[0].'-'.strval($i-1)));
				if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
				$month_c_url=build_url($map,'_SELF');
				$month_rows->attach(do_template('CALENDAR_YEAR_MONTH_ROW',array('_GUID'=>'cf4f7b66a32afee1c69b2e4338af8dd5','MONTHS'=>$months,'MONTH_A_URL'=>$month_a_url,'MONTH_B_URL'=>$month_b_url,'MONTH_C_URL'=>$month_c_url,'MONTH_A'=>$month_a,'MONTH_B'=>$month_b,'MONTH_C'=>$month_c)));
				$months='';
			}

			foreach ($happenings as $happening)
			{
				list($e_id,$event,$from,$to,$real_from,$real_to,$utc_real_from)=$happening;
				$date=date('m',$from);
				if (intval($date)==$i)
				{
					if (is_numeric($e_id))
					{
						$map=array_merge($filter,array('page'=>'_SELF','type'=>'view','id'=>$event['e_id'],'day'=>date('Y-m-d',$utc_real_from),'date'=>$view_id,'back'=>'year'));
						if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
						$url=build_url($map,'_SELF');
					} else
					{
						$url=$e_id;
					}
					$icon=$event['t_logo'];
					$title=is_integer($event['e_title'])?get_translated_text($event['e_title']):$event['e_title'];
					$date=locale_filter(date(do_lang('calendar_date'),$real_from));
					$_from=$from;
					do
					{
						$_day=intval(date('d',$_from));

						if (!array_key_exists($_day,$entries))
						{
							$entries[$_day]=array('ID'=>strval($event['e_id']),'T_TITLE'=>array_key_exists('t_title',$event)?(is_string($event['t_title'])?$event['t_title']:get_translated_text($event['t_title'])):'RSS','PRIORITY'=>strval($event['e_priority']),'ICON'=>$icon,'TIME'=>$date,'TITLE'=>$title,'URL'=>$url);
							$priorities[$_day]=$event['e_priority'];
						} else // If we have more than one, we don't store a map, we just count them
						{
							$entries[$_day]=(is_array($entries[$_day]))?2:($entries[$_day]+1);
							$priorities[$_day]=min($priorities[$_day],$event['e_priority']);
						}

						// Maybe spanning multiple days of month
						if (!is_null($to))
						{
							$_explode=explode('-',date('d-m',$to));
							$continues=(intval($_explode[0])!=$_day) || (intval($_explode[1])!=$i);
							if ($continues)
							{
								$_from=mktime(0,0,0,intval(date('m',$_from)),intval(date('d',$_from))+1,intval(date('Y',$_from)));
								if (intval(date('m',$_from))!=$i) $continues=false; // Don't let it roll to another month
							}
						} else $continues=false;
					}
					while ($continues);

					// Push to other months too
					if (!is_null($to))
					{
						$date2=date('m',$to);
						if (intval($date2)>$i)
						{
							$ntime=mktime(0,0,0,intval(date('m',$from))+1,1,intval(date('Y',$from)));
							$happenings[]=array($e_id,$event,$ntime,$to,$real_from,$real_to,$utc_real_from);
						}
					}
				}
			}

			$_period_start=mktime(0,0,0,$i,1,intval($explode[0]));
			$_period_end=mktime(0,0,0,$i+1,0,intval($explode[0]));
			$_days=intval(round(floatval($_period_end-$_period_start)/floatval(60*60*24)));

			$_entries=new ocp_tempcode();
			$__entries=new ocp_tempcode();
			$_dotw=date('D',$_period_start);
			if (get_option('ssw')=='0')
			{
				$ex_array=array('Mon'=>0,'Tue'=>1,'Wed'=>2,'Thu'=>3,'Fri'=>4,'Sat'=>5,'Sun'=>6);
			} else
			{
				$ex_array=array('Sun'=>0,'Mon'=>1,'Tue'=>2,'Wed'=>3,'Thu'=>4,'Fri'=>5,'Sat'=>6);
			}
			$dotw=$ex_array[$_dotw];
			for ($j=0;$j<$dotw;$j++)
				$__entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_SPACER'));
			for ($j=1;$j<=$_days+1;$j++)
			{
				$date=$explode[0].'-'.str_pad(strval($i),2,'0',STR_PAD_LEFT).'-'.str_pad(strval($j),2,'0',STR_PAD_LEFT);
				$date_formatted=locale_filter(date(do_lang('calendar_date'),mktime(0,0,0,$i,$j,intval($explode[0]))));
				$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'day','id'=>$date));
				if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
				$day_url=build_url($map,'_SELF');

				if (!array_key_exists($j,$entries))
				{
					$class='free_time';
					$__entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_FREE',array('_GUID'=>'cccf839895121ae14fd1948ed4024d4b','CURRENT'=>date('Y-m-d')==$date,'DAY_URL'=>$day_url,'DATE'=>$date_formatted,'DAY'=>strval($j),'CLASS'=>$class)));
				}
				elseif (is_array($entries[$j]))
				{
					$class='single';
					$events_and_priority_lang=do_lang_tempcode('TOTAL_EVENTS_AND_HIGHEST_PRIORITY','1',do_lang_tempcode('PRIORITY_'.strval($priorities[$j])));
					$__entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_ACTIVE',array_merge(array('CURRENT'=>date('Y-m-d')==$date,'DAY_URL'=>$day_url,'DATE'=>$date_formatted,'DAY'=>strval($j),'CLASS'=>$class,'COUNT'=>'1','EVENTS_AND_PRIORITY_LANG'=>$events_and_priority_lang),$entries[$j])));
				}
				else
				{
					$class='multiple';
					$e_count=integer_format($entries[$j]);
					$events_and_priority_lang=do_lang_tempcode('TOTAL_EVENTS_AND_HIGHEST_PRIORITY',make_string_tempcode($e_count),do_lang_tempcode('PRIORITY_'.strval($priorities[$j])));
					$__entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_ACTIVE',array('_GUID'=>'9f016773fce6402eca1a7a0afa6bb89f','CURRENT'=>date('Y-m-d')==$date,'DAY_URL'=>$day_url,'DATE'=>$date_formatted,'TITLE'=>'','TIME'=>'','URL'=>'','ID'=>'','PRIORITY'=>strval($priorities[$j]),'DAY'=>strval($j),'ICON'=>'','COUNT'=>$e_count,'EVENTS_AND_PRIORITY_LANG'=>$events_and_priority_lang)));
				}

				if ($dotw==6)
				{
					$_entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_ROW',array('_GUID'=>'f0d29f0cf53ba119a7ab4351807a9f2f','ENTRIES'=>$__entries)));
					$__entries=new ocp_tempcode();
					$dotw=0;
				} else $dotw++;
			}

			if (!$__entries->is_empty())
			{
				for ($j=$dotw;$j<7;$j++)
					$__entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_SPACER'));
				$_entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_ROW',array('_GUID'=>'5c7fa5d50e1e9eb30f0a19cc7da03c4b','ENTRIES'=>$__entries)));
			}

			$month=do_template('CALENDAR_YEAR_MONTH',array('_GUID'=>'58c9f4cc04186dce6e7ea3dd8ec9269b','ENTRIES'=>$_entries));
			$months.=$month->evaluate()/*FUDGEFUDGE*/;
		}

		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'month','id'=>$explode[0].'-'.strval($i-3)));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$month_a_url=build_url($map,'_SELF');
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'month','id'=>$explode[0].'-'.strval($i-2)));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$month_b_url=build_url($map,'_SELF');
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>'month','id'=>$explode[0].'-'.strval($i-1)));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$month_c_url=build_url($map,'_SELF');
		list($month_a,$month_b,$month_c)=array(do_lang_tempcode('OCTOBER'),do_lang_tempcode('NOVEMBER'),do_lang_tempcode('DECEMBER'));
		$month_rows->attach(do_template('CALENDAR_YEAR_MONTH_ROW',array('_GUID'=>'d169c06319ca2c089faa6fbb92b39115','MONTHS'=>$months,'MONTH_A_URL'=>$month_a_url,'MONTH_B_URL'=>$month_b_url,'MONTH_C_URL'=>$month_c_url,'MONTH_A'=>$month_a,'MONTH_B'=>$month_b,'MONTH_C'=>$month_c)));

		return do_template('CALENDAR_YEAR',array('_GUID'=>'ee8f09d406eff18637b37b762bad62cd','MONTH_ROWS'=>$month_rows,'PERIOD_START'=>strval($period_start),'PERIOD_END'=>strval($period_end)));
	}

	/**
	 * View an event.
	 *
	 * @return tempcode		The UI
	 */
	function view_event()
	{
		check_specific_permission('view_calendar');

		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='back';

		$id=get_param_integer('id');
		$filter=$this->get_filter();
		$GLOBALS['FEED_URL']=find_script('backend').'?mode=calendar&filter='.implode(',',$this->get_and_filter());

		$rows=$GLOBALS['SITE_DB']->query_select('calendar_events e LEFT JOIN '.$GLOBALS['SITE_DB']->get_table_prefix().'calendar_types t ON t.id=e.e_type',array('*'),array('e.id'=>$id),'',1);
		if (!array_key_exists(0,$rows))
		{
			warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
		}
		$event=$rows[0];
		if ($event['e_is_public']==0) enforce_personal_access($event['e_submitter'],'view_personal_events');

		if (!has_category_access(get_member(),'calendar',strval($event['e_type']))) access_denied('CATEGORY_ACCESS');

		// Validation
		$warning_details=new ocp_tempcode();
		if (($event['validated']==0) && ($event['e_is_public']==1))
		{
			if (!has_specific_permission(get_member(),'jump_to_unvalidated'))
				access_denied('PRIVILEGE','jump_to_unvalidated');

			$warning_details->attach(do_template('WARNING_BOX',array('_GUID'=>'332faacba974e648a67e5e91ffd3d8e5','WARNING'=>do_lang_tempcode((get_param_integer('redirected',0)==1)?'UNVALIDATED_TEXT_NON_DIRECT':'UNVALIDATED_TEXT'))));
		}

		if (addon_installed('awards'))
		{
			require_code('awards');
			$awards=find_awards_for('event',strval($id));
		} else $awards=array();

		$_title=get_translated_text($event['e_title']);
		$title_to_use=do_lang_tempcode('CALENDAR_EVENT_VCAL',escape_html($_title));
		$title_to_use_2=do_lang('CALENDAR_EVENT',$_title);
		$title=get_screen_title($title_to_use,false,NULL,NULL,$awards);

		seo_meta_load_for('event',strval($id),$title_to_use_2);

		$content=($event['e_type']==db_get_first_id())?make_string_tempcode(get_translated_text($event['e_content'])):get_translated_tempcode($event['e_content']);
		$type=get_translated_text($event['t_title']);
		$subscribed=new ocp_tempcode();
		if ((has_specific_permission(get_member(),'view_event_subscriptions')) && (cron_installed()))
		{
			$subscriptions=$GLOBALS['SITE_DB']->query_select('calendar_reminders',array('DISTINCT n_member_id'),array('e_id'=>$id),'',100);
			if (count($subscriptions)<100)
			{
				foreach ($subscriptions as $subscription)
				{
					$username=$GLOBALS['FORUM_DRIVER']->get_username($subscription['n_member_id']);
					if (!is_null($username))
					{
						$member_url=$GLOBALS['FORUM_DRIVER']->member_profile_url($subscription['n_member_id'],false,true);
						$subscribed->attach(do_template('CALENDAR_EVENT_SCREEN_SUBSCRIPTION',array('_GUID'=>'c756b8b3f0c57494fd46a94e9abce029','MEMBER_URL'=>$member_url,'USERNAME'=>$username)));
					}
				}
			}
		}

		$start_day_of_month=find_concrete_day_of_month($event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_start_monthly_spec_type'],is_null($event['e_start_hour'])?find_timezone_start_hour_in_utc($event['e_timezone'],$event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_start_monthly_spec_type']):$event['e_start_hour'],is_null($event['e_start_minute'])?find_timezone_start_minute_in_utc($event['e_timezone'],$event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_start_monthly_spec_type']):$event['e_start_minute'],$event['e_timezone'],$event['e_do_timezone_conv']==1);
		$__first_date=mktime($event['e_start_hour'],$event['e_start_minute'],0,$event['e_start_month'],$start_day_of_month,$event['e_start_year']);
		$_first_date=cal_utctime_to_usertime(
			$__first_date,
			$event['e_timezone'],
			$event['e_do_timezone_conv']==1
		);
		$first_date=date('Y-m-d',$_first_date);
		$date=get_param('date',$first_date); // It's year 10,000 compliant when it comes to year display ;).
		$back_type=get_param('back','day');
		$map=array_merge($filter,array('page'=>'_SELF','type'=>'misc','view'=>$back_type,'id'=>$date));
		if (get_param_integer('member_id',get_member())!=get_member()) $map['member_id']=get_param_integer('member_id');
		$back_url=build_url($map,'_SELF');

		list($rating_details,$comment_details,$trackback_details)=embed_feedback_systems(
			'events',
			strval($id).(($event['e_seg_recurrences']==1)?('_'.$date):''),
			$event['allow_rating'],
			$event['allow_comments'],
			$event['allow_trackbacks'],
			((get_option('is_on_strong_forum_tie')=='0') || ($event['e_is_public']==1))?1:0,
			$event['e_submitter'],
			build_url(array('page'=>'_SELF','type'=>'entry','id'=>$id),'_SELF',NULL,false,false,true),
			$_title,
			get_value('comment_forum__calendar')
		);

		$_subscriptions=new ocp_tempcode();
		if ((is_guest()) || (!cron_installed()))
		{
			$subscribe_url=new ocp_tempcode();
		} else
		{
			$subscriptions=$GLOBALS['SITE_DB']->query_select('calendar_reminders',array('id','n_seconds_before'),array('e_id'=>$id,'n_member_id'=>get_member()));
			foreach ($subscriptions as $subscription)
			{
				$time=display_time_period(intval($subscription['n_seconds_before']));
				$unsubscribe_url=build_url(array('page'=>'_SELF','type'=>'unsubscribe_event','id'=>$id,'reminder_id'=>$subscription['id']),'_SELF');
				$_subscriptions->attach(do_template('CALENDAR_EVENT_SCREEN_PERSONAL_SUBSCRIPTION',array('_GUID'=>'cc36c75ea516abfab1c65328c0e290fc','UNSUBSCRIBE_URL'=>$unsubscribe_url,'TIME'=>$time)));
			}
			$subscribe_url=build_url(array('page'=>'_SELF','type'=>'subscribe_event','id'=>$id),'_SELF');
		}

		if ((has_actual_page_access(NULL,'cms_calendar',NULL,NULL)) && (has_edit_permission(($event['e_is_public']==0)?'low':'mid',get_member(),$event['e_submitter'],'cms_calendar',array('calendar',$event['e_type']))))
		{
			$edit_url=build_url(array('page'=>'cms_calendar','type'=>'_ed','id'=>$id),get_module_zone('cms_calendar'));
		} else
		{
			$edit_url=new ocp_tempcode();
		}

		if ($event['e_seg_recurrences']==0)
		{
			$NON_CANONICAL_PARAMS[]='day';
			$NON_CANONICAL_PARAMS[]='date';
		}

		$day=get_param('day','');
		if ($day!='')
		{
			$explode=explode('-',$day);
			if (count($explode)==3)
			{
				$orig_start_year=$event['e_start_year'];
				$orig_start_month=$event['e_start_month'];
				$orig_start_day=$event['e_start_day'];

				$event['e_start_year']=intval($explode[0]);
				$event['e_start_month']=intval($explode[1]);
				$event['e_start_day']=intval($explode[2]);
				$start_day_of_month=$event['e_start_day'];
				$event['e_start_monthly_spec_type']='day_of_month';

				// Crossing a DST in our reference timezone? (as we store in UTC, which is DST-less, we need to specially accomodate for this)
				if (!is_null($event['e_start_hour']))
				{
					$event['e_start_hour']-=intval(date('H',tz_time(mktime(0,0,0,$event['e_start_month'],$event['e_start_day'],$event['e_start_year']),$event['e_timezone'])))-intval(date('H',tz_time(mktime(0,0,0,$orig_start_month,$orig_start_day,$orig_start_year),$event['e_timezone'])));
					$event['e_start_minute']-=intval(date('i',tz_time(mktime(0,0,0,$event['e_start_month'],$event['e_start_day'],$event['e_start_year']),$event['e_timezone'])))-intval(date('i',tz_time(mktime(0,0,0,$orig_start_month,$orig_start_day,$orig_start_year),$event['e_timezone'])));
				}

				if (is_null($event['e_start_hour'])) // All day event
				{
					if (is_null($event['e_end_year']) || is_null($event['e_end_month']) || is_null($event['e_end_day']))
					{
						$event['e_end_day']=$orig_start_day;
						$event['e_end_month']=$orig_start_month;
						$event['e_end_year']=$orig_start_year;
					}
				}
				if (!is_null($event['e_end_year']) && !is_null($event['e_end_month']) && !is_null($event['e_end_day']))
				{
					$orig_end_year=$event['e_end_year'];
					$orig_end_month=$event['e_end_month'];
					$orig_end_day=$event['e_end_day'];

					if ($event['e_start_monthly_spec_type']!='day_of_month')
					{
						//$event['e_end_day']=find_concrete_day_of_month($event['e_end_year'],$event['e_end_month'],$event['e_end_day'],$event['e_end_monthly_spec_type'],is_null($event['e_end_hour'])?find_timezone_end_hour_in_utc($event['e_timezone'],$event['e_end_year'],$event['e_end_month'],$event['e_end_day'],$event['e_end_monthly_spec_type']):$event['e_end_hour'],is_null($event['e_end_minute'])?find_timezone_end_minute_in_utc($event['e_timezone'],$event['e_end_year'],$event['e_end_month'],$event['e_end_day'],$event['e_end_monthly_spec_type']):$event['e_end_minute'],$event['e_timezone'],$event['e_do_timezone_conv']==1);
						$event['e_end_day']=$event['e_start_day']+get_days_between($orig_start_year,$orig_start_month,$orig_start_day,$event['e_end_year'],$event['e_end_month'],$event['e_end_day']);
						$event['e_end_month']=$event['e_start_month'];
						$event['e_end_year']=$event['e_start_year'];
						$event['e_end_monthly_spec_type']='day_of_month';
					}

					$event['e_end_day']+=intval($explode[2])-$orig_start_day;
					$event['e_end_month']+=intval($explode[1])-$orig_start_month;
					$event['e_end_year']+=intval($explode[0])-$orig_start_year;

					// Crossing a DST in our reference timezone? (as we store in UTC, which is DST-less, we need to specially accomodate for this)
					if (!is_null($event['e_end_hour']))
					{
						$event['e_end_hour']-=intval(date('H',tz_time(mktime(0,0,0,$event['e_end_month'],$event['e_end_day'],$event['e_end_year']),$event['e_timezone'])))-intval(date('H',tz_time(mktime(0,0,0,$orig_end_month,$orig_end_day,$orig_end_year),$event['e_timezone'])));
						$event['e_end_minute']-=intval(date('i',tz_time(mktime(0,0,0,$event['e_end_month'],$event['e_end_day'],$event['e_end_year']),$event['e_timezone'])))-intval(date('i',tz_time(mktime(0,0,0,$orig_end_month,$orig_end_day,$orig_end_year),$event['e_timezone'])));
					}
				}
			}
		}
		$time_raw=cal_get_start_utctime_for_event($event['e_timezone'],$event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_start_monthly_spec_type'],$event['e_start_hour'],$event['e_start_minute'],$event['e_do_timezone_conv']==1);
		$from=cal_utctime_to_usertime($time_raw,$event['e_timezone'],$event['e_do_timezone_conv']==1);
		if ($day!='')
		{
			if (date('Y-m-d',$from)!=$day) // Possibly the day given in URL is invalid due to a day shift across timezones, adjust if required and recalculate
			{
				$day_dif=$event['e_start_day']-intval(date('d',$from));
				$event['e_start_day']+=$day_dif;
				$event['e_end_day']+=$day_dif;
				$time_raw=cal_get_start_utctime_for_event($event['e_timezone'],$event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_start_monthly_spec_type'],$event['e_start_hour'],$event['e_start_minute'],$event['e_do_timezone_conv']==1);
				$from=cal_utctime_to_usertime($time_raw,$event['e_timezone'],$event['e_do_timezone_conv']==1);
			}
		}
		$day_formatted=locale_filter(date(do_lang('calendar_date'),$from));
		if (!is_null($event['e_end_year']) && !is_null($event['e_end_month']) && !is_null($event['e_end_day']))
		{
			if ($event['e_end_monthly_spec_type']!='day_of_month')
			{
				$event['e_end_monthly_spec_type']='day_of_month';
				$dif_days=get_days_between($event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_end_year'],$event['e_end_month'],$event['e_end_day']);
				$event['e_end_year']=$event['e_start_year'];
				$event['e_end_month']=$event['e_start_month'];
				$event['e_end_day']=$event['e_start_day']+$dif_days;
			}

			$to_raw=cal_get_end_utctime_for_event($event['e_timezone'],$event['e_end_year'],$event['e_end_month'],$event['e_end_day'],$event['e_end_monthly_spec_type'],$event['e_end_hour'],$event['e_end_minute'],$event['e_do_timezone_conv']==1);
			$to=cal_utctime_to_usertime($to_raw,$event['e_timezone'],$event['e_do_timezone_conv']==1);
			$to_day_formatted=locale_filter(date(do_lang('calendar_date'),$to));
			$time2=date_range($from,$to,!is_null($event['e_start_hour']));
		} else
		{
			$to_raw=NULL;
			$time2=is_null($event['e_start_hour'])?'':locale_filter(my_strftime(do_lang('calendar_minute'),$from));
			$to=NULL;
			$to_day_formatted=NULL;
		}

		$priority=$event['e_priority'];
		$is_public=($event['e_is_public']==1)?do_lang_tempcode('YES'):do_lang_tempcode('NO');

		$recurrence=do_lang_tempcode('NA_EM');
		if (($event['e_recurrence']!='none') && ($event['e_recurrence']!=''))
		{
			$l_code=explode(' ',strtoupper($event['e_recurrence']));
			$recurrence=do_lang_tempcode($l_code[0]);

			if (!is_null($event['e_recurrences']))
			{
				$recurrence=do_lang_tempcode('RECURRENCE_ITERATION',$recurrence,make_string_tempcode(integer_format($event['e_recurrences'])));
			}
		} elseif ($day=='') $day=$first_date;

		$priority_lang=do_lang_tempcode('PRIORITY_'.strval($priority));

		// Views
		if (get_db_type()!='xml')
		{
			$event['e_views']++;
			$GLOBALS['SITE_DB']->query_update('calendar_events',array('e_views'=>$event['e_views']),array('id'=>$id),'',1,NULL,false,true);
		}

		breadcrumb_set_parents(array(array($back_url,do_lang_tempcode('CALENDAR'))));

		$GLOBALS['META_DATA']+=array(
			'created'=>date('Y-m-d',$event['e_add_date']),
			'creator'=>$GLOBALS['FORUM_DRIVER']->get_username($event['e_submitter']),
			'publisher'=>'', // blank means same as creator
			'modified'=>is_null($event['e_edit_date'])?'':date('Y-m-d',$event['e_edit_date']),
			'type'=>'Calendar event',
			'title'=>get_translated_text($event['e_title']),
			'identifier'=>'_SEARCH:calendar:view:'.strval($id),
			'description'=>get_translated_text($event['e_content']),
			'image'=>find_theme_image('bigicons/calendar'),
		);

		$map=array(
			'TITLE'=>$title,
			'_TITLE'=>get_translated_text($event['e_title']),
			'ID'=>strval($id),
			'TAGS'=>get_loaded_tags('calendar'),
			'WARNING_DETAILS'=>$warning_details,
			'SUBMITTER'=>strval($event['e_submitter']),
			'ADD_DATE'=>get_timezoned_date($event['e_add_date']),
			'ADD_DATE_RAW'=>strval($event['e_add_date']),
			'EDIT_DATE_RAW'=>is_null($event['e_edit_date'])?'':strval($event['e_edit_date']),
			'VIEWS'=>integer_format($event['e_views']),
			'LOGO'=>$event['t_logo'],
			'DAY'=>$day_formatted,
			'TO_DAY'=>$to_day_formatted,
			'RECURRENCE'=>$recurrence,
			'IS_PUBLIC'=>$is_public,
			'PRIORITY'=>strval($priority),
			'PRIORITY_LANG'=>$priority_lang,
			'TYPE'=>$type,
			'TIME'=>$time2,
			'TIME_RAW'=>strval($time_raw),
			'TIME_VCAL'=>date('Ymd',$time_raw)."T".date('His',$time_raw),
			'TO_TIME_VCAL'=>is_null($to_raw)?NULL:(date('Ymd',$to_raw)."T".date('His',$to_raw)),
			'EDIT_URL'=>$edit_url,
			'SUBSCRIPTIONS'=>$_subscriptions,
			'SUBSCRIBE_URL'=>$subscribe_url,
			'BACK_URL'=>$back_url,
			'CONTENT'=>$content,
			'SUBSCRIBED'=>$subscribed,
			'RATING_DETAILS'=>$rating_details,
			'TRACKBACK_DETAILS'=>$trackback_details,
			'COMMENT_DETAILS'=>$comment_details,
			'_GUID'=>'602e6f86f586ef0a24efed950eafd426',
		);

		if ($event['e_do_timezone_conv']==0)
		{
			$timezone_map=get_timezone_list();
			$map['TIMEZONE']=$timezone_map[$event['e_timezone']];
		}

		return do_template('CALENDAR_EVENT_SCREEN',$map);
	}

	/**
	 * Interface to subscribe for reminders to an event.
	 *
	 * @return tempcode		The UI
	 */
	function subscribe_event()
	{
		$title=get_screen_title('SUBSCRIBE_EVENT');

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('CALENDAR'))));

		// Check access
		$id=get_param_integer('id');
		check_specific_permission('view_calendar');
		$rows=$GLOBALS['SITE_DB']->query_select('calendar_events',array('*'),array('id'=>$id),'',1);
		if (!array_key_exists(0,$rows))
		{
			warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
		}
		$event=$rows[0];
		if ($event['e_is_public']==0) enforce_personal_access($event['e_submitter'],'view_personal_events');

		if (!has_category_access(get_member(),'calendar',strval($event['e_type']))) access_denied('CATEGORY_ACCESS');

		require_code('form_templates');

		$post_url=build_url(array('page'=>'_SELF','type'=>'_subscribe_event','id'=>$id),'_SELF');
		$fields=new ocp_tempcode();
		$fields->attach(form_input_float(do_lang_tempcode('REMINDER_TIME'),do_lang_tempcode('DESCRIPTION_REMINDER_TIME'),'hours_before',1.0,true));
		$submit_name=do_lang_tempcode('SUBSCRIBE_EVENT');

		return do_template('FORM_SCREEN',array('_GUID'=>'8f6a962617031264ee1af552701804ca','SKIP_VALIDATION'=>true,'HIDDEN'=>'','TITLE'=>$title,'TEXT'=>'','FIELDS'=>$fields,'URL'=>$post_url,'SUBMIT_NAME'=>$submit_name));
	}

	/**
	 * Subscribe for reminders to an event.
	 *
	 * @return tempcode		The UI
	 */
	function _subscribe_event()
	{
		$title=get_screen_title('SUBSCRIBE_EVENT');

		$seconds_before=intval(floatval(post_param('hours_before'))*3600.0);

		$id=get_param_integer('id'); // The event ID
		$events=$GLOBALS['SITE_DB']->query_select('calendar_events',array('*'),array('id'=>get_param_integer('id')),'',1);
		$event=$events[0];

		$rem_id=$GLOBALS['SITE_DB']->query_insert('calendar_reminders',array(
			'e_id'=>$id,
			'n_member_id'=>get_member(),
			'n_seconds_before'=>$seconds_before
		),true);

		if ((has_actual_page_access($GLOBALS['FORUM_DRIVER']->get_guest_id(),'calendar')) && (has_category_access($GLOBALS['FORUM_DRIVER']->get_guest_id(),'calendar',strval($event['e_type']))))
		{
			$start_day_of_month=find_concrete_day_of_month($event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_start_monthly_spec_type'],is_null($event['e_start_hour'])?find_timezone_start_hour_in_utc($event['e_timezone'],$event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_start_monthly_spec_type']):$event['e_start_hour'],is_null($event['e_start_minute'])?find_timezone_start_minute_in_utc($event['e_timezone'],$event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_start_monthly_spec_type']):$event['e_start_minute'],$event['e_timezone'],$event['e_do_timezone_conv']==1);
			$_from=cal_get_start_utctime_for_event($event['e_timezone'],$event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_start_monthly_spec_type'],$event['e_start_hour'],$event['e_start_minute'],$event['e_do_timezone_conv']==1);
			$from=cal_utctime_to_usertime($_from,$event['e_timezone'],$event['e_do_timezone_conv']==1);
			$to=mixed();
			if (!is_null($event['e_end_year']) && !is_null($event['e_end_month']) && !is_null($event['e_end_day']))
			{
				$_to=cal_get_end_utctime_for_event($event['e_timezone'],$event['e_end_year'],$event['e_end_month'],$event['e_end_day'],$event['e_end_monthly_spec_type'],$event['e_end_hour'],$event['e_end_minute'],$event['e_do_timezone_conv']==1);
				$to=cal_utctime_to_usertime($_to,$event['e_timezone'],$event['e_do_timezone_conv']==1);
			}

			syndicate_described_activity('calendar:ACTIVITY_SUBSCRIBED_EVENT',get_translated_text($event['e_title']),date_range($from,$to,!is_null($event['e_start_hour'])),'','_SEARCH:calendar:view:'.strval($id),'','','calendar',1,NULL,true);
		}

		// Add next reminder to job system
		$recurrences=find_periods_recurrence($event['e_timezone'],1,$event['e_start_year'],$event['e_start_month'],$event['e_start_day'],$event['e_start_monthly_spec_type'],is_null($event['e_start_hour'])?0:$event['e_start_hour'],is_null($event['e_start_minute'])?0:$event['e_start_minute'],$event['e_end_year'],$event['e_end_month'],$event['e_end_day'],$event['e_end_monthly_spec_type'],is_null($event['e_end_hour'])?0:$event['e_end_hour'],is_null($event['e_end_minute'])?0:$event['e_end_minute'],$event['e_recurrence'],min(1,$event['e_recurrences']));
		if (array_key_exists(0,$recurrences))
		{
			$GLOBALS['SITE_DB']->query_insert('calendar_jobs',array(
				'j_time'=>usertime_to_utctime($recurrences[0][0])-$seconds_before,
				'j_reminder_id'=>$rem_id,
				'j_member_id'=>get_member(),
				'j_event_id'=>get_param_integer('id')
			));
		}

		$url=build_url(array('page'=>'_SELF','type'=>'view','id'=>get_param_integer('id')),'_SELF');
		return redirect_screen($title,$url,do_lang_tempcode('SUCCESS'));
	}

	/**
	 * Unsubscribe for reminders to an event.
	 *
	 * @return tempcode		The UI
	 */
	function unsubscribe_event()
	{
		$title=get_screen_title('UNSUBSCRIBE_EVENT');

		$GLOBALS['SITE_DB']->query_delete('calendar_reminders',array('id'=>get_param_integer('reminder_id')),'',1); // reminder_id being the reminder ID

		$GLOBALS['SITE_DB']->query_delete('calendar_jobs',array(
			'j_reminder_id'=>get_param_integer('reminder_id'),
			'j_member_id'=>get_member()
		));

		$url=build_url(array('page'=>'_SELF','type'=>'view','id'=>get_param_integer('id')),'_SELF'); // id being the event ID
		return redirect_screen($title,$url,do_lang_tempcode('SUCCESS'));
	}

	/**
	 * Declare interests for event types.
	 *
	 * @return tempcode		The UI
	 */
	function interests()
	{
		$types=$GLOBALS['SITE_DB']->query_select('calendar_types',array('id'));

		$GLOBALS['SITE_DB']->query_delete('calendar_interests',array('i_member_id'=>get_member()));
		foreach ($types as $type)
		{
			$status=(post_param_integer('int_'.strval($type['id']),0)==1 || (post_param_integer('id',NULL)===$type['id']));
			if ($status)
			{
				$GLOBALS['SITE_DB']->query_insert('calendar_interests',array('i_member_id'=>get_member(),'t_type'=>$type['id']));
			}
		}

		$url=build_url(array('page'=>'_SELF','type'=>'misc','view'=>get_param('view'),'id'=>get_param('id')),'_SELF'); // id being the event ID
		$title=get_screen_title('DECLARE_EVENT_INTEREST');
		return redirect_screen($title,$url,do_lang_tempcode('SUCCESS'));
	}

	/**
	 * Declare interest to an event type.
	 *
	 * @return tempcode		The UI
	 */
	function declare_interest()
	{
		$title=get_screen_title('DECLARE_EVENT_INTEREST');

		$GLOBALS['SITE_DB']->query_insert('calendar_interests',array('i_member_id'=>get_member(),'t_type'=>get_param_integer('t_type')));

		$url=build_url(array('page'=>'_SELF','type'=>'misc','view'=>get_param('view'),'id'=>get_param('id')),'_SELF');
		return redirect_screen($title,$url,do_lang_tempcode('SUCCESS'));
	}

	/**
	 * Undeclare interest to an event type.
	 *
	 * @return tempcode		The UI
	 */
	function undeclare_interest()
	{
		$title=get_screen_title('UNDECLARE_EVENT_INTEREST');

		$GLOBALS['SITE_DB']->query_delete('calendar_interests',array('i_member_id'=>get_member(),'t_type'=>get_param('t_type')),'',1);

		$url=build_url(array('page'=>'_SELF','type'=>'misc','view'=>get_param('view'),'id'=>get_param('id')),'_SELF');
		return redirect_screen($title,$url,do_lang_tempcode('SUCCESS'));
	}

}


