← Back to team overview

anewt-developers team mailing list archive

[Branch ~uws/anewt/anewt.uws] Rev 1724: [calendar] Completely reworked calendar module

 

------------------------------------------------------------
revno: 1724
committer: Wouter Bolsterlee <uws@xxxxxxxxx>
branch nick: anewt.uws
timestamp: Sun 2009-08-02 20:49:43 +0200
message:
  [calendar] Completely reworked calendar module
  
  The API and code is modernised to conform to current Anewt
  coding practice, e.g. the iCalendar class is now named
  AnewtCalendar and has a cleaner API. The API documentation
  is 100% complete. Also added some simple test/demo code.
added:
  calendar/calendar.test.php
renamed:
  calendar/ical.lib.php => calendar/calendar.lib.php
modified:
  calendar/main.lib.php
  calendar/calendar.lib.php


--
lp:anewt
https://code.launchpad.net/~uws/anewt/anewt.uws

Your team Anewt developers is subscribed to branch lp:anewt.
To unsubscribe from this branch go to https://code.launchpad.net/~uws/anewt/anewt.uws/+edit-subscription.
=== renamed file 'calendar/ical.lib.php' => 'calendar/calendar.lib.php'
--- calendar/ical.lib.php	2009-08-02 16:32:09 +0000
+++ calendar/calendar.lib.php	2009-08-02 18:49:43 +0000
@@ -1,30 +1,15 @@
 <?php
 
-/* vim:set fdm=indent: */
-
 /*
  * Anewt, Almost No Effort Web Toolkit, calendar module
  *
- * Copyright (C) 2006  Wouter Bolsterlee <uws@xxxxxxxxx>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA+
+ * This code is copyrighted and distributed under the terms of the GNU LGPL.
+ * See the README file for more information.
  */
 
 
 /*
- * Content types not (yet) supported (not really needed for the basic
+ * XXX: Content types not (yet) supported (not really needed for the basic
  * functionality):
  *
  * - VTODO
@@ -36,266 +21,286 @@
 
 
 /**
- * A class representing an iCalendar calendar object. It has some basic
- * properties and may contain one or more events. An instance of this class can
- * be serialized to a text/calendar (.ics) file, which can be imported into
- * calendering applications such as Evolution, iCal or Outlook.
+ * A class representing an iCalendar calendar object.
+ *
+ * It has some basic properties and may contain one or more events (represented
+ * by AnewtCalendarEvent). An instance of this class can be serialized to
+ * a <code>text/calendar</code> (<code>.ics</code>) file, which can be imported
+ * into calendering applications such as Evolution, iCal or Outlook.
+ *
+ * The available properties are:
+ *
+ * - \c filename contains the filename for this calendar
+ * - \c method defines the \c METHOD for the calendar. This is \c PUBLISH by
+ *   default.
+ * - \c generator contains the product identifier (optional, defaults to an
+ *   Anewt-specific string)
+ *
+ * A filename is optional, but it is recommended to set one for better
+ * client compatibility, since some platforms ignore the MIME type information
+ * and rely on the file name (extension) to determine what actions to perform.
+ *
+ * \see AnewtCalendarEvent
  */
