<?php
/**
* contentline.php: iCalendar contentline class
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* @copyright Copyright (C) 2008 Nigel Swinson, hide@address.com
* @author Nigel Swinson
* @version 1.0
*/
require_once 'parameterfactory.php';
$iPHPICALLIB_PropertyIndex = 1;
define('PHPICALLIB_PROPERTY_BEGIN', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_END', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.7
//3.7. Calendar Properties
define('PHPICALLIB_PROPERTY_CALSCALE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_METHOD', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_PRODID', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_VERSION', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.1
//3.8.1. Descriptive Component Properties
define('PHPICALLIB_PROPERTY_ATTACH', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_CATEGORIES', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_CLASS', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_COMMENT', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DESCRIPTION', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_GEO', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_LOCATION', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_PERCENT_COMPLETE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_PRIORITY', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_RESOURCES', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_STATUS', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_SUMMARY', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.2
//3.8.2. Date and Time Component Properties
define('PHPICALLIB_PROPERTY_COMPLETED', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DTEND', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DUE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DTSTART', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DURATION', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_FREEBUSY', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TRANSP', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.3
//3.8.3. Time Zone Component Properties
define('PHPICALLIB_PROPERTY_TZID', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TZNAME', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TZOFFSETFROM', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TZOFFSETTO', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TZURL', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.4
//3.8.4. Relationship Component Properties
define('PHPICALLIB_PROPERTY_ATTENDEE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_CONTACT', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_ORGANIZER', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_RECURRENCE_ID', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_RELATED_TO', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_URL', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_UID', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.5
//3.8.5. Recurrence Component Properties
define('PHPICALLIB_PROPERTY_EXDATE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_RDATE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_RRULE', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.6
//3.8.6. Alarm Component Properties
define('PHPICALLIB_PROPERTY_ACTION', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_REPEAT', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TRIGGER', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.7
//3.8.7. Change Management Component Properties
define('PHPICALLIB_PROPERTY_CREATED', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DTSTAMP', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_LASTMODIFIED', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_SEQUENCE', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.8
//3.8.8. Miscellaneous Component Properties
define('PHPICALLIB_PROPERTY_IANAPROP', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_XPROP', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_REQUEST_STATUS', $iPHPICALLIB_PropertyIndex++);
// If you add another, then be sure to amend the PhpiCalLib_ContentLine::TypeMap as well
//define('PHPICALLIB_PROPERTY_', $iPHPICALLIB_PropertyIndex++);
/**
* Represents a contentline token.
*
* ContentLines in iCalendar mostly represent properties, but sometimes indicate the start/end
* of a component.
*/
class PhpiCalLib_ContentLine {
/**
* The "name" token
* @var string
*/
protected $Name = '';
/**
* An enumerated version of $Name
* @var PHPICALLIB_PROPERTY_?
*/
protected $Type = 0;
/**
* The "param" tokens
* @var array
*/
protected $aParameters = null;
/**
* The "value" token
* @var string
*/
protected $EncodedValue = '';
/**
* Info about which parameters are permitted for this property
*
* Empty array means all parameters are permitted. Each entry is keyed
* by id. The values indicate the min and max number of instances. For
* example, the spec says this for ATTENDEE.
* {@link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.4.1}
* <pre>
* attparam = *(
*
* ; the following are OPTIONAL,
* ; but MUST NOT occur more than once
* (";" cutypeparam) / (";" memberparam) /
* (";" roleparam) / (";" partstatparam) /
* (";" rsvpparam) / (";" deltoparam) /
* (";" delfromparam) / (";" sentbyparam) /
* (";" cnparam) / (";" dirparam) /
* (";" languageparam) /
*
* ; the following is OPTIONAL,
* ; and MAY occur more than once
* (";" other-param)
*
* )
* </pre>
*
* This would be implemented as:
*
* <code>
* $this->aPermittedParameters = array(
* // ; the following are OPTIONAL,
* // ; but MUST NOT occur more than once
* // (";" cutypeparam) / (";" memberparam) /
* // (";" roleparam) / (";" partstatparam) /
* // (";" rsvpparam) / (";" deltoparam) /
* // (";" delfromparam) / (";" sentbyparam) /
* // (";" cnparam) / (";" dirparam) /
* // (";" languageparam) /
* PHPICALLIB_PARAMETER_CUTYPE => array(0,1),
* PHPICALLIB_PARAMETER_MEMBER => array(0,1),
* PHPICALLIB_PARAMETER_ROLE => array(0,1),
* PHPICALLIB_PARAMETER_PARTSTAT => array(0,1),
* PHPICALLIB_PARAMETER_RSVP => array(0,1),
* PHPICALLIB_PARAMETER_DELEGATED_TO => array(0,1),
* PHPICALLIB_PARAMETER_DELEGATED_FROM => array(0,1),
* PHPICALLIB_PARAMETER_SENT_BY => array(0,1),
* PHPICALLIB_PARAMETER_CN => array(0,1),
* PHPICALLIB_PARAMETER_DIR => array(0,1),
* PHPICALLIB_PARAMETER_LANGUAGE => array(0,1),
* // ; the following is OPTIONAL,
* // ; and MAY occur more than once
* // (";" other-param)
* PHPICALLIB_PARAMETER_IANAPARAM => array(0,-1),
* PHPICALLIB_PARAMETER_XPARAM => array(0,-1),
* </code>
*
* Note that this mechanism isn't sufficient to ensure the property only has valid
* parameters, for example two parameters may be mutually exclusive. Where that is the
* case the user should override (@link AddParameterPermitted()}, {@link RemoveParameterPermitted} or
* {@link SetParameterPermitted} to make sure that any remaining rules have been followed.
*
* @var array Keyed by PHPICALLIB_PARAMETER_* ids, value is an array(mininstances, maxinstances)
*/
protected $aPermittedParameters = array();
/**
* Regex to capture validate an iana-token
* @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
* iana-token = 1*(ALPHA / DIGIT / "-")
*
* @var string
*/
public static $IanaTokenRegex = '[a-zA-Z0-9-]+';
/**
* Regex to validate a name token
* A quick regular expression to validate the name token
* ==>
* name = iana-token / x-name
* iana-token = 1*(ALPHA / DIGIT / "-")
* x-name = "X-" [vendorid "-"] 1*(ALPHA / DIGIT / "-")
* vendorid = 3*(ALPHA / DIGIT)
* ==>
* name = 1*(ALPHA / DIGIT / "-")
* / "X-" [3*(ALPHA / DIGIT) "-"] 1*(ALPHA / DIGIT / "-")
* ==>
* name = 1*(ALPHA / DIGIT / "-")
* @var string
*/
public static $NameSplitRegex = '[a-zA-Z0-9-]+';
/**
* Map a property type to it's string property name.
*
* @var array Keyed by PHPICALLIB_PROPERTY_?, value is a string
*/
private static $TypeMap = array(
PHPICALLIB_PROPERTY_BEGIN => 'BEGIN',
PHPICALLIB_PROPERTY_END => 'END',
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.7
//3.7. Calendar Properties
PHPICALLIB_PROPERTY_CALSCALE => 'CALSCALE',
PHPICALLIB_PROPERTY_METHOD => 'METHOD',
PHPICALLIB_PROPERTY_PRODID => 'PRODID',
PHPICALLIB_PROPERTY_VERSION => 'VERSION',
//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.1
//3.8.1. Descriptive Component Properties
PHPICALLIB_PROPERTY_ATTACH => 'ATTACH',
PHPICALLIB_PROPERTY_CATEGORIES => 'CATEGORIES',
PHPICALLIB_PROPERTY_CLASS => 'CLASS',
PHPICALLIB_PROPERTY_COMMENT => 'COMMENT',
PHPICALLIB_PROPERTY_DESCRIPTION => 'DESCRIPTION',
PHPICALLIB_PROPERTY_GEO => 'GEO',
PHPICALLIB_PROPERTY_LOCATION => 'LOCATION',
PHPICALLIB_PROPERTY_PERCENT_COMPLETE => 'PERCENT-COMPLETE',
PHPICALLIB_PROPERTY_PRIORITY => 'PRIORITY',
PHPICALLIB_PROPERTY_RESOURCES => 'RESOURCES',
PHPICALLIB_PROPERTY_STATUS => 'STATUS',
PHPICALLIB_PROPERTY_SUMMARY => 'SUMMARY',
//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.2
//3.8.2. Date and Time Component Properties
PHPICALLIB_PROPERTY_COMPLETED => 'COMPLETED',
PHPICALLIB_PROPERTY_DTEND => 'DTEND',
PHPICALLIB_PROPERTY_DUE => 'DUE',
PHPICALLIB_PROPERTY_DTSTART => 'DTSTART',
PHPICALLIB_PROPERTY_DURATION => 'DURATION',
PHPICALLIB_PROPERTY_FREEBUSY => 'FREEBUSY',
PHPICALLIB_PROPERTY_TRANSP => 'TRANSP',
//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.3
//3.8.3. Time Zone Component Properties
PHPICALLIB_PROPERTY_TZID => 'TZID',
PHPICALLIB_PROPERTY_TZNAME => 'TZNAME',
PHPICALLIB_PROPERTY_TZOFFSETFROM => 'TZOFFSETFROM',
PHPICALLIB_PROPERTY_TZOFFSETTO => 'TZOFFSETTO',
PHPICALLIB_PROPERTY_TZURL => 'TZURL',
//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.4
//3.8.4. Relationship Component Properties
PHPICALLIB_PROPERTY_ATTENDEE => 'ATTENDEE',
PHPICALLIB_PROPERTY_CONTACT => 'CONTACT',
PHPICALLIB_PROPERTY_ORGANIZER => 'ORGANIZER',
PHPICALLIB_PROPERTY_RECURRENCE_ID => 'RECURRENCE-ID',
PHPICALLIB_PROPERTY_RELATED_TO => 'RELATED-TO',
PHPICALLIB_PROPERTY_URL => 'URL',
PHPICALLIB_PROPERTY_UID => 'UID',
//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.5
//3.8.5. Recurrence Component Properties
PHPICALLIB_PROPERTY_EXDATE => 'EXDATE',
PHPICALLIB_PROPERTY_RDATE => 'RDATE',
PHPICALLIB_PROPERTY_RRULE => 'RRULE',
//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.6
//3.8.6. Alarm Component Properties
PHPICALLIB_PROPERTY_ACTION => 'ACTION',
PHPICALLIB_PROPERTY_REPEAT => 'REPEAT',
PHPICALLIB_PROPERTY_TRIGGER => 'TRIGGER',
//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.7
//3.8.7. Change Management Component Properties
PHPICALLIB_PROPERTY_CREATED => 'CREATED',
PHPICALLIB_PROPERTY_DTSTAMP => 'DTSTAMP',
PHPICALLIB_PROPERTY_LASTMODIFIED => 'LASTMODIFIED',
PHPICALLIB_PROPERTY_SEQUENCE => 'SEQUENCE',
PHPICALLIB_PROPERTY_REQUEST_STATUS => 'REQUEST-STATUS');
// array_flip() on $TypeMap, built on first use.
private static $ReverseTypeMap = null;
/**
* The parameter factory
*
* @var PhpiCalLib_ParameterFactory
*/
private $ParameterFactory = null;
/**
* Constructor
*
*/
public function __construct() {
$this->ParameterFactory = new PhpiCalLib_ParameterFactory();
}
/**
* Copy from the other object
*
* @param PhpiCalLib_ContentLine $ContentLine
*/
public function Copy($Other) {
$this->SetName($Other->Name);
$this->aParameters = null;
if (!empty($Other->aParameters)) {
$this->aParameters = $Other->aParameters;
}
$this->SetEncodedValue($Other->EncodedValue);
}
/**
* Convert a string to a list of content lines
*
* @param string $String A string of iCalendar data that represents a set of content lines
* @return array An ordered list of content lines
*/
public function ToContentLines($iCalString) {
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
// contentline = name *(";" param ) ":" value CRLF
// ; This ABNF is just a general definition for an initial parsing
// ; of the content line into its property name, parameter list,
// ; and value string
//
// ; When parsing a content line, folded lines MUST first
// ; be unfolded according to the unfolding procedure
// ; described above. When generating a content line, lines
// ; longer than 75 octets SHOULD be folded according to
// ; the folding procedure described above.
if (empty($iCalString)) return array();
if (!preg_match("/\r\n$/D", $iCalString)) {
throw new PhpiCalLib_ParseException(sprintf('Last line doesn\'t end with \r\n:%s', substr($iCalString, -100, 100)));
}
$aFoldedContentLines = preg_split("/\r\n/", $iCalString);
$aUnfoldedContentLines = array();
$iLineNumber = 0;
foreach ($aFoldedContentLines as $FoldedContentLine) {
if (empty($FoldedContentLine)) {
// Technically not allowed, so we treat as the EOF
//throw new PhpiCalLib_ParseException(sprintf('Line %d: Empty content lines are not allowed', $iLineNumber));
break;
}
switch ($FoldedContentLine[0]) {
case ' ':
case "\t":
// It's a continuation line. Check we have a line to continue
if (empty($aUnfoldedContentLines)) {
throw new PhpiCalLib_ParseException(sprintf('Line %d: Invalid continution line, no previous line to continue', $iLineNumber));
}
$aUnfoldedContentLines[] = array_pop($aUnfoldedContentLines) . substr($FoldedContentLine, 1);
break;
default:
// Append to the existing lines
$aUnfoldedContentLines[] = $FoldedContentLine;
}
$iLineNumber++;
}
// Now convert unfolded lines to content lines (properties)
$aContentLines = array();
$iContentLineNumber = 0;
foreach ($aUnfoldedContentLines as $UnfoldedContentLine) {
try {
$aContentLine = $this->Create($UnfoldedContentLine);
} catch (PhpiCalLib_Exception $Exception) {
$Exception->SetUnfoldedContentLine($iContentLineNumber);
throw $Exception;
}
$aContentLines[] = $aContentLine;
$iContentLineNumber++;
}
return $aContentLines;
}
/**
* Convert a property name to it's defined type
*
* @param string $Name a "name" RFC2445 token
* @return int one of the PHPICALLIB_PROPERTY_* properties
*/
static public function ToPropertyType($Name) {
// Build the type map on first access
if (!self::$ReverseTypeMap) {
self::$ReverseTypeMap = array_flip(self::$TypeMap);
}
// If we have a key for this name, return the unique type
if (isset(self::$ReverseTypeMap[$Name])) {
return self::$ReverseTypeMap[$Name];
}
// It must be an iana defined extension or x-prop that we don't really recognize.
if (preg_match('/^X-.*$/', $Name)) {
return PHPICALLIB_PROPERTY_XPROP;
}
return PHPICALLIB_PROPERTY_IANAPROP;
}
/**
* Convert a property type to it's defined name
*
* @param integer $Type one of the PHPICALLIB_PROPERTY_* properties
* @return string A "name" RFC2445 token
*/
static public function ToPropertyName($Type) {
if (empty(self::$TypeMap[$Type])) return '';
return self::$TypeMap[$Type];
}
/**
* Get the name of the property
*/
public function GetName() {
if (empty($this->Name)) {
throw new PhpiCalLib_Exception('No name assigned to this parameter yet');
}
return $this->Name;
}
/**
* Set the new name for the property
*
* @param string $Name
*/
public function SetName($Name) {
// Further parse the name before storage.
// name = iana-token / x-name
// iana-token = 1*(ALPHA / DIGIT / "-")
// x-name = "X-" [vendorid "-"] 1*(ALPHA / DIGIT / "-")
$XNameRegex = 'X-([a-zA-Z0-9]{3}-)?([a-zA-Z0-9-]+)';
// vendorid = 3*(ALPHA / DIGIT)
// Note an invalid x-name becomes an iana-token, so we just check for valid iana-tokens
// iana-token
$Pattern = sprintf("/^%s$/D", self::$IanaTokenRegex);
if (!preg_match($Pattern, $Name)) {
throw new PhpiCalLib_ParameterException(sprintf("Invalid iana-token (%s) in property name: %s", $Pattern, $Name));
}
if (strlen($Name) > 2 && substr($Name, 0, 2) == "X-") {
// x-name
$this->Type = PHPICALLIB_PROPERTY_XPROP;
$this->Name = $Name;
} else {
// iana-token
$this->Name = $Name;
$this->Type = self::ToPropertyType($Name);
}
}
/**
* Access the property type
*
* Note this will return PHPICALLIB_PROPERTY_IANAPROP or PHPICALLIB_PROPERTY_XPROP for properties
* that it doesn't recognize.
*
* @return PHPICALLIB_PROPERTY_*
*/
public function GetType() {
return $this->Type;
}
/**
* Set the type of the content line
*
* @param integer $Type
*/
public function SetType($Type) {
// Check they aren't trying to set this to an x-prop or an iana-prop
switch ($Type) {
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.8
//3.8.8. Miscellaneous Component Properties
case PHPICALLIB_PROPERTY_IANAPROP:
case PHPICALLIB_PROPERTY_XPROP:
throw new PhpiCalLib_Exception('Can\'t set contentline type to iana-prop or x-prop. Use SetName instead');
}
// Pull out the name, note if we ever add a PHPICALLIB_PROPERTY_? and forget to ament $TypeMap, then
// this can start to fail
$this->Name = self::$TypeMap[$Type];
// Store the type.
$this->Type = $Type;
}
/**
* Access the parameters
*
* @param string $Name If not null, specifies the name of the properties to return. If empty, all parameters
* will be returned
* @return array of PhpiCalLib_Parameter objects
*/
public function GetParameters($Type = null, $Name = null) {
if (empty($Type)) {
return empty($this->aParameters) ? array() : $this->aParameters;
}
$aParameters = array();
foreach ($this->aParameters as $Parameter) {
// Test the type
if ($Parameter->GetType() != $Type) continue;
// Also test the name if necessary
switch ($Type) {
case PHPICALLIB_PARAMETER_IANAPARAM:
case PHPICALLIB_PARAMETER_XPARAM:
// Should we filter by name too?
if (empty($Name)) {
$aParameters[] = $Parameter;
break;
}
// Must search by name
if ($Parameter->GetName() == $Name) {
$aParameters[] = $Parameter;
break;
}
break;
default:
$aParameters[] = $Parameter;
}
}
return $aParameters;
}
/**
* Get the parameter of the given name, returning a single PhpiCalLib object
*
* If there are multiple parameters of the given name, it will just return the first
*
* @param string $Name The name of the parameter to return
* @return string PhpiCalLib_Parameter object, or null if the parameter value isn't specified
*/
public function GetParameter($Type, $Name = null) {
if (empty($this->aParameters)) return null;
foreach ($this->aParameters as $Parameter) {
// Test the type
if ($Parameter->GetType() != $Type) continue;
// Also test the name if necessary
switch ($Type) {
case PHPICALLIB_PARAMETER_IANAPARAM:
case PHPICALLIB_PARAMETER_XPARAM:
// Should we filter by name too?
if (empty($Name)) {
return $Parameter;
}
// Must search by name
if ($Parameter->GetName() == $Name) {
return $Parameter;
}
break;
default:
return $Parameter;
}
}
// Not found.
return null;
}
/**
* Set the parameters
*
* @param array $aParameters
*/
public function SetParameters($aParameters) {
// Forget all the existing parameters
$this->aParameters = array();
// Call AddParameter to make sure each parameter is validated before addition
foreach ($aParameters as $aParameter) {
$this->AddParameter($aParameter);
}
// Check that we have all the parameters that were REQUIRED
if (!empty($this->aPermittedParameters)) {
foreach ($this->aPermittedParameters as $Type => $aTypeInfo) {
// If there's no lower limit, or a lower limit of 0, then no min threshold to check.
if ($aTypeInfo[0] <= 0) continue;
// Get all the properties of the give type
$aParameters = $this->GetParameters($Type);
// Check we have at least enough instances of the property
if (count($aParameters) < $aTypeInfo[0]) {
throw new PhpiCalLib_ParameterException(
sprintf("The %s parameters must occur %d times in the %s property and it only occurs %d times",
self::$TypeMap[$Type],
$aTypeInfo[0],
$this->Name,
count($aParameter)));
}
}
}
}
/**
* Removes all existing parameters of the given name, and adds the given parameter
*
* @param PhpiCalLib_Parameter $Parameter
*/
public function SetParameter($Parameter) {
// Check we are allowed to set this property
$this->SetParameterPermitted($Parameter);
// Remove it's current values, calling the private version to avoid
// failing if this property has to have at least one value.
$this->_RemoveParameter($Parameter->GetType(), $Parameter->GetName());
// Make sure we have a parameters collection
if (!$this->aParameters)
$this->aParameters = array();
// Add the new value
$this->aParameters[] = $Parameter;
}
/**
* Add a new parameter
*
* @param PhpiCalLib_Parameter $Parameter
*/
public function AddParameter($Parameter) {
// Check we are allowed to add this parameter
$this->AddParameterPermitted($Parameter);
// Make sure we have a parameters collection
if (!$this->aParameters)
$this->aParameters = array();
// Add the new value
$this->aParameters[] = $Parameter;
}
/**
* Removes all parameters of the given name
*
* @param string $Name
*/
public function RemoveParameter($Type, $Name = null) {
// If there's no parameters yet, this is easy
if (!$this->aParameters) return;
// Check we are allowed to remove this property
$this->RemoveParameterPermitted($Type, $Name);
$this->_RemoveParameter($Type,$Name);
}
/**
* Internal version of {@link RemoveParameter()} that doesn't validate if removal of the parameter is permitted
*
* @param integer $Type
* @param string $Name
*/
private function _RemoveParameter($Type, $Name) {
// Iterate through all parameters
for ($iIndex = count($this->aParameters) - 1; $iIndex >= 0; $iIndex--) {
$Parameter = $this->aParameters[$iIndex];
// Test the type
if ($Parameter->GetType() != $Type) continue;
// Also test the name if necessary
switch ($Type) {
case PHPICALLIB_PARAMETER_IANAPARAM:
case PHPICALLIB_PARAMETER_XPARAM:
// Should we filter by name too?
if (empty($Name)) {
array_splice($this->aParameters, $iIndex, 1);
break;
}
if ($Parameter->GetName() == $Name) {
array_splice($this->aParameters, $iIndex, 1);
break;
}
break;
default:
array_splice($this->aParameters, $iIndex, 1);
}
}
}
/**
* Access the property value as an encoded value
*
* @return string
*/
public function GetEncodedValue() {
return $this->EncodedValue;
}
/**
* Sets the property value by specifying the encoded value
*
* @param string $Value
*/
public function SetEncodedValue($Value) {
// Further parse the main value
// value = *VALUE-CHAR
// VALUE-CHAR = WSP / %x21-7E / NON-US-ASCII
// ==>
// value = *VALUE-CHAR
// VALUE-CHAR = %x09 / %x20-7E / NON-US-ASCII
$Pattern = '/^[\x09\x20-\x7E]*$/D';
if (!preg_match($Pattern, $Value)) {
$aTokens = preg_split('/[^\x09\x20-\x7E]/', $Value);
throw new PhpiCalLib_ParameterException(sprintf("Invalid value (%s) between these tokens '%s' in contentline: %s", $Pattern, implode("','", $aTokens), $Value));
}
$this->EncodedValue = $Value;
}
/**
* Create a new ContentLine object
*
* A component would override this class, providing a new version of this function
* in order to choose the right kind of class to create. The alternative is to create
* a generic ContentLine object, then us Copy() to establish the specialised version, but
* it's better to create the right object up front.
*
* @param string $Name The name of the contentline
* @return PhpiCalLib_ContentLine derived class
*/
protected function CreateContentLine($Name) {
$Result = new PhpiCalLib_ContentLine();
$Result->SetName($Name);
return $Result;
}
/**
* Parse a contentline from the unfolded string, returning the new object
*
* @param string $UnfoldedContentLine The unfolded line that we think represents a contentline
* @return A PhpiCalLib_ContentLine derived class, according to what CreateContentLine returned
*/
public function Create($UnfoldedContentLine) {
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
// contentline = name *(";" param ) ":" value CRLF
// ; This ABNF is just a general definition for an initial parsing
// ; of the content line into its property name, parameter list,
// ; and value string
//
// name = iana-token / x-name
// iana-token = 1*(ALPHA / DIGIT / "-")
// ; iCalendar identifier registered with IANA
// x-name = "X-" [vendorid "-"] 1*(ALPHA / DIGIT / "-")
// ; Reserved for experimental use.
// vendorid = 3*(ALPHA / DIGIT)
// ; Vendor identification
// param = param-name "=" param-value *("," param-value)
// ; Each property defines the specific ABNF for the parameters
// ; allowed on the property. Refer to specific properties for
// ; precise parameter ABNF.
// param-name = iana-token / x-name
// param-value = paramtext / quoted-string
// paramtext = *SAFE-CHAR
// quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
// SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E
// / NON-US-ASCII
// ; Any character except CONTROL, DQUOTE, ";", ":", ","
// http://tools.ietf.org/html/rfc2234#section-6.1
// ALPHA = %x41-5A / %x61-7A ; A-Z / a
// DIGIT = %x30-39
// ; 0-9
// DQUOTE = %x22
// ; " (Double Quote)
// HTAB = %x09
// ; horizontal tab
// LF = %x0A
// ; linefeed
// LWSP = *(WSP / CRLF WSP)
// ; linear white space (past newline)
// OCTET = %x00-FF
// ; 8 bits of data
// SP = %x20
// ; space
// VCHAR = %x21-7E
// ; visible (printing) characters
// WSP = SP / HTAB
// ; white space
// We'll break up the name, parameters and value in one go.
$ContentLineSplitPattern = sprintf('/^(%s)((;%s)*):(.*)$/D', self::$NameSplitRegex, PhpiCalLib_Parameter::GetParamRegex());
$aMatches = array();
if (!preg_match($ContentLineSplitPattern, $UnfoldedContentLine, $aMatches)) {
throw new PhpiCalLib_ParseException(sprintf("Doesn't match standard contentline pattern '%s': %s", $ContentLineSplitPattern, $UnfoldedContentLine));
}
// Extract the three components
$Name = $aMatches[1];
$Parameters = $aMatches[2];
$Value = $aMatches[count($aMatches)-1];
// Create the right kind of content line property to populate, assigning it's name
$ContentLine = $this->CreateContentLine($Name);
// Further parse the parameters before storage
if (!empty($Parameters)) {
// If we had parameters, then they must start with a ;
if ($Parameters[0] != ';') {
throw new PhpiCalLib_ParseException(sprintf("Expected to find a ';' to start the parameters: %s", $Parameters));
}
// While it would be nice to do a simple explode(';') it wouldn't be valid, as there can be ';' inside
// quoted-string arguments. So instead use a regex to peel off one parameter at a time.
$Pattern = sprintf('/^;(%s)((;.*)*)$/D',PhpiCalLib_Parameter::GetParamRegex());
$aMatches = array();
while (preg_match($Pattern, $Parameters, $aMatches)) {
$Parameters = $aMatches[count($aMatches) - 1];
$Parameter = $this->ParameterFactory->Create($aMatches[1]);
$ContentLine->AddParameter($Parameter);
}
// We should have no parameter text left
if (!empty($Parameters)) {
throw new PhpiCalLib_ParseException(sprintf("Failed to extract parameters (%s) from: %s", $Pattern, $Parameters));
}
}
$ContentLine->SetEncodedValue($Value);
// We're done :o)
return $ContentLine;
}
/**
* Convert the content line to a string
*
* @return A string representation in the default code page
*/
public function ToString() {
if (empty($this->Name)) {
throw new PhpiCalLib_Exception(sprintf('No name assigned to this parameter yet. It has value: %s',$this->EncodedValue));
}
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
// contentline = name *(";" param ) ":" value CRLF
$aParameters = array();
if ($this->aParameters) {
foreach ($this->aParameters as $Parameter) {
$aParameters[] = ";";
$aParameters[] = $Parameter->ToString();
}
}
return sprintf("%s%s:%s\r\n", $this->Name, implode('', $aParameters), $this->EncodedValue);
}
/**
* Determine if the given parameter can be changed
*
* @param PhpiCalLib_Parameter $ContentLine
*/
protected function SetParameterPermitted($Parameter) {
// If there's no permitted parameter info, then we can't validate, so assume it's ok
if (empty($this->aPermittedParameters)) return;
// Get the parameter info
$TypeId = $Parameter->GetType();
if (empty($this->aPermittedParameters[$TypeId])) {
throw new PhpiCalLib_ParameterException(
sprintf("The %s parameter is not permitted within the %s property",
$Parameter->GetName(),
$this->GetName()));
}
}
/**
* Determine if the given parameter is permitted for addition
*
* @param PhpiCalLib_Parameter $Parameter
*/
protected function AddParameterPermitted($Parameter) {
// If there's no permitted parameter info, then we can't validate, so assume it's ok
if (empty($this->aPermittedParameters)) return;
// Get the parameter info
$TypeId = $Parameter->GetType();
if (empty($this->aPermittedParameters[$TypeId])) {
throw new PhpiCalLib_ParameterException(
sprintf("The %s parameter is not permitted within the %s property",
$Parameter->GetName(),
$this->GetName()));
}
// Check the max instances is respected
$aParameterInfo = $this->aPermittedParameters[$TypeId];
if ($aParameterInfo[1] == 1) {
// If we are only allowed one of these parameters, and we already have one, then fail
$Parameter = $this->GetParameter($TypeId);
if (isset($Parameter)) {
throw new PhpiCalLib_ParameterException(
sprintf("The %s parameter is only permitted once within the %s property and already has value %s",
$Parameter->GetName(),
$this->GetName(),
$Parameter->GetEncodedValue()));
}
} else if ($aParameterInfo[1] > 1) {
// If there's a max limit, then check we aren't at our max limit already
$aParameters = $this->GetParameters($TypeId);
if (count($aParameters) >= $aParameterInfo[1]) {
throw new PhpiCalLib_ParameterException(
sprintf("The %s parameter is only permitted %d times within the %s property and already has %d values",
$Parameter->GetName(),
$aParameterInfo[1],
$this->GetName(),
count($aParameters)));
}
}
}
/**
* Determine if we are allowed to remove the given parameter
*
* @param integer $Type
* @param string $Name
*/
protected function RemoveParameterPermitted($Type, $Name) {
// If there's no permitted parameter info, then we can't validate, so assume it's ok
if (empty($this->aPermittedParameters)) return;
// Get the parameter info
if (empty($this->aPermittedParameters[$Type])) {
// They'd never have been allowed to add the parameter, so they are welcome to remove it.
return;
}
$aParameterInfo = $this->aPermittedParameters[$Type];
// If removing this parameter would drop us below our lower limit, then fail
if ($aParameterInfo[0] == 1) {
throw new PhpiCalLib_ParameterException(
sprintf("The %s (%s) parameter is REQUIRED for the %s property. Call SetParameter to change it's value.",
self::$TypeMap[$Type],
$Name,
$this->Name));
} else if ($aParameterInfo[0] > 0) {
// There's a min number of instances, check we aren't at that lower limit already
$aParameters = $this->GetParameters($Type);
if (count($aParameters) <= $aParameterInfo[0]) {
throw new PhpiCalLib_ParameterException(
sprintf("The %s (%s) parameter is REQUIRED at least %d times for the %s property. Call SetParameter/SetParameters/AddParameter to change/add values.",
self::$TypeMap[$Type],
$Name,
$aParameterInfo[0],
$this->Name));
}
}
}
}