PATH:
home
/
letacommog
/
crmleta
/
modules
/
Mobile
/
third-party
/
qCal
/
qCal
<?php /** * Base calendar component class. Events, Todos, and Calendars are * examples of components in qCal * @package qCal * @copyright Luke Visinoni (luke.visinoni@gmail.com) * @author Luke Visinoni (luke.visinoni@gmail.com) * @license GNU Lesser General Public License * * RFC 2445 Definition * * The body of the iCalendar object consists of a sequence of calendar * properties and one or more calendar components. The calendar * properties are attributes that apply to the calendar as a whole. The * calendar components are collections of properties that express a * particular calendar semantic. For example, the calendar component can * specify an event, a to-do, a journal entry, time zone information, or * free/busy time information, or an alarm. * * The body of the iCalendar object is defined by the following * notation: * * icalbody = calprops component * * calprops = 2*( * * ; 'prodid' and 'version' are both REQUIRED, * ; but MUST NOT occur more than once * * prodid /version / * * ; 'calscale' and 'method' are optional, * ; but MUST NOT occur more than once * * calscale / * method / * * x-prop * * ) * * component = 1*(eventc / todoc / journalc / freebusyc / * / timezonec / iana-comp / x-comp) * * iana-comp = "BEGIN" ":" iana-token CRLF * * 1*contentline * * "END" ":" iana-token CRLF * * x-comp = "BEGIN" ":" x-name CRLF * * 1*contentline * * "END" ":" x-name CRLF * * An iCalendar object MUST include the "PRODID" and "VERSION" calendar * properties. In addition, it MUST include at least one calendar * component. Special forms of iCalendar objects are possible to publish * just busy time (i.e., only a "VFREEBUSY" calendar component) or time * zone (i.e., only a "VTIMEZONE" calendar component) information. In * addition, a complex iCalendar object is possible that is used to * capture a complete snapshot of the contents of a calendar (e.g., * composite of many different calendar components). More commonly, an * iCalendar object will consist of just a single "VEVENT", "VTODO" or * "VJOURNAL" calendar component. */ abstract class qCal_Component { /** * The name of this component * @var string */ protected $name; /** * Contains a list of allowed parent components. * @var array */ protected $allowedComponents = array(); /** * Contains an array of this component's child components (if any). It uses * @var array */ protected $children = array(); /** * Contains an array of this component's properties. Properties provide * information about their respective components. This array is associative. * It uses property name as key and property object as value (or array of them * if said property can be set multiple times). This is so that I can quickly * look up any certain property. * @var array */ protected $properties = array(); /** * Contains an array of this component's required properties */ protected $requiredProperties = array(); /** * Parent component (all components but vcalendar should have one once attached) */ protected $parent; /** * Class constructor * Accepts an array of properties, which can be simple values or actual property objects * Pass in a null value to use a property's default value (some dont have defaults, so beware) * Example: * $cal = new qCal_Component_Calendar(array( * 'prodid' => '-// Some Property Id//', * 'someotherproperty' => null, * qCal_Property_Version(2.0), * ), array( * qCal_Component_Daylight(), * )); */ public function __construct($properties = array(), $components = array()) { foreach ($components as $component) { // if value is an array, then each value inside of it will be a component if ($component instanceof qCal_Component) { $this->attach($component); } else throw new qCal_Exception_InvalidComponent('The second argument is optional, but if provided, must be an array of components'); } foreach ($properties as $name => $value) { // if value is an array, then each value inside of it will be a property if (is_array($value)) { foreach ($value as $val) { if ($val instanceof qCal_Property) { $this->addProperty($val); } else { $this->addProperty($name, $val); } } } else { if ($value instanceof qCal_Property) { $this->addProperty($value); } else { $this->addProperty($name, $value); } } } // I think it would make more sense to do validation at render time. That way you don't have // to have all of the required components and properties when you instantiate. Also, that way // components don't need to be aware of eachother until render time (or until validate() is called // explicitly). @todo // $this->validate(); } /** * @todo (lazy load functionality) Check that this is a valid component. This method is sort of lazy-loaded. It only gets called * if the user has requested data that requires validation and the component has not been validated already. * @todo Shouldn't this loop over children and validate them too? Maybe optionally? */ public function validate() { // if we're missing any required properties and they have no default, throw an exception $properties = array(); foreach ($this->getProperties() as $property) { if (is_array($property)) { foreach ($property as $prop) { $properties[] = $prop->getName(); } } else { $properties[] = $property->getName(); } } $missing = array_diff($this->requiredProperties, array_unique($properties)); foreach ($missing as $propertyname) { // the property factory will throw an exception if it's passed a null value for a property with no default try { $property = qCal_Property::factory($propertyname, null); $this->addProperty($property); } catch (qCal_Exception_InvalidPropertyValue $e) { // if that's the case, catch the exception and throw a missing property exception throw new qCal_Exception_MissingProperty($this->getName() . " component requires " . $propertyname . " property"); } } // this allows per-component validation :) $this->doValidation(); } /** * Returns the component name * @return string */ public function getName() { return $this->name; } /** * Returns true if this component can be attached to $component * I'm sure there's a better way to do this, but this works for now */ public function canAttachTo(qCal_Component $component) { if (in_array($component->getName(), $this->allowedComponents)) return true; } /** * Attach a component to this component (alarm inside event for example) * @todo There may be an issue with the way this is done. When parsing a file, if a component * or property with a tzid comes before its corresponding vtimezone component, an exception * will be thrown. I'm don't think the RFC specifies that requirement (that timezone components * must come before their corresponding tzids) * @todo Sub-components such as Vevent need to be able to access the main vcalendar object * for several reasons. * - If a vtodo has a tzid, it needs to be able to determine that the corresponding * vtimezone component is available. * - If components need to relate to eachother, they can only find eachother through * the main vcalendar object. * - Freebusy time can only be determined by polling all components in the main vcalendar * object. * - More to come probably */ public function attach(qCal_Component $component) { if (!$component->canAttachTo($this)) { throw new qCal_Exception_InvalidComponent($component->getName() . ' cannot be attached to ' . $this->getName()); } $component->setParent($this); // make sure if a timezone is requested that it is available... $timezones = $this->getTimezones(); $tzids = array_keys($timezones); // we only need to check if tzid exists if we are attaching something other than a timezone... if (!($component instanceof qCal_Component_Vtimezone)) { foreach ($component->getProperties() as $pname => $properties) { $pname = strtoupper($pname); // probably redundant... foreach ($properties as $property) { switch ($pname) { case "TZID": $tzid = $property->getValue(); if (!array_key_exists($tzid, $tzids)) { throw new qCal_Exception_MissingComponent('TZID "' . $tzid . '" not defined'); } break; } $params = $property->getParams(); foreach ($params as $param => $val) { $param = strtoupper($param); // probably redundant... switch ($param) { case "TZID": $tzid = $val; if (!array_key_exists($tzid, $tzids)) { throw new qCal_Exception_MissingComponent('TZID "' . $tzid . '" not defined'); } break; } } } } } $this->children[$component->getName()][] = $component; } /** * Set the parent of this component * @todo I'm not sure this will suffice. See the attach method for reasoning behind this. */ public function setParent(qCal_Component $component) { $this->parent = $component; } /** * Get the parent of this component (if there is one) */ public function getParent() { return $this->parent; } /** * The only thing I need this for so far is the parser, but it may come in handy for the facade as well */ static public function factory($name, $properties = array()) { if (empty($name)) return false; // capitalize $component = ucfirst(strtolower($name)); $className = "qCal_Component_" . $component; $fileName = str_replace("_", DIRECTORY_SEPARATOR, $className) . ".php"; qCal_Loader::loadFile($fileName); $class = new $className($properties); return $class; } /** * I'm not sure how this should work. Not sure if it should be setProperty, * addProperty, both? Because properties on some components can be set multiple * times, while some properties have multiple values. :( I am trying to consider * a case where somebody needs to open a calendar, change a few properties on a * component (change event time for instance). I think the way I'll handle properties * that can be set multiple times is I'll create a method do delete properties based * on values, parameters, etc. since they don't really have IDs. So I tihnk I'll go * with addProperty :) */ public function addProperty($property, $value = null, $params = array()) { if (!($property instanceof qCal_Property)) { $property = qCal_Property::factory($property, $value, $params); } if (!$property->of($this)) { throw new qCal_Exception_InvalidProperty($this->getName() . " component does not allow " . $property->getName() . " property"); } if (!$property->allowMultiple()) { unset($this->properties[$property->getName()]); } $this->properties[$property->getName()][] = $property; } /** * Returns property of this component by name * * @todo Since the same property can appear in a component more than once, this method * doesn't make that much sense unless it returns all of the instances of the property * @return array of qCal_Property */ public function getProperty($name) { $name = strtoupper($name); if ($this->hasProperty($name)) { return $this->properties[$name]; } } /** * Returns true if this component contains a property of $name * * @return boolean */ public function hasProperty($name) { $name = strtoupper($name); return isset($this->properties[$name]); } /** * Returns true if this component contains a property of $name * * @return boolean */ public function hasComponent($name) { $name = strtoupper($name); return isset($this->children[$name]); } /** * Returns the child component requested */ public function getComponent($name) { $name = strtoupper($name); if ($this->hasComponent($name)) { return $this->children[$name]; } } /* public function clearProperties() { $this->properties = array(); } public function clearChildren() { $this->children = array(); } */ public function getProperties() { return $this->properties; } public function getChildren() { return $this->children; } /** * Gets the parent-most component in the tree. I would really like to come up * with a cleaner way to access other components from within a component, but oh well. */ public function getRootComponent() { $parent = $this; while (!($parent instanceof qCal_Component_Vcalendar)) { if (!$parent->getParent()) break; $parent = $parent->getParent(); } return $parent; } /** * Renders the calendar, by default in icalendar format. If you pass * in a renderer, it will use that instead * * @return mixed Depends on the renderer * @todo Would it make more sense to pass the component to the renderer, or the renderer * to the component? I'm not sure components should know about rendering. */ public function render(qCal_Renderer $renderer = null) { $this->validate(); if (is_null($renderer)) $renderer = new qCal_Renderer_iCalendar(); return $renderer->render($this); } /** * Output the icalendar component as a string (render it) */ public function __toString() { return $this->render(); } /** * getFreeBusyTime * Looks through all of the data in the calendar and returns a qCal_Component_Vfreebusy object * with free/busy time from $startdate to $enddate. The component will contain all components, but some * may have their transparency set to "transparent". * @todo This cannot be finished until recurring events are finished, since free/busy does not allow * recurrence rules, each instance of a recurrence would need to be calculated out and passed into the free/busy * component, so that the component would contain concrete instances of each event recurrence. */ public function getFreeBusyTime() { $root = $this->getRootComponent(); foreach ($root->getChildren() as $children) { foreach ($children as $child) { // now get the object's free/busy time } } } /** * getTimeZones */ public function getTimezones() { $tzs = array(); $root = $this->getRootComponent(); foreach ($root->getChildren() as $children) { foreach ($children as $child) { // if the child is a vtimezone, add it to the results // @todo make sure that tzid is available, throw exception otherwise if ($child instanceof qCal_Component_Vtimezone) { $tzid = $child->getTzid(); $tzid = strtoupper($tzid); $tzs[$tzid] = $child; } } } return $tzs; } /** * Get a specific timezone by tzid * @param string The timezone identifier */ public function getTimezone($tzid) { $tzid = strtoupper($tzid); $root = $this->getRootComponent(); $timezones = $root->getTimezones(); if (array_key_exists($tzid, $timezones)) { return $timezones[$tzid]; } return false; } /** * Allows for components to get and set property values by calling * qCal_Component::getPropertyName() and qCal_Component::setPropertyName('2.0') where propertyName is the property name * to be set and $val is the property value. * This is just a convenience facade, it isn't going to be used within the library as much as by end-users * @todo I can't decided whether to maybe get rid of the facade methods at least for now since some properties * can potentially return multiple values and that makes the interface inconsistent */ public function __call($method, $params) { $firstthree = substr($method, 0, 3); $name = substr($method, 3); if ($firstthree == "get") { // if property is allowed multiple times, an array is returned, otherwise just the one component if ($this->hasProperty($name)) { $property = $this->getProperty($name); if (!$property[0]->allowMultiple()) { return $property[0]; } else { return $property; } } } elseif ($firstthree == "set") { $value = isset($params[0]) ? $params[0] : null; $params = isset($params[1]) ? $params[1] : array(); $property = qCal_Property::factory($name, $value, $params); $this->addProperty($property); } elseif ($firstthree == "add") { // add property type $property = qCal_Property::factory($name, $params); $this->addProperty($property); return $this; } // throw exception here? // throw new qCal_Exception(); } }
[+]
..
[-] Exception.php
[edit]
[-] Renderer.php
[edit]
[-] Value.php
[edit]
[-] Property.php
[edit]
[+]
Property
[-] Timezone.php
[edit]
[+]
DateTime
[+]
Parser
[-] Date.php
[edit]
[-] Time.php
[edit]
[+]
Renderer
[-] Component.php
[edit]
[-] Loader.php
[edit]
[+]
Component
[+]
Value
[+]
Exception
[-] Parser.php
[edit]
[-] DateTime.php
[edit]