-class iCalendar extends AnewtContainer
+class AnewtCalendar extends AnewtContainer
 {
-	var $events = array(); /**< \private Array holding all events */
+	/* Static members */
 
 	/**
-	 * \static
-	 *
 	 * Escape a string so that it can be used for TEXT fields. Newlines and
 	 * other characters are escaped to match the iCalendar specification.
 	 *
 	 * \param $str
 	 *   A string to escape.
 	 *
+	 * \param $multiline
+	 *   Boolean to indicate whether this is a multiline string. This affects
+	 *   how the string is escaped.
+	 *
 	 * \return
 	 *   The escaped string
 	 */
-	function escape_string($str)
+	private static function escape_string($str, $multiline)
 	{
 		assert('is_string($str)');
+		assert('is_bool($multiline)');
 
 		/* Escape comma's, semicolons, backslashes */
 		$str = str_replace('\\', '\\\\', $str);
 		$str = str_replace(',', '\,', $str);
 		$str = str_replace(';', '\;', $str);
 
-		/* Newlines need escaping too: literal \N should be put in the output */
-		$str = preg_replace('/\r?\n/', '\N', $str);
+		/* Remove or escape newlines. Newlines are escaped as a literal \N */
+
+		$newline_replacement = $multiline ? '\N' : ' ';
+		$str = preg_replace('/\r?\n/', $newline_replacement, $str);
 
 		return $str;
 	}
 
-	/**
-	 * Create a new calendar instance.
-	 */
-	function iCalendar()
+
+	/* Instance members */
+
+	/**
+	 * Array holding all events
+	 */
+	private $events = array();
+
+	/**
+	 * Construct a new AnewtCalendar instance.
+	 */
+	public function __construct()
 	{
-		/* Do nothing */
+		parent::__construct();
+		$this->_seed(array (
+			'filename'  => null,
+			'generator' => '-//Almost No Effort Web Toolkit//Calendar module//EN',
+			'method'    => 'PUBLISH',
+		));
 	}
 
 	/**
 	 * Add an event to the calendar.
 	 *
 	 * \param $event
-	 *   An iCalendarEvent instance
+	 *   An AnewtCalendarEvent instance
 	 */
-	function add_event(&$event)
+	function add_event($event)
 	{
-		assert('is_object($event)');
-		assert('method_exists($event, "to_ical")');
-		$this->events[] = &$event;
+		assert('$event instanceof AnewtCalendarEvent');
+		$this->events[] = $event;
 	}
 
 	/**
-	 * Returns a string representing this iCalender.
+	 * Render this calendar to a string in iCal format.
 	 *
 	 * \return
-	 *   String with iCalender data.
+	 *   String representation of this calendar
 	 */
-	function to_ical()
+	function render()
 	{
-		$r = array();
-
-		/* Generic calendar data */
-		$r[] = 'BEGIN:VCALENDAR';
-		$r[] = 'VERSION:2.0';
-		$r[] = sprintf('METHOD:%s', $this->getdefault('method', 'PUBLISH'));
-		$r[] = sprintf('PRODID:%s', $this->getdefault('generator', '-//Almost No Effort Web Toolkit//Calendar module//EN'));
-
-		$r[] = '';
-
-		/* Loop over all events in the calendar */
-		foreach (array_keys($this->events) as $key)
+		$out = array();
+
+		$out[] = 'BEGIN:VCALENDAR';
+		$out[] = 'VERSION:2.0';
+		$out[] = sprintf('METHOD:%s', $this->method);
+		$out[] = sprintf('PRODID:%s', $this->generator);
+		$out[] = '';
+
+		foreach ($this->events as $event)
 		{
-			$event = &$this->events[$key];
-			$r[] = $event->to_ical();
+			$out[] = 'BEGIN:VEVENT';
+
+			/* Generation date */
+
+			$out[] = sprintf('DTSTAMP;VALUE=DATE:%s', AnewtDateTime::iso8601_compact(AnewtDateTime::now()));
+
+
+			/* Summary and description */
+
+			assert('!is_null($event->summary)');
+
+			$description = $event->description;
+			if (is_null($description))
+				$description = $event->summary;
+
+			$out[] = sprintf('SUMMARY:%s', AnewtCalendar::escape_string($event->summary, false));
+			$out[] = sprintf('DESCRIPTION:%s', AnewtCalendar::escape_string($description, true));
+
+
+			/* Unique ID */
+
+			$uid = $event->uid;
+			if (is_null($uid))
+			{
+				/* Generate a unique id */
+				$uid = strtoupper(sprintf('%s-%s',
+						md5($event->summary),
+						md5(AnewtDateTime::iso8601_compact($event->date_start))
+				));
+			}
+			$out[] = sprintf('UID:%s', $uid);
+
+
+			/* Dates */
+
+			assert('$event->date_start instanceof AnewtDateTimeAtom');
+			$datestart_str  = $event->all_day
+				? AnewtDateTime::iso8601_date_compact($event->date_start)   /* Without time */
+				: AnewtDateTime::iso8601_compact($event->date_start);       /* With time */
+			$out[] = sprintf('DTSTART;VALUE=DATE:%s', $datestart_str);
+
+			if (!is_null($event->date_end))
+			{
+				assert('$event->date_end instanceof AnewtDateTimeAtom');
+				$date_end_str = $event->all_day
+					? AnewtDateTime::iso8601_date_compact($event->date_end) /* Without time */
+					: AnewtDateTime::iso8601_compact($event->date_end);     /* With time */
+				$out[] = sprintf('DTEND;VALUE=DATE:%s', $date_end_str);
+			}
+
+			$out[] = sprintf('TRANSP:%s', $event->transparent ? 'TRANSPARENT' : 'OPAQUE');
+
+
+			/* Misc  */
+
+			if (!is_null($event->location))
+				$out[] = sprintf('LOCATION:%s', $event->location);
+
+			if (!is_null($event->url))
+				$out[] = sprintf('URL:%s', $event->url);
+
+			$out[] = 'END:VEVENT';
+			$out[] = '';
 		}
 
-		$r[] = 'END:VCALENDAR';
+		$out[] = 'END:VCALENDAR';
 
-		return implode("\r\n", $r);
+		return implode(CRLF, $out);
 	}
 
 	/**
-	 * Outputs the iCalendar to a browser.
+	 * Output the calendar to a browser.
 	 *
-	 * \param $exit_after_flush
-	 *   Boolean value indicating whether to stop execution after flushing to
-	 *   the client (default false). If true, make sure the call to flush() is
-	 *   the last call in your code, because statements below the call to
-	 *   flush() will never be executed.
+	 * This renders the calendar and all its asssociated events, and sends the
+	 * output to the browser with the correct HTTP headers for the MIME type and
+	 * (optionally) the downoad filename.
 	 */
-	function flush($exit_after_flush=false)
+	function flush()
 	{
-		/* MIME type */
 		header('Content-Type: text/calendar');
 
-		/* Easier debugging (without a browser "save as" dialog) */
-		if ($this->getdefault('debug', false))
-			header('Content-Type: text/plain');
-
-		/* A filename is optional, but it is recommended to set one for better
-		 * client compatibility (some platforms ignore the MIME type information
-		 * and use the file name (extension) to determine what actions to
-		 * perform. */
-		if ($this->is_set('filename'))
+		$filename = $this->filename;
+		if (!is_null($filename))
 		{
-			$filename = $this->get('filename');
-			$filename = str_strip_suffix($filename, '.ics');
-			header(sprintf('Content-Disposition: inline; filename=%s.ics', $filename));
+			/* Make sure the filename ends with .ics */
+			if (!str_has_prefix($filename, 'ics'))
+				$filename = sprintf('%s.ics', $filename);
+
+			header(sprintf('Content-Disposition: inline; filename=%s', $filename));
 		}
 
-		echo $this->to_ical();
-
-		assert('is_bool($exit_after_flush)');
-		if ($exit_after_flush)
-			exit(0);
+		echo to_string($this), NL;
 	}
 }
 
 
 /**
- * Represents an event on a calendar. Several properties can be set: summary,
- * description, date-start, date-end, all-day (boolean), location, url.
+ * Calendar event for AnewtCalendar calendars.
+ *
+ * AnewtCalendarEvent instances can be added to an AnewtCalendar using
+ * AnewtCalendar::add_event().
+ *
+ * The properties that can be set on AnewtCalendar instances are:
+ *
+ * - <code>uid</code>: A unique ID for this event (optional, defaults to an
+ *   autogenerated, deterministic ID based on the summary and start date)
+ * - <code>summary</code>: The summary line for this event
+ * - <code>description</code>: A longer (multiline) description for this event
+ *   (optional)
+ * - <code>location</code>: The location of this event (optional)
+ * - <code>url</code>: An associated URL for this event (optional)
+ * - <code>date-start</code>: The start date for this event (AnewtDateTimeAtom
+ *   instance)
+ * - <code>date-end</code>: The end date for this event (AnewtDateTimeAtom
+ *   instance, optional)
+ * - <code>all-day</code>: Whether this is an all-day event (boolean, defaults
+ *   to \c false)
+ * - <code>transparent</code>: Whether this event is transparent and hence
+ *   should not conflict with other appointments (boolean, defaults to \c false)
+ *
+ * Note that the \c summary and \c date-start properties are required by the
+ * iCalendar specification.
+ *
+ * \see AnewtCalendar
  */
