CVE-2018-25076: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: A vulnerability classified as critical was found in Events Extension on BigTree. Affected by this vulnerability is the function getRandomFeaturedEventByDate/getUpcomingFeaturedEventsInCategoriesWithSubcategories/recacheEvent/searchResults of the file classes/events.php. The manipulation leads to sql injection. The patch is named 11169e48ab1249109485fdb1e0c9fca3d25ba01d. It is recommended to apply a patch to fix this issue. The associated identifier of this vulnerability is VDB-218395.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 9.8
Published: 2023-01-16T12:15Z
Repository: https://github.com/timbuckingham/bigtree-events
Language: PHP
Files changed: README.md, ajax/month.php, ajax/search.php, classes/events.php, css/events.css, field-types/date-chooser/draw.php, field-types/date-chooser/process.php, js/events.js, manifest.json, modules/events/_header.php, modules/events/calendar.php, modules/events/edit-check.php, modules/events/search.php, modules/events/separate.php, modules/events/view-day.php
Commit date: 2018-03-19 11:03:37 -0400
Commit message: Added methods for getting a page of upcoming events, fixed SQL injection in searches

Diff: diff --git a/README.md b/README.md
new file mode 100644
index 0000000..787eca0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+Events
+======
+
+The Events extension implements an admin interface for users to add events to an event calendar including recurring events with the following recurrence types:
+- Daily
+- Weekly (on a given day -- i.e. Thursday)
+- Monthly (on a given day or given week & day of each month -- i.e. 1st of every month or 1st Friday of every month)
+- Yearly (on a given day)
+
+The admin provides a search interface and calendar interface for editing specific occurences of events as well as a simple list view for editing the "main" event entries.
+
+The BTXEvents class is provided with many useful methods for pulling event data to the front end of your website.
+
+Compatibility
+-------------
+This module is bundled as an extension for BigTree 4.2 and higher.
+
+Licensing
+---------
+This field type is publicly licensed under the [GNU Lesser General Public License](http://www.gnu.org/copyleft/lesser.html).
+If you would like to use this field type under a different license, please [contact us](mailto:info@fastspot.com).
\ No newline at end of file
diff --git a/ajax/month.php b/ajax/month.php
new file mode 100755
index 0000000..dc609bd
--- /dev/null
+++ b/ajax/month.php
@@ -0,0 +1,129 @@
+<?php
+	if (isset($_GET["month"])) {
+		$month = $_GET["month"];
+	}
+
+	if (!defined("MODULE_ROOT")) {
+		define("MODULE_ROOT", $_GET["module_root"]);
+	}
+
+	$month = strtotime(date("F 1, Y",$month));
+	
+	// Get the events for this month
+	$e = BTXEvents::getEventsByDateRange(date("Y-m-1",$month),date("Y-m-t",$month));
+	// Change the events into an array with the key being a date and the value being an array of events ocurring on that day
+	$month_of_events = BTXEvents::getKeyedDateRangeForEvents($e);
+?>
+<div id="btx_events_calendar">
+	<header>
+		<a class="today" href="#<?=time()?>">Today is: <?=date("F j")?></a>
+		<nav>
+			<a href="#<?=($month-60*60)?>" class="previous month_button"><span>&laquo;</span></a>
+			<span><?=date("F Y",$month)?></span>
+			<a href="#<?=(strtotime(date("F t, Y",$month))+60*60*24)?>" class="next month_button"><span>&raquo;</span></a>
+		</nav>
+	</header>
+	<summary>
+		<section>Sunday</section>
+		<section>Monday</section>
+		<section>Tuesday</section>
+		<section>Wednesday</section>
+		<section>Thursday</section>
+		<section>Friday</section>
+		<section>Saturday</section>
+	</summary>
+	<ul class="dates">
+		<?php
+			// Get the number of days in the previous month.
+			$pmonth_days = date("t",strtotime(date("F 1, Y",$month))-60*60);
+			// Get the first day of the month's day (Sunday, Monday, etc) in numeric form.
+			$first_day_of_month = date("w",$month);
+			// Get the number of days in this month.
+			$days_in_month = date("t",$month);
+			
+			// Figure out when the last row starts
+			$days_in_first_row = 7 - $first_day_of_month;
+			$days_after_first_row = $days_in_month - $days_in_first_row;
+			$full_rows = $days_after_first_row - ($days_after_first_row % 7);
+			$days_in_last_row = $days_in_month - $days_in_first_row - $full_rows;
+			$rows = 1 + ($full_rows / 7);
+			
+			if ($days_in_last_row) {
+				$rows++;
+			}
+									
+			// $y is the counter for number of cells drawn.
+			$y = 0;
+			
+			// Show the first couple days of the last month grayed out.
+			$d = $first_day_of_month;
+			
+			while ($d) {
+		?>
+		<li><span class="not_active_month"><?=($pmonth_days-$d+1)?></span></li>
+		<?php
+				$d--;
+				$y++;
+			}
+			
+			// Let's go through this month. $x is the counter for the day of the month.
+			$x = 0;
+			
+			while ($x < $days_in_month) {
+				// Increase the day of the month and the number of cells drawn.
+				$x++;
+				$y++;
+				
+				$class = array();
+				
+				if ($y % 7 == 0) {
+					$class[] = "saturday";
+				}
+
+				// See if we're in the last row
+				if ($y / 7 > ($rows -1)) {
+					$class[] = "last_row";
+				}
+		?>
+		<li class="<?=implode(" ",$class)?>"><span><?=$x?></span>
+		<?php			
+			// Format the day with preceding 0.
+			$day = str_pad($x,2,"0",STR_PAD_LEFT);
+
+			// See if there are any events for this day.
+			$items = $month_of_events[date("Y",$month)."-".date("m",$month)."-".$day];
+  			if (count($items)) {
+  				echo '<ul class="calendar_events">';
+  				$xx = 0;
+				
+				foreach ($items as $item) {
+					if ($xx < 4) {
+						echo '<li><a href="'.MODULE_ROOT."edit-check/?event=".$item["id"].'&date='.date("Y",$month)."-".date("m",$month)."-".$day.'">'.$item["title"]."</a></li>";
+					}
+					
+					$xx++;
+				}
+
+				echo '</ul>';
+				
+				if ($xx > 4) {
+					echo '<a class="search" href="'.MODULE_ROOT.'view-day/'.date("Y",$month)."-".date("m",$month)."-".$day.'/">View All</a>';
+				}
+			}
+		?>
+		</li>
+		<?php
+			}
+			
+			$x = 0;
+			
+			while ($y % 7 != 0) {
+				$x++;
+				$y++;
+		?>
+		<li class="last_row<?php if ($y % 7 == 0) { ?> saturday<?php } ?>"><span class="not_active_month"><?=$x?></span></li>
+		<?php
+			}
+		?>
+	</ul>
+</div>
\ No newline at end of file
diff --git a/ajax/search.php b/ajax/search.php
new file mode 100755
index 0000000..2e7f69c
--- /dev/null
+++ b/ajax/search.php
@@ -0,0 +1,27 @@
+<?php
+	$start_date = $_POST["start_date"] ? date("Y-m-d",strtotime($_POST["start_date"])) : date("Y-m-d");
+	$end_date = $_POST["end_date"] ? date("Y-m-d",strtotime($_POST["end_date"])) : date("Y-m-d",strtotime("+2 years"));
+	$items = BTXEvents::getSearchResultsInDateRange($_POST["keywords"],$start_date,$end_date);
+?>
+<div class="table">
+	<header>
+		<span class="view_column btx_events_column_date">Date</span>
+		<span class="view_column btx_events_column_title">Title</span>
+		<span class="view_action">Recurs</span>
+		<span class="view_action">Edit</span>
+		<span class="view_action">Delete</span>
+	</header>
+	<ul>
+		<?php foreach ($items as $item) { ?>
+		<li>
+			<section class="view_column btx_events_column_date"><?=date("m/d/Y",strtotime($item["start"]))?></section>
+			<section class="view_column btx_events_column_title"><?=htmlspecialchars($item["title"])?></section>
+			<section class="view_action">
+				<span class="icon_repeat<?php if ($item["recurrence_type"]) { ?> icon_repeat_on<?php } ?>"></span>
+			</section>
+			<section class="view_action"><a href="<?=ADMIN_ROOT?>com.fastspot.events*events/edit-check/?event=<?=$item["id"]?>&date=<?=date("Y-m-d",strtotime($item["start"]))?>" class="icon_edit"></a></section>
+			<section class="view_action"><a href="#<?=$item["id"]?>" class="icon_delete"></a></section>
+		</li>
+		<?php } ?>
+	</ul>
+</div>
\ No newline at end of file
diff --git a/classes/events.php b/classes/events.php
new file mode 100755
index 0000000..9162742
--- /dev/null
+++ b/classes/events.php
@@ -0,0 +1,1561 @@
+<?php
+	/*
+		Class: BTXEvents
+			A class to handle events in BigTree.
+	*/
+	
+	class BTXEvents {
+		
+		/*
+			Constructor:
+				Re-caches stale events.
+		*/
+		
+		function __construct() {
+			$q = sqlquery("SELECT * FROM btx_events_events WHERE recurrence_type != '' AND (last_updated < '".date("Y-m-d",strtotime("-1 year"))."' OR last_updated IS NULL)");
+			
+			while ($f = sqlfetch($q)) {
+				self::recacheEvent($f["id"]);
+				sqlquery("UPDATE btx_events_events SET last_updated = NOW() WHERE id = '".$f["id"]."'");
+			}
+		}
+
+		/*
+			Function: cacheEvent
+				Caches an event.
+			
+			Parameters:
+				id - The id of the event to cache.
+		*/
+		
+		static function cacheEvent($id) {
+			$id = sqlescape($id);
+			$item = self::get($id);
+			$title_route = sqlescape($item["route"]);
+			
+			// It's just a normal event!  Thank heavens!
+			if (!$item["recurrence_type"]) {
+				list($start_date,$end_date) = self::getCacheTimes($item,$item["start_date"],$item["end_date"]);
+				
+				$date_route = date("Y-m-d",strtotime($start_date));
+
+				// Put the date in the database!
+				sqlquery("INSERT INTO btx_events_date_cache (`event`,`start`,`end`,`title_route`,`date_route`) VALUES ('$id','$start_date','$end_date','$title_route','$date_route')");
+			// It's repeating :(  We need to cache all previous recurrences that happened in the past year, and all that will happen in the next two years.
+			} elseif (!$item["recurring_end_date"] || strtotime($item["recurring_end_date"]) > time()) {
+				// If there's a start date to the recurrence, use it
+				if ($item["start_date"]) {
+					$start = strtotime($item["start_date"]);
+				} else {
+					$start = strtotime("-1 year");
+				}
+				// If there's an end date, stop there
+				if ($item["end_date"] || $item["recurring_end_date"]) {
+					$end = strtotime($item["recurring_end_date"] ? $item["recurring_end_date"] : $item["end_date"]);
+				} else {
+					$end = strtotime("+2 years");
+				}
+				// If we've already passed the end date, we don't need to cache things anymore.
+				if ($end < $start) {
+					return;
+				}
+				// Get a list of the canceled items
+				$canceled = json_decode($item["canceled_recurrences"],true);
+				// Loop through our start and end dates finding the next recurrence of events
+				while ($start <= $end) {
+					$next = self::findNextRecurrence($item["recurrence_type"],$item["recurrence_detail"],$canceled,$start);
+					// The next time the event occurs could fall outside our caching period
+					if ($next <= $end) {
+						list($start_date,$end_date) = self::getCacheTimes($item,date("Y-m-d",$next),date("Y-m-d",$next));
+						$date_route = date("Y-m-d",strtotime($start_date));
+						sqlquery("INSERT INTO btx_events_date_cache (`event`,`start`,`end`,`title_route`,`date_route`) VALUES ('".$item["id"]."','$start_date','$end_date','$title_route','$date_route')");
+					}
+					$start = strtotime(date("Y-m-d",$next)." +1 day");
+				}
+			}
+		}
+		
+		/*
+			Function: findNextRecurrence
+				Finds the next time an event exists.
+			
+			Parameters:
+				type - The recurrence type.
+				detail - The recurrence detail.
+				canceled - An array of canceled recurrences.
+				time - The start time to begin looking from (in seconds since Unix epoch).
+				
+			Returns:
+				The next occurence of the event in seconds since Unix epoch.
+		*/
+
+		static function findNextRecurrence($type,$detail,$canceled = array(),$time = false) {
+			if (!$time) {
+				$time = time();
+			}
+			// Daily Recurrence
+			if ($type == "daily") {
+				// If it hasn't been canceled, return the date, otherwise try again tomorrow.
+				if (!in_array(date("Y-m-d",$time),$canceled)) {
+					return $time;
+				} else {
+					return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-d",$time)." +1 day"));
+				}
+			}
+			// Weekly Recurrence
+			if ($type == "weekly") {
+				$current_day_of_week = date("w",$time);
+				if ($detail > $current_day_of_week) {
+					$time = strtotime(date("Y-m-d",$time)." +".($detail - $current_day_of_week)." days");
+				} elseif ($detail < $current_day_of_week) {
+					$time = strtotime(date("Y-m-d",$time)." +".(7 - $current_day_of_week + $detail)." days");
+				}
+
+				// If it's canceled, next week please.
+				if (!in_array(date("Y-m-d",$time),$canceled)) {
+					return $time;
+				} else {
+					return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-d",$time)." +1 week"));
+				}
+			}
+			// Monthly Recurrence
+			if ($type == "monthly") {
+				// If the detail is numeric, it's simply the (x)th day of the month.
+				if (is_numeric($detail)) {
+					$current_day_of_month = date("j",$time);
+					// Move to the 1st of the next month if the repeat has already occurred this month.
+					if ($current_day_of_month > $detail) {
+						$time = strtotime(date("Y-m-1",$time)." +1 month");
+					}
+					// If we're looking for something on the 31st and a month only has 30 days, skip it.
+					if ($detail > date("t",$time)) {
+						return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-1",$time)." +1 month"));
+					}
+					$time = strtotime(date("Y-m-".$detail,$time));
+
+					// If it's canceled, next month please.
+					if (!in_array(date("Y-m-d",$time),$canceled)) {
+						return $time;
+					} else {
+						return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-1",$time)." +1 month"));
+					}
+				// We need to calculate a more crazy date like the second Thursday of each month.
+				} else {
+					list($x,$week,$day) = explode("#",$detail);
+					
+					// New strategy, start at the first day of the current month and walk through each day.  If the x'th day is already passed or is canceled, then we'll move on.
+					$start = strtotime(date("Y-m-1",$time));
+					$end = strtotime(date("Y-m-t",$time));
+					$current_week = 1;
+					
+					while ($start <= $end) {
+						$current_day = date("w",$start);
+						if ($current_week == $week && $current_day == $day) {
+							if ($time > $start || in_array(date("Y-m-d",$start),$canceled)) {
+								return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-1",$time)." +1 month"));
+							} else {
+								return $start;
+							}
+						}
+						if ($current_day == $day) {
+							$current_week++;
+						}
+
+						// Add a day
+						$start = BigTree::dateFromOffset($start,"+1 day","U");
+					}
+					
+					if ($current_week < $week) {
+						return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-1",$time)." +1 month"));
+					}
+					
+					return false;
+				}
+
+			}
+			// Yearly Recurrence
+			if ($type == "yearly") {
+				$next = strtotime(date("Y",$time)."-".$detail);
+				if ($next < $time) {
+					$next = strtotime((date("Y",$time)+1)."-".$detail);
+				}
+				// If it's canceled, next year please.
+				if (!in_array(date("Y-m-d",$next),$canceled)) {
+					return $next;
+				} else {
+					return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-d",$next)." +1 year"));
+				}
+			}
+			return false;
+		}
+
+		/*
+			Function: formattedDate
+				Returns a string of formatted date/time based on an event having a start/end date and times.
+			
+			Parameters:
+				item - The event instance array.
+				date_format - The date format (compatible with PHP's date function)
+				time_format - The time format (compatible with PHP's time format)
+			
+			Returns:
+				A date/time string.
+		*/
+		
+		static function formattedDate($item,$date_format = "F j, Y",$time_format = "g:ia") {
+			$s = strtotime($item["start"]);
+			$e = strtotime($item["end"]);
+			// If it's a single all day event...
+			if ($item["all_day"]) {
+				if (date("Y-m-d",$s) == date("Y-m-d",$e)) {
+					return date($date_format,$s);
+				} else {
+					return date($date_format,$s)." - ".date($date_format,$e);
+				}
+			} else {
+				// Single day event
+				if (date("Y-m-d",$s) == date("Y-m-d",$e)) {
+					if ($s != $e && $item["end_time"] != "") {
+						return date($date_format,$s)." &mdash; ".date($time_format,$s)." - ".date($time_format,$e);
+					} else {
+						return date($date_format,$s)." &mdash; ".date($time_format,$s);
+					}
+				// Multi day event
+				} else {
+					// Starts one night, ends next morning?
+					if (date("H",$s) > date("H",$e)) {
+						return date($date_format,$s)." &mdash; ".date($time_format,$s)." - ".date($time_format,$e);
+					// Probably meant an event to be on multiple days for a few hours each day.
+					} else {
+						return date($date_format,$s)." - ".date($date_format,$e)." &mdash; ".date($time_format,$s)." - ".date($time_format,$e);
+					}
+				}
+			}
+		}
+		
+		/*
+			Function: formattedTime
+				Returns a string of formatted time based on an event having start/end times.
+			
+			Parameters:
+				item - The event instance array.
+				time_format - The time format (compatible with PHP's time format)
+			
+			Returns:
+				A date/time string.
+		*/
+		
+		static function formattedTime($item,$time_format = "gi:a") {
+			$s = strtotime($item["start"]);
+			$e = strtotime($item["end"]);
+			if ($item["all_day"]) {
+				return "All Day";
+			}
+			if ($s != $e && $item["end_time"] != "") {
+			    return date($time_format,$s)." - ".date($time_format,$e);
+			} else {
+			    return date($time_format,$s);
+			}
+		}
+		
+		/*
+			Function: publishHook
+				Used by the BigTree form to cache the event on publish.
+		*/
+		
+		static function publishHook($table,$id,$changes,$many_to_many,$tags) {
+			self::recacheEvent($id);
+		}
+
+		/*
+			Function: get
+				Returns an event with its fields decoded.
+			
+			Parameters:
+				item - Either the event id or an event array.
+			
+			Returns:
+				An event array with its fields decoded.
+		*/
+		
+		static function get($item) {
+			global $cms;
+			
+			if (!is_array($item)) {
+				$item = sqlfetch(sqlquery("SELECT * FROM btx_events_events WHERE id = '".sqlescape($item)."'"));
+			}
+			if (!is_array($item)) {
+				return false;
+			}
+			
+			foreach ($item as $key => $val) {
+				if (is_array($val)) {
+					$item[$key] = BigTree::untranslateArray($val);
+				} elseif (is_array(json_decode($val,true))) {
+					$item[$key] = BigTree::untranslateArray(json_decode($val,true));
+				} else {
+					$item[$key] = $cms->replaceInternalPageLinks($val);
+				}
+			}
+
+			return $item;
+		}
+		
+		/*
+			Function: getCacheTimes
+				Returns the start and end timestamps for an event.
+			
+			Parameters:
+				item - Event array.
+				start_date - The date of the occurence.
+				end_date - The end date of the occurence.
+			
+			Returns:
+				An array of timestamps (first being start, second being end)
+		*/				
+
+		static function getCacheTimes($item,$start_date,$end_date) {
+			// If they didn't enter an end date, we're going to assume it ends the same day it starts
+			if ($end_date == "0000-00-00" || !$end_date) {
+				$end_date = $start_date;
+			}
+			// If it's an all day event or we don't know the start time, set the end time to 11:59
+			if ($item["all_day"] || !$item["start_time"]) {
+				$start_date = strtotime($start_date." 00:00:00");
+				$end_date = strtotime($end_date." 23:59:59");
+			} else {
+				$start_date = strtotime($start_date." ".$item["start_time"]);
+				// If we have an end time, let's see if it's actually the next day.
+				if ($item["end_time"]) {
+					if (strtotime($item["start_time"]) < strtotime($item["end_time"]) && $start_date == $end_date) {
+						$end_date = strtotime($start_date." ".$item["end_time"]." +1 day");
+					} else {
+						$end_date = strtotime($end_date." ".$item["end_time"]);
+					}
+				} else {
+					$end_date = strtotime($end_date." 23:59:59");
+				}
+			}
+			$start_date = date("Y-m-d H:i:s",$start_date);
+			$end_date = date("Y-m-d H:i:s",$end_date);
+			return array($start_date,$end_date);
+		}
+
+		/*
+			Function: getCategoriesByParent
+				Returns an array of categories with a given parent ID.
+
+			Parameters:
+				parent - The parent ID to check.
+				sort - The sort order of the categories (defaults to positioned).
+
+			Returns:
+				An array of categories.
+		*/
+
+		static function getCategoriesByParent($parent = false,$sort = "position DESC, id ASC") {
+			$categories = array();
+			if (!$parent) {
+				$q = sqlquery("SELECT * FROM btx_events_categories WHERE parent IS NULL ORDER BY $sort");
+			} else {
+				$q = sqlquery("SELECT * FROM btx_events_categories WHERE parent = '$parent' ORDER BY $sort");
+			}
+			while ($f = sqlfetch($q)) {
+				$categories[] = $f;
+			}
+			return $categories;
+		}
+		
+		/*
+			Function: getCategory
+				Returns a category for the given id.
+
+			Parameters:
+				id - The category id.
+
+			Returns:
+				A category entry.
+		*/
+
+		static function getCategory($id) {
+			$id = sqlescape($id);
+			return sqlfetch(sqlquery("SELECT * FROM btx_events_categories WHERE id = '$id'"));
+		}
+
+		/*
+			Function: getCategoryByRoute
+				Returns a category for the given route.
+
+			Parameters:
+				route - The category route.
+
+			Returns:
+				A category entry.
+		*/
+
+		static function getCategoryByRoute($route) {
+			$route = sqlescape($route);
+			return sqlfetch(sqlquery("SELECT * FROM btx_events_categories WHERE route = '$route'"));
+		}
+
+		/*
+			Function: getCategoryLineage
+				Returns an array of the ancestors of a given category.
+			
+			Parameters:
+				category - A category ID or category array.
+			
+			Returns:
+				An array of categories starting with the "oldest".
+		*/
+		
+		static function getCategoryLineage($category,$ancestors = array()) {
+			if (!is_array($category)) {
+				$category = sqlfetch(sqlquery("SELECT * FROM btx_events_categories WHERE id = '".sqlescape($category)."'"));
+			}
+			if ($category["parent"] && $category["parent"] != $category["id"]) {
+				$parent = sqlfetch(sqlquery("SELECT * FROM btx_events_categories WHERE id = '".sqlescape($category["parent"])."'"));
+				$ancestors = array_merge(array($parent),$ancestors);
+				return self::getCategoryLineage($parent,$ancestors);
+			}
+			return $ancestors;
+		}
+		
+		/*
+			Function: getEventCategories
+				Returns an array of categories that an event belongs to.
+
+			Parameters:
+				event - The event entry or event id.
+
+			Returns:
+				An array of IDs.
+		*/
+
+		static function getEventCategories($event) {
+			$event = is_array($event) ? sqlescape($event["id"]) : sqlescape($event);
+			$categories = array();
+			$q = sqlquery("SELECT btx_events_categories.* FROM btx_events_categories JOIN btx_events_event_categories ON btx_events_categories.id = btx_events_event_categories.category WHERE btx_events_event_categories.event = '$event'");
+			while ($f = sqlfetch($q)) {
+				$categories[] = $f;
+			}
+			return $categories;
+		}
+
+		/*
+			Function: getEventCategoryIDs
+				Returns an array of category IDs that an event belongs to.
+
+			Parameters:
+				event - The event entry or event id.
+
+			Returns:
+				An array of IDs.
+		*/
+
+		static function getEventCategoryIDs($event) {
+			$event = is_array($event) ? sqlescape($event["id"]) : sqlescape($event);
+			$categories = array();
+			$q = sqlquery("SELECT * FROM btx_events_event_categories WHERE event = '$event'");
+			while ($f = sqlfetch($q)) {
+				$categories[] = $f["category"];
+			}
+			return $categories;
+		}
+
+		/*
+			Function: getEventCategoryList
+				Returns a nested category list.
+		*/
+		
+		static function getEventCategoryList($original_list,$parent = 0,$level = "") {
+			$list = array();
+			$q = sqlquery("SELECT * FROM btx_events_categories WHERE parent = '".sqlescape($parent)."' ORDER BY name");
+			while ($f = sqlfetch($q)) {
+				$list[$f["id"]] = $level.$f["name"];
+				$list = $list + self::getEventCategoryList(false,$f["id"],trim($level)."--- ");
+			}
+			return $list;
+		}
+
+		/*
+			Function: getEventInstances
+				Returns instances of an event.
+
+			Parameters:
+				event - An event array.
+				upcoming - Whether to return only upcoming instances (defaults to false)
+
+			Returns:
+				An array of event instances
+
+			See Also:
+				<getUpcomingEventInstances>
+		*/
+
+		static function getEventInstances($event,$upcoming = false) {
+			$instances = array();
+			$upcoming = $upcoming ? " AND end >= NOW()" : "";
+
+			$q = sqlquery("SELECT * FROM btx_events_date_cache WHERE event = '".sqlescape($event["id"])."' $upcoming ORDER BY start ASC");
+			while ($f = sqlfetch($q)) {
+				$f["instance"] = $f["id"];
+				$instances[] = array_merge($f,$event);
+			}
+
+			return $instances;
+		}
+
+		/*
+			Function: getEventsByDate
+				Returns event instances for a given date.
+			
+			Parameters:
+				date - The date (Y-m-d format) to pull events for.
+				featured - Whether to pull only featured events or not.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateRange>
+		*/
+		
+		static function getEventsByDate($date,$featured = false) {
+			return self::getEventsByDateRange($date,$date,$featured);
+		}
+		
+		
+		/*
+			Function: getEventsByDateInCategories
+				Returns event instances for a given date in given categories.
+			
+			Parameters:
+				date - The date (Y-m-d format) to pull events for.
+				categories - An array of categories to get events for.
+				featured - Whether to pull only featured events or not.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateRangeInCategories>
+		*/
+		
+		static function getEventsByDateInCategories($date,$categories,$featured = false) {
+			return self::getEventsByDateRangeInCategories($date,$date,$categories,$featured);
+		}
+
+		/*
+			Function: getEventsByDateInCategoriesWithSubcategories
+				Returns event instances for a given date in given categories and their subcategories.
+			
+			Parameters:
+				date - The date (Y-m-d format) to pull events for.
+				categories - An array of categories to get events for.
+				featured - Whether to pull only featured events or not.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateRangeInCategories>
+		*/
+		
+		static function getEventsByDateInCategoriesWithSubcategories($date,$categories,$featured = false) {
+			$with_sub = $categories;
+			foreach ($categories as $cat) {
+				$with_sub = array_merge($with_sub,self::getSubcategoriesOfCategory($cat));
+			}
+			return self::getEventsByDateInCategories($date,$with_sub,$featured);
+		}
+
+		/*
+			Function: getEventsByDateInCategory
+				Returns event instances for a given date in given category.
+			
+			Parameters:
+				date - The date (Y-m-d format) to pull events for.
+				category - A category array or ID.
+				featured - Whether to pull only featured events or not.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateRangeInCategories>
+		*/
+		
+		static function getEventsByDateInCategory($date,$category,$featured = false) {
+			return self::getEventsByDateRangeInCategories($date,$date,array($category),$featured);
+		}
+
+		/*
+			Function: getEventsByDateInCategoryWithSubcategories
+				Returns event instances for a given date in given category and its subcategories.
+			
+			Parameters:
+				date - The date (Y-m-d format) to pull events for.
+				category - A category array or ID.
+				featured - Whether to pull only featured events or not.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateRangeInCategoriesWithSubcategories>
+		*/
+		
+		static function getEventsByDateInCategoryWithSubcategories($date,$category,$featured = false) {
+			return self::getEventsByDateRangeInCategoriesWithSubcategories($date,$date,array($category),$featured);
+		}
+
+		/*
+			Function: getEventsByDateRange
+				Returns event instances for a given date range.
+			
+			Parameters:
+				start_date - The start date (Y-m-d format) to pull events for.
+				end_date - The end date (Y-m-d format) to pull events for.
+				featured - Whether to pull only featured events or not.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDate>
+		*/
+		
+		static function getEventsByDateRange($start_date,$end_date,$featured = false) {
+			$events = array();
+			if ($featured) {
+				$featured = " AND btx_events_events.featured = 'on' ";
+			}
+
+			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.end >= '$start_date 00:00:00' AND btx_events_date_cache.start <= '$end_date 23:59:59' $featured ORDER BY btx_events_date_cache.start ASC");
+
+			while ($f = sqlfetch($q)) {
+				$events[] = self::get($f);
+			}
+			return $events;
+		}
+
+		/*
+			Function: getEventsByDateRangeInCategories
+				Returns event instances for a given date range in the given categories.
+			
+			Parameters:
+				start_date - The start date (Y-m-d format) to pull events for.
+				end_date - The end date (Y-m-d format) to pull events for.
+				categories - An array of categories to get events for.
+				featured - Whether to pull only featured events or not.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateInCategories>
+		*/
+		
+		static function getEventsByDateRangeInCategories($start_date,$end_date,$categories,$featured = false) {
+			$events = array();
+			if ($featured) {
+				$featured = " AND btx_events_events.featured = 'on' ";
+			}
+
+			$cat_search = array();
+			foreach ($categories as $category) {
+				$category = is_array($category) ? sqlescape($category["id"]) : sqlescape($category);
+				$cat_search[] = "btx_events_event_categories.category = '$category'";
+			}
+
+			if (!count($cat_search)) {
+				return false;
+			}
+			
+			$q = sqlquery("SELECT DISTINCT(CONCAT(btx_events_date_cache.event,btx_events_date_cache.start,btx_events_date_cache.end)),btx_events_date_cache.event,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_date_cache.start,btx_events_date_cache.end FROM btx_events_date_cache JOIN btx_events_event_categories WHERE btx_events_date_cache.event = btx_events_event_categories.event AND btx_events_date_cache.end >= '$start_date 00:00:00' AND btx_events_date_cache.start <= '$end_date 23:59:59' AND (".implode(" OR ",$cat_search).") $featured ORDER BY btx_events_date_cache.start ASC");
+
+			while ($f = sqlfetch($q)) {
+				$event = self::get($f["event"]);
+				if ($event) {
+					$event["start"] = $f["start"];
+					$event["end"] = $f["end"];
+					$event["instance"] = $f["instance"];
+					$event["title_route"] = $f["title_route"];
+					$event["date_route"] = $f["date_route"];
+					$events[] = $event;
+				}
+			}
+			return $events;
+		}
+
+		/*
+			Function: getEventsByDateRangeInCategoriesWithSubcategories
+				Returns event instances for a given date range in the given categories and their subcategories.
+			
+			Parameters:
+				start_date - The start date (Y-m-d format) to pull events for.
+				end_date - The end date (Y-m-d format) to pull events for.
+				categories - An array of categories to get events for.
+				featured - Whether to pull only featured events or not.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateInCategoriesWithSubcategories>
+		*/
+		
+		static function getEventsByDateRangeInCategoriesWithSubcategories($start_date,$end_date,$categories,$featured = false) {
+			$with_sub = $categories;
+			foreach ($categories as $cat) {
+				$with_sub = array_merge($with_sub,self::getSubcategoriesOfCategory($cat));
+			}
+			return self::getEventsByDateRangeInCategories($start_date,$end_date,$with_sub,$featured);
+		}
+
+		/*
+			Function: getEventsByDateRangeInCategory
+				Returns event instances for a given date range in the given category.
+			
+			Parameters:
+				start_date - The start date (Y-m-d format) to pull events for.
+				end_date - The end date (Y-m-d format) to pull events for.
+				category - A category array or category ID.
+				featured - Whether to pull only featured events or not.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateInCategories>
+		*/
+		
+		static function getEventsByDateRangeInCategory($start_date,$end_date,$category,$featured = false) {
+			return self::getEventsByDateRangeInCategories($start_date,$end_date,array($category),$featured);
+		}
+
+		/*
+			Function: getEventsByDateRangeInCategoryWithSubcategories
+				Returns event instances for a given date range in the given category and its subcategories.
+			
+			Parameters:
+				start_date - The start date (Y-m-d format) to pull events for.
+				end_date - The end date (Y-m-d format) to pull events for.
+				category - A category array or category ID.
+				featured - Whether to pull only featured events or not.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateInCategoryWithSubcategories>
+		*/
+		
+		static function getEventsByDateRangeInCategoryWithSubcategories($start_date,$end_date,$category,$featured = false) {
+			return self::getEventsByDateRangeInCategoriesWithSubcategories($start_date,$end_date,array($category),$featured);
+		}
+
+		/*
+			Function: getFeaturedEventsByDate
+				Returns featured event instances for a given date.
+			
+			Parameters:
+				date - The date (Y-m-d format) to pull events for.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDate>
+		*/
+
+		static function getFeaturedEventsByDate($date) {
+			return self::getEventsByDate($date,true);
+		}
+
+		/*
+			Function: getFeaturedEventsByDateRange
+				Returns featured event instances for a given date range.
+			
+			Parameters:
+				start_date - The start date (Y-m-d format) to pull events for.
+				end_date - The end date (Y-m-d format) to pull events for.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateRange>
+		*/
+
+		static function getFeaturedEventsByDateRange($start_date,$end_date) {
+			return self::getEventsByDateRange($start_date,$end_date,true);
+		}
+
+		/*
+			Function: getFeaturedEventsByDateRangeInCategories
+				Returns featured event instances for a given date range in the provided categories.
+			
+			Parameters:
+				start_date - The start date (Y-m-d format) to pull events for.
+				end_date - The end date (Y-m-d format) to pull events for.
+				categories - An array of categories to get events for.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateRangeInCategories>
+		*/
+
+		static function getFeaturedEventsByDateRangeInCategories($start_date,$end_date,$categories) {
+			return self::getEventsByDateRangeInCategories($start_date,$end_date,$categories,true);
+		}
+
+		/*
+			Function: getFeaturedEventsByDateRangeInCategoriesWithSubcategories
+				Returns featured event instances for a given date range in the provided categories and their subcategories.
+			
+			Parameters:
+				start_date - The start date (Y-m-d format) to pull events for.
+				end_date - The end date (Y-m-d format) to pull events for.
+				categories - An array of categories to get events for.
+			
+			Returns:
+				An array of event instances.
+			
+			See Also:
+				<getEventsByDateRangeInCategoriesWithSubcategories>
+		*/
+
+		static function getFeaturedEventsByDateRangeInCategoriesWithSubcategories($start_date,$end_date,$categories) {
+			return self::getEventsByDateRangeInCategoriesWithSubcategories($start_date,$end_date,$categories,true);
+		}
+
+		/*
+			Function: getFeaturedSearchResultsInDateRange
+				Returns featured event instances matching a given query in a specified date range.
+			
+			Parameters:
+				query - The string to search for.
+				start_date - Beginning date (Y-m-d).
+				end_date - Ending date (Y-m-d).
+			
+			Returns:
+				An array of event instances ordered by soonest.
+
+			See Also:
+				<getSearchResultsInDateRange>
+		*/
+
+		static function getFeaturedSearchResultsInDateRange($query,$start_date,$end_date) {
+			return self::getSearchResultsInDateRange($query,$start_date,$end_date,true);
+		}
+		
+		/*
+			Function: getInstance
+				Returns an instance of an event (combined date cache and event entry).
+			
+			Parameters:
+				id - The id of the event instance.
+			
+			Returns:
+				An event array with its fields decoded.
+		*/
+		
+		static function getInstance($id) {
+			return self::get(sqlfetch(sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.id = '$id'")));
+		}
+		
+		/*
+			Function: getInstanceByRoute
+				Returns an instance of an event (combined date cache and event entry).
+			
+			Parameters:
+				title_route - The title route of the event instance.
+				date_route - The date route of the event instance.
+			
+			Returns:
+				An event array with its fields decoded.
+		*/
+		
+		static function getInstanceByRoute($title_route,$date_route) {
+			return self::get(sqlfetch(sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.title_route = '".sqlescape($title_route)."' AND btx_events_date_cache.date_route = '".sqlescape($date_route)."'")));
+		
+		}
+
+		/*
+			Function: getKeyedEventsDateRangeForEvents
+				Returns an array of days as keys with the events that fall in each day as an array.
+			
+			Parameters:
+				events - An array of event instances.
+			
+			Returns:
+				A keyed array (dates are keys, array of events are vals) for the events passed in.
+		*/
+		
+		static function getKeyedDateRangeForEvents($events) {
+			$days = array();
+			foreach ($events as $event) {
+				$days[date("Y-m-d",strtotime($event["start"]))][] = $event;
+			}
+			return $days;
+		}
+
+		/*
+			Function: getNumberOfEventsOnDate
+				Returns number of events occurring on a given date.
+			
+			Parameters:
+				date - The date (Y-m-d format) to pull events for.
+			
+			Returns:
+				A number.
+		*/
+		
+		static function getNumberOfEventsOnDate($date) {
+			$date = date("Y-m-d",strtotime($date));
+			$f = sqlfetch(sqlquery("SELECT COUNT(id) AS `count` FROM btx_events_date_cache WHERE start >= '$date 00:00:00' AND end <= '$date 23:59:59'"));
+			return $f["count"];
+		}
+
+		/*
+			Function: getRandomEvent
+				Returns a random event instance occurring in the future.
+			
+			Parameters:
+				featured - Whether to return a featured event or not.
+			
+			Returns:
+				An event instance.
+			
+			See Also:
+				<getRandomEventByDate>
+		*/
+		
+		static function getRandomEvent($featured = false) {
+			if ($featured) {
+				$featured = " AND btx_events_events.featured = 'on' ";
+			}
+
+			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.end >= NOW() $featured ORDER BY RAND() LIMIT 1");
+
+			return self::get(sqlfetch($q));
+		}
+		
+		/*
+			Function: getRandomEventByDate
+				Returns a random event instance occurring on a specific date.
+			
+			Parameters:
+				date - The date to pull an event for.
+			
+			Returns:
+				An event instance.
+			
+			See Also:
+				<getSingleEventByDate>
+		*/
+
+		static function getRandomEventByDate($date) {
+			return self::getSingleEventByDate($date,"","RAND()");
+		}
+
+		/*
+			Function: getRandomFeaturedEvent
+				Returns a random featured event instance occurring in the future.
+				
+			Returns:
+				An event instance.
+			
+			See Also:
+				<getRandomEvent>
+		*/
+		
+		static function getRandomFeaturedEvent() {
+			return self::getRandomEvent(true);
+		}
+		
+		/*
+			Function: getRandomFeaturedEventByDate
+				Returns a random featured event instance occurring on a specific date.
+			
+			Parameters:
+				date - The date to pull an event for.
+			
+			Returns:
+				An event instance.
+			
+			See Also:
+				<getRandomEventByDate>
+		*/
+
+		static function getRandomFeaturedEventByDate($date) {
+			return self::getSingleEventByDate($date,true,"RAND()");
+		}
+
+		/*
+			Function: getSearchResultsInDateRange
+				Returns event instances matching a given query in a specified date range.
+			
+			Parameters:
+				query - The string to search for.
+				start_date - Beginning date (Y-m-d).
+				end_date - Ending date (Y-m-d).
+				featured - Whether to only return featured or not (defaults to false for all).
+			
+			Returns:
+				An array of event instances ordered by soonest.
+		*/
+
+		static function getSearchResultsInDateRange($query,$start_date,$end_date,$featured = false) {
+			$events = array();
+			$words = explode(" ",$query);
+			$qwords = array();
+
+			if ($featured) {
+				$featured = " AND btx_events_events.featured = 'on' ";
+			}
+
+			if ($words) {
+				foreach ($words as $word) {
+					$word = sqlescape($word);
+					$qwords[] = "(btx_events_events.title LIKE '%$word%' OR btx_events_events.description LIKE '%$word%')";
+				}
+				$qwords = implode(" AND ",$qwords)." AND ";
+			} else {
+				$qwords = "";
+			}
+
+			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND $qwords btx_events_date_cache.end >= '$start_date 00:00:00' AND btx_events_date_cache.start <= '$end_date 23:59:59' $featured ORDER BY btx_events_date_cache.start ASC");
+
+			while ($f = sqlfetch($q)) {
+				$event = self::get($f);
+				$events[] = $event;
+			}
+			return $events;
+		}
+
+		/*
+			Function: getSingleEventByDate
+				Returns a single event for a given date.
+
+			Parameters:
+				date - The date to check for events (in Y-m-d format).
+				featured - Whether to limit the search exclusively to featured events.
+				sort - The sort to go by when picking the first event (defaults to most recently created)
+
+			Returns:
+				An event instance.
+		*/
+
+		static function getSingleEventByDate($date,$featured = false,$sort = "id DESC") {
+			if ($featured) {
+				$featured = " AND btx_events_events.featured = 'on' ";
+			}
+
+			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.start <= '$date 23:59:59' AND btx_events_date_cache.end >= '$date 00:00:00' $featured ORDER BY $sort LIMIT 1");
+
+			return self::get(sqlfetch($q));
+		}
+
+		/*
+			Function: getSingleFeaturedEventByDate
+				Returns a single featured event for a given date.
+
+			Parameters:
+				date - The date to check for events (in Y-m-d format).
+				sort - The sort to go by when picking the first event (defaults to most recently created)
+
+			Returns:
+				An event instance.
+		*/
+
+		static function getSingleFeaturedEventByDate($date,$sort = "id DESC") {
+			return self::getSingleEventByDate($date,true,$sort);
+		}
+
+		/*
+			Function: getSubcategoriesOfCategory
+				Returns all the subcategories (and their subcategories) of a given category.
+			
+			Parameters:
+				category - Either a category id or array.
+			
+			Returns:
+				An array of categories.
+		*/
+		
+		static function getSubcategoriesOfCategory($category) {
+			$category = is_array($category) ? sqlescape($category["id"]) : sqlescape($category);
+			$categories = array();
+			$q = sqlquery("SELECT * FROM btx_events_categories WHERE parent = '$category'");
+			while ($f = sqlfetch($q)) {
+				$categories[] = $f;
+				$categories = array_merge($categories,self::getSubcategoriesOfCategory($f));
+			}
+			return $categories;
+		}
+
+		/*
+			Function: getUpcomingEventInstances
+				Returns instances of an event that occur in the future.
+
+			Parameters:
+				event - An event ID or event array.
+
+			Returns:
+				An array of event instances
+
+			See Also:
+				<getEventInstances>
+		*/
+
+		static function getUpcomingEventInstances($event) {
+			return static::getEventInstances($event,true);
+		}
+
+		/*
+			Function: getUpcomingEvents
+				Returns an array of event instances occurring in the future ordered by those happening soonest.
+
+			Parameters:
+				limit - The number of events to return.
+				featured - Whether to limit the results to exclusively featured events or not (defaults to false).
+				page - The page to return (defaults to the first page).
+
+			Returns:
+				An array of event instances.
+		*/
+
+		static function getUpcomingEvents($limit = 5,$featured = false,$page = 1) {
+			$page = $page ? ($page - 1) : 0;
+			$events = array();
+			if ($featured) {
+				$featured = " AND btx_events_events.featured = 'on' ";
+			}
+
+			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.end >= NOW() $featured ORDER BY btx_events_date_cache.start ASC LIMIT ".($page * $limit).",$limit");
+			while ($f = sqlfetch($q)) {
+				$event = self::get($f);
+				$events[] = $event;
+			}
+			return $events;
+		}
+
+		/*
+			Function: getUpcomingEventsPageCount
+				Returns the number of pages of upcoming events.
+
+			Parameters:
+				per_page - The number of events per page.
+
+			Returns:
+				The number of pages.
+		*/
+
+		static function getUpcomingEventsPageCount($per_page = 5) {
+			$f = sqlfetch(sqlquery("SELECT COUNT(id) AS `count` FROM btx_events_date_cache WHERE end >= NOW()"));
+			$pages = ceil($f["count"] / $per_page);
+			return $pages ? $pages : 1;
+		}
+
+		/*
+			Function: getUpcomingFeaturedEvents
+				Returns an array of featured event instances occurring in the future ordered by those happening soonest.
+
+			Parameters:
+				limit - The number of events to return.
+				page - The page to return (defaults to the first page).
+
+			Returns:
+				An array of event instances.
+
+			See Also:
+				<getUpcomingEvents>
+		*/
+
+		static function getUpcomingFeaturedEvents($limit = 5,$page = 1) {
+			return self::getUpcomingEvents($limit,true,$page);
+		}
+
+		/*
+			Function: getUpcomingEventsInCategories
+				Returns an array of event instances occurring in the future in the provided categories ordered by those happening soonest.
+
+			Parameters:
+				limit - The number of events to return.
+				categories - An array of categories.
+				featured - Whether to limit the results to exclusively featured events or not (defaults to false).
+				page - The page to return (defaults to the first page).
+
+			Returns:
+				An array of event instances.
+		*/
+
+		static function getUpcomingEventsInCategories($limit = 5,$categories = array(),$featured = false,$page = 1) {
+			$page = $page ? ($page - 1) : 0;
+			$events = array();
+			if ($featured) {
+				$featured = " AND btx_events_events.featured = 'on' ";
+			}
+
+			$cat_search = array();
+			foreach ($categories as $category) {
+				$category = is_array($category) ? sqlescape($category["id"]) : sqlescape($category);
+				$cat_search[] = "btx_events_event_categories.category = '$category'";
+			}
+
+			$q = sqlquery("SELECT DISTINCT(CONCAT(btx_events_date_cache.event,btx_events_date_cache.start,btx_events_date_cache.end)),btx_events_date_cache.event,btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route FROM btx_events_date_cache JOIN btx_events_event_categories JOIN btx_events_events ON btx_events_events.id = btx_events_date_cache.event WHERE btx_events_date_cache.event = btx_events_event_categories.event AND btx_events_date_cache.end >= NOW() AND (".implode(" OR ",$cat_search).") $featured ORDER BY btx_events_date_cache.start ASC LIMIT ".($page * $limit).",$limit");
+
+			while ($f = sqlfetch($q)) {
+				$event = self::get($f["event"]);
+				if ($event) {
+					$event["start"] = $f["start"];
+					$event["end"] = $f["end"];
+					$event["instance"] = $f["instance"];
+					$event["title_route"] = $f["title_route"];
+					$event["date_route"] = $f["date_route"];
+					$events[] = $event;
+				}
+			}
+			return $events;
+		}
+
+		/*
+			Function: getUpcomingEventsInCategoriesWithSubcategories
+				Returns an array of event instances occurring in the future in the provided categories and all of their children ordered by those happening soonest.
+
+			Parameters:
+				limit - The number of events to return.
+				categories - An array of categories.
+				featured - Whether to limit the results to exclusively featured events or not (defaults to false).
+				page - The page to return (defaults to the first page).
+
+			Returns:
+				An array of event instances.
+		*/
+		static function getUpcomingEventsInCategoriesWithSubcategories($limit,$categories = array(),$featured = false,$page = 1) {
+			$with_sub = $categories;
+			foreach ($categories as $cat) {
+				$with_sub = array_merge($with_sub,self::getSubcategoriesOfCategory($cat));
+			}
+			return self::getUpcomingEventsInCategories($limit,$with_sub,$featured,$page);
+		}
+
+		/*
+			Function: getUpcomingFeaturedEventsInCategories
+				Returns an array of featured event instances occurring in the future in the provided categories ordered by those happening soonest.
+
+			Parameters:
+				limit - The number of events to return.
+				categories - An array of categories.
+				page - The page to return (defaults to the first page).
+
+			Returns:
+				An array of event instances.
+
+			See Also:
+				<getUpcomingEventsInCategories>
+		*/
+
+		static function getUpcomingFeaturedEventsInCategories($limit = 5,$categories = array(),$page = 1) {
+			return self::getUpcomingEventsInCategories($limit,$categories,true,$page);
+		}
+
+		/*
+			Function: getUpcomingFeaturedEventsInCategoriesWithSubcategories
+				Returns an array of featured event instances occurring in the future in the provided categories and their subcategories ordered by those happening soonest.
+
+			Parameters:
+				limit - The number of events to return.
+				categories - An array of categories.
+				page - The page to return (defaults to the first page).
+
+			Returns:
+				An array of event instances.
+
+			See Also:
+				<getUpcomingEventsInCategories>
+		*/
+
+		static function getUpcomingFeaturedEventsInCategoriesWithSubcategories($limit = 5,$categories = array(),$page = 1) {
+			return self::getUpcomingEventsInCategoriesWithSubcategories($limit,$categories,true,$page);
+		}
+
+		/*
+			Function: getUpcomingSearchResults
+				Returns event instances matching a given query that are occurring in the future.
+			
+			Parameters:
+				query - The string to search for.
+				limit - The number of event instances to return.
+				featured - Whether to only return featured or not (defaults to false for all).
+			
+			Returns:
+				An array of event instances ordered by soonest.
+		*/
+
+		static function getUpcomingSearchResults($query, $limit = 5, $featured = false) {
+			$events = array();
+			$qwords = array();
+			$words = explode(" ",$query);
+
+			if ($featured) {
+				$featured = " AND btx_events_events.featured = 'on' ";
+			}
+
+			if ($words) {
+				foreach ($words as $word) {
+					$word = sqlescape($word);
+					$qwords[] = "(btx_events_events.title LIKE '%$word%' OR btx_events_events.description LIKE '%$word%')";
+				}
+
+				$qwords = implode(" AND ",$qwords)." AND ";
+			} else {
+				$qwords = "";
+			}
+
+			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND $qwords btx_events_date_cache.end >= NOW() $featured ORDER BY btx_events_date_cache.start ASC LIMIT $limit");
+
+			while ($f = sqlfetch($q)) {
+				$event = self::get($f);
+				$events[] = $event;
+			}
+			return $events;
+		}
+
+		/*
+			Function: getUpcomingSearchResultsPage
+				Returns a page of event instances matching a given query that are occurring in the future.
+			
+			Parameters:
+				query - The string to search for.
+				page - The page to return
+				per_page - The number of event instances to return per page.
+			
+			Returns:
+				An array of event instances ordered by soonest.
+		*/
+
+		static function getUpcomingSearchResultsPage($query, $page = 1, $per_page = 10) {
+			$events = array();
+			$words = explode(" ",$query);
+			$qwords = array();
+
+			if ($words) {
+				foreach ($words as $word) {
+					$word = sqlescape($word);
+					$qwords[] = "(btx_events_events.title LIKE '%$word%' OR btx_events_events.description LIKE '%$word%')";
+				}
+
+				$qwords = implode(" AND ",$qwords)." AND ";
+			} else {
+				$qwords = "";
+			}
+
+			$limit = (($page - 1) * $per_page).", $per_page";
+
+			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND $qwords btx_events_date_cache.end >= NOW() ORDER BY btx_events_date_cache.start ASC LIMIT $limit");
+
+			while ($f = sqlfetch($q)) {
+				$event = self::get($f);
+				$events[] = $event;
+			}
+
+			return $events;
+		}
+
+		/*
+			Function: getUpcomingSearchResultsPage
+				Returns the number of pages of event instances matching a given query that are occurring in the future.
+			
+			Parameters:
+				query - The string to search for.
+				per_page - The number of event instances per page.
+			
+			Returns:
+				An integer.
+		*/
+
+		static function getUpcomingSearchResultsPageCount($query, $per_page = 10) {
+			$events = array();
+			$words = explode(" ",$query);
+			$qwords = array();
+
+			if ($words) {
+				foreach ($words as $word) {
+					$word = sqlescape($word);
+					$qwords[] = "(btx_events_events.title LIKE '%$word%' OR btx_events_events.description LIKE '%$word%')";
+				}
+				
+				$qwords = implode(" AND ",$qwords)." AND ";
+			} else {
+				$qwords = "";
+			}
+
+			$limit = (($page - 1) * $per_page).", $per_page";
+
+			$f = sqlfetch(sqlquery("SELECT COUNT(btx_events_date_cache.id) AS `count`
+									FROM btx_events_events JOIN btx_events_date_cache
+									ON btx_events_date_cache.event = btx_events_events.id
+									WHERE $qwords btx_events_date_cache.end >= NOW()"));
+
+			$pages = ceil($f["count"] / $per_page);
+
+			return $pages ?: 1;
+		}
+
+		/*
+			Function: getUpcomingFeaturedSearchResults
+				Returns featured event instances matching a given query that are occurring in the future.
+			
+			Parameters:
+				query - The string to search for.
+				limit - The number of event instances to return.
+			
+			Returns:
+				An array of event instances ordered by soonest.
+		*/
+
+		static function getUpcomingFeaturedSearchResults($query,$limit = 5) {
+			return self::getUpcomingSearchResults($query,$limit,true);
+		}
+
+		/*
+			Function: recacheEvent
+				Removes cached occurrences and then caches the event again.
+			
+			Parameters:
+				id - The event id.
+		*/
+
+		static function recacheEvent($id) {
+			self::uncacheEvent($id);
+			self::cacheEvent($id);
+		}
+
+		/*
+			Function: searchResults
+				Returns event entries that match a given query (not instances).
+			
+			Parameters:
+				query - The string to search for.
+			
+			Returns:
+				An array of decoded event entries from the database.
+		*/
+
+		static function searchResults($query) {
+			$words = explode(" ",$query);
+			$qwords = array();
+
+			if ($words) {
+				foreach ($words as $word) {
+					$word = sqlescape($word);
+					$qwords[] = "(title LIKE '%$word%' OR description LIKE '%$word%')";
+				}
+
+				$qwords = " AND ".implode(" AND ",$qwords);
+			} else {
+				$qwords = "";
+			}
+
+			$q = sqlquery("SELECT * FROM btx_events_events WHERE 1 $qwords ORDER BY id DESC");
+
+			$events = array();
+
+			while ($f = sqlfetch($q)) {
+				$event = self::get($f);
+				$events[] = $event;
+			}
+
+			return $events;
+		}
+
+		/*
+			Function: searchResultsInCategory
+				Returns event entries that match a given query (not instances) that belong to a category (or one of its subcategories).
+			
+			Parameters:
+				query - The string to search for.
+				category - The category to check against.
+			
+			Returns:
+				An array of decoded event entries from the database.
+		*/
+
+		static function searchResultsInCategory($query, $category) {
+			$category = is_array($category) ? sqlescape($category["id"]) : sqlescape($category);
+			$with_sub = array_merge(array($category),self::getSubcategoriesOfCategory($category));
+			$cat_search = array();
+
+			foreach ($with_sub as $category) {
+				$cat_search[] = "btx_events_event_categories.category = '$category'";
+			}
+
+			$words = explode(" ",$query);
+			$qwords = array();
+
+			if ($words) {
+				foreach ($words as $word) {
+					$word = sqlescape($word);
+					$qwords[] = "(btx_events_events.title LIKE '%$word%' OR btx_events_events.description LIKE '%$word%')";
+				}
+
+				$qwords = " AND ".implode(" AND ",$qwords);
+			} else {
+				$qwords = "";
+			}
+
+			$q = sqlquery("SELECT DISTINCT(btx_events_event_categories.event),btx_events_events.* FROM btx_events_events JOIN btx_events_event_categories WHERE btx_events_events.id = btx_events_event_categories.event $qwords AND (".implode(" OR ",$cat_search).") ORDER BY id DESC");
+
+			$events = array();
+
+			while ($f = sqlfetch($q)) {
+				$event = self::get($f);
+				$events[] = $event;
+			}
+
+			return $events;
+		}
+		
+		/*
+			Function: uncacheEvent
+				Removes cached occurrences of an event.
+			
+			Parameters:
+				id - The event id.
+		*/
+
+		static function uncacheEvent($id) {
+			$id = sqlescape($id);
+			sqlquery("DELETE FROM btx_events_date_cache WHERE event = '$id'");
+		}
+
+		/*
+			Function: parseMTM
+				Helper function for the admin to create sensible category tagging.
+		*/
+		
+		static function parseMTM($list,$everything = false) {
+			if (!$everything) {
+				return $list;
+			}
+			$parsed = array();
+			foreach ($list as $id => $name) {
+				$ancestors = self::getCategoryLineage($id);
+				$path = array();
+				foreach ($ancestors as $a) {
+					$path[] = $a["name"];
+				}
+				$path[] = $name;
+				$parsed[$id] = implode(" » ",$path);
+			}
+			asort($parsed);
+			return $parsed;
+		}
+	}
+	
\ No newline at end of file
diff --git a/css/events.css b/css/events.css
new file mode 100755
index 0000000..0627b9e
--- /dev/null
+++ b/css/events.css
@@ -0,0 +1,52 @@
+/* Search Events */
+#btx_events_search .select { margin: 0 10px 0 0; }
+#btx_events_search .date_range_spacer { float: left; height: 32px; line-height: 32px; margin: 0 10px 0 0; }
+
+#btx_events_search_results div.table { border-radius: 2px; box-shadow: none; margin: 0; }
+#btx_events_search_results div.table > header { margin-top: 0; }
+#btx_events_search_results .table .view_action span { margin: 5px 0 0 0; }
+.table .btx_events_column_date { width: 65px; }
+.table .btx_events_column_title { width: 665px; }
+
+.btx_events_categories_table > section { padding: 0; }
+.btx_events_categories_table section ul { width: 958px; }
+.btx_events_categories_table li > span { float: left; height: 32px; text-align: center; }
+.btx_events_categories_table .btx_events_category_name { display: block; float: left; font-size: 15px; height: 32px; line-height: 32px; padding: 0 0 0 15px; text-align: left; width: 833px; }
+.btx_events_categories_table .depth_2 .btx_events_category_name { width: 823px; }
+.btx_events_categories_table .depth_3 .btx_events_category_name { width: 813px; }
+.btx_events_categories_table .depth_4 .btx_events_category_name { width: 803px; }
+.btx_events_categories_table .depth_5 .btx_events_category_name { width: 793px; }
+.btx_events_categories_table .depth_6 .btx_events_category_name { width: 783px; }
+.btx_events_categories_table .depth_7 .btx_events_category_name { width: 773px; }
+.btx_events_categories_table .depth_8 .btx_events_category_name { width: 763px; }
+.btx_events_categories_table .depth_9 .btx_events_category_name { width: 753px; }
+.btx_events_categories_table .depth_10 .btx_events_category_name { width: 743px; }
+
+/* Calendar */
+#btx_events_calendar { border: 1px solid #CCC; border-radius: 2px; overflow: hidden; }
+#btx_events_calendar a { color: #333; }
+#btx_events_calendar header { background: #EEE; border-bottom: 1px solid #CCC; border-radius: 2px 2px 0 0; height: 50px; overflow: hidden; }
+#btx_events_calendar header .today { background: #FAFAFA; border: 1px solid #CCC; border-radius: 2px; float: left; font-size: 10px; height: 30px; line-height: 30px; margin: 9px 0 0 10px; padding: 0 15px; text-align: center; }
+#btx_events_calendar header .today:hover { background: #FFF; }
+#btx_events_calendar header nav { margin: 18px 0 0 347px; position: absolute; }
+#btx_events_calendar header nav * { display: block; float: left; }
+#btx_events_calendar header nav span { color: #333; font-size: 14px; text-align: center; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8); width: 240px; }
+#btx_events_calendar header a.previous span { background: url("../../../images/icon-sprite.svg") no-repeat scroll -137px -116px transparent; display: block; height: 9px; overflow: hidden; text-indent: -1000em; width: 6px; }
+#btx_events_calendar header a.next span { background: url("../../../images/icon-sprite.svg") no-repeat scroll -137px -125px transparent; display: block; height: 9px; overflow: hidden; text-indent: -1000em; width: 6px; }
+#btx_events_calendar header a.previous, #btx_events_calendar header a.next { height: 9px; padding: 3px 5px; width: 6px; }
+
+#btx_events_calendar summary { background: #FFF; border-bottom: 1px solid #CCC; overflow: hidden; }
+#btx_events_calendar summary section { display: block; float: left; font-size: 12px; padding: 10px 0; text-align: center; width: 136px; }
+
+#btx_events_calendar .dates { background: #FCFCFC; margin: 0; overflow: hidden; }
+#btx_events_calendar .dates li { border: solid 1px #DDD; border-left: none; border-top: none; display: block; float: left; height: 116px; padding: 10px; width: 116px; }
+#btx_events_calendar .dates li.saturday { border-right: none; }
+#btx_events_calendar .dates li.last_row { border-bottom: none; }
+#btx_events_calendar .dates li a { color: #666; }
+#btx_events_calendar .dates li span { float: right; font-size: 11px; }
+#btx_events_calendar .dates li .not_active_month { color: #CCC; }
+#btx_events_calendar .dates li .search { display: block; font-size: 10px; line-height: 16px; margin-top: 12px; padding-left: 20px; }
+#btx_events_calendar .dates .active { background: #F4F4F4; }
+#btx_events_calendar .dates li ul { display: block; margin: 18px 0 0 0; overflow: hidden; }
+#btx_events_calendar .dates li ul li { border: none; display: block; font-size: 10px; height: 12px; line-height: 12px; margin: 8px 0 0 0; overflow: hidden; padding: 0; text-overflow: ellipsis; white-space: nowrap; width: 116px; }
+#btx_events_calendar .dates li ul li a:hover { background: #F3F3F3; border-radius: 3px; color: #333; margin: -3px 0 0 -6px; padding: 3px 6px; position: absolute; z-index: 500; }
\ No newline at end of file
diff --git a/field-types/date-chooser/draw.php b/field-types/date-chooser/draw.php
new file mode 100755
index 0000000..18156f3
--- /dev/null
+++ b/field-types/date-chooser/draw.php
@@ -0,0 +1,74 @@
+<?php
+	$bigtree["datepickers"][] = "field_start_date";
+	$bigtree["datepickers"][] = "field_end_date";
+	$bigtree["datepickers"][] = "field_recurring_end_date";
+	$bigtree["timepickers"][] = "field_start_time";
+	$bigtree["timepickers"][] = "field_end_time";
+	if (!in_array("events.js",$bigtree["js"])) {
+		$bigtree["js"][] = "events.js";
+	}
+
+
+	// Format Start Date w/ Chosen Date Format
+	if ($bigtree["entry"]["start_date"] && $bigtree["entry"]["start_date"] != "0000-00-00") {
+		$start_date = date($bigtree["config"]["date_format"],strtotime($bigtree["entry"]["start_date"]));
+	} else {
+		$start_date = "";
+	}
+
+	// Format End Date w/ Chosen Date Format
+	if ($bigtree["entry"]["end_date"] && $bigtree["entry"]["end_date"] != "0000-00-00") {
+		$end_date = date($bigtree["config"]["date_format"],strtotime($bigtree["entry"]["end_date"]));
+	} else {
+		$end_date = "";
+	}
+?>
+<div class="sub_section" style="margin-bottom: -30px; margin-top: 30px;">
+	<div style="float: left; margin: 0 15px 0 0;">
+		<fieldset>
+			<label for="field_start_date">Start Date</label>
+			<input type="text" class="date_picker" id="field_start_date" autocomplete="off" value="<?=$start_date?>" name="start_date" tabindex="7" />
+			<span class="icon_small icon_small_calendar date_picker_icon"></span>
+		</fieldset>
+		<fieldset>
+			<label for="field_start_time">Start Time</label>
+			<input type="text" class="time_picker" id="field_start_time" autocomplete="off" value="<?php if ($bigtree["entry"]["start_time"]) { echo date("g:ia",strtotime($bigtree["entry"]["start_time"])); } ?>" name="start_time" tabindex="9" <?php if ($bigtree["entry"]["all_day"]) { ?>disabled="disabled" <?php } ?>/>
+			<span class="icon_small icon_small_clock time_picker_icon"></span>
+		</fieldset>
+	</div>
+	<div style="float: left; margin: 0 15px 15px 0;">
+		<fieldset>
+			<label for="field_end_date">End Date</label>
+			<input type="text" class="date_picker" id="field_end_date" autocomplete="off" value="<?=$end_date?>" name="end_date" tabindex="8" />
+			<span class="icon_small icon_small_calendar date_picker_icon"></span>
+		</fieldset>
+		<fieldset>
+			<label for="field_end_time">End Time</label>
+			<input type="text" class="time_picker" id="field_end_time" autocomplete="off" value="<?php if ($bigtree["entry"]["end_time"]) { echo date("g:ia",strtotime($bigtree["entry"]["end_time"])); } ?>" name="end_time" tabindex="10" <?php if ($bigtree["entry"]["all_day"]) { ?>disabled="disabled" <?php } ?>/>
+			<span class="icon_small icon_small_clock time_picker_icon"></span>
+		</fieldset>
+	</div>
+	<br class="clear" />
+	<fieldset>
+		<input type="checkbox" id="field_all_day" tabindex="11" name="all_day" <?php if ($bigtree["entry"]["all_day"]) { ?>checked="checked" <?php } ?>/> <label class="for_checkbox" for="field_all_day">All Day</label>
+	</fieldset>
+	<fieldset style="float: left; margin: 0 15px 0 0;">
+		<label for="field_recurrence_type">Reoccur</label>
+		<select name="recurrence_type" id="field_recurrence_type" tabindex="12">
+			<option value="">Never</option>
+			<option value="daily"<?php if ($bigtree["entry"]["recurrence_type"] == "daily") { ?> selected="selected"<?php } ?>>Daily</option>
+			<option value="weekly"<?php if ($bigtree["entry"]["recurrence_type"] == "weekly") { ?> selected="selected"<?php } ?>>Weekly</option>
+			<option value="monthly"<?php if ($bigtree["entry"]["recurrence_type"] == "monthly") { ?> selected="selected"<?php } ?>>Monthly</option>
+			<option value="yearly"<?php if ($bigtree["entry"]["recurrence_type"] == "yearly") { ?> selected="selected"<?php } ?>>Yearly</option>
+		</select>
+	</fieldset>
+	<fieldset style="float: left; margin: 0 15px 0 0;<?php if (!$bigtree["entry"]["recurrence_type"] || $bigtree["entry"]["recurrence_type"] == "daily") { ?> display: none;<?php } ?>" id="recurrence_detail_area">
+		<input type="hidden" name="recurrence_detail" value="<?=$bigtree["entry"]["recurrence_detail"]?>" />
+	</fieldset>
+	<fieldset style="float: left; margin: 0;<?php if (!$bigtree["entry"]["recurrence_type"]) { ?> display: none;<?php } ?>" id="recurrence_end_area">
+		<label>Until</label>
+		<input type="text" class="date_picker" id="field_recurring_end_date" autocomplete="off" value="<?php if ($bigtree["entry"]["recurring_end_date"]) { echo date("m/d/Y",strtotime($bigtree["entry"]["recurring_end_date"])); } ?>" name="recurring_end_date" />
+		<span class="icon_small icon_small_calendar date_picker_icon"></span>
+	</fieldset>
+	<br class="clear" />
+</div>
\ No newline at end of file
diff --git a/field-types/date-chooser/options.php b/field-types/date-chooser/options.php
new file mode 100755
index 0000000..e69de29
diff --git a/field-types/date-chooser/process.php b/field-types/date-chooser/process.php
new file mode 100755
index 0000000..5a46549
--- /dev/null
+++ b/field-types/date-chooser/process.php
@@ -0,0 +1,58 @@
+<?php
+	// Start date / end date
+	$raw_start = BigTree::dateFormat($bigtree["post_data"]["start_date"],"U");
+	$raw_end = BigTree::dateFormat($bigtree["post_data"]["end_date"],"U");
+
+	$bigtree["entry"]["start_date"] = $raw_start ? date("Y-m-d",$raw_start) : "";
+	$bigtree["entry"]["end_date"] = ($raw_end && $raw_end > $raw_start) ? date("Y-m-d",$raw_end) : "";
+
+	if ($raw_end && $raw_end < $raw_start) {
+		$bigtree["errors"][] = array("field" => "End Date", "error" => "The end date you entered was earlier than the start date.");
+	}
+
+	// Start time / end time / all day
+	if ($bigtree["post_data"]["all_day"] || !$bigtree["post_data"]["start_time"]) {
+		$bigtree["entry"]["start_time"] = "";
+		$bigtree["entry"]["end_time"] = "";
+		$bigtree["entry"]["all_day"] = "on";
+	} else {
+		$bigtree["entry"]["all_day"] = "";
+		$bigtree["entry"]["start_time"] = date("H:i:s",strtotime($bigtree["post_data"]["start_time"]));
+		
+		if ($bigtree["post_data"]["end_time"]) {
+			// If the end time is before the start time and the event doesn't span multiple days, kill the end time.
+			if (strtotime($bigtree["post_data"]["end_time"]) < strtotime($bigtree["post_data"]["start_time"]) && !$bigtree["entry"]["end_date"]) {
+				$bigtree["entry"]["end_time"] = "";
+				$bigtree["errors"][] = array("field" => "End Time", "error" => "The end time you entered was earlier than the start time.");
+			} else {
+				$bigtree["entry"]["end_time"] = date("H:i:s",strtotime($bigtree["post_data"]["end_time"]));
+			}
+		} else {
+			$bigtree["entry"]["end_time"] = "";
+		}
+	}
+	
+	if ($bigtree["post_data"]["recurrence_type"]) {
+		$bigtree["entry"]["recurrence_type"] = $bigtree["post_data"]["recurrence_type"];
+		$bigtree["entry"]["recurrence_detail"] = $bigtree["post_data"]["recurrence_detail"];
+
+		$raw_recurring_end = BigTree::dateFormat($bigtree["post_data"]["recurring_end_date"],"U");
+		
+		if ($raw_recurring_end) {
+			if ($raw_recurring_end > $raw_start) {
+				$bigtree["entry"]["recurring_end_date"] = date("Y-m-d",$raw_recurring_end);
+			} else {
+				$bigtree["entry"]["recurring_end_date"] = "";
+				$bigtree["errors"][] = array("field" => "Recurring End Date", "error" => "The recurring end date you entered was earlier than the start date.");
+			}
+		}
+	} else {
+		$bigtree["entry"]["recurrence_type"] = "";
+		$bigtree["entry"]["recurrence_detail"] = "";
+		$bigtree["entry"]["recurring_end_date"] = "";
+		$bigtree["entry"]["canceled_recurrences"] = "";
+	}
+	
+	$field["ignore"] = true;
+
+	BigTreeAutoModule::clearCache("btx_events_date_cache");
diff --git a/js/events.js b/js/events.js
new file mode 100755
index 0000000..47c508d
--- /dev/null
+++ b/js/events.js
@@ -0,0 +1,65 @@
+$(document).ready(function() {
+
+	var $CalendarData = $("#btx_events_calendar_data");
+	var $RecurrenceDetailArea = $("#recurrence_detail_area");
+	var $RecurrenceEndArea = $("#recurrence_end_area");
+	var $RecurrenceType = $("#field_recurrence_type");
+	var $SearchResults = $("#btx_events_search_results");
+
+	// Search
+	$("#btx_events_search").submit(function(ev) {
+		ev.preventDefault();
+		$SearchResults.html('<img src="admin_root/images/spinner.gif" alt="" /> Querying Events...').parent().show();
+		$SearchResults.load("admin_root/*/com.fastspot.events/ajax/search/", $(this).serializeJSON())
+	});
+
+	// Calendar
+	$CalendarData.on("click", ".month_button", function(ev) {
+		ev.preventDefault();
+		$CalendarData.load("admin_root/*/com.fastspot.events/ajax/month/?module_root=" + escape(BTXEventsModuleRoot) + "&month=" + $(this).attr("href").substr(1));
+	});
+
+	// Events Forms
+	$RecurrenceType.change(updateRecurrenceType);
+	if ($RecurrenceType.length) {
+		updateRecurrenceType("", "", $RecurrenceDetailArea.find("input").val());
+	}
+	
+	$("#field_all_day").change(function() {
+		if ($(this).prop("checked")) {
+			$("#field_start_time").prop("disabled",true);
+			$("#field_end_time").prop("disabled",true);
+		} else {
+			$("#field_start_time").prop("disabled",false);
+			$("#field_end_time").prop("disabled",false);
+		}
+	});
+
+	function updateRecurrenceType(ev, element, existing_val) {
+		var val = $RecurrenceType.val();
+		if (!val) {
+			$RecurrenceDetailArea.hide();
+			$RecurrenceEndArea.hide();
+		} else {
+			if (val == "daily") {
+				$RecurrenceDetailArea.hide();
+			} else if (val == "weekly") {
+				$RecurrenceDetailArea.html('<label>On</label><select name="recurrence_detail" id="recurrence_dd"><option value="0">Sunday</option><option value="1">Monday</option><option value="2">Tuesday</option><option value="3">Wednesday</option><option value="4">Thursday</option><option value="5">Friday</option><option value="6">Saturday</option></select>').show();
+			} else if (val == "monthly") {
+				$RecurrenceDetailArea.html('<label>On</label><select name="recurrence_detail" id="recurrence_dd"><option value="1">The 1st day of the month</option><option value="2">The 2nd day of the month</option><option value="3">The 3rd day of the month</option><option value="4">The 4th day of the month</option><option value="5">The 5th day of the month</option><option value="6">The 6th day of the month</option><option value="7">The 7th day of the month</option><option value="8">The 8th day of the month</option><option value="9">The 9th day of the month</option><option value="10">The 10th day of the month</option><option value="11">The 11th day of the month</option><option value="12">The 12th day of the month</option><option value="13">The 13th day of the month</option><option value="14">The 14th day of the month</option><option value="15">The 15th day of the month</option><option value="16">The 16th day of the month</option><option value="17">The 17th day of the month</option><option value="18">The 18th day of the month</option><option value="19">The 19th day of the month</option><option value="20">The 20th day of the month</option><option value="21">The 21st day of the month</option><option value="22">The 22nd day of the month</option><option value="23">The 23rd day of the month</option><option value="24">The 24th day of the month</option><option value="25">The 25th day of the month</option><option value="26">The 26th day of the month</option><option value="27">The 27th day of the month</option><option value="28">The 28th day of the month</option><option value="29">The 29th day of the month</option><option value="30">The 30th day of the month</option><option value="31">The 31st day of the month</option><option value="day#1#0">The 1st Sunday of the month</option><option value="day#1#1">The 1st Monday of the month</option><option value="day#1#2">The 1st Tuesday of the month</option><option value="day#1#3">The 1st Wednesday of the month</option><option value="day#1#4">The 1st Thursday of the month</option><option value="day#1#5">The 1st Friday of the month</option><option value="day#1#6">The 1st Saturday of the month</option><option value="day#2#0">The 2nd Sunday of the month</option><option value="day#2#1">The 2nd Monday of the month</option><option value="day#2#2">The 2nd Tuesday of the month</option><option value="day#2#3">The 2nd Wednesday of the month</option><option value="day#2#4">The 2nd Thursday of the month</option><option value="day#2#5">The 2nd Friday of the month</option><option value="day#2#6">The 2nd Saturday of the month</option><option value="day#3#0">The 3rd Sunday of the month</option><option value="day#3#1">The 3rd Monday of the month</option><option value="day#3#2">The 3rd Tuesday of the month</option><option value="day#3#3">The 3rd Wednesday of the month</option><option value="day#3#4">The 3rd Thursday of the month</option><option value="day#3#5">The 3rd Friday of the month</option><option value="day#3#6">The 3rd Saturday of the month</option><option value="day#4#0">The 4th Sunday of the month</option><option value="day#4#1">The 4th Monday of the month</option><option value="day#4#2">The 4th Tuesday of the month</option><option value="day#4#3">The 4th Wednesday of the month</option><option value="day#4#4">The 4th Thursday of the month</option><option value="day#4#5">The 4th Friday of the month</option><option value="day#4#6">The 4th Saturday of the month</option></select>').show();
+			} else if (val == "yearly") {
+				$RecurrenceDetailArea.html('<label>On</label><select name="recurrence_detail" id="recurrence_dd"><option value="01-01">January 1st</option><option value="01-02">January 2nd</option><option value="01-03">January 3rd</option><option value="01-04">January 4th</option><option value="01-05">January 5th</option><option value="01-06">January 6th</option><option value="01-07">January 7th</option><option value="01-08">January 8th</option><option value="01-09">January 9th</option><option value="01-10">January 10th</option><option value="01-11">January 11th</option><option value="01-12">January 12th</option><option value="01-13">January 13th</option><option value="01-14">January 14th</option><option value="01-15">January 15th</option><option value="01-16">January 16th</option><option value="01-17">January 17th</option><option value="01-18">January 18th</option><option value="01-19">January 19th</option><option value="01-20">January 20th</option><option value="01-21">January 21st</option><option value="01-22">January 22nd</option><option value="01-23">January 23rd</option><option value="01-24">January 24th</option><option value="01-25">January 25th</option><option value="01-26">January 26th</option><option value="01-27">January 27th</option><option value="01-28">January 28th</option><option value="01-29">January 29th</option><option value="01-30">January 30th</option><option value="01-31">January 31st</option><option value="02-01">February 1st</option><option value="02-02">February 2nd</option><option value="02-03">February 3rd</option><option value="02-04">February 4th</option><option value="02-05">February 5th</option><option value="02-06">February 6th</option><option value="02-07">February 7th</option><option value="02-08">February 8th</option><option value="02-09">February 9th</option><option value="02-10">February 10th</option><option value="02-11">February 11th</option><option value="02-12">February 12th</option><option value="02-13">February 13th</option><option value="02-14">February 14th</option><option value="02-15">February 15th</option><option value="02-16">February 16th</option><option value="02-17">February 17th</option><option value="02-18">February 18th</option><option value="02-19">February 19th</option><option value="02-20">February 20th</option><option value="02-21">February 21st</option><option value="02-22">February 22nd</option><option value="02-23">February 23rd</option><option value="02-24">February 24th</option><option value="02-25">February 25th</option><option value="02-26">February 26th</option><option value="02-27">February 27th</option><option value="02-28">February 28th</option><option value="02-29">February 29th</option><option value="03-01">March 1st</option><option value="03-02">March 2nd</option><option value="03-03">March 3rd</option><option value="03-04">March 4th</option><option value="03-05">March 5th</option><option value="03-06">March 6th</option><option value="03-07">March 7th</option><option value="03-08">March 8th</option><option value="03-09">March 9th</option><option value="03-10">March 10th</option><option value="03-11">March 11th</option><option value="03-12">March 12th</option><option value="03-13">March 13th</option><option value="03-14">March 14th</option><option value="03-15">March 15th</option><option value="03-16">March 16th</option><option value="03-17">March 17th</option><option value="03-18">March 18th</option><option value="03-19">March 19th</option><option value="03-20">March 20th</option><option value="03-21">March 21st</option><option value="03-22">March 22nd</option><option value="03-23">March 23rd</option><option value="03-24">March 24th</option><option value="03-25">March 25th</option><option value="03-26">March 26th</option><option value="03-27">March 27th</option><option value="03-28">March 28th</option><option value="03-29">March 29th</option><option value="03-30">March 30th</option><option value="03-31">March 31st</option><option value="04-01">April 1st</option><option value="04-02">April 2nd</option><option value="04-03">April 3rd</option><option value="04-04">April 4th</option><option value="04-05">April 5th</option><option value="04-06">April 6th</option><option value="04-07">April 7th</option><option value="04-08">April 8th</option><option value="04-09">April 9th</option><option value="04-10">April 10th</option><option value="04-11">April 11th</option><option value="04-12">April 12th</option><option value="04-13">April 13th</option><option value="04-14">April 14th</option><option value="04-15">April 15th</option><option value="04-16">April 16th</option><option value="04-17">April 17th</option><option value="04-18">April 18th</option><option value="04-19">April 19th</option><option value="04-20">April 20th</option><option value="04-21">April 21st</option><option value="04-22">April 22nd</option><option value="04-23">April 23rd</option><option value="04-24">April 24th</option><option value="04-25">April 25th</option><option value="04-26">April 26th</option><option value="04-27">April 27th</option><option value="04-28">April 28th</option><option value="04-29">April 29th</option><option value="04-30">April 30th</option><option value="05-01">May 1st</option><option value="05-02">May 2nd</option><option value="05-03">May 3rd</option><option value="05-04">May 4th</option><option value="05-05">May 5th</option><option value="05-06">May 6th</option><option value="05-07">May 7th</option><option value="05-08">May 8th</option><option value="05-09">May 9th</option><option value="05-10">May 10th</option><option value="05-11">May 11th</option><option value="05-12">May 12th</option><option value="05-13">May 13th</option><option value="05-14">May 14th</option><option value="05-15">May 15th</option><option value="05-16">May 16th</option><option value="05-17">May 17th</option><option value="05-18">May 18th</option><option value="05-19">May 19th</option><option value="05-20">May 20th</option><option value="05-21">May 21st</option><option value="05-22">May 22nd</option><option value="05-23">May 23rd</option><option value="05-24">May 24th</option><option value="05-25">May 25th</option><option value="05-26">May 26th</option><option value="05-27">May 27th</option><option value="05-28">May 28th</option><option value="05-29">May 29th</option><option value="05-30">May 30th</option><option value="05-31">May 31st</option><option value="06-01">June 1st</option><option value="06-02">June 2nd</option><option value="06-03">June 3rd</option><option value="06-04">June 4th</option><option value="06-05">June 5th</option><option value="06-06">June 6th</option><option value="06-07">June 7th</option><option value="06-08">June 8th</option><option value="06-09">June 9th</option><option value="06-10">June 10th</option><option value="06-11">June 11th</option><option value="06-12">June 12th</option><option value="06-13">June 13th</option><option value="06-14">June 14th</option><option value="06-15">June 15th</option><option value="06-16">June 16th</option><option value="06-17">June 17th</option><option value="06-18">June 18th</option><option value="06-19">June 19th</option><option value="06-20">June 20th</option><option value="06-21">June 21st</option><option value="06-22">June 22nd</option><option value="06-23">June 23rd</option><option value="06-24">June 24th</option><option value="06-25">June 25th</option><option value="06-26">June 26th</option><option value="06-27">June 27th</option><option value="06-28">June 28th</option><option value="06-29">June 29th</option><option value="06-30">June 30th</option><option value="07-01">July 1st</option><option value="07-02">July 2nd</option><option value="07-03">July 3rd</option><option value="07-04">July 4th</option><option value="07-05">July 5th</option><option value="07-06">July 6th</option><option value="07-07">July 7th</option><option value="07-08">July 8th</option><option value="07-09">July 9th</option><option value="07-10">July 10th</option><option value="07-11">July 11th</option><option value="07-12">July 12th</option><option value="07-13">July 13th</option><option value="07-14">July 14th</option><option value="07-15">July 15th</option><option value="07-16">July 16th</option><option value="07-17">July 17th</option><option value="07-18">July 18th</option><option value="07-19">July 19th</option><option value="07-20">July 20th</option><option value="07-21">July 21st</option><option value="07-22">July 22nd</option><option value="07-23">July 23rd</option><option value="07-24">July 24th</option><option value="07-25">July 25th</option><option value="07-26">July 26th</option><option value="07-27">July 27th</option><option value="07-28">July 28th</option><option value="07-29">July 29th</option><option value="07-30">July 30th</option><option value="07-31">July 31st</option><option value="08-01">August 1st</option><option value="08-02">August 2nd</option><option value="08-03">August 3rd</option><option value="08-04">August 4th</option><option value="08-05">August 5th</option><option value="08-06">August 6th</option><option value="08-07">August 7th</option><option value="08-08">August 8th</option><option value="08-09">August 9th</option><option value="08-10">August 10th</option><option value="08-11">August 11th</option><option value="08-12">August 12th</option><option value="08-13">August 13th</option><option value="08-14">August 14th</option><option value="08-15">August 15th</option><option value="08-16">August 16th</option><option value="08-17">August 17th</option><option value="08-18">August 18th</option><option value="08-19">August 19th</option><option value="08-20">August 20th</option><option value="08-21">August 21st</option><option value="08-22">August 22nd</option><option value="08-23">August 23rd</option><option value="08-24">August 24th</option><option value="08-25">August 25th</option><option value="08-26">August 26th</option><option value="08-27">August 27th</option><option value="08-28">August 28th</option><option value="08-29">August 29th</option><option value="08-30">August 30th</option><option value="08-31">August 31st</option><option value="09-01">September 1st</option><option value="09-02">September 2nd</option><option value="09-03">September 3rd</option><option value="09-04">September 4th</option><option value="09-05">September 5th</option><option value="09-06">September 6th</option><option value="09-07">September 7th</option><option value="09-08">September 8th</option><option value="09-09">September 9th</option><option value="09-10">September 10th</option><option value="09-11">September 11th</option><option value="09-12">September 12th</option><option value="09-13">September 13th</option><option value="09-14">September 14th</option><option value="09-15">September 15th</option><option value="09-16">September 16th</option><option value="09-17">September 17th</option><option value="09-18">September 18th</option><option value="09-19">September 19th</option><option value="09-20">September 20th</option><option value="09-21">September 21st</option><option value="09-22">September 22nd</option><option value="09-23">September 23rd</option><option value="09-24">September 24th</option><option value="09-25">September 25th</option><option value="09-26">September 26th</option><option value="09-27">September 27th</option><option value="09-28">September 28th</option><option value="09-29">September 29th</option><option value="09-30">September 30th</option><option value="10-01">October 1st</option><option value="10-02">October 2nd</option><option value="10-03">October 3rd</option><option value="10-04">October 4th</option><option value="10-05">October 5th</option><option value="10-06">October 6th</option><option value="10-07">October 7th</option><option value="10-08">October 8th</option><option value="10-09">October 9th</option><option value="10-10">October 10th</option><option value="10-11">October 11th</option><option value="10-12">October 12th</option><option value="10-13">October 13th</option><option value="10-14">October 14th</option><option value="10-15">October 15th</option><option value="10-16">October 16th</option><option value="10-17">October 17th</option><option value="10-18">October 18th</option><option value="10-19">October 19th</option><option value="10-20">October 20th</option><option value="10-21">October 21st</option><option value="10-22">October 22nd</option><option value="10-23">October 23rd</option><option value="10-24">October 24th</option><option value="10-25">October 25th</option><option value="10-26">October 26th</option><option value="10-27">October 27th</option><option value="10-28">October 28th</option><option value="10-29">October 29th</option><option value="10-30">October 30th</option><option value="10-31">October 31st</option><option value="11-01">November 1st</option><option value="11-02">November 2nd</option><option value="11-03">November 3rd</option><option value="11-04">November 4th</option><option value="11-05">November 5th</option><option value="11-06">November 6th</option><option value="11-07">November 7th</option><option value="11-08">November 8th</option><option value="11-09">November 9th</option><option value="11-10">November 10th</option><option value="11-11">November 11th</option><option value="11-12">November 12th</option><option value="11-13">November 13th</option><option value="11-14">November 14th</option><option value="11-15">November 15th</option><option value="11-16">November 16th</option><option value="11-17">November 17th</option><option value="11-18">November 18th</option><option value="11-19">November 19th</option><option value="11-20">November 20th</option><option value="11-21">November 21st</option><option value="11-22">November 22nd</option><option value="11-23">November 23rd</option><option value="11-24">November 24th</option><option value="11-25">November 25th</option><option value="11-26">November 26th</option><option value="11-27">November 27th</option><option value="11-28">November 28th</option><option value="11-29">November 29th</option><option value="11-30">November 30th</option><option value="12-01">December 1st</option><option value="12-02">December 2nd</option><option value="12-03">December 3rd</option><option value="12-04">December 4th</option><option value="12-05">December 5th</option><option value="12-06">December 6th</option><option value="12-07">December 7th</option><option value="12-08">December 8th</option><option value="12-09">December 9th</option><option value="12-10">December 10th</option><option value="12-11">December 11th</option><option value="12-12">December 12th</option><option value="12-13">December 13th</option><option value="12-14">December 14th</option><option value="12-15">December 15th</option><option value="12-16">December 16th</option><option value="12-17">December 17th</option><option value="12-18">December 18th</option><option value="12-19">December 19th</option><option value="12-20">December 20th</option><option value="12-21">December 21st</option><option value="12-22">December 22nd</option><option value="12-23">December 23rd</option><option value="12-24">December 24th</option><option value="12-25">December 25th</option><option value="12-26">December 26th</option><option value="12-27">December 27th</option><option value="12-28">December 28th</option><option value="12-29">December 29th</option><option value="12-30">December 30th</option><option value="12-31">December 31st</option></select>').show();
+			}
+
+			$RecurrenceEndArea.show();
+			BigTreeCustomControls();
+
+			if (existing_val) {
+				// Update the dropdown
+				$RecurrenceDetailArea.find("select").val(existing_val);
+				var el = $RecurrenceDetailArea.find("select").get(0);
+				el.customControl.Container.find("span").html('<figure class="handle"></figure>' + el.options[el.selectedIndex].text);
+			}
+		}
+	}
+});
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
new file mode 100755
index 0000000..55c4116
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,382 @@
+{
+    "type": "extension",
+    "id": "com.fastspot.events",
+    "version": "1.2.1",
+    "revision": 6,
+    "compatibility": "4.2.4+",
+    "title": "Events",
+    "description": "A very fast (performance wise) events system that supports recurring events and a powerful class for pulling events into the front end of your site.",
+    "keywords": [
+        "events",
+        "recurring"
+    ],
+    "author": {
+        "name": "Tim Buckingham",
+        "url": "http://www.fastspot.com",
+        "email": "tim@fastspot.com"
+    },
+    "licenses": {
+        "LGPL v3": "http://opensource.org/licenses/LGPL-3.0"
+    },
+    "components": {
+        "module_groups": [],
+        "modules": [
+            {
+                "id": "3",
+                "group": null,
+                "name": "Events",
+                "route": "com.fastspot.events*events",
+                "class": "BTXEvents",
+                "icon": "events",
+                "gbp": {
+                    "name": "",
+                    "table": "",
+                    "other_table": ""
+                },
+                "position": "0",
+                "extension": "com.fastspot.events",
+                "actions": [
+                    {
+                        "id": "7",
+                        "module": "3",
+                        "name": "View Events",
+                        "route": "",
+                        "in_nav": "on",
+                        "form": null,
+                        "view": "4",
+                        "report": null,
+                        "class": "list",
+                        "level": "0",
+                        "position": "6"
+                    },
+                    {
+                        "id": "8",
+                        "module": "3",
+                        "name": "Search",
+                        "route": "search",
+                        "in_nav": "on",
+                        "form": null,
+                        "view": null,
+                        "report": null,
+                        "class": "search2",
+                        "level": "0",
+                        "position": "5"
+                    },
+                    {
+                        "id": "9",
+                        "module": "3",
+                        "name": "Calendar",
+                        "route": "calendar",
+                        "in_nav": "on",
+                        "form": null,
+                        "view": null,
+                        "report": null,
+                        "class": "calendar2",
+                        "level": "0",
+                        "position": "4"
+                    },
+                    {
+                        "id": "10",
+                        "module": "3",
+                        "name": "Add Event",
+                        "route": "add",
+                        "in_nav": "on",
+                        "form": "4",
+                        "view": null,
+                        "report": null,
+                        "class": "add",
+                        "level": "0",
+                        "position": "3"
+                    },
+                    {
+                        "id": "11",
+                        "module": "3",
+                        "name": "View Categories",
+                        "route": "categories",
+                        "in_nav": "on",
+                        "form": null,
+                        "view": "3",
+                        "report": null,
+                        "class": "category",
+                        "level": "0",
+                        "position": "2"
+                    },
+                    {
+                        "id": "12",
+                        "module": "3",
+                        "name": "Add Category",
+                        "route": "add-category",
+                        "in_nav": "on",
+                        "form": "3",
+                        "view": null,
+                        "report": null,
+                        "class": "add",
+                        "level": "0",
+                        "position": "1"
+                    },
+                    {
+                        "id": "13",
+                        "module": "3",
+                        "name": "Edit Event",
+                        "route": "edit",
+                        "in_nav": "",
+                        "form": "4",
+                        "view": null,
+                        "report": null,
+                        "class": "edit",
+                        "level": "0",
+                        "position": "0"
+                    },
+                    {
+                        "id": "14",
+                        "module": "3",
+                        "name": "Edit Category",
+                        "route": "edit-category",
+                        "in_nav": "",
+                        "form": "3",
+                        "view": null,
+                        "report": null,
+                        "class": "edit",
+                        "level": "0",
+                        "position": "0"
+                    }
+                ],
+                "views": [
+                    {
+                        "id": "3",
+                        "module": "3",
+                        "title": "Categories",
+                        "description": "",
+                        "type": "nested",
+                        "table": "btx_events_categories",
+                        "fields": {
+                            "name": {
+                                "width": "788",
+                                "title": "Name",
+                                "parser": "",
+                                "numeric": false
+                            }
+                        },
+                        "options": {
+                            "nesting_column": "parent",
+                            "filter": ""
+                        },
+                        "actions": {
+                            "edit": "on",
+                            "delete": "on"
+                        },
+                        "preview_url": "",
+                        "related_form": null
+                    },
+                    {
+                        "id": "4",
+                        "module": "3",
+                        "title": "Events",
+                        "description": "",
+                        "type": "searchable",
+                        "table": "btx_events_events",
+                        "fields": {
+                            "start_date": {
+                                "title": "Start Date",
+                                "parser": "",
+                                "width": "98",
+                                "numeric": false
+                            },
+                            "title": {
+                                "title": "Title",
+                                "parser": "",
+                                "width": "521",
+                                "numeric": false
+                            },
+                            "recurrence_type": {
+                                "title": "Recurring",
+                                "parser": "",
+                                "width": "89",
+                                "numeric": false
+                            }
+                        },
+                        "options": {
+                            "sort_column": "start_date",
+                            "sort_direction": "DESC",
+                            "per_page": "15",
+                            "filter": ""
+                        },
+                        "actions": {
+                            "feature": "on",
+                            "edit": "on",
+                            "delete": "on"
+                        },
+                        "preview_url": "",
+                        "related_form": null
+                    }
+                ],
+                "forms": [
+                    {
+                        "id": "3",
+                        "module": "3",
+                        "title": "Category",
+                        "table": "btx_events_categories",
+                        "fields": [
+                            {
+                                "column": "parent",
+                                "type": "list",
+                                "title": "Parent Category",
+                                "subtitle": "",
+                                "options": {
+                                    "list_type": "db",
+                                    "allow-empty": "Yes",
+                                    "pop-table": "btx_events_categories",
+                                    "pop-description": "name",
+                                    "pop-sort": "`name` ASC",
+                                    "column": "parent"
+                                }
+                            },
+                            {
+                                "column": "name",
+                                "type": "text",
+                                "title": "Name",
+                                "subtitle": "",
+                                "options": {
+                                    "column": "name"
+                                }
+                            },
+                            {
+                                "column": "route",
+                                "type": "route",
+                                "title": "Route",
+                                "subtitle": "",
+                                "options": {
+                                    "source": "name",
+                                    "column": "route"
+                                }
+                            }
+                        ],
+                        "default_position": "Bottom",
+                        "return_view": "3",
+                        "return_url": "",
+                        "tagging": "",
+                        "hooks": null
+                    },
+                    {
+                        "id": "4",
+                        "module": "3",
+                        "title": "Event",
+                        "table": "btx_events_events",
+                        "fields": [
+                            {
+                                "column": "title",
+                                "type": "text",
+                                "title": "Title",
+                                "subtitle": "",
+                                "options": {
+                                    "column": "title"
+                                }
+                            },
+                            {
+                                "column": "description",
+                                "type": "html",
+                                "title": "Description",
+                                "subtitle": "",
+                                "options": {
+                                    "column": "description"
+                                }
+                            },
+                            {
+                                "column": "image",
+                                "type": "upload",
+                                "title": "Image",
+                                "subtitle": "(crops to 600x400)",
+                                "options": {
+                                    "directory": "files/events/",
+                                    "image": "on",
+                                    "min_width": "600",
+                                    "min_height": "400",
+                                    "preview_prefix": "",
+                                    "crops": {
+                                        "1": {
+                                            "prefix": "",
+                                            "width": "600",
+                                            "height": "400",
+                                            "grayscale": ""
+                                        }
+                                    },
+                                    "column": "image"
+                                }
+                            },
+                            {
+                                "column": "__mtm-1__",
+                                "type": "many-to-many",
+                                "title": "Categories",
+                                "subtitle": "",
+                                "options": {
+                                    "mtm-connecting-table": "btx_events_event_categories",
+                                    "mtm-my-id": "event",
+                                    "mtm-other-id": "category",
+                                    "mtm-other-table": "btx_events_categories",
+                                    "mtm-other-descriptor": "name",
+                                    "mtm-sort": "`name` ASC",
+                                    "mtm-list-parser": "BTXEvents::parseMTM",
+                                    "column": "__mtm-1__"
+                                }
+                            },
+                            {
+                                "column": "all_day",
+                                "type": "com.fastspot.events*date-chooser",
+                                "title": "Date Chooser",
+                                "subtitle": "",
+                                "options": {
+                                    "function": "BTXEvents::drawFormDateTime",
+                                    "process_function": "BTXEvents::parseFormDateTime",
+                                    "column": "all_day"
+                                }
+                            },
+                            {
+                                "column": "route",
+                                "type": "route",
+                                "title": "Route",
+                                "subtitle": "",
+                                "options": {
+                                    "source": "title",
+                                    "column": "route"
+                                }
+                            }
+                        ],
+                        "default_position": "",
+                        "return_view": "4",
+                        "return_url": "",
+                        "tagging": "",
+                        "hooks": {
+                            "pre": "",
+                            "post": "",
+                            "publish": "BTXEvents::publishHook"
+                        }
+                    }
+                ],
+                "embed_forms": [],
+                "reports": []
+            }
+        ],
+        "templates": [],
+        "callouts": [],
+        "settings": [],
+        "feeds": [],
+        "field_types": [
+            {
+                "id": "com.fastspot.events*date-chooser",
+                "name": "Events \u2014 Date Chooser",
+                "use_cases": {
+                    "modules": "on"
+                },
+                "self_draw": "on",
+                "extension": "com.fastspot.events"
+            }
+        ],
+        "tables": {
+            "btx_events_categories": "CREATE TABLE `btx_events_categories` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `parent` int(11) DEFAULT NULL,   `name` varchar(255) NOT NULL DEFAULT '',   `route` varchar(255) NOT NULL DEFAULT '',   `position` int(11) NOT NULL,   PRIMARY KEY (`id`),   KEY `parent` (`parent`),   KEY `route` (`route`),   KEY `position` (`position`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8",
+            "btx_events_date_cache": "CREATE TABLE `btx_events_date_cache` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `event` int(11) NOT NULL,   `start` datetime NOT NULL,   `end` datetime NOT NULL,   `date_cached` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,   `title_route` varchar(255) NOT NULL DEFAULT '',   `date_route` varchar(255) NOT NULL DEFAULT '',   PRIMARY KEY (`id`),   KEY `event` (`event`),   KEY `start` (`start`),   KEY `end` (`end`),   KEY `title_route` (`title_route`),   KEY `date_route` (`date_route`),   KEY `date_cached` (`date_cached`),    FOREIGN KEY (`event`) REFERENCES `btx_events_events` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8",
+            "btx_events_event_categories": "CREATE TABLE `btx_events_event_categories` (   `event` int(11) NOT NULL,   `category` int(11) NOT NULL,   KEY `event` (`event`),   KEY `category` (`category`),    FOREIGN KEY (`event`) REFERENCES `btx_events_events` (`id`) ON DELETE CASCADE,    FOREIGN KEY (`category`) REFERENCES `btx_events_categories` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8",
+            "btx_events_events": "CREATE TABLE `btx_events_events` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `title` varchar(255) NOT NULL DEFAULT '',   `description` mediumtext NOT NULL,   `image` varchar(255) NOT NULL,   `start_date` date DEFAULT NULL,   `end_date` date DEFAULT NULL,   `start_time` time DEFAULT NULL,   `end_time` time DEFAULT NULL,   `all_day` char(2) NOT NULL,   `recurrence_type` varchar(255) NOT NULL,   `recurrence_detail` varchar(255) NOT NULL,   `canceled_recurrences` text NOT NULL,   `recurring_end_date` date DEFAULT NULL,   `last_updated` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,   `featured` char(2) NOT NULL,   `route` varchar(255) DEFAULT NULL,   PRIMARY KEY (`id`),   KEY `start_date` (`start_date`),   KEY `end_date` (`end_date`),   KEY `recurring_end_date` (`recurring_end_date`),   KEY `recurrence_type` (`recurrence_type`),   KEY `featured` (`featured`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8"
+        }
+    },
+    "sql_revisions": []
+}
\ No newline at end of file
diff --git a/modules/events/_header.php b/modules/events/_header.php
new file mode 100755
index 0000000..3058d35
--- /dev/null
+++ b/modules/events/_header.php
@@ -0,0 +1,3 @@
+<?php
+	$bigtree["css"][] = "events.css";
+	$bigtree["js"][] = "events.js";
diff --git a/modules/events/calendar.php b/modules/events/calendar.php
new file mode 100755
index 0000000..d1b813e
--- /dev/null
+++ b/modules/events/calendar.php
@@ -0,0 +1,10 @@
+<div id="btx_events_calendar_data">
+	<?php
+		$month = time();
+		include EXTENSION_ROOT."ajax/month.php";
+	?>
+</div>
+<br class="clear"/>
+<script>
+	var BTXEventsModuleRoot = "<?=MODULE_ROOT?>";
+</script>
\ No newline at end of file
diff --git a/modules/events/edit-check.php b/modules/events/edit-check.php
new file mode 100755
index 0000000..a4d3238
--- /dev/null
+++ b/modules/events/edit-check.php
@@ -0,0 +1,24 @@
+<?php
+	$event = BTXEvents::get($_GET["event"]);
+	
+	if ($event["recurrence_type"]) {
+?>
+<div class="container">
+	<section>
+		<p><strong>The event you're attempting to edit is a recurring event.</strong></p>
+		<p>Do you want to edit the details of every recurrence of &ldquo;<?=$event["title"]?>&rdquo;, or only the specific date you clicked? (<?=date("F j, Y",strtotime($_GET["date"]))?>)</p>
+		<p>
+		    &mdash; Editing every recurrence of this event will cause each recurrence to be affected by your changes.<br />
+		    &mdash; Separating the event will remove that date from the recurring event and create a new event based on the recurring event that you can edit individually.
+		</p>
+	</section>
+	<footer>
+		<a class="button" href="<?=MODULE_ROOT?>edit/<?=$event["id"]?>/"><span class="icon_small icon_small_calendar"></span> Edit All Recurrences</a>
+		<a class="button" href="<?=MODULE_ROOT?>separate/?event=<?=$event["id"]?>&date=<?=$_GET["date"]?><?php if (method_exists($admin, "drawCSRFTokenGET")) { $admin->drawCSRFTokenGET(); } ?>"><span class="icon_small icon_small_blog"></span> Separate Event</a>
+	</footer>
+</div>
+<?php
+	} else {
+		BigTree::redirect(MODULE_ROOT."edit/".$event["id"]."/");
+	}
+?>
\ No newline at end of file
diff --git a/modules/events/search.php b/modules/events/search.php
new file mode 100755
index 0000000..88c0494
--- /dev/null
+++ b/modules/events/search.php
@@ -0,0 +1,58 @@
+<?php
+	$months = array(
+		"01" => "January",
+		"02" => "February",
+		"03" => "March",
+		"04" => "April",
+		"05" => "May",
+		"06" => "June",
+		"07" => "July",
+		"08" => "August",
+		"09" => "September",
+		"10" => "October",
+		"11" => "November",
+		"12" => "December"
+	);
+	
+	$keywords = "";
+	
+	if (count($bigtree["commands"])) {
+		$start_date = $bigtree["commands"][0];
+		$end_date = $bigtree["commands"][1];
+	} else {
+		$start_date = date("Y-m-d");
+		$end_date = date("Y-m-d",time() + 7 * 24 * 60 * 60);
+	}
+?>
+<div class="container">
+	<form method="post" action="" id="btx_events_search">
+		<section>
+			<div class="contain">
+				<fieldset class="float_margin">
+					<label>Start Date</label>
+					<input type="text" name="start_date" class="date_picker" autcomplete="off" />
+					<span class="icon_small icon_small_calendar date_picker_icon"></span>
+				</fieldset>
+				<fieldset class="float_margin">
+					<label>End Date</label>
+					<input type="text" name="end_date" class="date_picker" autcomplete="off" />
+					<span class="icon_small icon_small_calendar date_picker_icon"></span>
+				</fieldset>
+				<fieldset class="right last">
+				    <label for="keywords">Keywords <small>(separate words with spaces)</small></label>
+				    <input type="text" id="keywords" name="keywords" value="<?=htmlspecialchars($keywords)?>" class="typical_input"/>
+				</fieldset>
+			</div>
+		</section>
+		<section class="sub" style="display: none;">
+			<label>Results</label>
+			<div id="btx_events_search_results"></div>
+		</section>
+		<footer>
+			<input type="submit" class="button blue" value="Search" />
+		</footer>
+	</form>
+</div>
+<script>
+	$(".date_picker").datepicker();
+</script>
\ No newline at end of file
diff --git a/modules/events/separate.php b/modules/events/separate.php
new file mode 100755
index 0000000..2e79a86
--- /dev/null
+++ b/modules/events/separate.php
@@ -0,0 +1,41 @@
+<?php
+	if (method_exists($admin, "verifyCSRFToken")) {
+		$admin->verifyCSRFToken();
+	}
+
+	$event = BTXEvents::get($_GET["event"]);
+	
+	$canceled = json_decode($event["canceled_recurrences"],true);
+	$canceled[] = $_GET["date"];
+	$canceled = json_encode($canceled);
+	
+	$permission_level = $admin->getAccessLevel($bigtree["current_module"],$event,"btx_events_events");
+
+	// Prepare the new event array.
+	$event["start_date"] = $_GET["date"];
+	unset($event["id"]);
+	unset($event["end_date"]);
+	unset($event["recurrence_type"]);
+	unset($event["recurrence_detail"]);
+	unset($event["canceled_recurrences"]);
+	unset($event["recurring_end_date"]);
+		
+	// We're a publisher, we'll do as we please!
+	if ($permission_level == "p") {
+		// Un-recur the original day...
+		BigTreeAutoModule::updateItem("btx_events_events",$_GET["event"],array("canceled_recurrences" => $canceled));
+		// Create the new event
+		$id = BigTreeAutoModule::createItem("btx_events_events",$event);
+		$admin->growl("Events","Separated Event");
+	}
+
+	// We're going to request a cancelation of an event
+	if ($permission_level == "e") {
+		// Ask to un-recur this event.
+		BigTreeAutoModule::submitChange($bigtree["current_module"]["id"],"btx_events_events",$_GET["event"],array("canceled_recurrences" => $canceled));
+		// Ask to create the new one.
+		$id = "p".BigTreeAutoModule::createPendingItem($bigtree["current_module"]["id"],"btx_events_events",$event);
+		$admin->growl("Events","Separated Event Pending Approval");
+	}
+	
+	BigTree::redirect(MODULE_ROOT."edit/$id/");
diff --git a/modules/events/view-day.php b/modules/events/view-day.php
new file mode 100755
index 0000000..624af63
--- /dev/null
+++ b/modules/events/view-day.php
@@ -0,0 +1,31 @@
+<?php
+	$eventsMod = new BTXEvents;
+	$items = $eventsMod->getEventsByDate($bigtree["commands"][0]);
+?>
+<div class="container" id="btx_events_search_results">
+	<summary><h2>Events for <?=date("F j, Y",strtotime($bigtree["commands"][0]))?></h2></summary>
+	<section>
+		<div class="table">
+			<header>
+				<span class="view_column btx_events_column_date">Date</span>
+				<span class="view_column btx_events_column_title">Title</span>
+				<span class="view_action">Recurs</span>
+				<span class="view_action">Edit</span>
+				<span class="view_action">Delete</span>
+			</header>
+			<ul>
+				<?php foreach ($items as $item) { ?>
+				<li>
+					<section class="view_column btx_events_column_date"><?=date("m/d/Y",strtotime($item["start"]))?></section>
+					<section class="view_column btx_events_column_title"><?=htmlspecialchars($item["title"])?></section>
+					<section class="view_action">
+						<span class="icon_repeat<?php if ($item["recurrence_type"]) { ?> icon_repeat_on<?php } ?>"></span>
+					</section>
+					<section class="view_action"><a href="<?=MODULE_ROOT?>edit-check/?event=<?=$item["id"]?>&date=<?=date("Y-m-d",strtotime($item["start"]))?>" class="icon_edit"></a></section>
+					<section class="view_action"><a href="#<?=$item["id"]?>" class="icon_delete"></a></section>
+				</li>
+				<?php } ?>
+			</ul>
+		</div>
+	</section>
+</div>
\ No newline at end of file

Fixed code: Events
======

The Events extension implements an admin interface for users to add events to an event calendar including recurring events with the following recurrence types:
- Daily
- Weekly (on a given day -- i.e. Thursday)
- Monthly (on a given day or given week & day of each month -- i.e. 1st of every month or 1st Friday of every month)
- Yearly (on a given day)

The admin provides a search interface and calendar interface for editing specific occurences of events as well as a simple list view for editing the "main" event entries.

The BTXEvents class is provided with many useful methods for pulling event data to the front end of your website.

Compatibility
-------------
This module is bundled as an extension for BigTree 4.2 and higher.

Licensing
---------
This field type is publicly licensed under the [GNU Lesser General Public License](http://www.gnu.org/copyleft/lesser.html).
If you would like to use this field type under a different license, please [contact us](mailto:info@fastspot.com).
<?php
	if (isset($_GET["month"])) {
		$month = $_GET["month"];
	}

	if (!defined("MODULE_ROOT")) {
		define("MODULE_ROOT", $_GET["module_root"]);
	}

	$month = strtotime(date("F 1, Y",$month));
	
	// Get the events for this month
	$e = BTXEvents::getEventsByDateRange(date("Y-m-1",$month),date("Y-m-t",$month));
	// Change the events into an array with the key being a date and the value being an array of events ocurring on that day
	$month_of_events = BTXEvents::getKeyedDateRangeForEvents($e);
?>
<div id="btx_events_calendar">
	<header>
		<a class="today" href="#<?=time()?>">Today is: <?=date("F j")?></a>
		<nav>
			<a href="#<?=($month-60*60)?>" class="previous month_button"><span>&laquo;</span></a>
			<span><?=date("F Y",$month)?></span>
			<a href="#<?=(strtotime(date("F t, Y",$month))+60*60*24)?>" class="next month_button"><span>&raquo;</span></a>
		</nav>
	</header>
	<summary>
		<section>Sunday</section>
		<section>Monday</section>
		<section>Tuesday</section>
		<section>Wednesday</section>
		<section>Thursday</section>
		<section>Friday</section>
		<section>Saturday</section>
	</summary>
	<ul class="dates">
		<?php
			// Get the number of days in the previous month.
			$pmonth_days = date("t",strtotime(date("F 1, Y",$month))-60*60);
			// Get the first day of the month's day (Sunday, Monday, etc) in numeric form.
			$first_day_of_month = date("w",$month);
			// Get the number of days in this month.
			$days_in_month = date("t",$month);
			
			// Figure out when the last row starts
			$days_in_first_row = 7 - $first_day_of_month;
			$days_after_first_row = $days_in_month - $days_in_first_row;
			$full_rows = $days_after_first_row - ($days_after_first_row % 7);
			$days_in_last_row = $days_in_month - $days_in_first_row - $full_rows;
			$rows = 1 + ($full_rows / 7);
			
			if ($days_in_last_row) {
				$rows++;
			}
									
			// $y is the counter for number of cells drawn.
			$y = 0;
			
			// Show the first couple days of the last month grayed out.
			$d = $first_day_of_month;
			
			while ($d) {
		?>
		<li><span class="not_active_month"><?=($pmonth_days-$d+1)?></span></li>
		<?php
				$d--;
				$y++;
			}
			
			// Let's go through this month. $x is the counter for the day of the month.
			$x = 0;
			
			while ($x < $days_in_month) {
				// Increase the day of the month and the number of cells drawn.
				$x++;
				$y++;
				
				$class = array();
				
				if ($y % 7 == 0) {
					$class[] = "saturday";
				}

				// See if we're in the last row
				if ($y / 7 > ($rows -1)) {
					$class[] = "last_row";
				}
		?>
		<li class="<?=implode(" ",$class)?>"><span><?=$x?></span>
		<?php			
			// Format the day with preceding 0.
			$day = str_pad($x,2,"0",STR_PAD_LEFT);

			// See if there are any events for this day.
			$items = $month_of_events[date("Y",$month)."-".date("m",$month)."-".$day];
  			if (count($items)) {
  				echo '<ul class="calendar_events">';
  				$xx = 0;
				
				foreach ($items as $item) {
					if ($xx < 4) {
						echo '<li><a href="'.MODULE_ROOT."edit-check/?event=".$item["id"].'&date='.date("Y",$month)."-".date("m",$month)."-".$day.'">'.$item["title"]."</a></li>";
					}
					
					$xx++;
				}

				echo '</ul>';
				
				if ($xx > 4) {
					echo '<a class="search" href="'.MODULE_ROOT.'view-day/'.date("Y",$month)."-".date("m",$month)."-".$day.'/">View All</a>';
				}
			}
		?>
		</li>
		<?php
			}
			
			$x = 0;
			
			while ($y % 7 != 0) {
				$x++;
				$y++;
		?>
		<li class="last_row<?php if ($y % 7 == 0) { ?> saturday<?php } ?>"><span class="not_active_month"><?=$x?></span></li>
		<?php
			}
		?>
	</ul>
</div>
<?php
	$start_date = $_POST["start_date"] ? date("Y-m-d",strtotime($_POST["start_date"])) : date("Y-m-d");
	$end_date = $_POST["end_date"] ? date("Y-m-d",strtotime($_POST["end_date"])) : date("Y-m-d",strtotime("+2 years"));
	$items = BTXEvents::getSearchResultsInDateRange($_POST["keywords"],$start_date,$end_date);
?>
<div class="table">
	<header>
		<span class="view_column btx_events_column_date">Date</span>
		<span class="view_column btx_events_column_title">Title</span>
		<span class="view_action">Recurs</span>
		<span class="view_action">Edit</span>
		<span class="view_action">Delete</span>
	</header>
	<ul>
		<?php foreach ($items as $item) { ?>
		<li>
			<section class="view_column btx_events_column_date"><?=date("m/d/Y",strtotime($item["start"]))?></section>
			<section class="view_column btx_events_column_title"><?=htmlspecialchars($item["title"])?></section>
			<section class="view_action">
				<span class="icon_repeat<?php if ($item["recurrence_type"]) { ?> icon_repeat_on<?php } ?>"></span>
			</section>
			<section class="view_action"><a href="<?=ADMIN_ROOT?>com.fastspot.events*events/edit-check/?event=<?=$item["id"]?>&date=<?=date("Y-m-d",strtotime($item["start"]))?>" class="icon_edit"></a></section>
			<section class="view_action"><a href="#<?=$item["id"]?>" class="icon_delete"></a></section>
		</li>
		<?php } ?>
	</ul>
</div>
<?php
	/*
		Class: BTXEvents
			A class to handle events in BigTree.
	*/
	
	class BTXEvents {
		
		/*
			Constructor:
				Re-caches stale events.
		*/
		
		function __construct() {
			$q = sqlquery("SELECT * FROM btx_events_events WHERE recurrence_type != '' AND (last_updated < '".date("Y-m-d",strtotime("-1 year"))."' OR last_updated IS NULL)");
			
			while ($f = sqlfetch($q)) {
				self::recacheEvent($f["id"]);
				sqlquery("UPDATE btx_events_events SET last_updated = NOW() WHERE id = '".$f["id"]."'");
			}
		}

		/*
			Function: cacheEvent
				Caches an event.
			
			Parameters:
				id - The id of the event to cache.
		*/
		
		static function cacheEvent($id) {
			$id = sqlescape($id);
			$item = self::get($id);
			$title_route = sqlescape($item["route"]);
			
			// It's just a normal event!  Thank heavens!
			if (!$item["recurrence_type"]) {
				list($start_date,$end_date) = self::getCacheTimes($item,$item["start_date"],$item["end_date"]);
				
				$date_route = date("Y-m-d",strtotime($start_date));

				// Put the date in the database!
				sqlquery("INSERT INTO btx_events_date_cache (`event`,`start`,`end`,`title_route`,`date_route`) VALUES ('$id','$start_date','$end_date','$title_route','$date_route')");
			// It's repeating :(  We need to cache all previous recurrences that happened in the past year, and all that will happen in the next two years.
			} elseif (!$item["recurring_end_date"] || strtotime($item["recurring_end_date"]) > time()) {
				// If there's a start date to the recurrence, use it
				if ($item["start_date"]) {
					$start = strtotime($item["start_date"]);
				} else {
					$start = strtotime("-1 year");
				}
				// If there's an end date, stop there
				if ($item["end_date"] || $item["recurring_end_date"]) {
					$end = strtotime($item["recurring_end_date"] ? $item["recurring_end_date"] : $item["end_date"]);
				} else {
					$end = strtotime("+2 years");
				}
				// If we've already passed the end date, we don't need to cache things anymore.
				if ($end < $start) {
					return;
				}
				// Get a list of the canceled items
				$canceled = json_decode($item["canceled_recurrences"],true);
				// Loop through our start and end dates finding the next recurrence of events
				while ($start <= $end) {
					$next = self::findNextRecurrence($item["recurrence_type"],$item["recurrence_detail"],$canceled,$start);
					// The next time the event occurs could fall outside our caching period
					if ($next <= $end) {
						list($start_date,$end_date) = self::getCacheTimes($item,date("Y-m-d",$next),date("Y-m-d",$next));
						$date_route = date("Y-m-d",strtotime($start_date));
						sqlquery("INSERT INTO btx_events_date_cache (`event`,`start`,`end`,`title_route`,`date_route`) VALUES ('".$item["id"]."','$start_date','$end_date','$title_route','$date_route')");
					}
					$start = strtotime(date("Y-m-d",$next)." +1 day");
				}
			}
		}
		
		/*
			Function: findNextRecurrence
				Finds the next time an event exists.
			
			Parameters:
				type - The recurrence type.
				detail - The recurrence detail.
				canceled - An array of canceled recurrences.
				time - The start time to begin looking from (in seconds since Unix epoch).
				
			Returns:
				The next occurence of the event in seconds since Unix epoch.
		*/

		static function findNextRecurrence($type,$detail,$canceled = array(),$time = false) {
			if (!$time) {
				$time = time();
			}
			// Daily Recurrence
			if ($type == "daily") {
				// If it hasn't been canceled, return the date, otherwise try again tomorrow.
				if (!in_array(date("Y-m-d",$time),$canceled)) {
					return $time;
				} else {
					return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-d",$time)." +1 day"));
				}
			}
			// Weekly Recurrence
			if ($type == "weekly") {
				$current_day_of_week = date("w",$time);
				if ($detail > $current_day_of_week) {
					$time = strtotime(date("Y-m-d",$time)." +".($detail - $current_day_of_week)." days");
				} elseif ($detail < $current_day_of_week) {
					$time = strtotime(date("Y-m-d",$time)." +".(7 - $current_day_of_week + $detail)." days");
				}

				// If it's canceled, next week please.
				if (!in_array(date("Y-m-d",$time),$canceled)) {
					return $time;
				} else {
					return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-d",$time)." +1 week"));
				}
			}
			// Monthly Recurrence
			if ($type == "monthly") {
				// If the detail is numeric, it's simply the (x)th day of the month.
				if (is_numeric($detail)) {
					$current_day_of_month = date("j",$time);
					// Move to the 1st of the next month if the repeat has already occurred this month.
					if ($current_day_of_month > $detail) {
						$time = strtotime(date("Y-m-1",$time)." +1 month");
					}
					// If we're looking for something on the 31st and a month only has 30 days, skip it.
					if ($detail > date("t",$time)) {
						return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-1",$time)." +1 month"));
					}
					$time = strtotime(date("Y-m-".$detail,$time));

					// If it's canceled, next month please.
					if (!in_array(date("Y-m-d",$time),$canceled)) {
						return $time;
					} else {
						return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-1",$time)." +1 month"));
					}
				// We need to calculate a more crazy date like the second Thursday of each month.
				} else {
					list($x,$week,$day) = explode("#",$detail);
					
					// New strategy, start at the first day of the current month and walk through each day.  If the x'th day is already passed or is canceled, then we'll move on.
					$start = strtotime(date("Y-m-1",$time));
					$end = strtotime(date("Y-m-t",$time));
					$current_week = 1;
					
					while ($start <= $end) {
						$current_day = date("w",$start);
						if ($current_week == $week && $current_day == $day) {
							if ($time > $start || in_array(date("Y-m-d",$start),$canceled)) {
								return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-1",$time)." +1 month"));
							} else {
								return $start;
							}
						}
						if ($current_day == $day) {
							$current_week++;
						}

						// Add a day
						$start = BigTree::dateFromOffset($start,"+1 day","U");
					}
					
					if ($current_week < $week) {
						return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-1",$time)." +1 month"));
					}
					
					return false;
				}

			}
			// Yearly Recurrence
			if ($type == "yearly") {
				$next = strtotime(date("Y",$time)."-".$detail);
				if ($next < $time) {
					$next = strtotime((date("Y",$time)+1)."-".$detail);
				}
				// If it's canceled, next year please.
				if (!in_array(date("Y-m-d",$next),$canceled)) {
					return $next;
				} else {
					return self::findNextRecurrence($type,$detail,$canceled,strtotime(date("Y-m-d",$next)." +1 year"));
				}
			}
			return false;
		}

		/*
			Function: formattedDate
				Returns a string of formatted date/time based on an event having a start/end date and times.
			
			Parameters:
				item - The event instance array.
				date_format - The date format (compatible with PHP's date function)
				time_format - The time format (compatible with PHP's time format)
			
			Returns:
				A date/time string.
		*/
		
		static function formattedDate($item,$date_format = "F j, Y",$time_format = "g:ia") {
			$s = strtotime($item["start"]);
			$e = strtotime($item["end"]);
			// If it's a single all day event...
			if ($item["all_day"]) {
				if (date("Y-m-d",$s) == date("Y-m-d",$e)) {
					return date($date_format,$s);
				} else {
					return date($date_format,$s)." - ".date($date_format,$e);
				}
			} else {
				// Single day event
				if (date("Y-m-d",$s) == date("Y-m-d",$e)) {
					if ($s != $e && $item["end_time"] != "") {
						return date($date_format,$s)." &mdash; ".date($time_format,$s)." - ".date($time_format,$e);
					} else {
						return date($date_format,$s)." &mdash; ".date($time_format,$s);
					}
				// Multi day event
				} else {
					// Starts one night, ends next morning?
					if (date("H",$s) > date("H",$e)) {
						return date($date_format,$s)." &mdash; ".date($time_format,$s)." - ".date($time_format,$e);
					// Probably meant an event to be on multiple days for a few hours each day.
					} else {
						return date($date_format,$s)." - ".date($date_format,$e)." &mdash; ".date($time_format,$s)." - ".date($time_format,$e);
					}
				}
			}
		}
		
		/*
			Function: formattedTime
				Returns a string of formatted time based on an event having start/end times.
			
			Parameters:
				item - The event instance array.
				time_format - The time format (compatible with PHP's time format)
			
			Returns:
				A date/time string.
		*/
		
		static function formattedTime($item,$time_format = "gi:a") {
			$s = strtotime($item["start"]);
			$e = strtotime($item["end"]);
			if ($item["all_day"]) {
				return "All Day";
			}
			if ($s != $e && $item["end_time"] != "") {
			    return date($time_format,$s)." - ".date($time_format,$e);
			} else {
			    return date($time_format,$s);
			}
		}
		
		/*
			Function: publishHook
				Used by the BigTree form to cache the event on publish.
		*/
		
		static function publishHook($table,$id,$changes,$many_to_many,$tags) {
			self::recacheEvent($id);
		}

		/*
			Function: get
				Returns an event with its fields decoded.
			
			Parameters:
				item - Either the event id or an event array.
			
			Returns:
				An event array with its fields decoded.
		*/
		
		static function get($item) {
			global $cms;
			
			if (!is_array($item)) {
				$item = sqlfetch(sqlquery("SELECT * FROM btx_events_events WHERE id = '".sqlescape($item)."'"));
			}
			if (!is_array($item)) {
				return false;
			}
			
			foreach ($item as $key => $val) {
				if (is_array($val)) {
					$item[$key] = BigTree::untranslateArray($val);
				} elseif (is_array(json_decode($val,true))) {
					$item[$key] = BigTree::untranslateArray(json_decode($val,true));
				} else {
					$item[$key] = $cms->replaceInternalPageLinks($val);
				}
			}

			return $item;
		}
		
		/*
			Function: getCacheTimes
				Returns the start and end timestamps for an event.
			
			Parameters:
				item - Event array.
				start_date - The date of the occurence.
				end_date - The end date of the occurence.
			
			Returns:
				An array of timestamps (first being start, second being end)
		*/				

		static function getCacheTimes($item,$start_date,$end_date) {
			// If they didn't enter an end date, we're going to assume it ends the same day it starts
			if ($end_date == "0000-00-00" || !$end_date) {
				$end_date = $start_date;
			}
			// If it's an all day event or we don't know the start time, set the end time to 11:59
			if ($item["all_day"] || !$item["start_time"]) {
				$start_date = strtotime($start_date." 00:00:00");
				$end_date = strtotime($end_date." 23:59:59");
			} else {
				$start_date = strtotime($start_date." ".$item["start_time"]);
				// If we have an end time, let's see if it's actually the next day.
				if ($item["end_time"]) {
					if (strtotime($item["start_time"]) < strtotime($item["end_time"]) && $start_date == $end_date) {
						$end_date = strtotime($start_date." ".$item["end_time"]." +1 day");
					} else {
						$end_date = strtotime($end_date." ".$item["end_time"]);
					}
				} else {
					$end_date = strtotime($end_date." 23:59:59");
				}
			}
			$start_date = date("Y-m-d H:i:s",$start_date);
			$end_date = date("Y-m-d H:i:s",$end_date);
			return array($start_date,$end_date);
		}

		/*
			Function: getCategoriesByParent
				Returns an array of categories with a given parent ID.

			Parameters:
				parent - The parent ID to check.
				sort - The sort order of the categories (defaults to positioned).

			Returns:
				An array of categories.
		*/

		static function getCategoriesByParent($parent = false,$sort = "position DESC, id ASC") {
			$categories = array();
			if (!$parent) {
				$q = sqlquery("SELECT * FROM btx_events_categories WHERE parent IS NULL ORDER BY $sort");
			} else {
				$q = sqlquery("SELECT * FROM btx_events_categories WHERE parent = '$parent' ORDER BY $sort");
			}
			while ($f = sqlfetch($q)) {
				$categories[] = $f;
			}
			return $categories;
		}
		
		/*
			Function: getCategory
				Returns a category for the given id.

			Parameters:
				id - The category id.

			Returns:
				A category entry.
		*/

		static function getCategory($id) {
			$id = sqlescape($id);
			return sqlfetch(sqlquery("SELECT * FROM btx_events_categories WHERE id = '$id'"));
		}

		/*
			Function: getCategoryByRoute
				Returns a category for the given route.

			Parameters:
				route - The category route.

			Returns:
				A category entry.
		*/

		static function getCategoryByRoute($route) {
			$route = sqlescape($route);
			return sqlfetch(sqlquery("SELECT * FROM btx_events_categories WHERE route = '$route'"));
		}

		/*
			Function: getCategoryLineage
				Returns an array of the ancestors of a given category.
			
			Parameters:
				category - A category ID or category array.
			
			Returns:
				An array of categories starting with the "oldest".
		*/
		
		static function getCategoryLineage($category,$ancestors = array()) {
			if (!is_array($category)) {
				$category = sqlfetch(sqlquery("SELECT * FROM btx_events_categories WHERE id = '".sqlescape($category)."'"));
			}
			if ($category["parent"] && $category["parent"] != $category["id"]) {
				$parent = sqlfetch(sqlquery("SELECT * FROM btx_events_categories WHERE id = '".sqlescape($category["parent"])."'"));
				$ancestors = array_merge(array($parent),$ancestors);
				return self::getCategoryLineage($parent,$ancestors);
			}
			return $ancestors;
		}
		
		/*
			Function: getEventCategories
				Returns an array of categories that an event belongs to.

			Parameters:
				event - The event entry or event id.

			Returns:
				An array of IDs.
		*/

		static function getEventCategories($event) {
			$event = is_array($event) ? sqlescape($event["id"]) : sqlescape($event);
			$categories = array();
			$q = sqlquery("SELECT btx_events_categories.* FROM btx_events_categories JOIN btx_events_event_categories ON btx_events_categories.id = btx_events_event_categories.category WHERE btx_events_event_categories.event = '$event'");
			while ($f = sqlfetch($q)) {
				$categories[] = $f;
			}
			return $categories;
		}

		/*
			Function: getEventCategoryIDs
				Returns an array of category IDs that an event belongs to.

			Parameters:
				event - The event entry or event id.

			Returns:
				An array of IDs.
		*/

		static function getEventCategoryIDs($event) {
			$event = is_array($event) ? sqlescape($event["id"]) : sqlescape($event);
			$categories = array();
			$q = sqlquery("SELECT * FROM btx_events_event_categories WHERE event = '$event'");
			while ($f = sqlfetch($q)) {
				$categories[] = $f["category"];
			}
			return $categories;
		}

		/*
			Function: getEventCategoryList
				Returns a nested category list.
		*/
		
		static function getEventCategoryList($original_list,$parent = 0,$level = "") {
			$list = array();
			$q = sqlquery("SELECT * FROM btx_events_categories WHERE parent = '".sqlescape($parent)."' ORDER BY name");
			while ($f = sqlfetch($q)) {
				$list[$f["id"]] = $level.$f["name"];
				$list = $list + self::getEventCategoryList(false,$f["id"],trim($level)."--- ");
			}
			return $list;
		}

		/*
			Function: getEventInstances
				Returns instances of an event.

			Parameters:
				event - An event array.
				upcoming - Whether to return only upcoming instances (defaults to false)

			Returns:
				An array of event instances

			See Also:
				<getUpcomingEventInstances>
		*/

		static function getEventInstances($event,$upcoming = false) {
			$instances = array();
			$upcoming = $upcoming ? " AND end >= NOW()" : "";

			$q = sqlquery("SELECT * FROM btx_events_date_cache WHERE event = '".sqlescape($event["id"])."' $upcoming ORDER BY start ASC");
			while ($f = sqlfetch($q)) {
				$f["instance"] = $f["id"];
				$instances[] = array_merge($f,$event);
			}

			return $instances;
		}

		/*
			Function: getEventsByDate
				Returns event instances for a given date.
			
			Parameters:
				date - The date (Y-m-d format) to pull events for.
				featured - Whether to pull only featured events or not.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateRange>
		*/
		
		static function getEventsByDate($date,$featured = false) {
			return self::getEventsByDateRange($date,$date,$featured);
		}
		
		
		/*
			Function: getEventsByDateInCategories
				Returns event instances for a given date in given categories.
			
			Parameters:
				date - The date (Y-m-d format) to pull events for.
				categories - An array of categories to get events for.
				featured - Whether to pull only featured events or not.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateRangeInCategories>
		*/
		
		static function getEventsByDateInCategories($date,$categories,$featured = false) {
			return self::getEventsByDateRangeInCategories($date,$date,$categories,$featured);
		}

		/*
			Function: getEventsByDateInCategoriesWithSubcategories
				Returns event instances for a given date in given categories and their subcategories.
			
			Parameters:
				date - The date (Y-m-d format) to pull events for.
				categories - An array of categories to get events for.
				featured - Whether to pull only featured events or not.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateRangeInCategories>
		*/
		
		static function getEventsByDateInCategoriesWithSubcategories($date,$categories,$featured = false) {
			$with_sub = $categories;
			foreach ($categories as $cat) {
				$with_sub = array_merge($with_sub,self::getSubcategoriesOfCategory($cat));
			}
			return self::getEventsByDateInCategories($date,$with_sub,$featured);
		}

		/*
			Function: getEventsByDateInCategory
				Returns event instances for a given date in given category.
			
			Parameters:
				date - The date (Y-m-d format) to pull events for.
				category - A category array or ID.
				featured - Whether to pull only featured events or not.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateRangeInCategories>
		*/
		
		static function getEventsByDateInCategory($date,$category,$featured = false) {
			return self::getEventsByDateRangeInCategories($date,$date,array($category),$featured);
		}

		/*
			Function: getEventsByDateInCategoryWithSubcategories
				Returns event instances for a given date in given category and its subcategories.
			
			Parameters:
				date - The date (Y-m-d format) to pull events for.
				category - A category array or ID.
				featured - Whether to pull only featured events or not.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateRangeInCategoriesWithSubcategories>
		*/
		
		static function getEventsByDateInCategoryWithSubcategories($date,$category,$featured = false) {
			return self::getEventsByDateRangeInCategoriesWithSubcategories($date,$date,array($category),$featured);
		}

		/*
			Function: getEventsByDateRange
				Returns event instances for a given date range.
			
			Parameters:
				start_date - The start date (Y-m-d format) to pull events for.
				end_date - The end date (Y-m-d format) to pull events for.
				featured - Whether to pull only featured events or not.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDate>
		*/
		
		static function getEventsByDateRange($start_date,$end_date,$featured = false) {
			$events = array();
			if ($featured) {
				$featured = " AND btx_events_events.featured = 'on' ";
			}

			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.end >= '$start_date 00:00:00' AND btx_events_date_cache.start <= '$end_date 23:59:59' $featured ORDER BY btx_events_date_cache.start ASC");

			while ($f = sqlfetch($q)) {
				$events[] = self::get($f);
			}
			return $events;
		}

		/*
			Function: getEventsByDateRangeInCategories
				Returns event instances for a given date range in the given categories.
			
			Parameters:
				start_date - The start date (Y-m-d format) to pull events for.
				end_date - The end date (Y-m-d format) to pull events for.
				categories - An array of categories to get events for.
				featured - Whether to pull only featured events or not.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateInCategories>
		*/
		
		static function getEventsByDateRangeInCategories($start_date,$end_date,$categories,$featured = false) {
			$events = array();
			if ($featured) {
				$featured = " AND btx_events_events.featured = 'on' ";
			}

			$cat_search = array();
			foreach ($categories as $category) {
				$category = is_array($category) ? sqlescape($category["id"]) : sqlescape($category);
				$cat_search[] = "btx_events_event_categories.category = '$category'";
			}

			if (!count($cat_search)) {
				return false;
			}
			
			$q = sqlquery("SELECT DISTINCT(CONCAT(btx_events_date_cache.event,btx_events_date_cache.start,btx_events_date_cache.end)),btx_events_date_cache.event,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_date_cache.start,btx_events_date_cache.end FROM btx_events_date_cache JOIN btx_events_event_categories WHERE btx_events_date_cache.event = btx_events_event_categories.event AND btx_events_date_cache.end >= '$start_date 00:00:00' AND btx_events_date_cache.start <= '$end_date 23:59:59' AND (".implode(" OR ",$cat_search).") $featured ORDER BY btx_events_date_cache.start ASC");

			while ($f = sqlfetch($q)) {
				$event = self::get($f["event"]);
				if ($event) {
					$event["start"] = $f["start"];
					$event["end"] = $f["end"];
					$event["instance"] = $f["instance"];
					$event["title_route"] = $f["title_route"];
					$event["date_route"] = $f["date_route"];
					$events[] = $event;
				}
			}
			return $events;
		}

		/*
			Function: getEventsByDateRangeInCategoriesWithSubcategories
				Returns event instances for a given date range in the given categories and their subcategories.
			
			Parameters:
				start_date - The start date (Y-m-d format) to pull events for.
				end_date - The end date (Y-m-d format) to pull events for.
				categories - An array of categories to get events for.
				featured - Whether to pull only featured events or not.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateInCategoriesWithSubcategories>
		*/
		
		static function getEventsByDateRangeInCategoriesWithSubcategories($start_date,$end_date,$categories,$featured = false) {
			$with_sub = $categories;
			foreach ($categories as $cat) {
				$with_sub = array_merge($with_sub,self::getSubcategoriesOfCategory($cat));
			}
			return self::getEventsByDateRangeInCategories($start_date,$end_date,$with_sub,$featured);
		}

		/*
			Function: getEventsByDateRangeInCategory
				Returns event instances for a given date range in the given category.
			
			Parameters:
				start_date - The start date (Y-m-d format) to pull events for.
				end_date - The end date (Y-m-d format) to pull events for.
				category - A category array or category ID.
				featured - Whether to pull only featured events or not.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateInCategories>
		*/
		
		static function getEventsByDateRangeInCategory($start_date,$end_date,$category,$featured = false) {
			return self::getEventsByDateRangeInCategories($start_date,$end_date,array($category),$featured);
		}

		/*
			Function: getEventsByDateRangeInCategoryWithSubcategories
				Returns event instances for a given date range in the given category and its subcategories.
			
			Parameters:
				start_date - The start date (Y-m-d format) to pull events for.
				end_date - The end date (Y-m-d format) to pull events for.
				category - A category array or category ID.
				featured - Whether to pull only featured events or not.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateInCategoryWithSubcategories>
		*/
		
		static function getEventsByDateRangeInCategoryWithSubcategories($start_date,$end_date,$category,$featured = false) {
			return self::getEventsByDateRangeInCategoriesWithSubcategories($start_date,$end_date,array($category),$featured);
		}

		/*
			Function: getFeaturedEventsByDate
				Returns featured event instances for a given date.
			
			Parameters:
				date - The date (Y-m-d format) to pull events for.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDate>
		*/

		static function getFeaturedEventsByDate($date) {
			return self::getEventsByDate($date,true);
		}

		/*
			Function: getFeaturedEventsByDateRange
				Returns featured event instances for a given date range.
			
			Parameters:
				start_date - The start date (Y-m-d format) to pull events for.
				end_date - The end date (Y-m-d format) to pull events for.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateRange>
		*/

		static function getFeaturedEventsByDateRange($start_date,$end_date) {
			return self::getEventsByDateRange($start_date,$end_date,true);
		}

		/*
			Function: getFeaturedEventsByDateRangeInCategories
				Returns featured event instances for a given date range in the provided categories.
			
			Parameters:
				start_date - The start date (Y-m-d format) to pull events for.
				end_date - The end date (Y-m-d format) to pull events for.
				categories - An array of categories to get events for.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateRangeInCategories>
		*/

		static function getFeaturedEventsByDateRangeInCategories($start_date,$end_date,$categories) {
			return self::getEventsByDateRangeInCategories($start_date,$end_date,$categories,true);
		}

		/*
			Function: getFeaturedEventsByDateRangeInCategoriesWithSubcategories
				Returns featured event instances for a given date range in the provided categories and their subcategories.
			
			Parameters:
				start_date - The start date (Y-m-d format) to pull events for.
				end_date - The end date (Y-m-d format) to pull events for.
				categories - An array of categories to get events for.
			
			Returns:
				An array of event instances.
			
			See Also:
				<getEventsByDateRangeInCategoriesWithSubcategories>
		*/

		static function getFeaturedEventsByDateRangeInCategoriesWithSubcategories($start_date,$end_date,$categories) {
			return self::getEventsByDateRangeInCategoriesWithSubcategories($start_date,$end_date,$categories,true);
		}

		/*
			Function: getFeaturedSearchResultsInDateRange
				Returns featured event instances matching a given query in a specified date range.
			
			Parameters:
				query - The string to search for.
				start_date - Beginning date (Y-m-d).
				end_date - Ending date (Y-m-d).
			
			Returns:
				An array of event instances ordered by soonest.

			See Also:
				<getSearchResultsInDateRange>
		*/

		static function getFeaturedSearchResultsInDateRange($query,$start_date,$end_date) {
			return self::getSearchResultsInDateRange($query,$start_date,$end_date,true);
		}
		
		/*
			Function: getInstance
				Returns an instance of an event (combined date cache and event entry).
			
			Parameters:
				id - The id of the event instance.
			
			Returns:
				An event array with its fields decoded.
		*/
		
		static function getInstance($id) {
			return self::get(sqlfetch(sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.id = '$id'")));
		}
		
		/*
			Function: getInstanceByRoute
				Returns an instance of an event (combined date cache and event entry).
			
			Parameters:
				title_route - The title route of the event instance.
				date_route - The date route of the event instance.
			
			Returns:
				An event array with its fields decoded.
		*/
		
		static function getInstanceByRoute($title_route,$date_route) {
			return self::get(sqlfetch(sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.title_route = '".sqlescape($title_route)."' AND btx_events_date_cache.date_route = '".sqlescape($date_route)."'")));
		
		}

		/*
			Function: getKeyedEventsDateRangeForEvents
				Returns an array of days as keys with the events that fall in each day as an array.
			
			Parameters:
				events - An array of event instances.
			
			Returns:
				A keyed array (dates are keys, array of events are vals) for the events passed in.
		*/
		
		static function getKeyedDateRangeForEvents($events) {
			$days = array();
			foreach ($events as $event) {
				$days[date("Y-m-d",strtotime($event["start"]))][] = $event;
			}
			return $days;
		}

		/*
			Function: getNumberOfEventsOnDate
				Returns number of events occurring on a given date.
			
			Parameters:
				date - The date (Y-m-d format) to pull events for.
			
			Returns:
				A number.
		*/
		
		static function getNumberOfEventsOnDate($date) {
			$date = date("Y-m-d",strtotime($date));
			$f = sqlfetch(sqlquery("SELECT COUNT(id) AS `count` FROM btx_events_date_cache WHERE start >= '$date 00:00:00' AND end <= '$date 23:59:59'"));
			return $f["count"];
		}

		/*
			Function: getRandomEvent
				Returns a random event instance occurring in the future.
			
			Parameters:
				featured - Whether to return a featured event or not.
			
			Returns:
				An event instance.
			
			See Also:
				<getRandomEventByDate>
		*/
		
		static function getRandomEvent($featured = false) {
			if ($featured) {
				$featured = " AND btx_events_events.featured = 'on' ";
			}

			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.end >= NOW() $featured ORDER BY RAND() LIMIT 1");

			return self::get(sqlfetch($q));
		}
		
		/*
			Function: getRandomEventByDate
				Returns a random event instance occurring on a specific date.
			
			Parameters:
				date - The date to pull an event for.
			
			Returns:
				An event instance.
			
			See Also:
				<getSingleEventByDate>
		*/

		static function getRandomEventByDate($date) {
			return self::getSingleEventByDate($date,"","RAND()");
		}

		/*
			Function: getRandomFeaturedEvent
				Returns a random featured event instance occurring in the future.
				
			Returns:
				An event instance.
			
			See Also:
				<getRandomEvent>
		*/
		
		static function getRandomFeaturedEvent() {
			return self::getRandomEvent(true);
		}
		
		/*
			Function: getRandomFeaturedEventByDate
				Returns a random featured event instance occurring on a specific date.
			
			Parameters:
				date - The date to pull an event for.
			
			Returns:
				An event instance.
			
			See Also:
				<getRandomEventByDate>
		*/

		static function getRandomFeaturedEventByDate($date) {
			return self::getSingleEventByDate($date,true,"RAND()");
		}

		/*
			Function: getSearchResultsInDateRange
				Returns event instances matching a given query in a specified date range.
			
			Parameters:
				query - The string to search for.
				start_date - Beginning date (Y-m-d).
				end_date - Ending date (Y-m-d).
				featured - Whether to only return featured or not (defaults to false for all).
			
			Returns:
				An array of event instances ordered by soonest.
		*/

		static function getSearchResultsInDateRange($query,$start_date,$end_date,$featured = false) {
			$events = array();
			$words = explode(" ",$query);
			$qwords = array();

			if ($featured) {
				$featured = " AND btx_events_events.featured = 'on' ";
			}

			if ($words) {
				foreach ($words as $word) {
					$word = sqlescape($word);
					$qwords[] = "(btx_events_events.title LIKE '%$word%' OR btx_events_events.description LIKE '%$word%')";
				}
				$qwords = implode(" AND ",$qwords)." AND ";
			} else {
				$qwords = "";
			}

			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND $qwords btx_events_date_cache.end >= '$start_date 00:00:00' AND btx_events_date_cache.start <= '$end_date 23:59:59' $featured ORDER BY btx_events_date_cache.start ASC");

			while ($f = sqlfetch($q)) {
				$event = self::get($f);
				$events[] = $event;
			}
			return $events;
		}

		/*
			Function: getSingleEventByDate
				Returns a single event for a given date.

			Parameters:
				date - The date to check for events (in Y-m-d format).
				featured - Whether to limit the search exclusively to featured events.
				sort - The sort to go by when picking the first event (defaults to most recently created)

			Returns:
				An event instance.
		*/

		static function getSingleEventByDate($date,$featured = false,$sort = "id DESC") {
			if ($featured) {
				$featured = " AND btx_events_events.featured = 'on' ";
			}

			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.start <= '$date 23:59:59' AND btx_events_date_cache.end >= '$date 00:00:00' $featured ORDER BY $sort LIMIT 1");

			return self::get(sqlfetch($q));
		}

		/*
			Function: getSingleFeaturedEventByDate
				Returns a single featured event for a given date.

			Parameters:
				date - The date to check for events (in Y-m-d format).
				sort - The sort to go by when picking the first event (defaults to most recently created)

			Returns:
				An event instance.
		*/

		static function getSingleFeaturedEventByDate($date,$sort = "id DESC") {
			return self::getSingleEventByDate($date,true,$sort);
		}

		/*
			Function: getSubcategoriesOfCategory
				Returns all the subcategories (and their subcategories) of a given category.
			
			Parameters:
				category - Either a category id or array.
			
			Returns:
				An array of categories.
		*/
		
		static function getSubcategoriesOfCategory($category) {
			$category = is_array($category) ? sqlescape($category["id"]) : sqlescape($category);
			$categories = array();
			$q = sqlquery("SELECT * FROM btx_events_categories WHERE parent = '$category'");
			while ($f = sqlfetch($q)) {
				$categories[] = $f;
				$categories = array_merge($categories,self::getSubcategoriesOfCategory($f));
			}
			return $categories;
		}

		/*
			Function: getUpcomingEventInstances
				Returns instances of an event that occur in the future.

			Parameters:
				event - An event ID or event array.

			Returns:
				An array of event instances

			See Also:
				<getEventInstances>
		*/

		static function getUpcomingEventInstances($event) {
			return static::getEventInstances($event,true);
		}

		/*
			Function: getUpcomingEvents
				Returns an array of event instances occurring in the future ordered by those happening soonest.

			Parameters:
				limit - The number of events to return.
				featured - Whether to limit the results to exclusively featured events or not (defaults to false).
				page - The page to return (defaults to the first page).

			Returns:
				An array of event instances.
		*/

		static function getUpcomingEvents($limit = 5,$featured = false,$page = 1) {
			$page = $page ? ($page - 1) : 0;
			$events = array();
			if ($featured) {
				$featured = " AND btx_events_events.featured = 'on' ";
			}

			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND btx_events_date_cache.end >= NOW() $featured ORDER BY btx_events_date_cache.start ASC LIMIT ".($page * $limit).",$limit");
			while ($f = sqlfetch($q)) {
				$event = self::get($f);
				$events[] = $event;
			}
			return $events;
		}

		/*
			Function: getUpcomingEventsPageCount
				Returns the number of pages of upcoming events.

			Parameters:
				per_page - The number of events per page.

			Returns:
				The number of pages.
		*/

		static function getUpcomingEventsPageCount($per_page = 5) {
			$f = sqlfetch(sqlquery("SELECT COUNT(id) AS `count` FROM btx_events_date_cache WHERE end >= NOW()"));
			$pages = ceil($f["count"] / $per_page);
			return $pages ? $pages : 1;
		}

		/*
			Function: getUpcomingFeaturedEvents
				Returns an array of featured event instances occurring in the future ordered by those happening soonest.

			Parameters:
				limit - The number of events to return.
				page - The page to return (defaults to the first page).

			Returns:
				An array of event instances.

			See Also:
				<getUpcomingEvents>
		*/

		static function getUpcomingFeaturedEvents($limit = 5,$page = 1) {
			return self::getUpcomingEvents($limit,true,$page);
		}

		/*
			Function: getUpcomingEventsInCategories
				Returns an array of event instances occurring in the future in the provided categories ordered by those happening soonest.

			Parameters:
				limit - The number of events to return.
				categories - An array of categories.
				featured - Whether to limit the results to exclusively featured events or not (defaults to false).
				page - The page to return (defaults to the first page).

			Returns:
				An array of event instances.
		*/

		static function getUpcomingEventsInCategories($limit = 5,$categories = array(),$featured = false,$page = 1) {
			$page = $page ? ($page - 1) : 0;
			$events = array();
			if ($featured) {
				$featured = " AND btx_events_events.featured = 'on' ";
			}

			$cat_search = array();
			foreach ($categories as $category) {
				$category = is_array($category) ? sqlescape($category["id"]) : sqlescape($category);
				$cat_search[] = "btx_events_event_categories.category = '$category'";
			}

			$q = sqlquery("SELECT DISTINCT(CONCAT(btx_events_date_cache.event,btx_events_date_cache.start,btx_events_date_cache.end)),btx_events_date_cache.event,btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route FROM btx_events_date_cache JOIN btx_events_event_categories JOIN btx_events_events ON btx_events_events.id = btx_events_date_cache.event WHERE btx_events_date_cache.event = btx_events_event_categories.event AND btx_events_date_cache.end >= NOW() AND (".implode(" OR ",$cat_search).") $featured ORDER BY btx_events_date_cache.start ASC LIMIT ".($page * $limit).",$limit");

			while ($f = sqlfetch($q)) {
				$event = self::get($f["event"]);
				if ($event) {
					$event["start"] = $f["start"];
					$event["end"] = $f["end"];
					$event["instance"] = $f["instance"];
					$event["title_route"] = $f["title_route"];
					$event["date_route"] = $f["date_route"];
					$events[] = $event;
				}
			}
			return $events;
		}

		/*
			Function: getUpcomingEventsInCategoriesWithSubcategories
				Returns an array of event instances occurring in the future in the provided categories and all of their children ordered by those happening soonest.

			Parameters:
				limit - The number of events to return.
				categories - An array of categories.
				featured - Whether to limit the results to exclusively featured events or not (defaults to false).
				page - The page to return (defaults to the first page).

			Returns:
				An array of event instances.
		*/
		static function getUpcomingEventsInCategoriesWithSubcategories($limit,$categories = array(),$featured = false,$page = 1) {
			$with_sub = $categories;
			foreach ($categories as $cat) {
				$with_sub = array_merge($with_sub,self::getSubcategoriesOfCategory($cat));
			}
			return self::getUpcomingEventsInCategories($limit,$with_sub,$featured,$page);
		}

		/*
			Function: getUpcomingFeaturedEventsInCategories
				Returns an array of featured event instances occurring in the future in the provided categories ordered by those happening soonest.

			Parameters:
				limit - The number of events to return.
				categories - An array of categories.
				page - The page to return (defaults to the first page).

			Returns:
				An array of event instances.

			See Also:
				<getUpcomingEventsInCategories>
		*/

		static function getUpcomingFeaturedEventsInCategories($limit = 5,$categories = array(),$page = 1) {
			return self::getUpcomingEventsInCategories($limit,$categories,true,$page);
		}

		/*
			Function: getUpcomingFeaturedEventsInCategoriesWithSubcategories
				Returns an array of featured event instances occurring in the future in the provided categories and their subcategories ordered by those happening soonest.

			Parameters:
				limit - The number of events to return.
				categories - An array of categories.
				page - The page to return (defaults to the first page).

			Returns:
				An array of event instances.

			See Also:
				<getUpcomingEventsInCategories>
		*/

		static function getUpcomingFeaturedEventsInCategoriesWithSubcategories($limit = 5,$categories = array(),$page = 1) {
			return self::getUpcomingEventsInCategoriesWithSubcategories($limit,$categories,true,$page);
		}

		/*
			Function: getUpcomingSearchResults
				Returns event instances matching a given query that are occurring in the future.
			
			Parameters:
				query - The string to search for.
				limit - The number of event instances to return.
				featured - Whether to only return featured or not (defaults to false for all).
			
			Returns:
				An array of event instances ordered by soonest.
		*/

		static function getUpcomingSearchResults($query, $limit = 5, $featured = false) {
			$events = array();
			$qwords = array();
			$words = explode(" ",$query);

			if ($featured) {
				$featured = " AND btx_events_events.featured = 'on' ";
			}

			if ($words) {
				foreach ($words as $word) {
					$word = sqlescape($word);
					$qwords[] = "(btx_events_events.title LIKE '%$word%' OR btx_events_events.description LIKE '%$word%')";
				}

				$qwords = implode(" AND ",$qwords)." AND ";
			} else {
				$qwords = "";
			}

			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND $qwords btx_events_date_cache.end >= NOW() $featured ORDER BY btx_events_date_cache.start ASC LIMIT $limit");

			while ($f = sqlfetch($q)) {
				$event = self::get($f);
				$events[] = $event;
			}
			return $events;
		}

		/*
			Function: getUpcomingSearchResultsPage
				Returns a page of event instances matching a given query that are occurring in the future.
			
			Parameters:
				query - The string to search for.
				page - The page to return
				per_page - The number of event instances to return per page.
			
			Returns:
				An array of event instances ordered by soonest.
		*/

		static function getUpcomingSearchResultsPage($query, $page = 1, $per_page = 10) {
			$events = array();
			$words = explode(" ",$query);
			$qwords = array();

			if ($words) {
				foreach ($words as $word) {
					$word = sqlescape($word);
					$qwords[] = "(btx_events_events.title LIKE '%$word%' OR btx_events_events.description LIKE '%$word%')";
				}

				$qwords = implode(" AND ",$qwords)." AND ";
			} else {
				$qwords = "";
			}

			$limit = (($page - 1) * $per_page).", $per_page";

			$q = sqlquery("SELECT btx_events_date_cache.start,btx_events_date_cache.end,btx_events_date_cache.id as instance,btx_events_date_cache.title_route AS title_route, btx_events_date_cache.date_route AS date_route,btx_events_events.* FROM btx_events_events JOIN btx_events_date_cache WHERE btx_events_date_cache.event = btx_events_events.id AND $qwords btx_events_date_cache.end >= NOW() ORDER BY btx_events_date_cache.start ASC LIMIT $limit");

			while ($f = sqlfetch($q)) {
				$event = self::get($f);
				$events[] = $event;
			}

			return $events;
		}

		/*
			Function: getUpcomingSearchResultsPage
				Returns the number of pages of event instances matching a given query that are occurring in the future.
			
			Parameters:
				query - The string to search for.
				per_page - The number of event instances per page.
			
			Returns:
				An integer.
		*/

		static function getUpcomingSearchResultsPageCount($query, $per_page = 10) {
			$events = array();
			$words = explode(" ",$query);
			$qwords = array();

			if ($words) {
				foreach ($words as $word) {
					$word = sqlescape($word);
					$qwords[] = "(btx_events_events.title LIKE '%$word%' OR btx_events_events.description LIKE '%$word%')";
				}
				
				$qwords = implode(" AND ",$qwords)." AND ";
			} else {
				$qwords = "";
			}

			$limit = (($page - 1) * $per_page).", $per_page";

			$f = sqlfetch(sqlquery("SELECT COUNT(btx_events_date_cache.id) AS `count`
									FROM btx_events_events JOIN btx_events_date_cache
									ON btx_events_date_cache.event = btx_events_events.id
									WHERE $qwords btx_events_date_cache.end >= NOW()"));

			$pages = ceil($f["count"] / $per_page);

			return $pages ?: 1;
		}

		/*
			Function: getUpcomingFeaturedSearchResults
				Returns featured event instances matching a given query that are occurring in the future.
			
			Parameters:
				query - The string to search for.
				limit - The number of event instances to return.
			
			Returns:
				An array of event instances ordered by soonest.
		*/

		static function getUpcomingFeaturedSearchResults($query,$limit = 5) {
			return self::getUpcomingSearchResults($query,$limit,true);
		}

		/*
			Function: recacheEvent
				Removes cached occurrences and then caches the event again.
			
			Parameters:
				id - The event id.
		*/

		static function recacheEvent($id) {
			self::uncacheEvent($id);
			self::cacheEvent($id);
		}

		/*
			Function: searchResults
				Returns event entries that match a given query (not instances).
			
			Parameters:
				query - The string to search for.
			
			Returns:
				An array of decoded event entries from the database.
		*/

		static function searchResults($query) {
			$words = explode(" ",$query);
			$qwords = array();

			if ($words) {
				foreach ($words as $word) {
					$word = sqlescape($word);
					$qwords[] = "(title LIKE '%$word%' OR description LIKE '%$word%')";
				}

				$qwords = " AND ".implode(" AND ",$qwords);
			} else {
				$qwords = "";
			}

			$q = sqlquery("SELECT * FROM btx_events_events WHERE 1 $qwords ORDER BY id DESC");

			$events = array();

			while ($f = sqlfetch($q)) {
				$event = self::get($f);
				$events[] = $event;
			}

			return $events;
		}

		/*
			Function: searchResultsInCategory
				Returns event entries that match a given query (not instances) that belong to a category (or one of its subcategories).
			
			Parameters:
				query - The string to search for.
				category - The category to check against.
			
			Returns:
				An array of decoded event entries from the database.
		*/

		static function searchResultsInCategory($query, $category) {
			$category = is_array($category) ? sqlescape($category["id"]) : sqlescape($category);
			$with_sub = array_merge(array($category),self::getSubcategoriesOfCategory($category));
			$cat_search = array();

			foreach ($with_sub as $category) {
				$cat_search[] = "btx_events_event_categories.category = '$category'";
			}

			$words = explode(" ",$query);
			$qwords = array();

			if ($words) {
				foreach ($words as $word) {
					$word = sqlescape($word);
					$qwords[] = "(btx_events_events.title LIKE '%$word%' OR btx_events_events.description LIKE '%$word%')";
				}

				$qwords = " AND ".implode(" AND ",$qwords);
			} else {
				$qwords = "";
			}

			$q = sqlquery("SELECT DISTINCT(btx_events_event_categories.event),btx_events_events.* FROM btx_events_events JOIN btx_events_event_categories WHERE btx_events_events.id = btx_events_event_categories.event $qwords AND (".implode(" OR ",$cat_search).") ORDER BY id DESC");

			$events = array();

			while ($f = sqlfetch($q)) {
				$event = self::get($f);
				$events[] = $event;
			}

			return $events;
		}
		
		/*
			Function: uncacheEvent
				Removes cached occurrences of an event.
			
			Parameters:
				id - The event id.
		*/

		static function uncacheEvent($id) {
			$id = sqlescape($id);
			sqlquery("DELETE FROM btx_events_date_cache WHERE event = '$id'");
		}

		/*
			Function: parseMTM
				Helper function for the admin to create sensible category tagging.
		*/
		
		static function parseMTM($list,$everything = false) {
			if (!$everything) {
				return $list;
			}
			$parsed = array();
			foreach ($list as $id => $name) {
				$ancestors = self::getCategoryLineage($id);
				$path = array();
				foreach ($ancestors as $a) {
					$path[] = $a["name"];
				}
				$path[] = $name;
				$parsed[$id] = implode(" » ",$path);
			}
			asort($parsed);
			return $parsed;
		}
	}
	
/* Search Events */
#btx_events_search .select { margin: 0 10px 0 0; }
#btx_events_search .date_range_spacer { float: left; height: 32px; line-height: 32px; margin: 0 10px 0 0; }

#btx_events_search_results div.table { border-radius: 2px; box-shadow: none; margin: 0; }
#btx_events_search_results div.table > header { margin-top: 0; }
#btx_events_search_results .table .view_action span { margin: 5px 0 0 0; }
.table .btx_events_column_date { width: 65px; }
.table .btx_events_column_title { width: 665px; }

.btx_events_categories_table > section { padding: 0; }
.btx_events_categories_table section ul { width: 958px; }
.btx_events_categories_table li > span { float: left; height: 32px; text-align: center; }
.btx_events_categories_table .btx_events_category_name { display: block; float: left; font-size: 15px; height: 32px; line-height: 32px; padding: 0 0 0 15px; text-align: left; width: 833px; }
.btx_events_categories_table .depth_2 .btx_events_category_name { width: 823px; }
.btx_events_categories_table .depth_3 .btx_events_category_name { width: 813px; }
.btx_events_categories_table .depth_4 .btx_events_category_name { width: 803px; }
.btx_events_categories_table .depth_5 .btx_events_category_name { width: 793px; }
.btx_events_categories_table .depth_6 .btx_events_category_name { width: 783px; }
.btx_events_categories_table .depth_7 .btx_events_category_name { width: 773px; }
.btx_events_categories_table .depth_8 .btx_events_category_name { width: 763px; }
.btx_events_categories_table .depth_9 .btx_events_category_name { width: 753px; }
.btx_events_categories_table .depth_10 .btx_events_category_name { width: 743px; }

/* Calendar */
#btx_events_calendar { border: 1px solid #CCC; border-radius: 2px; overflow: hidden; }
#btx_events_calendar a { color: #333; }
#btx_events_calendar header { background: #EEE; border-bottom: 1px solid #CCC; border-radius: 2px 2px 0 0; height: 50px; overflow: hidden; }
#btx_events_calendar header .today { background: #FAFAFA; border: 1px solid #CCC; border-radius: 2px; float: left; font-size: 10px; height: 30px; line-height: 30px; margin: 9px 0 0 10px; padding: 0 15px; text-align: center; }
#btx_events_calendar header .today:hover { background: #FFF; }
#btx_events_calendar header nav { margin: 18px 0 0 347px; position: absolute; }
#btx_events_calendar header nav * { display: block; float: left; }
#btx_events_calendar header nav span { color: #333; font-size: 14px; text-align: center; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8); width: 240px; }
#btx_events_calendar header a.previous span { background: url("../../../images/icon-sprite.svg") no-repeat scroll -137px -116px transparent; display: block; height: 9px; overflow: hidden; text-indent: -1000em; width: 6px; }
#btx_events_calendar header a.next span { background: url("../../../images/icon-sprite.svg") no-repeat scroll -137px -125px transparent; display: block; height: 9px; overflow: hidden; text-indent: -1000em; width: 6px; }
#btx_events_calendar header a.previous, #btx_events_calendar header a.next { height: 9px; padding: 3px 5px; width: 6px; }

#btx_events_calendar summary { background: #FFF; border-bottom: 1px solid #CCC; overflow: hidden; }
#btx_events_calendar summary section { display: block; float: left; font-size: 12px; padding: 10px 0; text-align: center; width: 136px; }

#btx_events_calendar .dates { background: #FCFCFC; margin: 0; overflow: hidden; }
#btx_events_calendar .dates li { border: solid 1px #DDD; border-left: none; border-top: none; display: block; float: left; height: 116px; padding: 10px; width: 116px; }
#btx_events_calendar .dates li.saturday { border-right: none; }
#btx_events_calendar .dates li.last_row { border-bottom: none; }
#btx_events_calendar .dates li a { color: #666; }
#btx_events_calendar .dates li span { float: right; font-size: 11px; }
#btx_events_calendar .dates li .not_active_month { color: #CCC; }
#btx_events_calendar .dates li .search { display: block; font-size: 10px; line-height: 16px; margin-top: 12px; padding-left: 20px; }
#btx_events_calendar .dates .active { background: #F4F4F4; }
#btx_events_calendar .dates li ul { display: block; margin: 18px 0 0 0; overflow: hidden; }
#btx_events_calendar .dates li ul li { border: none; display: block; font-size: 10px; height: 12px; line-height: 12px; margin: 8px 0 0 0; overflow: hidden; padding: 0; text-overflow: ellipsis; white-space: nowrap; width: 116px; }
#btx_events_calendar .dates li ul li a:hover { background: #F3F3F3; border-radius: 3px; color: #333; margin: -3px 0 0 -6px; padding: 3px 6px; position: absolute; z-index: 500; }
<?php
	$bigtree["datepickers"][] = "field_start_date";
	$bigtree["datepickers"][] = "field_end_date";
	$bigtree["datepickers"][] = "field_recurring_end_date";
	$bigtree["timepickers"][] = "field_start_time";
	$bigtree["timepickers"][] = "field_end_time";
	if (!in_array("events.js",$bigtree["js"])) {
		$bigtree["js"][] = "events.js";
	}


	// Format Start Date w/ Chosen Date Format
	if ($bigtree["entry"]["start_date"] && $bigtree["entry"]["start_date"] != "0000-00-00") {
		$start_date = date($bigtree["config"]["date_format"],strtotime($bigtree["entry"]["start_date"]));
	} else {
		$start_date = "";
	}

	// Format End Date w/ Chosen Date Format
	if ($bigtree["entry"]["end_date"] && $bigtree["entry"]["end_date"] != "0000-00-00") {
		$end_date = date($bigtree["config"]["date_format"],strtotime($bigtree["entry"]["end_date"]));
	} else {
		$end_date = "";
	}
?>
<div class="sub_section" style="margin-bottom: -30px; margin-top: 30px;">
	<div style="float: left; margin: 0 15px 0 0;">
		<fieldset>
			<label for="field_start_date">Start Date</label>
			<input type="text" class="date_picker" id="field_start_date" autocomplete="off" value="<?=$start_date?>" name="start_date" tabindex="7" />
			<span class="icon_small icon_small_calendar date_picker_icon"></span>
		</fieldset>
		<fieldset>
			<label for="field_start_time">Start Time</label>
			<input type="text" class="time_picker" id="field_start_time" autocomplete="off" value="<?php if ($bigtree["entry"]["start_time"]) { echo date("g:ia",strtotime($bigtree["entry"]["start_time"])); } ?>" name="start_time" tabindex="9" <?php if ($bigtree["entry"]["all_day"]) { ?>disabled="disabled" <?php } ?>/>
			<span class="icon_small icon_small_clock time_picker_icon"></span>
		</fieldset>
	</div>
	<div style="float: left; margin: 0 15px 15px 0;">
		<fieldset>
			<label for="field_end_date">End Date</label>
			<input type="text" class="date_picker" id="field_end_date" autocomplete="off" value="<?=$end_date?>" name="end_date" tabindex="8" />
			<span class="icon_small icon_small_calendar date_picker_icon"></span>
		</fieldset>
		<fieldset>
			<label for="field_end_time">End Time</label>
			<input type="text" class="time_picker" id="field_end_time" autocomplete="off" value="<?php if ($bigtree["entry"]["end_time"]) { echo date("g:ia",strtotime($bigtree["entry"]["end_time"])); } ?>" name="end_time" tabindex="10" <?php if ($bigtree["entry"]["all_day"]) { ?>disabled="disabled" <?php } ?>/>
			<span class="icon_small icon_small_clock time_picker_icon"></span>
		</fieldset>
	</div>
	<br class="clear" />
	<fieldset>
		<input type="checkbox" id="field_all_day" tabindex="11" name="all_day" <?php if ($bigtree["entry"]["all_day"]) { ?>checked="checked" <?php } ?>/> <label class="for_checkbox" for="field_all_day">All Day</label>
	</fieldset>
	<fieldset style="float: left; margin: 0 15px 0 0;">
		<label for="field_recurrence_type">Reoccur</label>
		<select name="recurrence_type" id="field_recurrence_type" tabindex="12">
			<option value="">Never</option>
			<option value="daily"<?php if ($bigtree["entry"]["recurrence_type"] == "daily") { ?> selected="selected"<?php } ?>>Daily</option>
			<option value="weekly"<?php if ($bigtree["entry"]["recurrence_type"] == "weekly") { ?> selected="selected"<?php } ?>>Weekly</option>
			<option value="monthly"<?php if ($bigtree["entry"]["recurrence_type"] == "monthly") { ?> selected="selected"<?php } ?>>Monthly</option>
			<option value="yearly"<?php if ($bigtree["entry"]["recurrence_type"] == "yearly") { ?> selected="selected"<?php } ?>>Yearly</option>
		</select>
	</fieldset>
	<fieldset style="float: left; margin: 0 15px 0 0;<?php if (!$bigtree["entry"]["recurrence_type"] || $bigtree["entry"]["recurrence_type"] == "daily") { ?> display: none;<?php } ?>" id="recurrence_detail_area">
		<input type="hidden" name="recurrence_detail" value="<?=$bigtree["entry"]["recurrence_detail"]?>" />
	</fieldset>
	<fieldset style="float: left; margin: 0;<?php if (!$bigtree["entry"]["recurrence_type"]) { ?> display: none;<?php } ?>" id="recurrence_end_area">
		<label>Until</label>
		<input type="text" class="date_picker" id="field_recurring_end_date" autocomplete="off" value="<?php if ($bigtree["entry"]["recurring_end_date"]) { echo date("m/d/Y",strtotime($bigtree["entry"]["recurring_end_date"])); } ?>" name="recurring_end_date" />
		<span class="icon_small icon_small_calendar date_picker_icon"></span>
	</fieldset>
	<br class="clear" />
</div>
<?php
	// Start date / end date
	$raw_start = BigTree::dateFormat($bigtree["post_data"]["start_date"],"U");
	$raw_end = BigTree::dateFormat($bigtree["post_data"]["end_date"],"U");

	$bigtree["entry"]["start_date"] = $raw_start ? date("Y-m-d",$raw_start) : "";
	$bigtree["entry"]["end_date"] = ($raw_end && $raw_end > $raw_start) ? date("Y-m-d",$raw_end) : "";

	if ($raw_end && $raw_end < $raw_start) {
		$bigtree["errors"][] = array("field" => "End Date", "error" => "The end date you entered was earlier than the start date.");
	}

	// Start time / end time / all day
	if ($bigtree["post_data"]["all_day"] || !$bigtree["post_data"]["start_time"]) {
		$bigtree["entry"]["start_time"] = "";
		$bigtree["entry"]["end_time"] = "";
		$bigtree["entry"]["all_day"] = "on";
	} else {
		$bigtree["entry"]["all_day"] = "";
		$bigtree["entry"]["start_time"] = date("H:i:s",strtotime($bigtree["post_data"]["start_time"]));
		
		if ($bigtree["post_data"]["end_time"]) {
			// If the end time is before the start time and the event doesn't span multiple days, kill the end time.
			if (strtotime($bigtree["post_data"]["end_time"]) < strtotime($bigtree["post_data"]["start_time"]) && !$bigtree["entry"]["end_date"]) {
				$bigtree["entry"]["end_time"] = "";
				$bigtree["errors"][] = array("field" => "End Time", "error" => "The end time you entered was earlier than the start time.");
			} else {
				$bigtree["entry"]["end_time"] = date("H:i:s",strtotime($bigtree["post_data"]["end_time"]));
			}
		} else {
			$bigtree["entry"]["end_time"] = "";
		}
	}
	
	if ($bigtree["post_data"]["recurrence_type"]) {
		$bigtree["entry"]["recurrence_type"] = $bigtree["post_data"]["recurrence_type"];
		$bigtree["entry"]["recurrence_detail"] = $bigtree["post_data"]["recurrence_detail"];

		$raw_recurring_end = BigTree::dateFormat($bigtree["post_data"]["recurring_end_date"],"U");
		
		if ($raw_recurring_end) {
			if ($raw_recurring_end > $raw_start) {
				$bigtree["entry"]["recurring_end_date"] = date("Y-m-d",$raw_recurring_end);
			} else {
				$bigtree["entry"]["recurring_end_date"] = "";
				$bigtree["errors"][] = array("field" => "Recurring End Date", "error" => "The recurring end date you entered was earlier than the start date.");
			}
		}
	} else {
		$bigtree["entry"]["recurrence_type"] = "";
		$bigtree["entry"]["recurrence_detail"] = "";
		$bigtree["entry"]["recurring_end_date"] = "";
		$bigtree["entry"]["canceled_recurrences"] = "";
	}
	
	$field["ignore"] = true;

	BigTreeAutoModule::clearCache("btx_events_date_cache");
$(document).ready(function() {

	var $CalendarData = $("#btx_events_calendar_data");
	var $RecurrenceDetailArea = $("#recurrence_detail_area");
	var $RecurrenceEndArea = $("#recurrence_end_area");
	var $RecurrenceType = $("#field_recurrence_type");
	var $SearchResults = $("#btx_events_search_results");

	// Search
	$("#btx_events_search").submit(function(ev) {
		ev.preventDefault();
		$SearchResults.html('<img src="admin_root/images/spinner.gif" alt="" /> Querying Events...').parent().show();
		$SearchResults.load("admin_root/*/com.fastspot.events/ajax/search/", $(this).serializeJSON())
	});

	// Calendar
	$CalendarData.on("click", ".month_button", function(ev) {
		ev.preventDefault();
		$CalendarData.load("admin_root/*/com.fastspot.events/ajax/month/?module_root=" + escape(BTXEventsModuleRoot) + "&month=" + $(this).attr("href").substr(1));
	});

	// Events Forms
	$RecurrenceType.change(updateRecurrenceType);
	if ($RecurrenceType.length) {
		updateRecurrenceType("", "", $RecurrenceDetailArea.find("input").val());
	}
	
	$("#field_all_day").change(function() {
		if ($(this).prop("checked")) {
			$("#field_start_time").prop("disabled",true);
			$("#field_end_time").prop("disabled",true);
		} else {
			$("#field_start_time").prop("disabled",false);
			$("#field_end_time").prop("disabled",false);
		}
	});

	function updateRecurrenceType(ev, element, existing_val) {
		var val = $RecurrenceType.val();
		if (!val) {
			$RecurrenceDetailArea.hide();
			$RecurrenceEndArea.hide();
		} else {
			if (val == "daily") {
				$RecurrenceDetailArea.hide();
			} else if (val == "weekly") {
				$RecurrenceDetailArea.html('<label>On</label><select name="recurrence_detail" id="recurrence_dd"><option value="0">Sunday</option><option value="1">Monday</option><option value="2">Tuesday</option><option value="3">Wednesday</option><option value="4">Thursday</option><option value="5">Friday</option><option value="6">Saturday</option></select>').show();
			} else if (val == "monthly") {
				$RecurrenceDetailArea.html('<label>On</label><select name="recurrence_detail" id="recurrence_dd"><option value="1">The 1st day of the month</option><option value="2">The 2nd day of the month</option><option value="3">The 3rd day of the month</option><option value="4">The 4th day of the month</option><option value="5">The 5th day of the month</option><option value="6">The 6th day of the month</option><option value="7">The 7th day of the month</option><option value="8">The 8th day of the month</option><option value="9">The 9th day of the month</option><option value="10">The 10th day of the month</option><option value="11">The 11th day of the month</option><option value="12">The 12th day of the month</option><option value="13">The 13th day of the month</option><option value="14">The 14th day of the month</option><option value="15">The 15th day of the month</option><option value="16">The 16th day of the month</option><option value="17">The 17th day of the month</option><option value="18">The 18th day of the month</option><option value="19">The 19th day of the month</option><option value="20">The 20th day of the month</option><option value="21">The 21st day of the month</option><option value="22">The 22nd day of the month</option><option value="23">The 23rd day of the month</option><option value="24">The 24th day of the month</option><option value="25">The 25th day of the month</option><option value="26">The 26th day of the month</option><option value="27">The 27th day of the month</option><option value="28">The 28th day of the month</option><option value="29">The 29th day of the month</option><option value="30">The 30th day of the month</option><option value="31">The 31st day of the month</option><option value="day#1#0">The 1st Sunday of the month</option><option value="day#1#1">The 1st Monday of the month</option><option value="day#1#2">The 1st Tuesday of the month</option><option value="day#1#3">The 1st Wednesday of the month</option><option value="day#1#4">The 1st Thursday of the month</option><option value="day#1#5">The 1st Friday of the month</option><option value="day#1#6">The 1st Saturday of the month</option><option value="day#2#0">The 2nd Sunday of the month</option><option value="day#2#1">The 2nd Monday of the month</option><option value="day#2#2">The 2nd Tuesday of the month</option><option value="day#2#3">The 2nd Wednesday of the month</option><option value="day#2#4">The 2nd Thursday of the month</option><option value="day#2#5">The 2nd Friday of the month</option><option value="day#2#6">The 2nd Saturday of the month</option><option value="day#3#0">The 3rd Sunday of the month</option><option value="day#3#1">The 3rd Monday of the month</option><option value="day#3#2">The 3rd Tuesday of the month</option><option value="day#3#3">The 3rd Wednesday of the month</option><option value="day#3#4">The 3rd Thursday of the month</option><option value="day#3#5">The 3rd Friday of the month</option><option value="day#3#6">The 3rd Saturday of the month</option><option value="day#4#0">The 4th Sunday of the month</option><option value="day#4#1">The 4th Monday of the month</option><option value="day#4#2">The 4th Tuesday of the month</option><option value="day#4#3">The 4th Wednesday of the month</option><option value="day#4#4">The 4th Thursday of the month</option><option value="day#4#5">The 4th Friday of the month</option><option value="day#4#6">The 4th Saturday of the month</option></select>').show();
			} else if (val == "yearly") {
				$RecurrenceDetailArea.html('<label>On</label><select name="recurrence_detail" id="recurrence_dd"><option value="01-01">January 1st</option><option value="01-02">January 2nd</option><option value="01-03">January 3rd</option><option value="01-04">January 4th</option><option value="01-05">January 5th</option><option value="01-06">January 6th</option><option value="01-07">January 7th</option><option value="01-08">January 8th</option><option value="01-09">January 9th</option><option value="01-10">January 10th</option><option value="01-11">January 11th</option><option value="01-12">January 12th</option><option value="01-13">January 13th</option><option value="01-14">January 14th</option><option value="01-15">January 15th</option><option value="01-16">January 16th</option><option value="01-17">January 17th</option><option value="01-18">January 18th</option><option value="01-19">January 19th</option><option value="01-20">January 20th</option><option value="01-21">January 21st</option><option value="01-22">January 22nd</option><option value="01-23">January 23rd</option><option value="01-24">January 24th</option><option value="01-25">January 25th</option><option value="01-26">January 26th</option><option value="01-27">January 27th</option><option value="01-28">January 28th</option><option value="01-29">January 29th</option><option value="01-30">January 30th</option><option value="01-31">January 31st</option><option value="02-01">February 1st</option><option value="02-02">February 2nd</option><option value="02-03">February 3rd</option><option value="02-04">February 4th</option><option value="02-05">February 5th</option><option value="02-06">February 6th</option><option value="02-07">February 7th</option><option value="02-08">February 8th</option><option value="02-09">February 9th</option><option value="02-10">February 10th</option><option value="02-11">February 11th</option><option value="02-12">February 12th</option><option value="02-13">February 13th</option><option value="02-14">February 14th</option><option value="02-15">February 15th</option><option value="02-16">February 16th</option><option value="02-17">February 17th</option><option value="02-18">February 18th</option><option value="02-19">February 19th</option><option value="02-20">February 20th</option><option value="02-21">February 21st</option><option value="02-22">February 22nd</option><option value="02-23">February 23rd</option><option value="02-24">February 24th</option><option value="02-25">February 25th</option><option value="02-26">February 26th</option><option value="02-27">February 27th</option><option value="02-28">February 28th</option><option value="02-29">February 29th</option><option value="03-01">March 1st</option><option value="03-02">March 2nd</option><option value="03-03">March 3rd</option><option value="03-04">March 4th</option><option value="03-05">March 5th</option><option value="03-06">March 6th</option><option value="03-07">March 7th</option><option value="03-08">March 8th</option><option value="03-09">March 9th</option><option value="03-10">March 10th</option><option value="03-11">March 11th</option><option value="03-12">March 12th</option><option value="03-13">March 13th</option><option value="03-14">March 14th</option><option value="03-15">March 15th</option><option value="03-16">March 16th</option><option value="03-17">March 17th</option><option value="03-18">March 18th</option><option value="03-19">March 19th</option><option value="03-20">March 20th</option><option value="03-21">March 21st</option><option value="03-22">March 22nd</option><option value="03-23">March 23rd</option><option value="03-24">March 24th</option><option value="03-25">March 25th</option><option value="03-26">March 26th</option><option value="03-27">March 27th</option><option value="03-28">March 28th</option><option value="03-29">March 29th</option><option value="03-30">March 30th</option><option value="03-31">March 31st</option><option value="04-01">April 1st</option><option value="04-02">April 2nd</option><option value="04-03">April 3rd</option><option value="04-04">April 4th</option><option value="04-05">April 5th</option><option value="04-06">April 6th</option><option value="04-07">April 7th</option><option value="04-08">April 8th</option><option value="04-09">April 9th</option><option value="04-10">April 10th</option><option value="04-11">April 11th</option><option value="04-12">April 12th</option><option value="04-13">April 13th</option><option value="04-14">April 14th</option><option value="04-15">April 15th</option><option value="04-16">April 16th</option><option value="04-17">April 17th</option><option value="04-18">April 18th</option><option value="04-19">April 19th</option><option value="04-20">April 20th</option><option value="04-21">April 21st</option><option value="04-22">April 22nd</option><option value="04-23">April 23rd</option><option value="04-24">April 24th</option><option value="04-25">April 25th</option><option value="04-26">April 26th</option><option value="04-27">April 27th</option><option value="04-28">April 28th</option><option value="04-29">April 29th</option><option value="04-30">April 30th</option><option value="05-01">May 1st</option><option value="05-02">May 2nd</option><option value="05-03">May 3rd</option><option value="05-04">May 4th</option><option value="05-05">May 5th</option><option value="05-06">May 6th</option><option value="05-07">May 7th</option><option value="05-08">May 8th</option><option value="05-09">May 9th</option><option value="05-10">May 10th</option><option value="05-11">May 11th</option><option value="05-12">May 12th</option><option value="05-13">May 13th</option><option value="05-14">May 14th</option><option value="05-15">May 15th</option><option value="05-16">May 16th</option><option value="05-17">May 17th</option><option value="05-18">May 18th</option><option value="05-19">May 19th</option><option value="05-20">May 20th</option><option value="05-21">May 21st</option><option value="05-22">May 22nd</option><option value="05-23">May 23rd</option><option value="05-24">May 24th</option><option value="05-25">May 25th</option><option value="05-26">May 26th</option><option value="05-27">May 27th</option><option value="05-28">May 28th</option><option value="05-29">May 29th</option><option value="05-30">May 30th</option><option value="05-31">May 31st</option><option value="06-01">June 1st</option><option value="06-02">June 2nd</option><option value="06-03">June 3rd</option><option value="06-04">June 4th</option><option value="06-05">June 5th</option><option value="06-06">June 6th</option><option value="06-07">June 7th</option><option value="06-08">June 8th</option><option value="06-09">June 9th</option><option value="06-10">June 10th</option><option value="06-11">June 11th</option><option value="06-12">June 12th</option><option value="06-13">June 13th</option><option value="06-14">June 14th</option><option value="06-15">June 15th</option><option value="06-16">June 16th</option><option value="06-17">June 17th</option><option value="06-18">June 18th</option><option value="06-19">June 19th</option><option value="06-20">June 20th</option><option value="06-21">June 21st</option><option value="06-22">June 22nd</option><option value="06-23">June 23rd</option><option value="06-24">June 24th</option><option value="06-25">June 25th</option><option value="06-26">June 26th</option><option value="06-27">June 27th</option><option value="06-28">June 28th</option><option value="06-29">June 29th</option><option value="06-30">June 30th</option><option value="07-01">July 1st</option><option value="07-02">July 2nd</option><option value="07-03">July 3rd</option><option value="07-04">July 4th</option><option value="07-05">July 5th</option><option value="07-06">July 6th</option><option value="07-07">July 7th</option><option value="07-08">July 8th</option><option value="07-09">July 9th</option><option value="07-10">July 10th</option><option value="07-11">July 11th</option><option value="07-12">July 12th</option><option value="07-13">July 13th</option><option value="07-14">July 14th</option><option value="07-15">July 15th</option><option value="07-16">July 16th</option><option value="07-17">July 17th</option><option value="07-18">July 18th</option><option value="07-19">July 19th</option><option value="07-20">July 20th</option><option value="07-21">July 21st</option><option value="07-22">July 22nd</option><option value="07-23">July 23rd</option><option value="07-24">July 24th</option><option value="07-25">July 25th</option><option value="07-26">July 26th</option><option value="07-27">July 27th</option><option value="07-28">July 28th</option><option value="07-29">July 29th</option><option value="07-30">July 30th</option><option value="07-31">July 31st</option><option value="08-01">August 1st</option><option value="08-02">August 2nd</option><option value="08-03">August 3rd</option><option value="08-04">August 4th</option><option value="08-05">August 5th</option><option value="08-06">August 6th</option><option value="08-07">August 7th</option><option value="08-08">August 8th</option><option value="08-09">August 9th</option><option value="08-10">August 10th</option><option value="08-11">August 11th</option><option value="08-12">August 12th</option><option value="08-13">August 13th</option><option value="08-14">August 14th</option><option value="08-15">August 15th</option><option value="08-16">August 16th</option><option value="08-17">August 17th</option><option value="08-18">August 18th</option><option value="08-19">August 19th</option><option value="08-20">August 20th</option><option value="08-21">August 21st</option><option value="08-22">August 22nd</option><option value="08-23">August 23rd</option><option value="08-24">August 24th</option><option value="08-25">August 25th</option><option value="08-26">August 26th</option><option value="08-27">August 27th</option><option value="08-28">August 28th</option><option value="08-29">August 29th</option><option value="08-30">August 30th</option><option value="08-31">August 31st</option><option value="09-01">September 1st</option><option value="09-02">September 2nd</option><option value="09-03">September 3rd</option><option value="09-04">September 4th</option><option value="09-05">September 5th</option><option value="09-06">September 6th</option><option value="09-07">September 7th</option><option value="09-08">September 8th</option><option value="09-09">September 9th</option><option value="09-10">September 10th</option><option value="09-11">September 11th</option><option value="09-12">September 12th</option><option value="09-13">September 13th</option><option value="09-14">September 14th</option><option value="09-15">September 15th</option><option value="09-16">September 16th</option><option value="09-17">September 17th</option><option value="09-18">September 18th</option><option value="09-19">September 19th</option><option value="09-20">September 20th</option><option value="09-21">September 21st</option><option value="09-22">September 22nd</option><option value="09-23">September 23rd</option><option value="09-24">September 24th</option><option value="09-25">September 25th</option><option value="09-26">September 26th</option><option value="09-27">September 27th</option><option value="09-28">September 28th</option><option value="09-29">September 29th</option><option value="09-30">September 30th</option><option value="10-01">October 1st</option><option value="10-02">October 2nd</option><option value="10-03">October 3rd</option><option value="10-04">October 4th</option><option value="10-05">October 5th</option><option value="10-06">October 6th</option><option value="10-07">October 7th</option><option value="10-08">October 8th</option><option value="10-09">October 9th</option><option value="10-10">October 10th</option><option value="10-11">October 11th</option><option value="10-12">October 12th</option><option value="10-13">October 13th</option><option value="10-14">October 14th</option><option value="10-15">October 15th</option><option value="10-16">October 16th</option><option value="10-17">October 17th</option><option value="10-18">October 18th</option><option value="10-19">October 19th</option><option value="10-20">October 20th</option><option value="10-21">October 21st</option><option value="10-22">October 22nd</option><option value="10-23">October 23rd</option><option value="10-24">October 24th</option><option value="10-25">October 25th</option><option value="10-26">October 26th</option><option value="10-27">October 27th</option><option value="10-28">October 28th</option><option value="10-29">October 29th</option><option value="10-30">October 30th</option><option value="10-31">October 31st</option><option value="11-01">November 1st</option><option value="11-02">November 2nd</option><option value="11-03">November 3rd</option><option value="11-04">November 4th</option><option value="11-05">November 5th</option><option value="11-06">November 6th</option><option value="11-07">November 7th</option><option value="11-08">November 8th</option><option value="11-09">November 9th</option><option value="11-10">November 10th</option><option value="11-11">November 11th</option><option value="11-12">November 12th</option><option value="11-13">November 13th</option><option value="11-14">November 14th</option><option value="11-15">November 15th</option><option value="11-16">November 16th</option><option value="11-17">November 17th</option><option value="11-18">November 18th</option><option value="11-19">November 19th</option><option value="11-20">November 20th</option><option value="11-21">November 21st</option><option value="11-22">November 22nd</option><option value="11-23">November 23rd</option><option value="11-24">November 24th</option><option value="11-25">November 25th</option><option value="11-26">November 26th</option><option value="11-27">November 27th</option><option value="11-28">November 28th</option><option value="11-29">November 29th</option><option value="11-30">November 30th</option><option value="12-01">December 1st</option><option value="12-02">December 2nd</option><option value="12-03">December 3rd</option><option value="12-04">December 4th</option><option value="12-05">December 5th</option><option value="12-06">December 6th</option><option value="12-07">December 7th</option><option value="12-08">December 8th</option><option value="12-09">December 9th</option><option value="12-10">December 10th</option><option value="12-11">December 11th</option><option value="12-12">December 12th</option><option value="12-13">December 13th</option><option value="12-14">December 14th</option><option value="12-15">December 15th</option><option value="12-16">December 16th</option><option value="12-17">December 17th</option><option value="12-18">December 18th</option><option value="12-19">December 19th</option><option value="12-20">December 20th</option><option value="12-21">December 21st</option><option value="12-22">December 22nd</option><option value="12-23">December 23rd</option><option value="12-24">December 24th</option><option value="12-25">December 25th</option><option value="12-26">December 26th</option><option value="12-27">December 27th</option><option value="12-28">December 28th</option><option value="12-29">December 29th</option><option value="12-30">December 30th</option><option value="12-31">December 31st</option></select>').show();
			}

			$RecurrenceEndArea.show();
			BigTreeCustomControls();

			if (existing_val) {
				// Update the dropdown
				$RecurrenceDetailArea.find("select").val(existing_val);
				var el = $RecurrenceDetailArea.find("select").get(0);
				el.customControl.Container.find("span").html('<figure class="handle"></figure>' + el.options[el.selectedIndex].text);
			}
		}
	}
});
{
    "type": "extension",
    "id": "com.fastspot.events",
    "version": "1.2.1",
    "revision": 6,
    "compatibility": "4.2.4+",
    "title": "Events",
    "description": "A very fast (performance wise) events system that supports recurring events and a powerful class for pulling events into the front end of your site.",
    "keywords": [
        "events",
        "recurring"
    ],
    "author": {
        "name": "Tim Buckingham",
        "url": "http://www.fastspot.com",
        "email": "tim@fastspot.com"
    },
    "licenses": {
        "LGPL v3": "http://opensource.org/licenses/LGPL-3.0"
    },
    "components": {
        "module_groups": [],
        "modules": [
            {
                "id": "3",
                "group": null,
                "name": "Events",
                "route": "com.fastspot.events*events",
                "class": "BTXEvents",
                "icon": "events",
                "gbp": {
                    "name": "",
                    "table": "",
                    "other_table": ""
                },
                "position": "0",
                "extension": "com.fastspot.events",
                "actions": [
                    {
                        "id": "7",
                        "module": "3",
                        "name": "View Events",
                        "route": "",
                        "in_nav": "on",
                        "form": null,
                        "view": "4",
                        "report": null,
                        "class": "list",
                        "level": "0",
                        "position": "6"
                    },
                    {
                        "id": "8",
                        "module": "3",
                        "name": "Search",
                        "route": "search",
                        "in_nav": "on",
                        "form": null,
                        "view": null,
                        "report": null,
                        "class": "search2",
                        "level": "0",
                        "position": "5"
                    },
                    {
                        "id": "9",
                        "module": "3",
                        "name": "Calendar",
                        "route": "calendar",
                        "in_nav": "on",
                        "form": null,
                        "view": null,
                        "report": null,
                        "class": "calendar2",
                        "level": "0",
                        "position": "4"
                    },
                    {
                        "id": "10",
                        "module": "3",
                        "name": "Add Event",
                        "route": "add",
                        "in_nav": "on",
                        "form": "4",
                        "view": null,
                        "report": null,
                        "class": "add",
                        "level": "0",
                        "position": "3"
                    },
                    {
                        "id": "11",
                        "module": "3",
                        "name": "View Categories",
                        "route": "categories",
                        "in_nav": "on",
                        "form": null,
                        "view": "3",
                        "report": null,
                        "class": "category",
                        "level": "0",
                        "position": "2"
                    },
                    {
                        "id": "12",
                        "module": "3",
                        "name": "Add Category",
                        "route": "add-category",
                        "in_nav": "on",
                        "form": "3",
                        "view": null,
                        "report": null,
                        "class": "add",
                        "level": "0",
                        "position": "1"
                    },
                    {
                        "id": "13",
                        "module": "3",
                        "name": "Edit Event",
                        "route": "edit",
                        "in_nav": "",
                        "form": "4",
                        "view": null,
                        "report": null,
                        "class": "edit",
                        "level": "0",
                        "position": "0"
                    },
                    {
                        "id": "14",
                        "module": "3",
                        "name": "Edit Category",
                        "route": "edit-category",
                        "in_nav": "",
                        "form": "3",
                        "view": null,
                        "report": null,
                        "class": "edit",
                        "level": "0",
                        "position": "0"
                    }
                ],
                "views": [
                    {
                        "id": "3",
                        "module": "3",
                        "title": "Categories",
                        "description": "",
                        "type": "nested",
                        "table": "btx_events_categories",
                        "fields": {
                            "name": {
                                "width": "788",
                                "title": "Name",
                                "parser": "",
                                "numeric": false
                            }
                        },
                        "options": {
                            "nesting_column": "parent",
                            "filter": ""
                        },
                        "actions": {
                            "edit": "on",
                            "delete": "on"
                        },
                        "preview_url": "",
                        "related_form": null
                    },
                    {
                        "id": "4",
                        "module": "3",
                        "title": "Events",
                        "description": "",
                        "type": "searchable",
                        "table": "btx_events_events",
                        "fields": {
                            "start_date": {
                                "title": "Start Date",
                                "parser": "",
                                "width": "98",
                                "numeric": false
                            },
                            "title": {
                                "title": "Title",
                                "parser": "",
                                "width": "521",
                                "numeric": false
                            },
                            "recurrence_type": {
                                "title": "Recurring",
                                "parser": "",
                                "width": "89",
                                "numeric": false
                            }
                        },
                        "options": {
                            "sort_column": "start_date",
                            "sort_direction": "DESC",
                            "per_page": "15",
                            "filter": ""
                        },
                        "actions": {
                            "feature": "on",
                            "edit": "on",
                            "delete": "on"
                        },
                        "preview_url": "",
                        "related_form": null
                    }
                ],
                "forms": [
                    {
                        "id": "3",
                        "module": "3",
                        "title": "Category",
                        "table": "btx_events_categories",
                        "fields": [
                            {
                                "column": "parent",
                                "type": "list",
                                "title": "Parent Category",
                                "subtitle": "",
                                "options": {
                                    "list_type": "db",
                                    "allow-empty": "Yes",
                                    "pop-table": "btx_events_categories",
                                    "pop-description": "name",
                                    "pop-sort": "`name` ASC",
                                    "column": "parent"
                                }
                            },
                            {
                                "column": "name",
                                "type": "text",
                                "title": "Name",
                                "subtitle": "",
                                "options": {
                                    "column": "name"
                                }
                            },
                            {
                                "column": "route",
                                "type": "route",
                                "title": "Route",
                                "subtitle": "",
                                "options": {
                                    "source": "name",
                                    "column": "route"
                                }
                            }
                        ],
                        "default_position": "Bottom",
                        "return_view": "3",
                        "return_url": "",
                        "tagging": "",
                        "hooks": null
                    },
                    {
                        "id": "4",
                        "module": "3",
                        "title": "Event",
                        "table": "btx_events_events",
                        "fields": [
                            {
                                "column": "title",
                                "type": "text",
                                "title": "Title",
                                "subtitle": "",
                                "options": {
                                    "column": "title"
                                }
                            },
                            {
                                "column": "description",
                                "type": "html",
                                "title": "Description",
                                "subtitle": "",
                                "options": {
                                    "column": "description"
                                }
                            },
                            {
                                "column": "image",
                                "type": "upload",
                                "title": "Image",
                                "subtitle": "(crops to 600x400)",
                                "options": {
                                    "directory": "files/events/",
                                    "image": "on",
                                    "min_width": "600",
                                    "min_height": "400",
                                    "preview_prefix": "",
                                    "crops": {
                                        "1": {
                                            "prefix": "",
                                            "width": "600",
                                            "height": "400",
                                            "grayscale": ""
                                        }
                                    },
                                    "column": "image"
                                }
                            },
                            {
                                "column": "__mtm-1__",
                                "type": "many-to-many",
                                "title": "Categories",
                                "subtitle": "",
                                "options": {
                                    "mtm-connecting-table": "btx_events_event_categories",
                                    "mtm-my-id": "event",
                                    "mtm-other-id": "category",
                                    "mtm-other-table": "btx_events_categories",
                                    "mtm-other-descriptor": "name",
                                    "mtm-sort": "`name` ASC",
                                    "mtm-list-parser": "BTXEvents::parseMTM",
                                    "column": "__mtm-1__"
                                }
                            },
                            {
                                "column": "all_day",
                                "type": "com.fastspot.events*date-chooser",
                                "title": "Date Chooser",
                                "subtitle": "",
                                "options": {
                                    "function": "BTXEvents::drawFormDateTime",
                                    "process_function": "BTXEvents::parseFormDateTime",
                                    "column": "all_day"
                                }
                            },
                            {
                                "column": "route",
                                "type": "route",
                                "title": "Route",
                                "subtitle": "",
                                "options": {
                                    "source": "title",
                                    "column": "route"
                                }
                            }
                        ],
                        "default_position": "",
                        "return_view": "4",
                        "return_url": "",
                        "tagging": "",
                        "hooks": {
                            "pre": "",
                            "post": "",
                            "publish": "BTXEvents::publishHook"
                        }
                    }
                ],
                "embed_forms": [],
                "reports": []
            }
        ],
        "templates": [],
        "callouts": [],
        "settings": [],
        "feeds": [],
        "field_types": [
            {
                "id": "com.fastspot.events*date-chooser",
                "name": "Events \u2014 Date Chooser",
                "use_cases": {
                    "modules": "on"
                },
                "self_draw": "on",
                "extension": "com.fastspot.events"
            }
        ],
        "tables": {
            "btx_events_categories": "CREATE TABLE `btx_events_categories` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `parent` int(11) DEFAULT NULL,   `name` varchar(255) NOT NULL DEFAULT '',   `route` varchar(255) NOT NULL DEFAULT '',   `position` int(11) NOT NULL,   PRIMARY KEY (`id`),   KEY `parent` (`parent`),   KEY `route` (`route`),   KEY `position` (`position`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8",
            "btx_events_date_cache": "CREATE TABLE `btx_events_date_cache` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `event` int(11) NOT NULL,   `start` datetime NOT NULL,   `end` datetime NOT NULL,   `date_cached` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,   `title_route` varchar(255) NOT NULL DEFAULT '',   `date_route` varchar(255) NOT NULL DEFAULT '',   PRIMARY KEY (`id`),   KEY `event` (`event`),   KEY `start` (`start`),   KEY `end` (`end`),   KEY `title_route` (`title_route`),   KEY `date_route` (`date_route`),   KEY `date_cached` (`date_cached`),    FOREIGN KEY (`event`) REFERENCES `btx_events_events` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8",
            "btx_events_event_categories": "CREATE TABLE `btx_events_event_categories` (   `event` int(11) NOT NULL,   `category` int(11) NOT NULL,   KEY `event` (`event`),   KEY `category` (`category`),    FOREIGN KEY (`event`) REFERENCES `btx_events_events` (`id`) ON DELETE CASCADE,    FOREIGN KEY (`category`) REFERENCES `btx_events_categories` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8",
            "btx_events_events": "CREATE TABLE `btx_events_events` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `title` varchar(255) NOT NULL DEFAULT '',   `description` mediumtext NOT NULL,   `image` varchar(255) NOT NULL,   `start_date` date DEFAULT NULL,   `end_date` date DEFAULT NULL,   `start_time` time DEFAULT NULL,   `end_time` time DEFAULT NULL,   `all_day` char(2) NOT NULL,   `recurrence_type` varchar(255) NOT NULL,   `recurrence_detail` varchar(255) NOT NULL,   `canceled_recurrences` text NOT NULL,   `recurring_end_date` date DEFAULT NULL,   `last_updated` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,   `featured` char(2) NOT NULL,   `route` varchar(255) DEFAULT NULL,   PRIMARY KEY (`id`),   KEY `start_date` (`start_date`),   KEY `end_date` (`end_date`),   KEY `recurring_end_date` (`recurring_end_date`),   KEY `recurrence_type` (`recurrence_type`),   KEY `featured` (`featured`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8"
        }
    },
    "sql_revisions": []
}
<?php
	$bigtree["css"][] = "events.css";
	$bigtree["js"][] = "events.js";
<div id="btx_events_calendar_data">
	<?php
		$month = time();
		include EXTENSION_ROOT."ajax/month.php";
	?>
</div>
<br class="clear"/>
<script>
	var BTXEventsModuleRoot = "<?=MODULE_ROOT?>";
</script>
<?php
	$event = BTXEvents::get($_GET["event"]);
	
	if ($event["recurrence_type"]) {
?>
<div class="container">
	<section>
		<p><strong>The event you're attempting to edit is a recurring event.</strong></p>
		<p>Do you want to edit the details of every recurrence of &ldquo;<?=$event["title"]?>&rdquo;, or only the specific date you clicked? (<?=date("F j, Y",strtotime($_GET["date"]))?>)</p>
		<p>
		    &mdash; Editing every recurrence of this event will cause each recurrence to be affected by your changes.<br />
		    &mdash; Separating the event will remove that date from the recurring event and create a new event based on the recurring event that you can edit individually.
		</p>
	</section>
	<footer>
		<a class="button" href="<?=MODULE_ROOT?>edit/<?=$event["id"]?>/"><span class="icon_small icon_small_calendar"></span> Edit All Recurrences</a>
		<a class="button" href="<?=MODULE_ROOT?>separate/?event=<?=$event["id"]?>&date=<?=$_GET["date"]?><?php if (method_exists($admin, "drawCSRFTokenGET")) { $admin->drawCSRFTokenGET(); } ?>"><span class="icon_small icon_small_blog"></span> Separate Event</a>
	</footer>
</div>
<?php
	} else {
		BigTree::redirect(MODULE_ROOT."edit/".$event["id"]."/");
	}
?>
<?php
	$months = array(
		"01" => "January",
		"02" => "February",
		"03" => "March",
		"04" => "April",
		"05" => "May",
		"06" => "June",
		"07" => "July",
		"08" => "August",
		"09" => "September",
		"10" => "October",
		"11" => "November",
		"12" => "December"
	);
	
	$keywords = "";
	
	if (count($bigtree["commands"])) {
		$start_date = $bigtree["commands"][0];
		$end_date = $bigtree["commands"][1];
	} else {
		$start_date = date("Y-m-d");
		$end_date = date("Y-m-d",time() + 7 * 24 * 60 * 60);
	}
?>
<div class="container">
	<form method="post" action="" id="btx_events_search">
		<section>
			<div class="contain">
				<fieldset class="float_margin">
					<label>Start Date</label>
					<input type="text" name="start_date" class="date_picker" autcomplete="off" />
					<span class="icon_small icon_small_calendar date_picker_icon"></span>
				</fieldset>
				<fieldset class="float_margin">
					<label>End Date</label>
					<input type="text" name="end_date" class="date_picker" autcomplete="off" />
					<span class="icon_small icon_small_calendar date_picker_icon"></span>
				</fieldset>
				<fieldset class="right last">
				    <label for="keywords">Keywords <small>(separate words with spaces)</small></label>
				    <input type="text" id="keywords" name="keywords" value="<?=htmlspecialchars($keywords)?>" class="typical_input"/>
				</fieldset>
			</div>
		</section>
		<section class="sub" style="display: none;">
			<label>Results</label>
			<div id="btx_events_search_results"></div>
		</section>
		<footer>
			<input type="submit" class="button blue" value="Search" />
		</footer>
	</form>
</div>
<script>
	$(".date_picker").datepicker();
</script>
<?php
	if (method_exists($admin, "verifyCSRFToken")) {
		$admin->verifyCSRFToken();
	}

	$event = BTXEvents::get($_GET["event"]);
	
	$canceled = json_decode($event["canceled_recurrences"],true);
	$canceled[] = $_GET["date"];
	$canceled = json_encode($canceled);
	
	$permission_level = $admin->getAccessLevel($bigtree["current_module"],$event,"btx_events_events");

	// Prepare the new event array.
	$event["start_date"] = $_GET["date"];
	unset($event["id"]);
	unset($event["end_date"]);
	unset($event["recurrence_type"]);
	unset($event["recurrence_detail"]);
	unset($event["canceled_recurrences"]);
	unset($event["recurring_end_date"]);
		
	// We're a publisher, we'll do as we please!
	if ($permission_level == "p") {
		// Un-recur the original day...
		BigTreeAutoModule::updateItem("btx_events_events",$_GET["event"],array("canceled_recurrences" => $canceled));
		// Create the new event
		$id = BigTreeAutoModule::createItem("btx_events_events",$event);
		$admin->growl("Events","Separated Event");
	}

	// We're going to request a cancelation of an event
	if ($permission_level == "e") {
		// Ask to un-recur this event.
		BigTreeAutoModule::submitChange($bigtree["current_module"]["id"],"btx_events_events",$_GET["event"],array("canceled_recurrences" => $canceled));
		// Ask to create the new one.
		$id = "p".BigTreeAutoModule::createPendingItem($bigtree["current_module"]["id"],"btx_events_events",$event);
		$admin->growl("Events","Separated Event Pending Approval");
	}
	
	BigTree::redirect(MODULE_ROOT."edit/$id/");
<?php
	$eventsMod = new BTXEvents;
	$items = $eventsMod->getEventsByDate($bigtree["commands"][0]);
?>
<div class="container" id="btx_events_search_results">
	<summary><h2>Events for <?=date("F j, Y",strtotime($bigtree["commands"][0]))?></h2></summary>
	<section>
		<div class="table">
			<header>
				<span class="view_column btx_events_column_date">Date</span>
				<span class="view_column btx_events_column_title">Title</span>
				<span class="view_action">Recurs</span>
				<span class="view_action">Edit</span>
				<span class="view_action">Delete</span>
			</header>
			<ul>
				<?php foreach ($items as $item) { ?>
				<li>
					<section class="view_column btx_events_column_date"><?=date("m/d/Y",strtotime($item["start"]))?></section>
					<section class="view_column btx_events_column_title"><?=htmlspecialchars($item["title"])?></section>
					<section class="view_action">
						<span class="icon_repeat<?php if ($item["recurrence_type"]) { ?> icon_repeat_on<?php } ?>"></span>
					</section>
					<section class="view_action"><a href="<?=MODULE_ROOT?>edit-check/?event=<?=$item["id"]?>&date=<?=date("Y-m-d",strtotime($item["start"]))?>" class="icon_edit"></a></section>
					<section class="view_action"><a href="#<?=$item["id"]?>" class="icon_delete"></a></section>
				</li>
				<?php } ?>
			</ul>
		</div>
	</section>
</div>