<?php
/**
* parameters.php iCalendar parameter classes
*
* 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
* @package PhpiCalLib
* @version 1.0
*/
require_once 'exceptions.php';
require_once 'datatypes.php';
$iPHPICALLIB_PARAMETERINDEX = 1;
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2
// 3.2. Property Parameters
define('PHPICALLIB_PARAMETER_IANAPARAM', $iPHPICALLIB_PARAMETERINDEX++);
define('PHPICALLIB_PARAMETER_XPARAM', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.1
// 3.2.1. Alternate Text Representation . . . . . . . . . . . . 14
define('PHPICALLIB_PARAMETER_ALTREP', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.2
// 3.2.2. Common Name . . . . . . . . . . . . . . . . . . . . . 15
define('PHPICALLIB_PARAMETER_CN', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.3
// 3.2.3. Calendar User Type . . . . . . . . . . . . . . . . . 16
define('PHPICALLIB_PARAMETER_CUTYPE', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.4
// 3.2.4. Delegators . . . . . . . . . . . . . . . . . . . . . 16
define('PHPICALLIB_PARAMETER_DELEGATED_FROM', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.5
// 3.2.5. Delegatees . . . . . . . . . . . . . . . . . . . . . 17
define('PHPICALLIB_PARAMETER_DELEGATED_TO', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.6
// 3.2.6. Directory Entry Reference . . . . . . . . . . . . . . 18
define('PHPICALLIB_PARAMETER_DIR', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.7
// 3.2.7. Inline Encoding . . . . . . . . . . . . . . . . . . . 18
define('PHPICALLIB_PARAMETER_ENCODING', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.8
// 3.2.8. Format Type . . . . . . . . . . . . . . . . . . . . . 19
define('PHPICALLIB_PARAMETER_FMTTYPE', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.9
// 3.2.9. Free/Busy Time Type . . . . . . . . . . . . . . . . . 19
define('PHPICALLIB_PARAMETER_FBTYPE', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.10
// 3.2.10. Language . . . . . . . . . . . . . . . . . . . . . . 20
define('PHPICALLIB_PARAMETER_LANGUAGE', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.11
// 3.2.11. Group or List Membership . . . . . . . . . . . . . . 21
define('PHPICALLIB_PARAMETER_MEMBER', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.12
// 3.2.12. Participation Status . . . . . . . . . . . . . . . . 21
define('PHPICALLIB_PARAMETER_PARTSTAT', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.13
// 3.2.13. Recurrence Identifier Range . . . . . . . . . . . . . 24
define('PHPICALLIB_PARAMETER_RANGE', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.14
// 3.2.14. Alarm Trigger Relationship . . . . . . . . . . . . . 24
define('PHPICALLIB_PARAMETER_RELATED', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.15
// 3.2.15. Relationship Type . . . . . . . . . . . . . . . . . . 25
define('PHPICALLIB_PARAMETER_RELTYPE', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.16
// 3.2.16. Participation Role . . . . . . . . . . . . . . . . . 26
define('PHPICALLIB_PARAMETER_ROLE', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.17
// 3.2.17. RSVP Expectation . . . . . . . . . . . . . . . . . . 27
define('PHPICALLIB_PARAMETER_RSVP', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.18
// 3.2.18. Sent By . . . . . . . . . . . . . . . . . . . . . . . 27
define('PHPICALLIB_PARAMETER_SENT_BY', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.19
// 3.2.19. Time Zone Identifier . . . . . . . . . . . . . . . . 28
define('PHPICALLIB_PARAMETER_TZID', $iPHPICALLIB_PARAMETERINDEX++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.20
// 3.2.20. Value Data Types . . . . . . . . . . . . . . . . . . 29
define('PHPICALLIB_PARAMETER_VALUE', $iPHPICALLIB_PARAMETERINDEX++);
/**
* Base class for all contentline parameters
*
*/
class PhpiCalLib_Parameter {
/**
* The "param-name" token
* @var string
*/
protected $Name = '';
/**
* @var integer The type of this parameter
*/
protected $Type = 0;
/**
* The "param-value" tokens
* @var array: of strings
*/
protected $aValues = array();
/**
* Map a property type to it's string property name.
*
* @var array Keyed by PHPICALLIB_COMPONENT_?, value is a string
*/
private static $TypeMap = array(
PHPICALLIB_PARAMETER_ALTREP => 'ALTREP',
PHPICALLIB_PARAMETER_CN => 'CN',
PHPICALLIB_PARAMETER_CUTYPE => 'CUTYPE',
PHPICALLIB_PARAMETER_DELEGATED_FROM => 'DELEGATED-FROM',
PHPICALLIB_PARAMETER_DELEGATED_TO => 'DELEGATED-TO',
PHPICALLIB_PARAMETER_DIR => 'DIR',
PHPICALLIB_PARAMETER_ENCODING => 'ENCODING',
PHPICALLIB_PARAMETER_FMTTYPE => 'FMTTYPE',
PHPICALLIB_PARAMETER_FBTYPE => 'FBTYPE',
PHPICALLIB_PARAMETER_LANGUAGE => 'LANGUAGE',
PHPICALLIB_PARAMETER_MEMBER => 'MEMBER',
PHPICALLIB_PARAMETER_PARTSTAT => 'PARTSTAT',
PHPICALLIB_PARAMETER_RANGE => 'RANGE',
PHPICALLIB_PARAMETER_RELATED => 'RELATED',
PHPICALLIB_PARAMETER_RELTYPE => 'RELTYPE',
PHPICALLIB_PARAMETER_ROLE => 'ROLE',
PHPICALLIB_PARAMETER_RSVP => 'RSVP',
PHPICALLIB_PARAMETER_SENT_BY => 'SENT-BY',
PHPICALLIB_PARAMETER_TZID => 'TZID',
PHPICALLIB_PARAMETER_VALUE => 'VALUE');
// array_flip() on $TypeMap, built on first use.
private static $ReverseTypeMap = null;
/**
* 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
*/
static public $IanaTokenRegex = '[a-zA-Z0-9-]+';
/**
* A regular express to match the SAFE-CHAR token.
* <pre>
* SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E
* / NON-US-ASCII
* ; Any character except CONTROL, DQUOTE, ";", ":", ","
* ==>
* ### Note we are going to assume that the utf-8 decoding has been done for us already and that
* our string is essentially in unicode
* SAFE-CHAR = %x09 / %x20-21 / %x23-2B / %x2D-39 / %x3C-7E
* </pre>
* @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
* @var string
*/
static public $SafeCharRegex = '[\x09\x20\x21\x23-\x2B\x2D-\x39\x3C-\x7E]';
/**
* An array to locate the converse of {@link SafeCharRegex}.
*
* @var string
* @see SafeCharRegex
*/
static public $UnSafeCharRegex = '[^\x09\x20\x21\x23-\x2B\x2D-\x39\x3C-\x7E]';
/**
* A regular expression to match the QSAFE-CHAR token.
* <pre>
* QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-US-ASCII
* ==>
* ### Note we are going to assume that the utf-8 decoding has been done for us already and that
* our string is essentially in unicode
* QSAFE-CHAR = %x09 / %x20-21 / %x23-7E
* </pre>
* @var string
* @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
*/
static public $QSafeCharRegex = '[\x09\x20\x21\x23-\x7E]';
/**
* A quick regular expression to validate a param token.
* <pre>
* ==>
* param = param-name "=" param-value *("," param-value)
* param-name = iana-token / x-name
* </pre>
* @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
* @return string
*/
static public function GetParamRegex() {
return sprintf('%s=%s(,%s)*',self::$IanaTokenRegex,self::GetParamValueRegex(), self::GetParamValueRegex());
}
/**
* A quick regular expression to validate a param-value
* param-value = paramtext / quoted-string
* paramtext = *SAFE-CHAR
* ; Any character except CONTROL, DQUOTE, ";", ":", ","
* quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
* ; Any character except CONTROL and DQUOTE
* </pre>
* @
* @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
* @return string
*/
private static function GetParamValueRegex() {
return sprintf('(%s*|"%s*")',self::$SafeCharRegex,self::$QSafeCharRegex);
}
static public $BadParamSplitRegex = '[^\x09\x20-\x39\x3c-\x7e]';
/**
* Constructor
*/
public function __construct() {
$this->aValues = array();
}
/**
* Convert a parameter name to it's defined type
*
* @param string $Name a "name" RFC2445 token
* @return int one of the PHPICALLIB_PARAMETER_* properties
*/
static public function ToParameterType($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-comp that we don't really recognize.
if (preg_match('/^X-.*$/', $Name)) {
return PHPICALLIB_PARAMETER_XPARAM;
}
return PHPICALLIB_PARAMETER_IANAPARAM;
}
/**
* Create a new Parameter 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 which will offer better validation.
*
* @param string $Name The name of the param
* @return PhpiCalLib_Parameter derived class
*/
protected function CreateParameter($Name) {
$Result = new PhpiCalLib_Parameter();
$Result->SetName($Name);
return $Result;
}
/**
* Parse a param from the string, returning the new object
*
* @param string $String The encoded value that represents the parameter
* @return A PhpiCalLib_Parameter derived class, according to what CreateParameter returned
*/
public function Create($Param) {
// Do a quick check first
$Pattern = sprintf('/^%s$/D', self::GetParamRegex());
if (!preg_match($Pattern, $Param)) {
throw new PhpiCalLib_ParseException(sprintf("Invalid param (%s) token: %s", $Pattern, $Param));
}
// Split out the param-name
// param = param-name "=" param-value *("," param-value)
// param-name = iana-token / x-name
$Pattern = '/^([a-zA-Z0-9-]+)=(.*)$/D';
$aMatches = array();
if (!preg_match($Pattern, $Param, $aMatches)) {
throw new PhpiCalLib_ParseException(sprintf("Invalid param (%s) in contentline: %s", $Pattern, $Param));
}
$Name = $aMatches[1];
$aValues = array();
// Check we got at least one param-value. Note ',' is valid inside a param-value by means of quoted-string, but
// we'll address that later.
$aParamValues = explode(",",$aMatches[2]);
// Process each of the param-values
// param-value = paramtext / quoted-string
// paramtext = *SAFE-CHAR
// quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
while (!empty($aParamValues)) {
$ParamValue = array_shift($aParamValues);
// If the value is empty, that's legitimate, store and continue;
if (empty($ParamValue)) {
$aValues[] = '';
continue;
}
// If it starts with a DQUOTE, then it's a quoted string
if ($ParamValue[0] == '"') {
// A ',' is valid inside a param-value by means of a a quoted-string, but we explode(',') to begin with
// so we may have to stitch together the quoted-strings again
while (strlen($ParamValue) == 1 || substr($ParamValue, -1) != '"') {
if (empty($aParamValues)) {
throw new PhpiCalLib_ParseException(sprintf("Invalid quoted-string (%s) in param: %s", $Pattern, $ParamValue));
}
$ParamValue = sprintf("%s,%s", $ParamValue, array_shift($aParamValues));
}
// It's a quoted-string
// quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
// QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-US-ASCII
$Pattern = sprintf('/^"(%s*)"$/D', self::$QSafeCharRegex);
$aMatches = array();
if (!preg_match($Pattern, $ParamValue, $aMatches)) {
throw new PhpiCalLib_ParseException(sprintf("Invalid quoted-string (%s) in param: %s", $Pattern, $ParamValue));
}
$aValues[] = $aMatches[1];
} else {
// It's a sraight text value
// paramtext = *SAFE-CHAR
// SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E
// / NON-US-ASCII
$Pattern = sprintf('/^%s*$/D', self::$SafeCharRegex);
if (!preg_match($Pattern, $ParamValue)) {
throw new PhpiCalLib_ParseException(sprintf("Invalid paramtext (%s) in param: %s", $Pattern, $ParamValue));
}
$aValues[] = $ParamValue;
}
}
// Create the right kind of content line property to populate, assigning it's name
$Result = $this->CreateParameter($Name);
$Result->SetValues($aValues);
return $Result;
}
/**
* Access the name of the parameter
*
* @return string
*/
public function GetName() {
if (empty($this->Name)) {
throw new PhpiCalLib_Exception('No name assigned to this parameter yet');
}
return $this->Name;
}
/**
* Set the name of the parameter
*
* @param string $Name
*/
public function SetName($Name) {
// Further parse the name before storage.
// param-name = iana-token / x-name
// iana-token = 1*(ALPHA / DIGIT / "-")
// x-name = "X-" [vendorid "-"] 1*(ALPHA / DIGIT / "-")
// vendorid = 3*(ALPHA / DIGIT)
// Note an invalid x-name becomes an iana-token, so we just check for valid iana-tokens
$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-param
$this->Name = $Name;
$this->Type = PHPICALLIB_PARAMETER_XPARAM;
} else {
// iana-param
$this->Type = $this->ToParameterType($Name);
$this->Name = $Name;
}
}
/**
* Access the component type
*
* Note this will return PHPICALLIB_PARAMETER_IANAPARAM for components that it doesn't recognize
*
* @return PHPICALLIB_PARAMETER_*
*/
public function GetType() {
return $this->Type;
}
/**
* Set the type of the component
*
* @param integer $Type
*/
public function SetType($Type) {
// Check they aren't trying to set this to an x-comp or an iana-comp
switch ($Type) {
case PHPICALLIB_PARAMETER_IANAPARAM:
case PHPICALLIB_PARAMETER_XPARAM:
throw new PhpiCalLib_Exception('Can\'t set parameter type to iana-param or x-param. Use SetName instead');
}
// Pull out the name, note if we ever add a PHPICALLIB_PARAMETER_? and forget to amend $TypeMap, then
// this can start to fail
$this->Name = self::$TypeMap[$Type];
// Store the type.
$this->Type = $Type;
}
/**
* Get all the values of this parameter
*
* @return array Of the parameter values
*/
public function GetValues() {
return $this->aValues;
}
/**
* Add another parameter value
*
* @param string $ParamValue
*/
public function AddValue($ParamValue) {
// If the value is empty, that's legitimate, store and continue;
if (empty($ParamValue)) {
$this->aValues[] = '';
return;
}
// We'll be able to store paramtext or quoted-string.
// quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
// QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-US-ASCII
// paramtext = *SAFE-CHAR
// SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E
// / NON-US-ASCII
// quoted-strings are more flexible, so we check it could be a valid quoted string
$Pattern = sprintf('/^(%s*)$/D', self::$QSafeCharRegex);
$aMatches = array();
if (!preg_match($Pattern, $ParamValue, $aMatches)) {
throw new PhpiCalLib_ParseException(sprintf("Invalid quoted-string (%s) in param: %s", $Pattern, $ParamValue));
}
$this->aValues[] = $ParamValue;
}
/**
* Set the new property values.
*
* @param array $aValues The new parameter values, to replace the existing values.
*/
public function SetValues($aValues) {
// Empty the existing values
$this->aValues = array();
foreach ($aValues as $Value) {
$this->AddValue($Value);
}
}
/**
* Get the value of this single value property
*
* If the property has more than one value, it returns the first.
* If it has no values, it returns an empty string
* @return string
*/
public function GetValue() {
if (empty($this->aValues)) return '';
return $this->aValues[0];
}
/**
* Set the values to a single new value
*
* @param string $Value
*/
public function SetValue($Value) {
$this->SetValues(array($Value));
}
/**
* Dump the parameter to a string
*
* @return A string representation in the default code page
*/
public function ToString() {
// param = param-name "=" param-value *("," param-value)
$aParamValues = array();
foreach ($this->aValues as $Value) {
// If the string contains any unsafe chars, we must print it as a quoted-string
$Pattern = sprintf('/%s/',self::$UnSafeCharRegex);
if (preg_match($Pattern, $Value)) {
$aParamValues[] = sprintf('"%s"', $Value);
} else {
$aParamValues[] = $Value;
}
}
return sprintf("%s=%s", $this->Name, implode(',', $aParamValues));
}
}
/**
* The VALUE parameter.
* <pre>
* valuetypeparam = "VALUE" "=" valuetype
* valuetype = ("BINARY"
* / "BOOLEAN"
* / "CAL-ADDRESS"
* / "DATE"
* / "DATE-TIME"
* / "DURATION"
* / "FLOAT"
* / "INTEGER"
* / "PERIOD"
* / "RECUR"
* / "TEXT"
* / "TIME"
* / "URI"
* / "UTC-OFFSET"
* / x-name
* ; Some experimental iCalendar value type.
* / iana-token)
* ; Some other IANA registered iCalendar value type.
* </pre>
* @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.2.20
*/
class PhpiCalLib_Parameters_Value extends PhpiCalLib_Parameter {
public function __construct() {
parent::__construct();
$this->SetType(PHPICALLIB_PARAMETER_VALUE);
}
/**
* Overridden because this property only allows one value
*
* @param string $Value The new value
*/
public function AddValue($Value) {
if (count($this->aValues) >= 1) {
throw new PhpiCalLib_ParameterException('The VALUE parameter can only have one value');
}
// valuetype = ("BINARY"
// / "BOOLEAN"
// / "CAL-ADDRESS"
// / "DATE"
// / "DATE-TIME"
// / "DURATION"
// / "FLOAT"
// / "INTEGER"
// / "PERIOD"
// / "RECUR"
// / "TEXT"
// / "TIME"
// / "URI"
// / "UTC-OFFSET"
// / x-name
// ; Some experimental iCalendar value type.
// / iana-token)
// ; Some other IANA registered iCalendar value type.
// Note an invalid x-name becomes an iana-token, so we just check for valid iana-tokens
// iana-token
$Pattern = sprintf("/^%s$/D", PhpiCalLib_DataType::$IanaTokenRegex);
if (!preg_match($Pattern, $Value)) {
throw new PhpiCalLib_ParameterException(sprintf("Invalid iana-token (%s) in value parameter: %s", $Pattern, $Value));
}
// Carry on and add the parameter
parent::AddValue($Value);
}
/**
* Access the data type id
*
* @return integer One of the PHPICALLIB_DATATYPE_* values
*/
public function ToDataType() {
return PhpiCalLib_DataType::ToDataType($this->aValues[0]);
}
/**
* Set the parameter value by specifying the data type
*
* @param integer $DataTypeId
*/
public function FromDataType($DataTypeId) {
$this->SetValue(PhpiCalLib_DataType::FromDataType($DataTypeId));
}
}
?>