-class iCalendarEvent extends AnewtContainer {
+class AnewtCalendarEvent extends AnewtContainer
+{
 	/**
-	 * Construct creates a new iCalendar event.
+	 * Construct a new AnewtCalendarEvent instance.
+	 *
+	 * If you don't specify \c $summary or \c $date_start these values need to
+	 * be set later.
 	 *
 	 * \param $summary
 	 *   A summary line for this event (optional).
 	 *
-	 * \param $datestart
+	 * \param $date_start
 	 *   A AnewtDateTimeAtom instance describing the start date for this event
 	 *   (optional).
 	 */
-	function iCalendarEvent($summary=null, $datestart=null)
+	function __construct($summary=null, $date_start=null)
 	{
-		$this->set('all-day', false);
+		parent::__construct();
+
+		$now = AnewtDateTime::now();
+
+		$this->_seed(array(
+
+			'uid'         => null,
+
+			'summary'     => null,
+			'description' => null,
+			'location'    => null,
+			'url'         => null,
+
+			'date-start'  => $now,
+			'date-end'    => $now,
+			'all-day'     => false,
+			'transparent' => false,
+		));
 
 		if (!is_null($summary))
 		{
 			assert('is_string($summary)');
-			$this->set('summary', $summary);
-		}
-
-		if (!is_null($datestart))
-		{
-			assert('$datestart instanceof AnewtDateTimeAtom');
-			$this->set('datestart', $datestart);
-		}
-	}
-
-	/**
-	 * Setter for the event summary. This string must not contain newlines, so
-	 * they will be replaced with space characters.
-	 *
-	 * \param $str
-	 *   The summary to set
-	 */
-	function set_summary($str)
-	{
-		$str = preg_replace('/\r?\n/', '', $str);
-		$this->_set('summary', $str);
-	}
-
-	/**
-	 * Setter for the event description. This value needs to be escaped.
-	 *
-	 * \param $str
-	 *   The description to set
-	 */
-	function set_description($str)
-	{
-		$this->_set('description', iCalendar::escape_string($str));
-	}
-
-	/**
-	 * Return a string representing this event.
-	 *
-	 * \return
-	 *   String with iCalender data.
-	 */
-	function to_ical()
-	{
-		$r = array();
-		$r[] = 'BEGIN:VEVENT';
-
-		/* Generation date */
-		$r[] = sprintf('DTSTAMP;VALUE=DATE:%s', AnewtDateTime::iso8601_compact(AnewtDateTime::now()));
-
-		/* Summary */
-		$r[] = sprintf('SUMMARY:%s', $this->get('summary'));
-
-		/* Description */
-		if (!$this->is_set('description'))
-		{
-			$this->set('description', $this->get('summary'));
-		}
-		$r[] = sprintf('DESCRIPTION:%s', $this->get('description'));
-
-		/* Unique ID */
-		if (!$this->is_set('uid'))
-		{
-			/* Generate a unique id */
-			$uid = strtoupper(sprintf('%s-%s',
-					md5($this->get('summary')),
-					md5(AnewtDateTime::iso8601_compact($this->get('datestart')))));
-			$this->set('uid', $uid);
-		}
-		$r[] = sprintf('UID:%s', $this->get('uid'));
-
-
-		/* All day event? */
-		$all_day = $this->get('all-day');
-		assert('is_bool($all_day)');
-
-		/* Start date */
-		$datestart = $this->get('datestart');
-		$datestart_str  = $all_day
-			? AnewtDateTime::iso8601_date_compact($datestart)   /* without time */
-			: AnewtDateTime::iso8601_compact($datestart);       /* with time */
-		$r[] = sprintf('DTSTART;VALUE=DATE:%s', $datestart_str);
-
-		/* End date */
-		if ($this->is_set('dateend'))
-		{
-			$dateend = $this->get('dateend');
-			assert('$dateend instanceof AnewtDateTimeAtom');
-			$dateend_str = $all_day
-				? AnewtDateTime::iso8601_date_compact($dateend) /* without time */
-				: AnewtDateTime::iso8601_compact($dateend);     /* with time */
-			$r[] = sprintf('DTEND;VALUE=DATE:%s', $dateend_str);
-		}
-
-		/* Transparency */
-		$transparent = $this->getdefault('transparent', false);
-		assert('is_bool($transparent)');
-		$r[] = sprintf('TRANSP:%s', $transparent ? 'TRANSPARENT' : 'OPAQUE');
-
-		/* Location  */
-		if ($this->is_set('location'))
-		{
-			$r[] = sprintf('LOCATION:%s', $this->get('location'));
-		}
-
-		/* URL  */
-		if ($this->is_set('url'))
-		{
-			$r[] = sprintf('URL:%s', $this->get('url'));
-		}
-
-		$r[] = 'END:VEVENT';
-		$r[] = '';
-
-		return implode("\r\n", $r);
+			$this->summary = $summary;
+		}
+
+		if (!is_null($date_start))
+		{
+			assert('$date_start instanceof AnewtDateTimeAtom');
+			$this->date_start = $date_start;
+		}
 	}
 }
 

=== added file 'calendar/calendar.test.php'
--- calendar/calendar.test.php	1970-01-01 00:00:00 +0000
+++ calendar/calendar.test.php	2009-08-02 18:49:43 +0000
@@ -0,0 +1,26 @@
+<?php
+
+error_reporting(E_ALL | E_STRICT);
+require_once dirname(__FILE__) . '/../anewt.lib.php';
+
+anewt_include('calendar');
+
+$calendar = new AnewtCalendar();
+
+$event = new AnewtCalendarEvent('Title');
+$event->date_start = AnewtDateTime::parse_string('2009-01-01 12:00');
+$event->date_end = AnewtDateTime::parse_string('2009-01-01 14:00');
+$event->summary = 'This is the summary';
+$event->location = 'This is the location';
+$event->url = 'http://example.org/foo';
+$event->uid = 'abc1234567890';
+$calendar->add_event($event);
+
+$event = new AnewtCalendarEvent('Another event', AnewtDateTime::now());
+$event->summary = "This is a multiline\nsummary";
+$event->summary = "This is a multiline\ndescription";
+$calendar->add_event($event);
+
+$calendar->flush();
+
+?>

=== modified file 'calendar/main.lib.php'
--- calendar/main.lib.php	2006-07-28 14:56:22 +0000
+++ calendar/main.lib.php	2009-08-02 18:49:43 +0000
@@ -3,24 +3,11 @@
 /*
  * Anewt, Almost No Effort Web Toolkit, calendar module
  *
- * Copyright (C) 2006  Wouter Bolsterlee <uws@xxxxxxxxx>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA+
+ * This code is copyrighted and distributed under the terms of the GNU LGPL.
+ * See the README file for more information.
  */
 
 
-anewt_include('calendar/ical');
+anewt_include('calendar/calendar');
 
 ?>