mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-30 20:49:20 +03:00
Remote Synchronization (CardDAV) improvements:
now it supports address book url auto detection (and works with Google contacts, icloud and etc.)
This commit is contained in:
parent
92f2caf700
commit
1fb553e77e
249 changed files with 1338 additions and 1453 deletions
735
rainloop/v/0.0.0/app/libraries/SabreForRainLoop/VObject/Cli.php
Normal file
735
rainloop/v/0.0.0/app/libraries/SabreForRainLoop/VObject/Cli.php
Normal file
|
|
@ -0,0 +1,735 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
use
|
||||
InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* This is the CLI interface for sabre-vobject.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Cli {
|
||||
|
||||
/**
|
||||
* No output
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $quiet = false;
|
||||
|
||||
/**
|
||||
* Help display
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $showHelp = false;
|
||||
|
||||
/**
|
||||
* Wether to spit out 'mimedir' or 'json' format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $format;
|
||||
|
||||
/**
|
||||
* JSON pretty print
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $pretty;
|
||||
|
||||
/**
|
||||
* Source file
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $inputPath;
|
||||
|
||||
/**
|
||||
* Destination file
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $outputPath;
|
||||
|
||||
/**
|
||||
* output stream
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $stdout;
|
||||
|
||||
/**
|
||||
* stdin
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $stdin;
|
||||
|
||||
/**
|
||||
* stderr
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $stderr;
|
||||
|
||||
/**
|
||||
* Input format (one of json or mimedir)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $inputFormat;
|
||||
|
||||
/**
|
||||
* Makes the parser less strict.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $forgiving = false;
|
||||
|
||||
/**
|
||||
* Main function
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function main(array $argv) {
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
// We cannot easily test this, so we'll skip it. Pretty basic anyway.
|
||||
|
||||
if (!$this->stderr) {
|
||||
$this->stderr = STDERR;
|
||||
}
|
||||
if (!$this->stdout) {
|
||||
$this->stdout = STDOUT;
|
||||
}
|
||||
if (!$this->stdin) {
|
||||
$this->stdin = STDIN;
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
|
||||
try {
|
||||
|
||||
list($options, $positional) = $this->parseArguments($argv);
|
||||
|
||||
if (isset($options['q'])) {
|
||||
$this->quiet = true;
|
||||
}
|
||||
$this->log($this->colorize('green', "sabre-vobject ") . $this->colorize('yellow', Version::VERSION));
|
||||
|
||||
foreach($options as $name=>$value) {
|
||||
|
||||
switch($name) {
|
||||
|
||||
case 'q' :
|
||||
// Already handled earlier.
|
||||
break;
|
||||
case 'h' :
|
||||
case 'help' :
|
||||
$this->showHelp();
|
||||
return 0;
|
||||
break;
|
||||
case 'format' :
|
||||
switch($value) {
|
||||
|
||||
// jcard/jcal documents
|
||||
case 'jcard' :
|
||||
case 'jcal' :
|
||||
|
||||
// specific document versions
|
||||
case 'vcard21' :
|
||||
case 'vcard30' :
|
||||
case 'vcard40' :
|
||||
case 'icalendar20' :
|
||||
|
||||
// specific formats
|
||||
case 'json' :
|
||||
case 'mimedir' :
|
||||
|
||||
// icalendar/vcad
|
||||
case 'icalendar' :
|
||||
case 'vcard' :
|
||||
$this->format = $value;
|
||||
break;
|
||||
|
||||
default :
|
||||
throw new InvalidArgumentException('Unknown format: ' . $value);
|
||||
|
||||
}
|
||||
break;
|
||||
case 'pretty' :
|
||||
if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
|
||||
$this->pretty = true;
|
||||
}
|
||||
break;
|
||||
case 'forgiving' :
|
||||
$this->forgiving = true;
|
||||
break;
|
||||
case 'inputformat' :
|
||||
switch($value) {
|
||||
// json formats
|
||||
case 'jcard' :
|
||||
case 'jcal' :
|
||||
case 'json' :
|
||||
$this->inputFormat = 'json';
|
||||
break;
|
||||
|
||||
// mimedir formats
|
||||
case 'mimedir' :
|
||||
case 'icalendar' :
|
||||
case 'vcard' :
|
||||
case 'vcard21' :
|
||||
case 'vcard30' :
|
||||
case 'vcard40' :
|
||||
case 'icalendar20' :
|
||||
|
||||
$this->inputFormat = 'mimedir';
|
||||
break;
|
||||
|
||||
default :
|
||||
throw new InvalidArgumentException('Unknown format: ' . $value);
|
||||
|
||||
}
|
||||
break;
|
||||
default :
|
||||
throw new InvalidArgumentException('Unknown option: ' . $name);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (count($positional) === 0) {
|
||||
$this->showHelp();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (count($positional) === 1) {
|
||||
throw new InvalidArgumentException('Inputfile is a required argument');
|
||||
}
|
||||
|
||||
if (count($positional) > 3) {
|
||||
throw new InvalidArgumentException('Too many arguments');
|
||||
}
|
||||
|
||||
if (!in_array($positional[0], array('validate','repair','convert','color'))) {
|
||||
throw new InvalidArgumentException('Uknown command: ' . $positional[0]);
|
||||
}
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$this->showHelp();
|
||||
$this->log('Error: ' . $e->getMessage(),'red');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$command = $positional[0];
|
||||
|
||||
$this->inputPath = $positional[1];
|
||||
$this->outputPath = isset($positional[2])?$positional[2]:'-';
|
||||
|
||||
if ($this->outputPath !== '-') {
|
||||
$this->stdout = fopen($this->outputPath,'w');
|
||||
}
|
||||
|
||||
if (!$this->inputFormat) {
|
||||
if (substr($this->inputPath,-5)==='.json') {
|
||||
$this->inputFormat = 'json';
|
||||
} else {
|
||||
$this->inputFormat = 'mimedir';
|
||||
}
|
||||
}
|
||||
if (!$this->format) {
|
||||
if (substr($this->outputPath,-5)==='.json') {
|
||||
$this->format = 'json';
|
||||
} else {
|
||||
$this->format = 'mimedir';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$realCode = 0;
|
||||
|
||||
try {
|
||||
|
||||
while($input = $this->readInput()) {
|
||||
|
||||
$returnCode = $this->$command($input);
|
||||
if ($returnCode!==0) $realCode = $returnCode;
|
||||
|
||||
}
|
||||
|
||||
} catch (EofException $e) {
|
||||
// end of file
|
||||
} catch (\Exception $e) {
|
||||
$this->log('Error: ' . $e->getMessage(),'red');
|
||||
return 2;
|
||||
}
|
||||
|
||||
return $realCode;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the help message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function showHelp() {
|
||||
|
||||
$this->log('Usage:', 'yellow');
|
||||
$this->log(" vobject [options] command [arguments]");
|
||||
$this->log('');
|
||||
$this->log('Options:', 'yellow');
|
||||
$this->log($this->colorize('green', ' -q ') . "Don't output anything.");
|
||||
$this->log($this->colorize('green', ' -help -h ') . "Display this help message.");
|
||||
$this->log($this->colorize('green', ' --format ') . "Convert to a specific format. Must be one of: vcard, vcard21,");
|
||||
$this->log($this->colorize('green', ' --forgiving ') . "Makes the parser less strict.");
|
||||
$this->log(" vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir.");
|
||||
$this->log($this->colorize('green', ' --inputformat ') . "If the input format cannot be guessed from the extension, it");
|
||||
$this->log(" must be specified here.");
|
||||
// Only PHP 5.4 and up
|
||||
if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
|
||||
$this->log($this->colorize('green', ' --pretty ') . "json pretty-print.");
|
||||
}
|
||||
$this->log('');
|
||||
$this->log('Commands:', 'yellow');
|
||||
$this->log($this->colorize('green', ' validate') . ' source_file Validates a file for correctness.');
|
||||
$this->log($this->colorize('green', ' repair') . ' source_file [output_file] Repairs a file.');
|
||||
$this->log($this->colorize('green', ' convert') . ' source_file [output_file] Converts a file.');
|
||||
$this->log($this->colorize('green', ' color') . ' source_file Colorize a file, useful for debbugging.');
|
||||
$this->log(<<<HELP
|
||||
|
||||
If source_file is set as '-', STDIN will be used.
|
||||
If output_file is omitted, STDOUT will be used.
|
||||
All other output is sent to STDERR.
|
||||
|
||||
HELP
|
||||
);
|
||||
|
||||
$this->log('Examples:', 'yellow');
|
||||
$this->log(' vobject convert contact.vcf contact.json');
|
||||
$this->log(' vobject convert --format=vcard40 old.vcf new.vcf');
|
||||
$this->log(' vobject convert --inputformat=json --format=mimedir - -');
|
||||
$this->log(' vobject color calendar.ics');
|
||||
$this->log('');
|
||||
$this->log('https://github.com/fruux/sabre-vobject','purple');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a VObject file
|
||||
*
|
||||
* @param Component $vObj
|
||||
* @return int
|
||||
*/
|
||||
protected function validate($vObj) {
|
||||
|
||||
$returnCode = 0;
|
||||
|
||||
switch($vObj->name) {
|
||||
case 'VCALENDAR' :
|
||||
$this->log("iCalendar: " . (string)$vObj->VERSION);
|
||||
break;
|
||||
case 'VCARD' :
|
||||
$this->log("vCard: " . (string)$vObj->VERSION);
|
||||
break;
|
||||
}
|
||||
|
||||
$warnings = $vObj->validate();
|
||||
if (!count($warnings)) {
|
||||
$this->log(" No warnings!");
|
||||
} else {
|
||||
|
||||
$returnCode = 2;
|
||||
foreach($warnings as $warn) {
|
||||
|
||||
$this->log(" " . $warn['message']);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $returnCode;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Repairs a VObject file
|
||||
*
|
||||
* @param Component $vObj
|
||||
* @return int
|
||||
*/
|
||||
protected function repair($vObj) {
|
||||
|
||||
$returnCode = 0;
|
||||
|
||||
switch($vObj->name) {
|
||||
case 'VCALENDAR' :
|
||||
$this->log("iCalendar: " . (string)$vObj->VERSION);
|
||||
break;
|
||||
case 'VCARD' :
|
||||
$this->log("vCard: " . (string)$vObj->VERSION);
|
||||
break;
|
||||
}
|
||||
|
||||
$warnings = $vObj->validate(Node::REPAIR);
|
||||
if (!count($warnings)) {
|
||||
$this->log(" No warnings!");
|
||||
} else {
|
||||
foreach($warnings as $warn) {
|
||||
|
||||
$returnCode = 2;
|
||||
$this->log(" " . $warn['message']);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
fwrite($this->stdout, $vObj->serialize());
|
||||
|
||||
return $returnCode;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a vObject file to a new format.
|
||||
*
|
||||
* @param Component $vObj
|
||||
* @return int
|
||||
*/
|
||||
protected function convert($vObj) {
|
||||
|
||||
$json = false;
|
||||
$convertVersion = null;
|
||||
$forceInput = null;
|
||||
|
||||
switch($this->format) {
|
||||
case 'json' :
|
||||
$json = true;
|
||||
if ($vObj->name === 'VCARD') {
|
||||
$convertVersion = Document::VCARD40;
|
||||
}
|
||||
break;
|
||||
case 'jcard' :
|
||||
$json = true;
|
||||
$forceInput = 'VCARD';
|
||||
$convertVersion = Document::VCARD40;
|
||||
break;
|
||||
case 'jcal' :
|
||||
$json = true;
|
||||
$forceInput = 'VCALENDAR';
|
||||
break;
|
||||
case 'mimedir' :
|
||||
case 'icalendar' :
|
||||
case 'icalendar20' :
|
||||
case 'vcard' :
|
||||
break;
|
||||
case 'vcard21' :
|
||||
$convertVersion = Document::VCARD21;
|
||||
break;
|
||||
case 'vcard30' :
|
||||
$convertVersion = Document::VCARD30;
|
||||
break;
|
||||
case 'vcard40' :
|
||||
$convertVersion = Document::VCARD40;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if ($forceInput && $vObj->name !== $forceInput) {
|
||||
throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format);
|
||||
}
|
||||
if ($convertVersion) {
|
||||
$vObj = $vObj->convert($convertVersion);
|
||||
}
|
||||
if ($json) {
|
||||
$jsonOptions = 0;
|
||||
if ($this->pretty) {
|
||||
$jsonOptions = JSON_PRETTY_PRINT;
|
||||
}
|
||||
fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
|
||||
} else {
|
||||
fwrite($this->stdout, $vObj->serialize());
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorizes a file
|
||||
*
|
||||
* @param Component $vObj
|
||||
* @return int
|
||||
*/
|
||||
protected function color($vObj) {
|
||||
|
||||
fwrite($this->stdout, $this->serializeComponent($vObj));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ansi color string for a color name.
|
||||
*
|
||||
* @param string $color
|
||||
* @return string
|
||||
*/
|
||||
protected function colorize($color, $str, $resetTo = 'default') {
|
||||
|
||||
$colors = array(
|
||||
'cyan' => '1;36',
|
||||
'red' => '1;31',
|
||||
'yellow' => '1;33',
|
||||
'blue' => '0;34',
|
||||
'green' => '0;32',
|
||||
'default' => '0',
|
||||
'purple' => '0;35',
|
||||
);
|
||||
return "\033[" . $colors[$color] . 'm' . $str . "\033[".$colors[$resetTo]."m";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes out a string in specific color.
|
||||
*
|
||||
* @param string $color
|
||||
* @param string $str
|
||||
* @return void
|
||||
*/
|
||||
protected function cWrite($color, $str) {
|
||||
|
||||
fwrite($this->stdout, $this->colorize($color, $str));
|
||||
|
||||
}
|
||||
|
||||
protected function serializeComponent(Component $vObj) {
|
||||
|
||||
$this->cWrite('cyan', 'BEGIN');
|
||||
$this->cWrite('red', ':');
|
||||
$this->cWrite('yellow', $vObj->name . "\n");
|
||||
|
||||
/**
|
||||
* Gives a component a 'score' for sorting purposes.
|
||||
*
|
||||
* This is solely used by the childrenSort method.
|
||||
*
|
||||
* A higher score means the item will be lower in the list.
|
||||
* To avoid score collisions, each "score category" has a reasonable
|
||||
* space to accomodate elements. The $key is added to the $score to
|
||||
* preserve the original relative order of elements.
|
||||
*
|
||||
* @param int $key
|
||||
* @param array $array
|
||||
* @return int
|
||||
*/
|
||||
$sortScore = function($key, $array) {
|
||||
|
||||
if ($array[$key] instanceof Component) {
|
||||
|
||||
// We want to encode VTIMEZONE first, this is a personal
|
||||
// preference.
|
||||
if ($array[$key]->name === 'VTIMEZONE') {
|
||||
$score=300000000;
|
||||
return $score+$key;
|
||||
} else {
|
||||
$score=400000000;
|
||||
return $score+$key;
|
||||
}
|
||||
} else {
|
||||
// Properties get encoded first
|
||||
// VCARD version 4.0 wants the VERSION property to appear first
|
||||
if ($array[$key] instanceof Property) {
|
||||
if ($array[$key]->name === 'VERSION') {
|
||||
$score=100000000;
|
||||
return $score+$key;
|
||||
} else {
|
||||
// All other properties
|
||||
$score=200000000;
|
||||
return $score+$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$tmp = $vObj->children;
|
||||
uksort($vObj->children, function($a, $b) use ($sortScore, $tmp) {
|
||||
|
||||
$sA = $sortScore($a, $tmp);
|
||||
$sB = $sortScore($b, $tmp);
|
||||
|
||||
return $sA - $sB;
|
||||
|
||||
});
|
||||
|
||||
foreach($vObj->children as $child) {
|
||||
if ($child instanceof Component) {
|
||||
$this->serializeComponent($child);
|
||||
} else {
|
||||
$this->serializeProperty($child);
|
||||
}
|
||||
}
|
||||
|
||||
$this->cWrite('cyan', 'END');
|
||||
$this->cWrite('red', ':');
|
||||
$this->cWrite('yellow', $vObj->name . "\n");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorizes a property.
|
||||
*
|
||||
* @param Property $property
|
||||
* @return void
|
||||
*/
|
||||
protected function serializeProperty(Property $property) {
|
||||
|
||||
if ($property->group) {
|
||||
$this->cWrite('default', $property->group);
|
||||
$this->cWrite('red', '.');
|
||||
}
|
||||
|
||||
$str = '';
|
||||
$this->cWrite('yellow', $property->name);
|
||||
|
||||
foreach($property->parameters as $param) {
|
||||
|
||||
$this->cWrite('red',';');
|
||||
$this->cWrite('blue', $param->serialize());
|
||||
|
||||
}
|
||||
$this->cWrite('red',':');
|
||||
|
||||
if ($property instanceof Property\Binary) {
|
||||
|
||||
$this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)');
|
||||
|
||||
} else {
|
||||
|
||||
$parts = $property->getParts();
|
||||
$first1 = true;
|
||||
// Looping through property values
|
||||
foreach($parts as $part) {
|
||||
if ($first1) {
|
||||
$first1 = false;
|
||||
} else {
|
||||
$this->cWrite('red', $property->delimiter);
|
||||
}
|
||||
$first2 = true;
|
||||
// Looping through property sub-values
|
||||
foreach((array)$part as $subPart) {
|
||||
if ($first2) {
|
||||
$first2 = false;
|
||||
} else {
|
||||
// The sub-value delimiter is always comma
|
||||
$this->cWrite('red', ',');
|
||||
}
|
||||
|
||||
$subPart = strtr($subPart, array(
|
||||
'\\' => $this->colorize('purple', '\\\\', 'green'),
|
||||
';' => $this->colorize('purple', '\;', 'green'),
|
||||
',' => $this->colorize('purple', '\,', 'green'),
|
||||
"\n" => $this->colorize('purple', "\\n\n\t", 'green'),
|
||||
"\r" => "",
|
||||
));
|
||||
|
||||
$this->cWrite('green', $subPart);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
$this->cWrite("default", "\n");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the list of arguments.
|
||||
*
|
||||
* @param array $argv
|
||||
* @return void
|
||||
*/
|
||||
protected function parseArguments(array $argv) {
|
||||
|
||||
$positional = array();
|
||||
$options = array();
|
||||
|
||||
for($ii=0; $ii < count($argv); $ii++) {
|
||||
|
||||
// Skipping the first argument.
|
||||
if ($ii===0) continue;
|
||||
|
||||
$v = $argv[$ii];
|
||||
|
||||
if (substr($v,0,2)==='--') {
|
||||
// This is a long-form option.
|
||||
$optionName = substr($v,2);
|
||||
$optionValue = true;
|
||||
if (strpos($optionName,'=')) {
|
||||
list($optionName, $optionValue) = explode('=', $optionName);
|
||||
}
|
||||
$options[$optionName] = $optionValue;
|
||||
} elseif (substr($v,0,1) === '-' && strlen($v)>1) {
|
||||
// This is a short-form option.
|
||||
foreach(str_split(substr($v,1)) as $option) {
|
||||
$options[$option] = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$positional[] = $v;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return array($options, $positional);
|
||||
|
||||
}
|
||||
|
||||
protected $parser;
|
||||
|
||||
/**
|
||||
* Reads the input file
|
||||
*
|
||||
* @return Component
|
||||
*/
|
||||
protected function readInput() {
|
||||
|
||||
if (!$this->parser) {
|
||||
if ($this->inputPath!=='-') {
|
||||
$this->stdin = fopen($this->inputPath,'r');
|
||||
}
|
||||
|
||||
if ($this->inputFormat === 'mimedir') {
|
||||
$this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0));
|
||||
} else {
|
||||
$this->parser = new Parser\Json($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->parser->parse();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to STDERR.
|
||||
*
|
||||
* @param string $msg
|
||||
* @return void
|
||||
*/
|
||||
protected function log($msg, $color = 'default') {
|
||||
|
||||
if (!$this->quiet) {
|
||||
if ($color!=='default') {
|
||||
$msg = $this->colorize($color, $msg);
|
||||
}
|
||||
fwrite($this->stderr, $msg . "\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,473 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*
|
||||
* A component represents a group of properties, such as VCALENDAR, VEVENT, or
|
||||
* VCARD.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Component extends Node {
|
||||
|
||||
/**
|
||||
* Component name.
|
||||
*
|
||||
* This will contain a string such as VEVENT, VTODO, VCALENDAR, VCARD.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* A list of properties and/or sub-components.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $children = array();
|
||||
|
||||
/**
|
||||
* Creates a new component.
|
||||
*
|
||||
* You can specify the children either in key=>value syntax, in which case
|
||||
* properties will automatically be created, or you can just pass a list of
|
||||
* Component and Property object.
|
||||
*
|
||||
* By default, a set of sensible values will be added to the component. For
|
||||
* an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
|
||||
* ensure that this does not happen, set $defaults to false.
|
||||
*
|
||||
* @param Document $root
|
||||
* @param string $name such as VCALENDAR, VEVENT.
|
||||
* @param array $children
|
||||
* @param bool $defaults
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Document $root, $name, array $children = array(), $defaults = true) {
|
||||
|
||||
$this->name = strtoupper($name);
|
||||
$this->root = $root;
|
||||
|
||||
if ($defaults) {
|
||||
$children = array_merge($this->getDefaults(), $children);
|
||||
}
|
||||
|
||||
foreach($children as $k=>$child) {
|
||||
if ($child instanceof Node) {
|
||||
|
||||
// Component or Property
|
||||
$this->add($child);
|
||||
} else {
|
||||
|
||||
// Property key=>value
|
||||
$this->add($k, $child);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new property or component, and returns the new item.
|
||||
*
|
||||
* This method has 3 possible signatures:
|
||||
*
|
||||
* add(Component $comp) // Adds a new component
|
||||
* add(Property $prop) // Adds a new property
|
||||
* add($name, $value, array $parameters = array()) // Adds a new property
|
||||
* add($name, array $children = array()) // Adds a new component
|
||||
* by name.
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public function add($a1, $a2 = null, $a3 = null) {
|
||||
|
||||
if ($a1 instanceof Node) {
|
||||
if (!is_null($a2)) {
|
||||
throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
|
||||
}
|
||||
$a1->parent = $this;
|
||||
$this->children[] = $a1;
|
||||
|
||||
return $a1;
|
||||
|
||||
} elseif(is_string($a1)) {
|
||||
|
||||
$item = $this->root->create($a1, $a2, $a3);
|
||||
$item->parent = $this;
|
||||
$this->children[] = $item;
|
||||
|
||||
return $item;
|
||||
|
||||
} else {
|
||||
|
||||
throw new \InvalidArgumentException('The first argument must either be a \\SabreForRainLoop\\VObject\\Node or a string');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method removes a component or property from this component.
|
||||
*
|
||||
* You can either specify the item by name (like DTSTART), in which case
|
||||
* all properties/components with that name will be removed, or you can
|
||||
* pass an instance of a property or component, in which case only that
|
||||
* exact item will be removed.
|
||||
*
|
||||
* The removed item will be returned. In case there were more than 1 items
|
||||
* removed, only the last one will be returned.
|
||||
*
|
||||
* @param mixed $item
|
||||
* @return void
|
||||
*/
|
||||
public function remove($item) {
|
||||
|
||||
if (is_string($item)) {
|
||||
$children = $this->select($item);
|
||||
foreach($children as $k=>$child) {
|
||||
unset($this->children[$k]);
|
||||
}
|
||||
return $child;
|
||||
} else {
|
||||
foreach($this->children as $k => $child) {
|
||||
if ($child===$item) {
|
||||
unset($this->children[$k]);
|
||||
return $child;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterable list of children
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function children() {
|
||||
|
||||
return $this->children;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method only returns a list of sub-components. Properties are
|
||||
* ignored.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getComponents() {
|
||||
|
||||
$result = array();
|
||||
foreach($this->children as $child) {
|
||||
if ($child instanceof Component) {
|
||||
$result[] = $child;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with elements that match the specified name.
|
||||
*
|
||||
* This function is also aware of MIME-Directory groups (as they appear in
|
||||
* vcards). This means that if a property is grouped as "HOME.EMAIL", it
|
||||
* will also be returned when searching for just "EMAIL". If you want to
|
||||
* search for a property in a specific group, you can select on the entire
|
||||
* string ("HOME.EMAIL"). If you want to search on a specific property that
|
||||
* has not been assigned a group, specify ".EMAIL".
|
||||
*
|
||||
* Keys are retained from the 'children' array, which may be confusing in
|
||||
* certain cases.
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public function select($name) {
|
||||
|
||||
$group = null;
|
||||
$name = strtoupper($name);
|
||||
if (strpos($name,'.')!==false) {
|
||||
list($group,$name) = explode('.', $name, 2);
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach($this->children as $key=>$child) {
|
||||
|
||||
if (
|
||||
strtoupper($child->name) === $name &&
|
||||
(is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group))
|
||||
) {
|
||||
|
||||
$result[$key] = $child;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
reset($result);
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the object back into a serialized blob.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize() {
|
||||
|
||||
$str = "BEGIN:" . $this->name . "\r\n";
|
||||
|
||||
/**
|
||||
* Gives a component a 'score' for sorting purposes.
|
||||
*
|
||||
* This is solely used by the childrenSort method.
|
||||
*
|
||||
* A higher score means the item will be lower in the list.
|
||||
* To avoid score collisions, each "score category" has a reasonable
|
||||
* space to accomodate elements. The $key is added to the $score to
|
||||
* preserve the original relative order of elements.
|
||||
*
|
||||
* @param int $key
|
||||
* @param array $array
|
||||
* @return int
|
||||
*/
|
||||
$sortScore = function($key, $array) {
|
||||
|
||||
if ($array[$key] instanceof Component) {
|
||||
|
||||
// We want to encode VTIMEZONE first, this is a personal
|
||||
// preference.
|
||||
if ($array[$key]->name === 'VTIMEZONE') {
|
||||
$score=300000000;
|
||||
return $score+$key;
|
||||
} else {
|
||||
$score=400000000;
|
||||
return $score+$key;
|
||||
}
|
||||
} else {
|
||||
// Properties get encoded first
|
||||
// VCARD version 4.0 wants the VERSION property to appear first
|
||||
if ($array[$key] instanceof Property) {
|
||||
if ($array[$key]->name === 'VERSION') {
|
||||
$score=100000000;
|
||||
return $score+$key;
|
||||
} else {
|
||||
// All other properties
|
||||
$score=200000000;
|
||||
return $score+$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$tmp = $this->children;
|
||||
uksort($this->children, function($a, $b) use ($sortScore, $tmp) {
|
||||
|
||||
$sA = $sortScore($a, $tmp);
|
||||
$sB = $sortScore($b, $tmp);
|
||||
|
||||
return $sA - $sB;
|
||||
|
||||
});
|
||||
|
||||
foreach($this->children as $child) $str.=$child->serialize();
|
||||
$str.= "END:" . $this->name . "\r\n";
|
||||
|
||||
return $str;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns an array, with the representation as it should be
|
||||
* encoded in json. This is used to create jCard or jCal documents.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
|
||||
$components = array();
|
||||
$properties = array();
|
||||
|
||||
foreach($this->children as $child) {
|
||||
if ($child instanceof Component) {
|
||||
$components[] = $child->jsonSerialize();
|
||||
} else {
|
||||
$properties[] = $child->jsonSerialize();
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
strtolower($this->name),
|
||||
$properties,
|
||||
$components
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should return a list of default property values.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaults() {
|
||||
|
||||
return array();
|
||||
|
||||
}
|
||||
|
||||
/* Magic property accessors {{{ */
|
||||
|
||||
/**
|
||||
* Using 'get' you will either get a property or component.
|
||||
*
|
||||
* If there were no child-elements found with the specified name,
|
||||
* null is returned.
|
||||
*
|
||||
* To use this, this may look something like this:
|
||||
*
|
||||
* $event = $calendar->VEVENT;
|
||||
*
|
||||
* @param string $name
|
||||
* @return Property
|
||||
*/
|
||||
public function __get($name) {
|
||||
|
||||
$matches = $this->select($name);
|
||||
if (count($matches)===0) {
|
||||
return null;
|
||||
} else {
|
||||
$firstMatch = current($matches);
|
||||
/** @var $firstMatch Property */
|
||||
$firstMatch->setIterator(new ElementList(array_values($matches)));
|
||||
return $firstMatch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if a sub-element with the specified name exists.
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($name) {
|
||||
|
||||
$matches = $this->select($name);
|
||||
return count($matches)>0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the setter method you can add properties or subcomponents
|
||||
*
|
||||
* You can either pass a Component, Property
|
||||
* object, or a string to automatically create a Property.
|
||||
*
|
||||
* If the item already exists, it will be removed. If you want to add
|
||||
* a new item with the same name, always use the add() method.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function __set($name, $value) {
|
||||
|
||||
$matches = $this->select($name);
|
||||
$overWrite = count($matches)?key($matches):null;
|
||||
|
||||
if ($value instanceof Component || $value instanceof Property) {
|
||||
$value->parent = $this;
|
||||
if (!is_null($overWrite)) {
|
||||
$this->children[$overWrite] = $value;
|
||||
} else {
|
||||
$this->children[] = $value;
|
||||
}
|
||||
} elseif (is_scalar($value) || is_array($value) || is_null($value)) {
|
||||
$property = $this->root->create($name,$value);
|
||||
$property->parent = $this;
|
||||
if (!is_null($overWrite)) {
|
||||
$this->children[$overWrite] = $property;
|
||||
} else {
|
||||
$this->children[] = $property;
|
||||
}
|
||||
} else {
|
||||
throw new \InvalidArgumentException('You must pass a \\SabreForRainLoop\\VObject\\Component, \\SabreForRainLoop\\VObject\\Property or scalar type');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all properties and components within this component with the
|
||||
* specified name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __unset($name) {
|
||||
|
||||
$matches = $this->select($name);
|
||||
foreach($matches as $k=>$child) {
|
||||
|
||||
unset($this->children[$k]);
|
||||
$child->parent = null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/**
|
||||
* This method is automatically called when the object is cloned.
|
||||
* Specifically, this will ensure all child elements are also cloned.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone() {
|
||||
|
||||
foreach($this->children as $key=>$child) {
|
||||
$this->children[$key] = clone $child;
|
||||
$this->children[$key]->parent = $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the node for correctness.
|
||||
*
|
||||
* The following options are supported:
|
||||
* - Node::REPAIR - If something is broken, an automatic repair may
|
||||
* be attempted.
|
||||
*
|
||||
* An array is returned with warnings.
|
||||
*
|
||||
* Every item in the array has the following properties:
|
||||
* * level - (number between 1 and 3 with severity information)
|
||||
* * message - (human readable message)
|
||||
* * node - (reference to the offending node)
|
||||
*
|
||||
* @param int $options
|
||||
* @return array
|
||||
*/
|
||||
public function validate($options = 0) {
|
||||
|
||||
$result = array();
|
||||
foreach($this->children as $child) {
|
||||
$result = array_merge($result, $child->validate($options));
|
||||
}
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Component;
|
||||
use SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* VAlarm component
|
||||
*
|
||||
* This component contains some additional functionality specific for VALARMs.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class VAlarm extends VObject\Component {
|
||||
|
||||
/**
|
||||
* Returns a DateTime object when this alarm is going to trigger.
|
||||
*
|
||||
* This ignores repeated alarm, only the first trigger is returned.
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public function getEffectiveTriggerTime() {
|
||||
|
||||
$trigger = $this->TRIGGER;
|
||||
if(!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
|
||||
$triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
|
||||
$related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
|
||||
|
||||
$parentComponent = $this->parent;
|
||||
if ($related === 'START') {
|
||||
|
||||
if ($parentComponent->name === 'VTODO') {
|
||||
$propName = 'DUE';
|
||||
} else {
|
||||
$propName = 'DTSTART';
|
||||
}
|
||||
|
||||
$effectiveTrigger = clone $parentComponent->$propName->getDateTime();
|
||||
$effectiveTrigger->add($triggerDuration);
|
||||
} else {
|
||||
if ($parentComponent->name === 'VTODO') {
|
||||
$endProp = 'DUE';
|
||||
} elseif ($parentComponent->name === 'VEVENT') {
|
||||
$endProp = 'DTEND';
|
||||
} else {
|
||||
throw new \LogicException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT');
|
||||
}
|
||||
|
||||
if (isset($parentComponent->$endProp)) {
|
||||
$effectiveTrigger = clone $parentComponent->$endProp->getDateTime();
|
||||
$effectiveTrigger->add($triggerDuration);
|
||||
} elseif (isset($parentComponent->DURATION)) {
|
||||
$effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
|
||||
$duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION);
|
||||
$effectiveTrigger->add($duration);
|
||||
$effectiveTrigger->add($triggerDuration);
|
||||
} else {
|
||||
$effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
|
||||
$effectiveTrigger->add($triggerDuration);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$effectiveTrigger = $trigger->getDateTime();
|
||||
}
|
||||
return $effectiveTrigger;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true or false depending on if the event falls in the specified
|
||||
* time-range. This is used for filtering purposes.
|
||||
*
|
||||
* The rules used to determine if an event falls within the specified
|
||||
* time-range is based on the CalDAV specification.
|
||||
*
|
||||
* @param \DateTime $start
|
||||
* @param \DateTime $end
|
||||
* @return bool
|
||||
*/
|
||||
public function isInTimeRange(\DateTime $start, \DateTime $end) {
|
||||
|
||||
$effectiveTrigger = $this->getEffectiveTriggerTime();
|
||||
|
||||
if (isset($this->DURATION)) {
|
||||
$duration = VObject\DateTimeParser::parseDuration($this->DURATION);
|
||||
$repeat = (string)$this->repeat;
|
||||
if (!$repeat) {
|
||||
$repeat = 1;
|
||||
}
|
||||
|
||||
$period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat);
|
||||
|
||||
foreach($period as $occurrence) {
|
||||
|
||||
if ($start <= $occurrence && $end > $occurrence) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return ($start <= $effectiveTrigger && $end > $effectiveTrigger);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,369 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Component;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject,
|
||||
SabreForRainLoop\VObject\Component;
|
||||
|
||||
/**
|
||||
* The VCalendar component
|
||||
*
|
||||
* This component adds functionality to a component, specific for a VCALENDAR.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class VCalendar extends VObject\Document {
|
||||
|
||||
/**
|
||||
* The default name for this component.
|
||||
*
|
||||
* This should be 'VCALENDAR' or 'VCARD'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
static public $defaultName = 'VCALENDAR';
|
||||
|
||||
/**
|
||||
* This is a list of components, and which classes they should map to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static public $componentMap = array(
|
||||
'VEVENT' => 'SabreForRainLoop\\VObject\\Component\\VEvent',
|
||||
'VFREEBUSY' => 'SabreForRainLoop\\VObject\\Component\\VFreeBusy',
|
||||
'VJOURNAL' => 'SabreForRainLoop\\VObject\\Component\\VJournal',
|
||||
'VTODO' => 'SabreForRainLoop\\VObject\\Component\\VTodo',
|
||||
'VALARM' => 'SabreForRainLoop\\VObject\\Component\\VAlarm',
|
||||
);
|
||||
|
||||
/**
|
||||
* List of value-types, and which classes they map to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static public $valueMap = array(
|
||||
'BINARY' => 'SabreForRainLoop\\VObject\\Property\\Binary',
|
||||
'BOOLEAN' => 'SabreForRainLoop\\VObject\\Property\\Boolean',
|
||||
'CAL-ADDRESS' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\CalAddress',
|
||||
'DATE' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\Date',
|
||||
'DATE-TIME' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'DURATION' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\Duration',
|
||||
'FLOAT' => 'SabreForRainLoop\\VObject\\Property\\Float',
|
||||
'INTEGER' => 'SabreForRainLoop\\VObject\\Property\\Integer',
|
||||
'PERIOD' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\Period',
|
||||
'RECUR' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\Recur',
|
||||
'TEXT' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'TIME' => 'SabreForRainLoop\\VObject\\Property\\Time',
|
||||
'UNKNOWN' => 'SabreForRainLoop\\VObject\\Property\\Unknown', // jCard / jCal-only.
|
||||
'URI' => 'SabreForRainLoop\\VObject\\Property\\Uri',
|
||||
'UTC-OFFSET' => 'SabreForRainLoop\\VObject\\Property\\UtcOffset',
|
||||
);
|
||||
|
||||
/**
|
||||
* List of properties, and which classes they map to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static public $propertyMap = array(
|
||||
// Calendar properties
|
||||
'CALSCALE' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'METHOD' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'PRODID' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'VERSION' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
|
||||
// Component properties
|
||||
'ATTACH' => 'SabreForRainLoop\\VObject\\Property\\Binary',
|
||||
'CATEGORIES' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'CLASS' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'COMMENT' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'DESCRIPTION' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'GEO' => 'SabreForRainLoop\\VObject\\Property\\Float',
|
||||
'LOCATION' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'PERCENT-COMPLETE' => 'SabreForRainLoop\\VObject\\Property\\Integer',
|
||||
'PRIORITY' => 'SabreForRainLoop\\VObject\\Property\\Integer',
|
||||
'RESOURCES' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'STATUS' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'SUMMARY' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
|
||||
// Date and Time Component Properties
|
||||
'COMPLETED' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'DTEND' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'DUE' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'DTSTART' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'DURATION' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\Duration',
|
||||
'FREEBUSY' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\Period',
|
||||
'TRANSP' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
|
||||
// Time Zone Component Properties
|
||||
'TZID' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'TZNAME' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'TZOFFSETFROM' => 'SabreForRainLoop\\VObject\\Property\\UtcOffset',
|
||||
'TZOFFSETTO' => 'SabreForRainLoop\\VObject\\Property\\UtcOffset',
|
||||
'TZURL' => 'SabreForRainLoop\\VObject\\Property\\Uri',
|
||||
|
||||
// Relationship Component Properties
|
||||
'ATTENDEE' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\CalAddress',
|
||||
'CONTACT' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'ORGANIZER' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\CalAddress',
|
||||
'RECURRENCE-ID' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'RELATED-TO' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'URL' => 'SabreForRainLoop\\VObject\\Property\\Uri',
|
||||
'UID' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
|
||||
// Recurrence Component Properties
|
||||
'EXDATE' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'RDATE' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'RRULE' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\Recur',
|
||||
'EXRULE' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545
|
||||
|
||||
// Alarm Component Properties
|
||||
'ACTION' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'REPEAT' => 'SabreForRainLoop\\VObject\\Property\\Integer',
|
||||
'TRIGGER' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\Duration',
|
||||
|
||||
// Change Management Component Properties
|
||||
'CREATED' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'DTSTAMP' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'LAST-MODIFIED' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'SEQUENCE' => 'SabreForRainLoop\\VObject\\Property\\Integer',
|
||||
|
||||
// Request Status
|
||||
'REQUEST-STATUS' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
|
||||
// Additions from draft-daboo-valarm-extensions-04
|
||||
'ALARM-AGENT' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'ACKNOWLEDGED' => 'SabreForRainLoop\\VObject\\Property\\ICalendar\\DateTime',
|
||||
'PROXIMITY' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'DEFAULT-ALARM' => 'SabreForRainLoop\\VObject\\Property\\Boolean',
|
||||
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the current document type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getDocumentType() {
|
||||
|
||||
return self::ICALENDAR20;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all 'base components'. For instance, if an Event has
|
||||
* a recurrence rule, and one instance is overridden, the overridden event
|
||||
* will have the same UID, but will be excluded from this list.
|
||||
*
|
||||
* VTIMEZONE components will always be excluded.
|
||||
*
|
||||
* @param string $componentName filter by component name
|
||||
* @return array
|
||||
*/
|
||||
public function getBaseComponents($componentName = null) {
|
||||
|
||||
$components = array();
|
||||
foreach($this->children as $component) {
|
||||
|
||||
if (!$component instanceof VObject\Component)
|
||||
continue;
|
||||
|
||||
if (isset($component->{'RECURRENCE-ID'}))
|
||||
continue;
|
||||
|
||||
if ($componentName && $component->name !== strtoupper($componentName))
|
||||
continue;
|
||||
|
||||
if ($component->name === 'VTIMEZONE')
|
||||
continue;
|
||||
|
||||
$components[] = $component;
|
||||
|
||||
}
|
||||
|
||||
return $components;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* If this calendar object, has events with recurrence rules, this method
|
||||
* can be used to expand the event into multiple sub-events.
|
||||
*
|
||||
* Each event will be stripped from it's recurrence information, and only
|
||||
* the instances of the event in the specified timerange will be left
|
||||
* alone.
|
||||
*
|
||||
* In addition, this method will cause timezone information to be stripped,
|
||||
* and normalized to UTC.
|
||||
*
|
||||
* This method will alter the VCalendar. This cannot be reversed.
|
||||
*
|
||||
* This functionality is specifically used by the CalDAV standard. It is
|
||||
* possible for clients to request expand events, if they are rather simple
|
||||
* clients and do not have the possibility to calculate recurrences.
|
||||
*
|
||||
* @param DateTime $start
|
||||
* @param DateTime $end
|
||||
* @return void
|
||||
*/
|
||||
public function expand(\DateTime $start, \DateTime $end) {
|
||||
|
||||
$newEvents = array();
|
||||
|
||||
foreach($this->select('VEVENT') as $key=>$vevent) {
|
||||
|
||||
if (isset($vevent->{'RECURRENCE-ID'})) {
|
||||
unset($this->children[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!$vevent->rrule) {
|
||||
unset($this->children[$key]);
|
||||
if ($vevent->isInTimeRange($start, $end)) {
|
||||
$newEvents[] = $vevent;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$uid = (string)$vevent->uid;
|
||||
if (!$uid) {
|
||||
throw new \LogicException('Event did not have a UID!');
|
||||
}
|
||||
|
||||
$it = new VObject\RecurrenceIterator($this, $vevent->uid);
|
||||
$it->fastForward($start);
|
||||
|
||||
while($it->valid() && $it->getDTStart() < $end) {
|
||||
|
||||
if ($it->getDTEnd() > $start) {
|
||||
|
||||
$newEvents[] = $it->getEventObject();
|
||||
|
||||
}
|
||||
$it->next();
|
||||
|
||||
}
|
||||
unset($this->children[$key]);
|
||||
|
||||
}
|
||||
|
||||
// Setting all properties to UTC time.
|
||||
foreach($newEvents as $newEvent) {
|
||||
|
||||
foreach($newEvent->children as $child) {
|
||||
if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) {
|
||||
$dt = $child->getDateTimes();
|
||||
// We only need to update the first timezone, because
|
||||
// setDateTimes will match all other timezones to the
|
||||
// first.
|
||||
$dt[0]->setTimeZone(new \DateTimeZone('UTC'));
|
||||
$child->setDateTimes($dt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->add($newEvent);
|
||||
|
||||
}
|
||||
|
||||
// Removing all VTIMEZONE components
|
||||
unset($this->VTIMEZONE);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should return a list of default property values.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaults() {
|
||||
|
||||
return array(
|
||||
'VERSION' => '2.0',
|
||||
'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
|
||||
'CALSCALE' => 'GREGORIAN',
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the node for correctness.
|
||||
* An array is returned with warnings.
|
||||
*
|
||||
* Every item in the array has the following properties:
|
||||
* * level - (number between 1 and 3 with severity information)
|
||||
* * message - (human readable message)
|
||||
* * node - (reference to the offending node)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function validate($options = 0) {
|
||||
|
||||
$warnings = array();
|
||||
|
||||
$version = $this->select('VERSION');
|
||||
if (count($version)!==1) {
|
||||
$warnings[] = array(
|
||||
'level' => 1,
|
||||
'message' => 'The VERSION property must appear in the VCALENDAR component exactly 1 time',
|
||||
'node' => $this,
|
||||
);
|
||||
} else {
|
||||
if ((string)$this->VERSION !== '2.0') {
|
||||
$warnings[] = array(
|
||||
'level' => 1,
|
||||
'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
|
||||
'node' => $this,
|
||||
);
|
||||
}
|
||||
}
|
||||
$version = $this->select('PRODID');
|
||||
if (count($version)!==1) {
|
||||
$warnings[] = array(
|
||||
'level' => 2,
|
||||
'message' => 'The PRODID property must appear in the VCALENDAR component exactly 1 time',
|
||||
'node' => $this,
|
||||
);
|
||||
}
|
||||
if (count($this->CALSCALE) > 1) {
|
||||
$warnings[] = array(
|
||||
'level' => 2,
|
||||
'message' => 'The CALSCALE property must not be specified more than once.',
|
||||
'node' => $this,
|
||||
);
|
||||
}
|
||||
if (count($this->METHOD) > 1) {
|
||||
$warnings[] = array(
|
||||
'level' => 2,
|
||||
'message' => 'The METHOD property must not be specified more than once.',
|
||||
'node' => $this,
|
||||
);
|
||||
}
|
||||
|
||||
$componentsFound = 0;
|
||||
foreach($this->children as $child) {
|
||||
if($child instanceof Component) {
|
||||
$componentsFound++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($componentsFound===0) {
|
||||
$warnings[] = array(
|
||||
'level' => 1,
|
||||
'message' => 'An iCalendar object must have at least 1 component.',
|
||||
'node' => $this,
|
||||
);
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
$warnings,
|
||||
parent::validate()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Component;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* The VCard component
|
||||
*
|
||||
* This component represents the BEGIN:VCARD and END:VCARD found in every
|
||||
* vcard.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class VCard extends VObject\Document {
|
||||
|
||||
/**
|
||||
* The default name for this component.
|
||||
*
|
||||
* This should be 'VCALENDAR' or 'VCARD'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
static public $defaultName = 'VCARD';
|
||||
|
||||
/**
|
||||
* Caching the version number
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $version = null;
|
||||
|
||||
/**
|
||||
* List of value-types, and which classes they map to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static public $valueMap = array(
|
||||
'BINARY' => 'SabreForRainLoop\\VObject\\Property\\Binary',
|
||||
'BOOLEAN' => 'SabreForRainLoop\\VObject\\Property\\Boolean',
|
||||
'CONTENT-ID' => 'SabreForRainLoop\\VObject\\Property\\FlatText', // vCard 2.1 only
|
||||
'DATE' => 'SabreForRainLoop\\VObject\\Property\\VCard\\Date',
|
||||
'DATE-TIME' => 'SabreForRainLoop\\VObject\\Property\\VCard\\DateTime',
|
||||
'DATE-AND-OR-TIME' => 'SabreForRainLoop\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only
|
||||
'FLOAT' => 'SabreForRainLoop\\VObject\\Property\\Float',
|
||||
'INTEGER' => 'SabreForRainLoop\\VObject\\Property\\Integer',
|
||||
'LANGUAGE-TAG' => 'SabreForRainLoop\\VObject\\Property\\VCard\\LanguageTag',
|
||||
'TIMESTAMP' => 'SabreForRainLoop\\VObject\\Property\\VCard\\TimeStamp',
|
||||
'TEXT' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'TIME' => 'SabreForRainLoop\\VObject\\Property\\Time',
|
||||
'UNKNOWN' => 'SabreForRainLoop\\VObject\\Property\\Unknown', // jCard / jCal-only.
|
||||
'URI' => 'SabreForRainLoop\\VObject\\Property\\Uri',
|
||||
'URL' => 'SabreForRainLoop\\VObject\\Property\\Uri', // vCard 2.1 only
|
||||
'UTC-OFFSET' => 'SabreForRainLoop\\VObject\\Property\\UtcOffset',
|
||||
);
|
||||
|
||||
/**
|
||||
* List of properties, and which classes they map to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static public $propertyMap = array(
|
||||
|
||||
// vCard 2.1 properties and up
|
||||
'N' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'FN' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'PHOTO' => 'SabreForRainLoop\\VObject\\Property\\Binary', // Todo: we should add a class for Binary values.
|
||||
'BDAY' => 'SabreForRainLoop\\VObject\\Property\\VCard\\DateAndOrTime',
|
||||
'ADR' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'LABEL' => 'SabreForRainLoop\\VObject\\Property\\FlatText', // Removed in vCard 4.0
|
||||
'TEL' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'EMAIL' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'MAILER' => 'SabreForRainLoop\\VObject\\Property\\FlatText', // Removed in vCard 4.0
|
||||
'GEO' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'TITLE' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'ROLE' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'LOGO' => 'SabreForRainLoop\\VObject\\Property\\Binary',
|
||||
// 'AGENT' => 'SabreForRainLoop\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so
|
||||
// not supported at the moment
|
||||
'ORG' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'NOTE' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'REV' => 'SabreForRainLoop\\VObject\\Property\\VCard\\TimeStamp',
|
||||
'SOUND' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'URL' => 'SabreForRainLoop\\VObject\\Property\\Uri',
|
||||
'UID' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'VERSION' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'KEY' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'TZ' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
|
||||
// vCard 3.0 properties
|
||||
'CATEGORIES' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'SORT-STRING' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'PRODID' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'NICKNAME' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'CLASS' => 'SabreForRainLoop\\VObject\\Property\\FlatText', // Removed in vCard 4.0
|
||||
|
||||
// rfc2739 properties
|
||||
'FBURL' => 'SabreForRainLoop\\VObject\\Property\\Uri',
|
||||
'CAPURI' => 'SabreForRainLoop\\VObject\\Property\\Uri',
|
||||
'CALURI' => 'SabreForRainLoop\\VObject\\Property\\Uri',
|
||||
|
||||
// rfc4770 properties
|
||||
'IMPP' => 'SabreForRainLoop\\VObject\\Property\\Uri',
|
||||
|
||||
// vCard 4.0 properties
|
||||
'XML' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
'ANNIVERSARY' => 'SabreForRainLoop\\VObject\\Property\\VCard\\DateAndOrTime',
|
||||
'CLIENTPIDMAP' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'LANG' => 'SabreForRainLoop\\VObject\\Property\\VCard\\LanguageTag',
|
||||
'GENDER' => 'SabreForRainLoop\\VObject\\Property\\Text',
|
||||
'KIND' => 'SabreForRainLoop\\VObject\\Property\\FlatText',
|
||||
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the current document type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getDocumentType() {
|
||||
|
||||
if (!$this->version) {
|
||||
$version = (string)$this->VERSION;
|
||||
switch($version) {
|
||||
case '2.1' :
|
||||
$this->version = self::VCARD21;
|
||||
break;
|
||||
case '3.0' :
|
||||
$this->version = self::VCARD30;
|
||||
break;
|
||||
case '4.0' :
|
||||
$this->version = self::VCARD40;
|
||||
break;
|
||||
default :
|
||||
$this->version = self::UNKNOWN;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $this->version;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the document to a different vcard version.
|
||||
*
|
||||
* Use one of the VCARD constants for the target. This method will return
|
||||
* a copy of the vcard in the new version.
|
||||
*
|
||||
* At the moment the only supported conversion is from 3.0 to 4.0.
|
||||
*
|
||||
* If input and output version are identical, a clone is returned.
|
||||
*
|
||||
* @param int $target
|
||||
* @return VCard
|
||||
*/
|
||||
public function convert($target) {
|
||||
|
||||
$converter = new VObject\VCardConverter();
|
||||
return $converter->convert($this, $target);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* VCards with version 2.1, 3.0 and 4.0 are found.
|
||||
*
|
||||
* If the VCARD doesn't know its version, 2.1 is assumed.
|
||||
*/
|
||||
const DEFAULT_VERSION = self::VCARD21;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Validates the node for correctness.
|
||||
*
|
||||
* The following options are supported:
|
||||
* - Node::REPAIR - If something is broken, and automatic repair may
|
||||
* be attempted.
|
||||
*
|
||||
* An array is returned with warnings.
|
||||
*
|
||||
* Every item in the array has the following properties:
|
||||
* * level - (number between 1 and 3 with severity information)
|
||||
* * message - (human readable message)
|
||||
* * node - (reference to the offending node)
|
||||
*
|
||||
* @param int $options
|
||||
* @return array
|
||||
*/
|
||||
public function validate($options = 0) {
|
||||
|
||||
$warnings = array();
|
||||
|
||||
$versionMap = array(
|
||||
self::VCARD21 => '2.1',
|
||||
self::VCARD30 => '3.0',
|
||||
self::VCARD40 => '4.0',
|
||||
);
|
||||
|
||||
$version = $this->select('VERSION');
|
||||
if (count($version)!==1) {
|
||||
$warnings[] = array(
|
||||
'level' => 1,
|
||||
'message' => 'The VERSION property must appear in the VCARD component exactly 1 time',
|
||||
'node' => $this,
|
||||
);
|
||||
if ($options & self::REPAIR) {
|
||||
$this->VERSION = $versionMap[self::DEFAULT_VERSION];
|
||||
}
|
||||
} else {
|
||||
$version = (string)$this->VERSION;
|
||||
if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') {
|
||||
$warnings[] = array(
|
||||
'level' => 1,
|
||||
'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
|
||||
'node' => $this,
|
||||
);
|
||||
if ($options & self::REPAIR) {
|
||||
$this->VERSION = $versionMap[self::DEFAULT_VERSION];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
$fn = $this->select('FN');
|
||||
if (count($fn)!==1) {
|
||||
$warnings[] = array(
|
||||
'level' => 1,
|
||||
'message' => 'The FN property must appear in the VCARD component exactly 1 time',
|
||||
'node' => $this,
|
||||
);
|
||||
if (($options & self::REPAIR) && count($fn) === 0) {
|
||||
// We're going to try to see if we can use the contents of the
|
||||
// N property.
|
||||
if (isset($this->N)) {
|
||||
$value = explode(';', (string)$this->N);
|
||||
if (isset($value[1]) && $value[1]) {
|
||||
$this->FN = $value[1] . ' ' . $value[0];
|
||||
} else {
|
||||
$this->FN = $value[0];
|
||||
}
|
||||
|
||||
// Otherwise, the ORG property may work
|
||||
} elseif (isset($this->ORG)) {
|
||||
$this->FN = (string)$this->ORG;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
parent::validate($options),
|
||||
$warnings
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a preferred field.
|
||||
*
|
||||
* VCards can indicate wether a field such as ADR, TEL or EMAIL is
|
||||
* preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x
|
||||
* being a number between 1 and 100).
|
||||
*
|
||||
* If neither of those parameters are specified, the first is returned, if
|
||||
* a field with that name does not exist, null is returned.
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @return VObject\Property|null
|
||||
*/
|
||||
public function preferred($propertyName) {
|
||||
|
||||
$preferred = null;
|
||||
$lastPref = 101;
|
||||
foreach($this->select($propertyName) as $field) {
|
||||
|
||||
$pref = 101;
|
||||
if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) {
|
||||
$pref = 1;
|
||||
} elseif (isset($field['PREF'])) {
|
||||
$pref = $field['PREF']->getValue();
|
||||
}
|
||||
|
||||
if ($pref < $lastPref || is_null($preferred)) {
|
||||
$preferred = $field;
|
||||
$lastPref = $pref;
|
||||
}
|
||||
|
||||
}
|
||||
return $preferred;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should return a list of default property values.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaults() {
|
||||
|
||||
return array(
|
||||
'VERSION' => '3.0',
|
||||
'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns an array, with the representation as it should be
|
||||
* encoded in json. This is used to create jCard or jCal documents.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
|
||||
// A vcard does not have sub-components, so we're overriding this
|
||||
// method to remove that array element.
|
||||
$properties = array();
|
||||
|
||||
foreach($this->children as $child) {
|
||||
$properties[] = $child->jsonSerialize();
|
||||
}
|
||||
|
||||
return array(
|
||||
strtolower($this->name),
|
||||
$properties,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default class for a property name.
|
||||
*
|
||||
* @param string $propertyName
|
||||
* @return string
|
||||
*/
|
||||
public function getClassNameForPropertyName($propertyName) {
|
||||
|
||||
$className = parent::getClassNameForPropertyName($propertyName);
|
||||
// In vCard 4, BINARY no longer exists, and we need URI instead.
|
||||
|
||||
if ($className == 'SabreForRainLoop\\VObject\\Property\\Binary' && $this->getDocumentType()===self::VCARD40) {
|
||||
return 'SabreForRainLoop\\VObject\\Property\\Uri';
|
||||
}
|
||||
return $className;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Component;
|
||||
use SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* VEvent component
|
||||
*
|
||||
* This component contains some additional functionality specific for VEVENT's.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class VEvent extends VObject\Component {
|
||||
|
||||
/**
|
||||
* Returns true or false depending on if the event falls in the specified
|
||||
* time-range. This is used for filtering purposes.
|
||||
*
|
||||
* The rules used to determine if an event falls within the specified
|
||||
* time-range is based on the CalDAV specification.
|
||||
*
|
||||
* @param \DateTime $start
|
||||
* @param \DateTime $end
|
||||
* @return bool
|
||||
*/
|
||||
public function isInTimeRange(\DateTime $start, \DateTime $end) {
|
||||
|
||||
if ($this->RRULE) {
|
||||
$it = new VObject\RecurrenceIterator($this);
|
||||
$it->fastForward($start);
|
||||
|
||||
// We fast-forwarded to a spot where the end-time of the
|
||||
// recurrence instance exceeded the start of the requested
|
||||
// time-range.
|
||||
//
|
||||
// If the starttime of the recurrence did not exceed the
|
||||
// end of the time range as well, we have a match.
|
||||
return ($it->getDTStart() < $end && $it->getDTEnd() > $start);
|
||||
|
||||
}
|
||||
|
||||
$effectiveStart = $this->DTSTART->getDateTime();
|
||||
if (isset($this->DTEND)) {
|
||||
|
||||
// The DTEND property is considered non inclusive. So for a 3 day
|
||||
// event in july, dtstart and dtend would have to be July 1st and
|
||||
// July 4th respectively.
|
||||
//
|
||||
// See:
|
||||
// http://tools.ietf.org/html/rfc5545#page-54
|
||||
$effectiveEnd = $this->DTEND->getDateTime();
|
||||
|
||||
} elseif (isset($this->DURATION)) {
|
||||
$effectiveEnd = clone $effectiveStart;
|
||||
$effectiveEnd->add( VObject\DateTimeParser::parseDuration($this->DURATION) );
|
||||
} elseif (!$this->DTSTART->hasTime()) {
|
||||
$effectiveEnd = clone $effectiveStart;
|
||||
$effectiveEnd->modify('+1 day');
|
||||
} else {
|
||||
$effectiveEnd = clone $effectiveStart;
|
||||
}
|
||||
return (
|
||||
($start <= $effectiveEnd) && ($end > $effectiveStart)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Component;
|
||||
|
||||
use SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* The VFreeBusy component
|
||||
*
|
||||
* This component adds functionality to a component, specific for VFREEBUSY
|
||||
* components.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class VFreeBusy extends VObject\Component {
|
||||
|
||||
/**
|
||||
* Checks based on the contained FREEBUSY information, if a timeslot is
|
||||
* available.
|
||||
*
|
||||
* @param DateTime $start
|
||||
* @param Datetime $end
|
||||
* @return bool
|
||||
*/
|
||||
public function isFree(\DateTime $start, \Datetime $end) {
|
||||
|
||||
foreach($this->select('FREEBUSY') as $freebusy) {
|
||||
|
||||
// We are only interested in FBTYPE=BUSY (the default),
|
||||
// FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE.
|
||||
if (isset($freebusy['FBTYPE']) && strtoupper(substr((string)$freebusy['FBTYPE'],0,4))!=='BUSY') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The freebusy component can hold more than 1 value, separated by
|
||||
// commas.
|
||||
$periods = explode(',', (string)$freebusy);
|
||||
|
||||
foreach($periods as $period) {
|
||||
// Every period is formatted as [start]/[end]. The start is an
|
||||
// absolute UTC time, the end may be an absolute UTC time, or
|
||||
// duration (relative) value.
|
||||
list($busyStart, $busyEnd) = explode('/', $period);
|
||||
|
||||
$busyStart = VObject\DateTimeParser::parse($busyStart);
|
||||
$busyEnd = VObject\DateTimeParser::parse($busyEnd);
|
||||
if ($busyEnd instanceof \DateInterval) {
|
||||
$tmp = clone $busyStart;
|
||||
$tmp->add($busyEnd);
|
||||
$busyEnd = $tmp;
|
||||
}
|
||||
|
||||
if($start < $busyEnd && $end > $busyStart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Component;
|
||||
|
||||
use SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* VJournal component
|
||||
*
|
||||
* This component contains some additional functionality specific for VJOURNALs.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class VJournal extends VObject\Component {
|
||||
|
||||
/**
|
||||
* Returns true or false depending on if the event falls in the specified
|
||||
* time-range. This is used for filtering purposes.
|
||||
*
|
||||
* The rules used to determine if an event falls within the specified
|
||||
* time-range is based on the CalDAV specification.
|
||||
*
|
||||
* @param DateTime $start
|
||||
* @param DateTime $end
|
||||
* @return bool
|
||||
*/
|
||||
public function isInTimeRange(\DateTime $start, \DateTime $end) {
|
||||
|
||||
$dtstart = isset($this->DTSTART)?$this->DTSTART->getDateTime():null;
|
||||
if ($dtstart) {
|
||||
$effectiveEnd = clone $dtstart;
|
||||
if (!$this->DTSTART->hasTime()) {
|
||||
$effectiveEnd->modify('+1 day');
|
||||
}
|
||||
|
||||
return ($start <= $effectiveEnd && $end > $dtstart);
|
||||
|
||||
}
|
||||
return false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Component;
|
||||
|
||||
use SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* VTodo component
|
||||
*
|
||||
* This component contains some additional functionality specific for VTODOs.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class VTodo extends VObject\Component {
|
||||
|
||||
/**
|
||||
* Returns true or false depending on if the event falls in the specified
|
||||
* time-range. This is used for filtering purposes.
|
||||
*
|
||||
* The rules used to determine if an event falls within the specified
|
||||
* time-range is based on the CalDAV specification.
|
||||
*
|
||||
* @param DateTime $start
|
||||
* @param DateTime $end
|
||||
* @return bool
|
||||
*/
|
||||
public function isInTimeRange(\DateTime $start, \DateTime $end) {
|
||||
|
||||
$dtstart = isset($this->DTSTART)?$this->DTSTART->getDateTime():null;
|
||||
$duration = isset($this->DURATION)?VObject\DateTimeParser::parseDuration($this->DURATION):null;
|
||||
$due = isset($this->DUE)?$this->DUE->getDateTime():null;
|
||||
$completed = isset($this->COMPLETED)?$this->COMPLETED->getDateTime():null;
|
||||
$created = isset($this->CREATED)?$this->CREATED->getDateTime():null;
|
||||
|
||||
if ($dtstart) {
|
||||
if ($duration) {
|
||||
$effectiveEnd = clone $dtstart;
|
||||
$effectiveEnd->add($duration);
|
||||
return $start <= $effectiveEnd && $end > $dtstart;
|
||||
} elseif ($due) {
|
||||
return
|
||||
($start < $due || $start <= $dtstart) &&
|
||||
($end > $dtstart || $end >= $due);
|
||||
} else {
|
||||
return $start <= $dtstart && $end > $dtstart;
|
||||
}
|
||||
}
|
||||
if ($due) {
|
||||
return ($start < $due && $end >= $due);
|
||||
}
|
||||
if ($completed && $created) {
|
||||
return
|
||||
($start <= $created || $start <= $completed) &&
|
||||
($end >= $created || $end >= $completed);
|
||||
}
|
||||
if ($completed) {
|
||||
return ($start <= $completed && $end >= $completed);
|
||||
}
|
||||
if ($created) {
|
||||
return ($end > $created);
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,415 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* DateTimeParser
|
||||
*
|
||||
* This class is responsible for parsing the several different date and time
|
||||
* formats iCalendar and vCards have.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class DateTimeParser {
|
||||
|
||||
/**
|
||||
* Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object
|
||||
*
|
||||
* Specifying a reference timezone is optional. It will only be used
|
||||
* if the non-UTC format is used. The argument is used as a reference, the
|
||||
* returned DateTime object will still be in the UTC timezone.
|
||||
*
|
||||
* @param string $dt
|
||||
* @param DateTimeZone $tz
|
||||
* @return DateTime
|
||||
*/
|
||||
static public function parseDateTime($dt,\DateTimeZone $tz = null) {
|
||||
|
||||
// Format is YYYYMMDD + "T" + hhmmss
|
||||
$result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/',$dt,$matches);
|
||||
|
||||
if (!$result) {
|
||||
throw new \LogicException('The supplied iCalendar datetime value is incorrect: ' . $dt);
|
||||
}
|
||||
|
||||
if ($matches[7]==='Z' || is_null($tz)) {
|
||||
$tz = new \DateTimeZone('UTC');
|
||||
}
|
||||
$date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);
|
||||
|
||||
// Still resetting the timezone, to normalize everything to UTC
|
||||
// $date->setTimeZone(new \DateTimeZone('UTC'));
|
||||
return $date;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an iCalendar (rfc5545) formatted date and returns a DateTime object
|
||||
*
|
||||
* @param string $date
|
||||
* @return DateTime
|
||||
*/
|
||||
static public function parseDate($date) {
|
||||
|
||||
// Format is YYYYMMDD
|
||||
$result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/',$date,$matches);
|
||||
|
||||
if (!$result) {
|
||||
throw new \LogicException('The supplied iCalendar date value is incorrect: ' . $date);
|
||||
}
|
||||
|
||||
$date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], new \DateTimeZone('UTC'));
|
||||
return $date;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an iCalendar (RFC5545) formatted duration value.
|
||||
*
|
||||
* This method will either return a DateTimeInterval object, or a string
|
||||
* suitable for strtotime or DateTime::modify.
|
||||
*
|
||||
* @param string $duration
|
||||
* @param bool $asString
|
||||
* @return DateInterval|string
|
||||
*/
|
||||
static public function parseDuration($duration, $asString = false) {
|
||||
|
||||
$result = preg_match('/^(?P<plusminus>\+|-)?P((?P<week>\d+)W)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?$/', $duration, $matches);
|
||||
if (!$result) {
|
||||
throw new \LogicException('The supplied iCalendar duration value is incorrect: ' . $duration);
|
||||
}
|
||||
|
||||
if (!$asString) {
|
||||
$invert = false;
|
||||
if ($matches['plusminus']==='-') {
|
||||
$invert = true;
|
||||
}
|
||||
|
||||
|
||||
$parts = array(
|
||||
'week',
|
||||
'day',
|
||||
'hour',
|
||||
'minute',
|
||||
'second',
|
||||
);
|
||||
foreach($parts as $part) {
|
||||
$matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0;
|
||||
}
|
||||
|
||||
|
||||
// We need to re-construct the $duration string, because weeks and
|
||||
// days are not supported by DateInterval in the same string.
|
||||
$duration = 'P';
|
||||
$days = $matches['day'];
|
||||
if ($matches['week']) {
|
||||
$days+=$matches['week']*7;
|
||||
}
|
||||
if ($days)
|
||||
$duration.=$days . 'D';
|
||||
|
||||
if ($matches['minute'] || $matches['second'] || $matches['hour']) {
|
||||
$duration.='T';
|
||||
|
||||
if ($matches['hour'])
|
||||
$duration.=$matches['hour'].'H';
|
||||
|
||||
if ($matches['minute'])
|
||||
$duration.=$matches['minute'].'M';
|
||||
|
||||
if ($matches['second'])
|
||||
$duration.=$matches['second'].'S';
|
||||
|
||||
}
|
||||
|
||||
if ($duration==='P') {
|
||||
$duration = 'PT0S';
|
||||
}
|
||||
$iv = new \DateInterval($duration);
|
||||
if ($invert) $iv->invert = true;
|
||||
|
||||
return $iv;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
$parts = array(
|
||||
'week',
|
||||
'day',
|
||||
'hour',
|
||||
'minute',
|
||||
'second',
|
||||
);
|
||||
|
||||
$newDur = '';
|
||||
foreach($parts as $part) {
|
||||
if (isset($matches[$part]) && $matches[$part]) {
|
||||
$newDur.=' '.$matches[$part] . ' ' . $part . 's';
|
||||
}
|
||||
}
|
||||
|
||||
$newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur);
|
||||
if ($newDur === '+') { $newDur = '+0 seconds'; };
|
||||
return $newDur;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses either a Date or DateTime, or Duration value.
|
||||
*
|
||||
* @param string $date
|
||||
* @param DateTimeZone|string $referenceTZ
|
||||
* @return DateTime|DateInterval
|
||||
*/
|
||||
static public function parse($date, $referenceTZ = null) {
|
||||
|
||||
if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) {
|
||||
return self::parseDuration($date);
|
||||
} elseif (strlen($date)===8) {
|
||||
return self::parseDate($date);
|
||||
} else {
|
||||
return self::parseDateTime($date, $referenceTZ);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method parses a vCard date and or time value.
|
||||
*
|
||||
* This can be used for the DATE, DATE-TIME, TIMESTAMP and
|
||||
* DATE-AND-OR-TIME value.
|
||||
*
|
||||
* This method returns an array, not a DateTime value.
|
||||
*
|
||||
* The elements in the array are in the following order:
|
||||
* year, month, date, hour, minute, second, timezone
|
||||
*
|
||||
* Almost any part of the string may be omitted. It's for example legal to
|
||||
* just specify seconds, leave out the year, etc.
|
||||
*
|
||||
* Timezone is either returned as 'Z' or as '+08:00'
|
||||
*
|
||||
* For any non-specified values null is returned.
|
||||
*
|
||||
* List of date formats that are supported:
|
||||
* YYYY
|
||||
* YYYY-MM
|
||||
* YYYYMMDD
|
||||
* --MMDD
|
||||
* ---DD
|
||||
*
|
||||
* YYYY-MM-DD
|
||||
* --MM-DD
|
||||
* ---DD
|
||||
*
|
||||
* List of supported time formats:
|
||||
*
|
||||
* HH
|
||||
* HHMM
|
||||
* HHMMSS
|
||||
* -MMSS
|
||||
* --SS
|
||||
*
|
||||
* HH
|
||||
* HH:MM
|
||||
* HH:MM:SS
|
||||
* -MM:SS
|
||||
* --SS
|
||||
*
|
||||
* A full basic-format date-time string looks like :
|
||||
* 20130603T133901
|
||||
*
|
||||
* A full extended-format date-time string looks like :
|
||||
* 2013-06-03T13:39:01
|
||||
*
|
||||
* Times may be postfixed by a timezone offset. This can be either 'Z' for
|
||||
* UTC, or a string like -0500 or +1100.
|
||||
*
|
||||
* @param string $date
|
||||
* @return array
|
||||
*/
|
||||
static public function parseVCardDateTime($date) {
|
||||
|
||||
$regex = '/^
|
||||
(?: # date part
|
||||
(?:
|
||||
(?: (?P<year> [0-9]{4}) (?: -)?| --)
|
||||
(?P<month> [0-9]{2})?
|
||||
|---)
|
||||
(?P<date> [0-9]{2})?
|
||||
)?
|
||||
(?:T # time part
|
||||
(?P<hour> [0-9]{2} | -)
|
||||
(?P<minute> [0-9]{2} | -)?
|
||||
(?P<second> [0-9]{2})?
|
||||
|
||||
(?P<timezone> # timezone offset
|
||||
|
||||
Z | (?: \+|-)(?: [0-9]{4})
|
||||
|
||||
)?
|
||||
|
||||
)?
|
||||
$/x';
|
||||
|
||||
|
||||
if (!preg_match($regex, $date, $matches)) {
|
||||
|
||||
// Attempting to parse the extended format.
|
||||
$regex = '/^
|
||||
(?: # date part
|
||||
(?: (?P<year> [0-9]{4}) - | -- )
|
||||
(?P<month> [0-9]{2}) -
|
||||
(?P<date> [0-9]{2})
|
||||
)?
|
||||
(?:T # time part
|
||||
|
||||
(?: (?P<hour> [0-9]{2}) : | -)
|
||||
(?: (?P<minute> [0-9]{2}) : | -)?
|
||||
(?P<second> [0-9]{2})?
|
||||
|
||||
(?P<timezone> # timezone offset
|
||||
|
||||
Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
|
||||
|
||||
)?
|
||||
|
||||
)?
|
||||
$/x';
|
||||
|
||||
if (!preg_match($regex, $date, $matches)) {
|
||||
throw new \InvalidArgumentException('Invalid vCard date-time string: ' . $date);
|
||||
}
|
||||
|
||||
}
|
||||
$parts = array(
|
||||
'year',
|
||||
'month',
|
||||
'date',
|
||||
'hour',
|
||||
'minute',
|
||||
'second',
|
||||
'timezone'
|
||||
);
|
||||
|
||||
$result = array();
|
||||
foreach($parts as $part) {
|
||||
|
||||
if (empty($matches[$part])) {
|
||||
$result[$part] = null;
|
||||
} elseif ($matches[$part] === '-' || $matches[$part] === '--') {
|
||||
$result[$part] = null;
|
||||
} else {
|
||||
$result[$part] = $matches[$part];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method parses a vCard TIME value.
|
||||
*
|
||||
* This method returns an array, not a DateTime value.
|
||||
*
|
||||
* The elements in the array are in the following order:
|
||||
* hour, minute, second, timezone
|
||||
*
|
||||
* Almost any part of the string may be omitted. It's for example legal to
|
||||
* just specify seconds, leave out the hour etc.
|
||||
*
|
||||
* Timezone is either returned as 'Z' or as '+08:00'
|
||||
*
|
||||
* For any non-specified values null is returned.
|
||||
*
|
||||
* List of supported time formats:
|
||||
*
|
||||
* HH
|
||||
* HHMM
|
||||
* HHMMSS
|
||||
* -MMSS
|
||||
* --SS
|
||||
*
|
||||
* HH
|
||||
* HH:MM
|
||||
* HH:MM:SS
|
||||
* -MM:SS
|
||||
* --SS
|
||||
*
|
||||
* A full basic-format time string looks like :
|
||||
* 133901
|
||||
*
|
||||
* A full extended-format time string looks like :
|
||||
* 13:39:01
|
||||
*
|
||||
* Times may be postfixed by a timezone offset. This can be either 'Z' for
|
||||
* UTC, or a string like -0500 or +11:00.
|
||||
*
|
||||
* @param string $date
|
||||
* @return array
|
||||
*/
|
||||
static public function parseVCardTime($date) {
|
||||
|
||||
$regex = '/^
|
||||
(?P<hour> [0-9]{2} | -)
|
||||
(?P<minute> [0-9]{2} | -)?
|
||||
(?P<second> [0-9]{2})?
|
||||
|
||||
(?P<timezone> # timezone offset
|
||||
|
||||
Z | (?: \+|-)(?: [0-9]{4})
|
||||
|
||||
)?
|
||||
$/x';
|
||||
|
||||
|
||||
if (!preg_match($regex, $date, $matches)) {
|
||||
|
||||
// Attempting to parse the extended format.
|
||||
$regex = '/^
|
||||
(?: (?P<hour> [0-9]{2}) : | -)
|
||||
(?: (?P<minute> [0-9]{2}) : | -)?
|
||||
(?P<second> [0-9]{2})?
|
||||
|
||||
(?P<timezone> # timezone offset
|
||||
|
||||
Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
|
||||
|
||||
)?
|
||||
$/x';
|
||||
|
||||
if (!preg_match($regex, $date, $matches)) {
|
||||
throw new \InvalidArgumentException('Invalid vCard time string: ' . $date);
|
||||
}
|
||||
|
||||
}
|
||||
$parts = array(
|
||||
'hour',
|
||||
'minute',
|
||||
'second',
|
||||
'timezone'
|
||||
);
|
||||
|
||||
$result = array();
|
||||
foreach($parts as $part) {
|
||||
|
||||
if (empty($matches[$part])) {
|
||||
$result[$part] = null;
|
||||
} elseif ($matches[$part] === '-') {
|
||||
$result[$part] = null;
|
||||
} else {
|
||||
$result[$part] = $matches[$part];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* Document
|
||||
*
|
||||
* A document is just like a component, except that it's also the top level
|
||||
* element.
|
||||
*
|
||||
* Both a VCALENDAR and a VCARD are considered documents.
|
||||
*
|
||||
* This class also provides a registry for document types.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
abstract class Document extends Component {
|
||||
|
||||
/**
|
||||
* Unknown document type
|
||||
*/
|
||||
const UNKNOWN = 1;
|
||||
|
||||
/**
|
||||
* vCalendar 1.0
|
||||
*/
|
||||
const VCALENDAR10 = 2;
|
||||
|
||||
/**
|
||||
* iCalendar 2.0
|
||||
*/
|
||||
const ICALENDAR20 = 3;
|
||||
|
||||
/**
|
||||
* vCard 2.1
|
||||
*/
|
||||
const VCARD21 = 4;
|
||||
|
||||
/**
|
||||
* vCard 3.0
|
||||
*/
|
||||
const VCARD30 = 5;
|
||||
|
||||
/**
|
||||
* vCard 4.0
|
||||
*/
|
||||
const VCARD40 = 6;
|
||||
|
||||
/**
|
||||
* The default name for this component.
|
||||
*
|
||||
* This should be 'VCALENDAR' or 'VCARD'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
static public $defaultName;
|
||||
|
||||
/**
|
||||
* List of properties, and which classes they map to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static public $propertyMap = array();
|
||||
|
||||
/**
|
||||
* List of components, along with which classes they map to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static public $componentMap = array();
|
||||
|
||||
/**
|
||||
* List of value-types, and which classes they map to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static public $valueMap = array();
|
||||
|
||||
/**
|
||||
* Creates a new document.
|
||||
*
|
||||
* We're changing the default behavior slightly here. First, we don't want
|
||||
* to have to specify a name (we already know it), and we want to allow
|
||||
* children to be specified in the first argument.
|
||||
*
|
||||
* But, the default behavior also works.
|
||||
*
|
||||
* So the two sigs:
|
||||
*
|
||||
* new Document(array $children = array(), $defaults = true);
|
||||
* new Document(string $name, array $children = array(), $defaults = true)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$args = func_get_args();
|
||||
if (count($args)===0 || is_array($args[0])) {
|
||||
array_unshift($args, $this, static::$defaultName);
|
||||
call_user_func_array(array('parent', '__construct'), $args);
|
||||
} else {
|
||||
array_unshift($args, $this);
|
||||
call_user_func_array(array('parent', '__construct'), $args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current document type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getDocumentType() {
|
||||
|
||||
return self::UNKNOWN;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new component or property.
|
||||
*
|
||||
* If it's a known component, we will automatically call createComponent.
|
||||
* otherwise, we'll assume it's a property and call createProperty instead.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $arg1,... Unlimited number of args
|
||||
* @return mixed
|
||||
*/
|
||||
public function create($name) {
|
||||
|
||||
if (isset(static::$componentMap[strtoupper($name)])) {
|
||||
|
||||
return call_user_func_array(array($this,'createComponent'), func_get_args());
|
||||
|
||||
} else {
|
||||
|
||||
return call_user_func_array(array($this,'createProperty'), func_get_args());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new component
|
||||
*
|
||||
* This method automatically searches for the correct component class, based
|
||||
* on its name.
|
||||
*
|
||||
* You can specify the children either in key=>value syntax, in which case
|
||||
* properties will automatically be created, or you can just pass a list of
|
||||
* Component and Property object.
|
||||
*
|
||||
* By default, a set of sensible values will be added to the component. For
|
||||
* an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
|
||||
* ensure that this does not happen, set $defaults to false.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $children
|
||||
* @param bool $defaults
|
||||
* @return Component
|
||||
*/
|
||||
public function createComponent($name, array $children = null, $defaults = true) {
|
||||
|
||||
$name = strtoupper($name);
|
||||
$class = 'SabreForRainLoop\\VObject\\Component';
|
||||
|
||||
if (isset(static::$componentMap[$name])) {
|
||||
$class=static::$componentMap[$name];
|
||||
}
|
||||
if (is_null($children)) $children = array();
|
||||
return new $class($this, $name, $children, $defaults);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for creating new properties
|
||||
*
|
||||
* This method automatically searches for the correct property class, based
|
||||
* on its name.
|
||||
*
|
||||
* You can specify the parameters either in key=>value syntax, in which case
|
||||
* parameters will automatically be created, or you can just pass a list of
|
||||
* Parameter objects.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @param array $parameters
|
||||
* @param string $valueType Force a specific valuetype, such as URI or TEXT
|
||||
* @return Property
|
||||
*/
|
||||
public function createProperty($name, $value = null, array $parameters = null, $valueType = null) {
|
||||
|
||||
// If there's a . in the name, it means it's prefixed by a groupname.
|
||||
if (($i=strpos($name,'.'))!==false) {
|
||||
$group = substr($name, 0, $i);
|
||||
$name = strtoupper(substr($name, $i+1));
|
||||
} else {
|
||||
$name = strtoupper($name);
|
||||
$group = null;
|
||||
}
|
||||
|
||||
$class = null;
|
||||
|
||||
if ($valueType) {
|
||||
// The valueType argument comes first to figure out the correct
|
||||
// class.
|
||||
$class = $this->getClassNameForPropertyValue($valueType);
|
||||
}
|
||||
|
||||
if (is_null($class) && isset($parameters['VALUE'])) {
|
||||
// If a VALUE parameter is supplied, we should use that.
|
||||
$class = $this->getClassNameForPropertyValue($parameters['VALUE']);
|
||||
}
|
||||
if (is_null($class)) {
|
||||
$class = $this->getClassNameForPropertyName($name);
|
||||
}
|
||||
if (is_null($parameters)) $parameters = array();
|
||||
|
||||
return new $class($this, $name, $value, $parameters, $group);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a full class-name for a value parameter.
|
||||
*
|
||||
* For instance, DTSTART may have VALUE=DATE. In that case we will look in
|
||||
* our valueMap table and return the appropriate class name.
|
||||
*
|
||||
* This method returns null if we don't have a specialized class.
|
||||
*
|
||||
* @param string $valueParam
|
||||
* @return void
|
||||
*/
|
||||
public function getClassNameForPropertyValue($valueParam) {
|
||||
|
||||
$valueParam = strtoupper($valueParam);
|
||||
if (isset(static::$valueMap[$valueParam])) {
|
||||
return static::$valueMap[$valueParam];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default class for a property name.
|
||||
*
|
||||
* @param string $propertyName
|
||||
* @return string
|
||||
*/
|
||||
public function getClassNameForPropertyName($propertyName) {
|
||||
|
||||
if (isset(static::$propertyMap[$propertyName])) {
|
||||
return static::$propertyMap[$propertyName];
|
||||
} else {
|
||||
return 'SabreForRainLoop\\VObject\\Property\\Unknown';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* VObject ElementList
|
||||
*
|
||||
* This class represents a list of elements. Lists are the result of queries,
|
||||
* such as doing $vcalendar->vevent where there's multiple VEVENT objects.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class ElementList implements \Iterator, \Countable, \ArrayAccess {
|
||||
|
||||
/**
|
||||
* Inner elements
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $elements = array();
|
||||
|
||||
/**
|
||||
* Creates the element list.
|
||||
*
|
||||
* @param array $elements
|
||||
*/
|
||||
public function __construct(array $elements) {
|
||||
|
||||
$this->elements = $elements;
|
||||
|
||||
}
|
||||
|
||||
/* {{{ Iterator interface */
|
||||
|
||||
/**
|
||||
* Current position
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $key = 0;
|
||||
|
||||
/**
|
||||
* Returns current item in iteration
|
||||
*
|
||||
* @return Element
|
||||
*/
|
||||
public function current() {
|
||||
|
||||
return $this->elements[$this->key];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* To the next item in the iterator
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next() {
|
||||
|
||||
$this->key++;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current iterator key
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function key() {
|
||||
|
||||
return $this->key;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current position in the iterator is a valid one
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function valid() {
|
||||
|
||||
return isset($this->elements[$this->key]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds the iterator
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind() {
|
||||
|
||||
$this->key = 0;
|
||||
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Countable interface */
|
||||
|
||||
/**
|
||||
* Returns the number of elements
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count() {
|
||||
|
||||
return count($this->elements);
|
||||
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* {{{ ArrayAccess Interface */
|
||||
|
||||
|
||||
/**
|
||||
* Checks if an item exists through ArrayAccess.
|
||||
*
|
||||
* @param int $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset) {
|
||||
|
||||
return isset($this->elements[$offset]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an item through ArrayAccess.
|
||||
*
|
||||
* @param int $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
|
||||
return $this->elements[$offset];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an item through ArrayAccess.
|
||||
*
|
||||
* @param int $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($offset,$value) {
|
||||
|
||||
throw new \LogicException('You can not add new objects to an ElementList');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an item through ArrayAccess.
|
||||
*
|
||||
* This method just forwards the request to the inner iterator
|
||||
*
|
||||
* @param int $offset
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($offset) {
|
||||
|
||||
throw new \LogicException('You can not remove objects from an ElementList');
|
||||
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* Exception thrown by parser when the end of the stream has been reached,
|
||||
* before this was expected.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class EofException extends ParseException { }
|
||||
|
|
@ -0,0 +1,322 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Component\VCalendar;
|
||||
|
||||
/**
|
||||
* This class helps with generating FREEBUSY reports based on existing sets of
|
||||
* objects.
|
||||
*
|
||||
* It only looks at VEVENT and VFREEBUSY objects from the sourcedata, and
|
||||
* generates a single VFREEBUSY object.
|
||||
*
|
||||
* VFREEBUSY components are described in RFC5545, The rules for what should
|
||||
* go in a single freebusy report is taken from RFC4791, section 7.10.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class FreeBusyGenerator {
|
||||
|
||||
/**
|
||||
* Input objects
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $objects;
|
||||
|
||||
/**
|
||||
* Start of range
|
||||
*
|
||||
* @var DateTime|null
|
||||
*/
|
||||
protected $start;
|
||||
|
||||
/**
|
||||
* End of range
|
||||
*
|
||||
* @var DateTime|null
|
||||
*/
|
||||
protected $end;
|
||||
|
||||
/**
|
||||
* VCALENDAR object
|
||||
*
|
||||
* @var Component
|
||||
*/
|
||||
protected $baseObject;
|
||||
|
||||
/**
|
||||
* Creates the generator.
|
||||
*
|
||||
* Check the setTimeRange and setObjects methods for details about the
|
||||
* arguments.
|
||||
*
|
||||
* @param DateTime $start
|
||||
* @param DateTime $end
|
||||
* @param mixed $objects
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(\DateTime $start = null, \DateTime $end = null, $objects = null) {
|
||||
|
||||
if ($start && $end) {
|
||||
$this->setTimeRange($start, $end);
|
||||
}
|
||||
|
||||
if ($objects) {
|
||||
$this->setObjects($objects);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the VCALENDAR object.
|
||||
*
|
||||
* If this is set, it will not be generated for you. You are responsible
|
||||
* for setting things like the METHOD, CALSCALE, VERSION, etc..
|
||||
*
|
||||
* The VFREEBUSY object will be automatically added though.
|
||||
*
|
||||
* @param Component $vcalendar
|
||||
* @return void
|
||||
*/
|
||||
public function setBaseObject(Component $vcalendar) {
|
||||
|
||||
$this->baseObject = $vcalendar;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input objects
|
||||
*
|
||||
* You must either specify a valendar object as a strong, or as the parse
|
||||
* Component.
|
||||
* It's also possible to specify multiple objects as an array.
|
||||
*
|
||||
* @param mixed $objects
|
||||
* @return void
|
||||
*/
|
||||
public function setObjects($objects) {
|
||||
|
||||
if (!is_array($objects)) {
|
||||
$objects = array($objects);
|
||||
}
|
||||
|
||||
$this->objects = array();
|
||||
foreach($objects as $object) {
|
||||
|
||||
if (is_string($object)) {
|
||||
$this->objects[] = Reader::read($object);
|
||||
} elseif ($object instanceof Component) {
|
||||
$this->objects[] = $object;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('You can only pass strings or \\SabreForRainLoop\\VObject\\Component arguments to setObjects');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time range
|
||||
*
|
||||
* Any freebusy object falling outside of this time range will be ignored.
|
||||
*
|
||||
* @param DateTime $start
|
||||
* @param DateTime $end
|
||||
* @return void
|
||||
*/
|
||||
public function setTimeRange(\DateTime $start = null, \DateTime $end = null) {
|
||||
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the input data and returns a correct VFREEBUSY object, wrapped in
|
||||
* a VCALENDAR.
|
||||
*
|
||||
* @return Component
|
||||
*/
|
||||
public function getResult() {
|
||||
|
||||
$busyTimes = array();
|
||||
|
||||
foreach($this->objects as $object) {
|
||||
|
||||
foreach($object->getBaseComponents() as $component) {
|
||||
|
||||
switch($component->name) {
|
||||
|
||||
case 'VEVENT' :
|
||||
|
||||
$FBTYPE = 'BUSY';
|
||||
if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
|
||||
break;
|
||||
}
|
||||
if (isset($component->STATUS)) {
|
||||
$status = strtoupper($component->STATUS);
|
||||
if ($status==='CANCELLED') {
|
||||
break;
|
||||
}
|
||||
if ($status==='TENTATIVE') {
|
||||
$FBTYPE = 'BUSY-TENTATIVE';
|
||||
}
|
||||
}
|
||||
|
||||
$times = array();
|
||||
|
||||
if ($component->RRULE) {
|
||||
|
||||
$iterator = new RecurrenceIterator($object, (string)$component->uid);
|
||||
if ($this->start) {
|
||||
$iterator->fastForward($this->start);
|
||||
}
|
||||
|
||||
$maxRecurrences = 200;
|
||||
|
||||
while($iterator->valid() && --$maxRecurrences) {
|
||||
|
||||
$startTime = $iterator->getDTStart();
|
||||
if ($this->end && $startTime > $this->end) {
|
||||
break;
|
||||
}
|
||||
$times[] = array(
|
||||
$iterator->getDTStart(),
|
||||
$iterator->getDTEnd(),
|
||||
);
|
||||
|
||||
$iterator->next();
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$startTime = $component->DTSTART->getDateTime();
|
||||
if ($this->end && $startTime > $this->end) {
|
||||
break;
|
||||
}
|
||||
$endTime = null;
|
||||
if (isset($component->DTEND)) {
|
||||
$endTime = $component->DTEND->getDateTime();
|
||||
} elseif (isset($component->DURATION)) {
|
||||
$duration = DateTimeParser::parseDuration((string)$component->DURATION);
|
||||
$endTime = clone $startTime;
|
||||
$endTime->add($duration);
|
||||
} elseif (!$component->DTSTART->hasTime()) {
|
||||
$endTime = clone $startTime;
|
||||
$endTime->modify('+1 day');
|
||||
} else {
|
||||
// The event had no duration (0 seconds)
|
||||
break;
|
||||
}
|
||||
|
||||
$times[] = array($startTime, $endTime);
|
||||
|
||||
}
|
||||
|
||||
foreach($times as $time) {
|
||||
|
||||
if ($this->end && $time[0] > $this->end) break;
|
||||
if ($this->start && $time[1] < $this->start) break;
|
||||
|
||||
$busyTimes[] = array(
|
||||
$time[0],
|
||||
$time[1],
|
||||
$FBTYPE,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'VFREEBUSY' :
|
||||
foreach($component->FREEBUSY as $freebusy) {
|
||||
|
||||
$fbType = isset($freebusy['FBTYPE'])?strtoupper($freebusy['FBTYPE']):'BUSY';
|
||||
|
||||
// Skipping intervals marked as 'free'
|
||||
if ($fbType==='FREE')
|
||||
continue;
|
||||
|
||||
$values = explode(',', $freebusy);
|
||||
foreach($values as $value) {
|
||||
list($startTime, $endTime) = explode('/', $value);
|
||||
$startTime = DateTimeParser::parseDateTime($startTime);
|
||||
|
||||
if (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') {
|
||||
$duration = DateTimeParser::parseDuration($endTime);
|
||||
$endTime = clone $startTime;
|
||||
$endTime->add($duration);
|
||||
} else {
|
||||
$endTime = DateTimeParser::parseDateTime($endTime);
|
||||
}
|
||||
|
||||
if($this->start && $this->start > $endTime) continue;
|
||||
if($this->end && $this->end < $startTime) continue;
|
||||
$busyTimes[] = array(
|
||||
$startTime,
|
||||
$endTime,
|
||||
$fbType
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($this->baseObject) {
|
||||
$calendar = $this->baseObject;
|
||||
} else {
|
||||
$calendar = new VCalendar();
|
||||
}
|
||||
|
||||
$vfreebusy = $calendar->createComponent('VFREEBUSY');
|
||||
$calendar->add($vfreebusy);
|
||||
|
||||
if ($this->start) {
|
||||
$dtstart = $calendar->createProperty('DTSTART');
|
||||
$dtstart->setDateTime($this->start);
|
||||
$vfreebusy->add($dtstart);
|
||||
}
|
||||
if ($this->end) {
|
||||
$dtend = $calendar->createProperty('DTEND');
|
||||
$dtend->setDateTime($this->end);
|
||||
$vfreebusy->add($dtend);
|
||||
}
|
||||
$dtstamp = $calendar->createProperty('DTSTAMP');
|
||||
$dtstamp->setDateTime(new \DateTime('now', new \DateTimeZone('UTC')));
|
||||
$vfreebusy->add($dtstamp);
|
||||
|
||||
foreach($busyTimes as $busyTime) {
|
||||
|
||||
$busyTime[0]->setTimeZone(new \DateTimeZone('UTC'));
|
||||
$busyTime[1]->setTimeZone(new \DateTimeZone('UTC'));
|
||||
|
||||
$prop = $calendar->createProperty(
|
||||
'FREEBUSY',
|
||||
$busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
|
||||
);
|
||||
$prop['FBTYPE'] = $busyTime[2];
|
||||
$vfreebusy->add($prop);
|
||||
|
||||
}
|
||||
|
||||
return $calendar;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
201
rainloop/v/0.0.0/app/libraries/SabreForRainLoop/VObject/Node.php
Normal file
201
rainloop/v/0.0.0/app/libraries/SabreForRainLoop/VObject/Node.php
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* A node is the root class for every element in an iCalendar of vCard object.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
|
||||
|
||||
/**
|
||||
* The following constants are used by the validate() method.
|
||||
*/
|
||||
const REPAIR = 1;
|
||||
|
||||
/**
|
||||
* Reference to the parent object, if this is not the top object.
|
||||
*
|
||||
* @var Node
|
||||
*/
|
||||
public $parent;
|
||||
|
||||
/**
|
||||
* Iterator override
|
||||
*
|
||||
* @var ElementList
|
||||
*/
|
||||
protected $iterator = null;
|
||||
|
||||
/**
|
||||
* The root document
|
||||
*
|
||||
* @var Component
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Serializes the node into a mimedir format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract function serialize();
|
||||
|
||||
/**
|
||||
* This method returns an array, with the representation as it should be
|
||||
* encoded in json. This is used to create jCard or jCal documents.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract function jsonSerialize();
|
||||
|
||||
/* {{{ IteratorAggregator interface */
|
||||
|
||||
/**
|
||||
* Returns the iterator for this object
|
||||
*
|
||||
* @return ElementList
|
||||
*/
|
||||
public function getIterator() {
|
||||
|
||||
if (!is_null($this->iterator))
|
||||
return $this->iterator;
|
||||
|
||||
return new ElementList(array($this));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the overridden iterator
|
||||
*
|
||||
* Note that this is not actually part of the iterator interface
|
||||
*
|
||||
* @param ElementList $iterator
|
||||
* @return void
|
||||
*/
|
||||
public function setIterator(ElementList $iterator) {
|
||||
|
||||
$this->iterator = $iterator;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the node for correctness.
|
||||
*
|
||||
* The following options are supported:
|
||||
* - Node::REPAIR - If something is broken, and automatic repair may
|
||||
* be attempted.
|
||||
*
|
||||
* An array is returned with warnings.
|
||||
*
|
||||
* Every item in the array has the following properties:
|
||||
* * level - (number between 1 and 3 with severity information)
|
||||
* * message - (human readable message)
|
||||
* * node - (reference to the offending node)
|
||||
*
|
||||
* @param int $options
|
||||
* @return array
|
||||
*/
|
||||
public function validate($options = 0) {
|
||||
|
||||
return array();
|
||||
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Countable interface */
|
||||
|
||||
/**
|
||||
* Returns the number of elements
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count() {
|
||||
|
||||
$it = $this->getIterator();
|
||||
return $it->count();
|
||||
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* {{{ ArrayAccess Interface */
|
||||
|
||||
|
||||
/**
|
||||
* Checks if an item exists through ArrayAccess.
|
||||
*
|
||||
* This method just forwards the request to the inner iterator
|
||||
*
|
||||
* @param int $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset) {
|
||||
|
||||
$iterator = $this->getIterator();
|
||||
return $iterator->offsetExists($offset);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an item through ArrayAccess.
|
||||
*
|
||||
* This method just forwards the request to the inner iterator
|
||||
*
|
||||
* @param int $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
|
||||
$iterator = $this->getIterator();
|
||||
return $iterator->offsetGet($offset);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an item through ArrayAccess.
|
||||
*
|
||||
* This method just forwards the request to the inner iterator
|
||||
*
|
||||
* @param int $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($offset,$value) {
|
||||
|
||||
$iterator = $this->getIterator();
|
||||
$iterator->offsetSet($offset,$value);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
//
|
||||
// This method always throws an exception, so we ignore the closing
|
||||
// brace
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
/**
|
||||
* Sets an item through ArrayAccess.
|
||||
*
|
||||
* This method just forwards the request to the inner iterator
|
||||
*
|
||||
* @param int $offset
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($offset) {
|
||||
|
||||
$iterator = $this->getIterator();
|
||||
$iterator->offsetUnset($offset);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
//
|
||||
// This method always throws an exception, so we ignore the closing
|
||||
// brace
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
/* }}} */
|
||||
}
|
||||
|
|
@ -0,0 +1,343 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
use
|
||||
ArrayObject;
|
||||
|
||||
/**
|
||||
* VObject Parameter
|
||||
*
|
||||
* This class represents a parameter. A parameter is always tied to a property.
|
||||
* In the case of:
|
||||
* DTSTART;VALUE=DATE:20101108
|
||||
* VALUE=DATE would be the parameter name and value.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Parameter extends Node {
|
||||
|
||||
/**
|
||||
* Parameter name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* vCard 2.1 allows parameters to be encoded without a name.
|
||||
*
|
||||
* We can deduce the parameter name based on it's value.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $noName = false;
|
||||
|
||||
/**
|
||||
* Parameter value
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Sets up the object.
|
||||
*
|
||||
* It's recommended to use the create:: factory method instead.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
*/
|
||||
public function __construct(Document $root, $name, $value = null) {
|
||||
|
||||
$this->name = strtoupper($name);
|
||||
$this->root = $root;
|
||||
if (is_null($name)) {
|
||||
$this->noName = true;
|
||||
$this->name = static::guessParameterNameByValue($value);
|
||||
}
|
||||
$this->setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to guess property name by value, can be used for vCard 2.1 nameless parameters.
|
||||
*
|
||||
* Figuring out what the name should have been. Note that a ton of
|
||||
* these are rather silly in 2013 and would probably rarely be
|
||||
* used, but we like to be complete.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public static function guessParameterNameByValue($value) {
|
||||
switch(strtoupper($value)) {
|
||||
|
||||
// Encodings
|
||||
case '7-BIT' :
|
||||
case 'QUOTED-PRINTABLE' :
|
||||
case 'BASE64' :
|
||||
$name = 'ENCODING';
|
||||
break;
|
||||
|
||||
// Common types
|
||||
case 'WORK' :
|
||||
case 'HOME' :
|
||||
case 'PREF' :
|
||||
|
||||
// Delivery Label Type
|
||||
case 'DOM' :
|
||||
case 'INTL' :
|
||||
case 'POSTAL' :
|
||||
case 'PARCEL' :
|
||||
|
||||
// Telephone types
|
||||
case 'VOICE' :
|
||||
case 'FAX' :
|
||||
case 'MSG' :
|
||||
case 'CELL' :
|
||||
case 'PAGER' :
|
||||
case 'BBS' :
|
||||
case 'MODEM' :
|
||||
case 'CAR' :
|
||||
case 'ISDN' :
|
||||
case 'VIDEO' :
|
||||
|
||||
// EMAIL types (lol)
|
||||
case 'AOL' :
|
||||
case 'APPLELINK' :
|
||||
case 'ATTMAIL' :
|
||||
case 'CIS' :
|
||||
case 'EWORLD' :
|
||||
case 'INTERNET' :
|
||||
case 'IBMMAIL' :
|
||||
case 'MCIMAIL' :
|
||||
case 'POWERSHARE' :
|
||||
case 'PRODIGY' :
|
||||
case 'TLX' :
|
||||
case 'X400' :
|
||||
|
||||
// Photo / Logo format types
|
||||
case 'GIF' :
|
||||
case 'CGM' :
|
||||
case 'WMF' :
|
||||
case 'BMP' :
|
||||
case 'DIB' :
|
||||
case 'PICT' :
|
||||
case 'TIFF' :
|
||||
case 'PDF ':
|
||||
case 'PS' :
|
||||
case 'JPEG' :
|
||||
case 'MPEG' :
|
||||
case 'MPEG2' :
|
||||
case 'AVI' :
|
||||
case 'QTIME' :
|
||||
|
||||
// Sound Digital Audio Type
|
||||
case 'WAVE' :
|
||||
case 'PCM' :
|
||||
case 'AIFF' :
|
||||
|
||||
// Key types
|
||||
case 'X509' :
|
||||
case 'PGP' :
|
||||
$name = 'TYPE';
|
||||
break;
|
||||
|
||||
// Value types
|
||||
case 'INLINE' :
|
||||
case 'URL' :
|
||||
case 'CONTENT-ID' :
|
||||
case 'CID' :
|
||||
$name = 'VALUE';
|
||||
break;
|
||||
|
||||
default:
|
||||
$name = '';
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current value.
|
||||
*
|
||||
* This may be either a single, or multiple strings in an array.
|
||||
*
|
||||
* @param string|array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setValue($value) {
|
||||
|
||||
$this->value = $value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value
|
||||
*
|
||||
* This method will always return a string, or null. If there were multiple
|
||||
* values, it will automatically concatinate them (separated by comma).
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getValue() {
|
||||
|
||||
if (is_array($this->value)) {
|
||||
return implode(',' , $this->value);
|
||||
} else {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple values for this parameter.
|
||||
*
|
||||
* @param array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setParts(array $value) {
|
||||
|
||||
$this->value = $value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all values for this parameter.
|
||||
*
|
||||
* If there were no values, an empty array will be returned.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParts() {
|
||||
|
||||
if (is_array($this->value)) {
|
||||
return $this->value;
|
||||
} elseif (is_null($this->value)) {
|
||||
return array();
|
||||
} else {
|
||||
return array($this->value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to this parameter
|
||||
*
|
||||
* If the argument is specified as an array, all items will be added to the
|
||||
* parameter value list.
|
||||
*
|
||||
* @param string|array $part
|
||||
* @return void
|
||||
*/
|
||||
public function addValue($part) {
|
||||
|
||||
if (is_null($this->value)) {
|
||||
$this->value = $part;
|
||||
} else {
|
||||
$this->value = array_merge((array)$this->value, (array)$part);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this parameter contains the specified value.
|
||||
*
|
||||
* This is a case-insensitive match. It makes sense to call this for for
|
||||
* instance the TYPE parameter, to see if it contains a keyword such as
|
||||
* 'WORK' or 'FAX'.
|
||||
*
|
||||
* @param string $value
|
||||
* @return bool
|
||||
*/
|
||||
public function has($value) {
|
||||
|
||||
return in_array(
|
||||
strtolower($value),
|
||||
array_map('strtolower', (array)$this->value)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the object back into a serialized blob.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize() {
|
||||
|
||||
$value = $this->getParts();
|
||||
|
||||
if (count($value)===0) {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) {
|
||||
|
||||
return implode(';', $value);
|
||||
|
||||
}
|
||||
|
||||
return $this->name . '=' . array_reduce($value, function($out, $item) {
|
||||
|
||||
if (!is_null($out)) $out.=',';
|
||||
|
||||
// If there's no special characters in the string, we'll use the simple
|
||||
// format
|
||||
if (!preg_match('#(?: [\n":;\^,] )#x', $item)) {
|
||||
return $out.$item;
|
||||
} else {
|
||||
// Enclosing in double-quotes, and using RFC6868 for encoding any
|
||||
// special characters
|
||||
$out.='"' . strtr($item, array(
|
||||
'^' => '^^',
|
||||
"\n" => '^n',
|
||||
'"' => '^\'',
|
||||
)) . '"';
|
||||
return $out;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns an array, with the representation as it should be
|
||||
* encoded in json. This is used to create jCard or jCal documents.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
|
||||
return $this->value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this object is being cast to a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
|
||||
return $this->getValue();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the iterator for this object
|
||||
*
|
||||
* @return ElementList
|
||||
*/
|
||||
public function getIterator() {
|
||||
|
||||
if (!is_null($this->iterator))
|
||||
return $this->iterator;
|
||||
|
||||
return $this->iterator = new ArrayObject((array)$this->value);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* Exception thrown by Reader if an invalid object was attempted to be parsed.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class ParseException extends \Exception { }
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Parser;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Component\VCalendar,
|
||||
SabreForRainLoop\VObject\Component\VCard,
|
||||
SabreForRainLoop\VObject\ParseException,
|
||||
SabreForRainLoop\VObject\EofException;
|
||||
|
||||
/**
|
||||
* Json Parser.
|
||||
*
|
||||
* This parser parses both the jCal and jCard formats.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Json extends Parser {
|
||||
|
||||
/**
|
||||
* The input data
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* Root component
|
||||
*
|
||||
* @var Document
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* This method starts the parsing process.
|
||||
*
|
||||
* If the input was not supplied during construction, it's possible to pass
|
||||
* it here instead.
|
||||
*
|
||||
* If either input or options are not supplied, the defaults will be used.
|
||||
*
|
||||
* @param resource|string|array|null $input
|
||||
* @param int|null $options
|
||||
* @return array
|
||||
*/
|
||||
public function parse($input = null, $options = null) {
|
||||
|
||||
if (!is_null($input)) {
|
||||
$this->setInput($input);
|
||||
}
|
||||
if (is_null($this->input)) {
|
||||
throw new EofException('End of input stream, or no input supplied');
|
||||
}
|
||||
|
||||
if (!is_null($options)) {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
switch($this->input[0]) {
|
||||
case 'vcalendar' :
|
||||
$this->root = new VCalendar(array(), false);
|
||||
break;
|
||||
case 'vcard' :
|
||||
$this->root = new VCard(array(), false);
|
||||
break;
|
||||
default :
|
||||
throw new ParseException('The root component must either be a vcalendar, or a vcard');
|
||||
|
||||
}
|
||||
foreach($this->input[1] as $prop) {
|
||||
$this->root->add($this->parseProperty($prop));
|
||||
}
|
||||
if (isset($this->input[2])) foreach($this->input[2] as $comp) {
|
||||
$this->root->add($this->parseComponent($comp));
|
||||
}
|
||||
|
||||
// Resetting the input so we can throw an feof exception the next time.
|
||||
$this->input = null;
|
||||
|
||||
return $this->root;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a component
|
||||
*
|
||||
* @param array $jComp
|
||||
* @return \SabreForRainLoop\VObject\Component
|
||||
*/
|
||||
public function parseComponent(array $jComp) {
|
||||
|
||||
// We can remove $self from PHP 5.4 onward.
|
||||
$self = $this;
|
||||
|
||||
$properties = array_map(function($jProp) use ($self) {
|
||||
return $self->parseProperty($jProp);
|
||||
}, $jComp[1]);
|
||||
|
||||
if (isset($jComp[2])) {
|
||||
|
||||
$components = array_map(function($jComp) use ($self) {
|
||||
return $self->parseComponent($jComp);
|
||||
}, $jComp[2]);
|
||||
|
||||
} else $components = array();
|
||||
|
||||
return $this->root->createComponent(
|
||||
$jComp[0],
|
||||
array_merge( $properties, $components),
|
||||
$defaults = false
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses properties.
|
||||
*
|
||||
* @param array $jProp
|
||||
* @return \SabreForRainLoop\VObject\Property
|
||||
*/
|
||||
public function parseProperty(array $jProp) {
|
||||
|
||||
list(
|
||||
$propertyName,
|
||||
$parameters,
|
||||
$valueType
|
||||
) = $jProp;
|
||||
|
||||
$propertyName = strtoupper($propertyName);
|
||||
|
||||
// This is the default class we would be using if we didn't know the
|
||||
// value type. We're using this value later in this function.
|
||||
$defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName);
|
||||
|
||||
$parameters = (array)$parameters;
|
||||
|
||||
$value = array_slice($jProp, 3);
|
||||
|
||||
$valueType = strtoupper($valueType);
|
||||
|
||||
if (isset($parameters['group'])) {
|
||||
$propertyName = $parameters['group'] . '.' . $propertyName;
|
||||
unset($parameters['group']);
|
||||
}
|
||||
|
||||
$prop = $this->root->createProperty($propertyName, null, $parameters, $valueType);
|
||||
$prop->setJsonValue($value);
|
||||
|
||||
// We have to do something awkward here. FlatText as well as Text
|
||||
// represents TEXT values. We have to normalize these here. In the
|
||||
// future we can get rid of FlatText once we're allowed to break BC
|
||||
// again.
|
||||
if ($defaultPropertyClass === 'SabreForRainLoop\VObject\Property\FlatText') {
|
||||
$defaultPropertyClass = 'SabreForRainLoop\VObject\Property\Text';
|
||||
}
|
||||
|
||||
// If the value type we received (e.g.: TEXT) was not the default value
|
||||
// type for the given property (e.g.: BDAY), we need to add a VALUE=
|
||||
// parameter.
|
||||
if ($defaultPropertyClass !== get_class($prop)) {
|
||||
$prop["VALUE"] = $valueType;
|
||||
}
|
||||
|
||||
return $prop;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input data
|
||||
*
|
||||
* @param resource|string|array $input
|
||||
* @return void
|
||||
*/
|
||||
public function setInput($input) {
|
||||
|
||||
if (is_resource($input)) {
|
||||
$input = stream_get_contents($input);
|
||||
}
|
||||
if (is_string($input)) {
|
||||
$input = json_decode($input);
|
||||
}
|
||||
$this->input = $input;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,602 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Parser;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\ParseException,
|
||||
SabreForRainLoop\VObject\EofException,
|
||||
SabreForRainLoop\VObject\Component,
|
||||
SabreForRainLoop\VObject\Property,
|
||||
SabreForRainLoop\VObject\Component\VCalendar,
|
||||
SabreForRainLoop\VObject\Component\VCard;
|
||||
|
||||
/**
|
||||
* MimeDir parser.
|
||||
*
|
||||
* This class parses iCalendar/vCard files and returns an array.
|
||||
*
|
||||
* The array is identical to the format jCard/jCal use.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class MimeDir extends Parser {
|
||||
|
||||
/**
|
||||
* The input stream.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* Root component
|
||||
*
|
||||
* @var Component
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Parses an iCalendar or vCard file
|
||||
*
|
||||
* Pass a stream or a string. If null is parsed, the existing buffer is
|
||||
* used.
|
||||
*
|
||||
* @param string|resource|null $input
|
||||
* @param int|null $options
|
||||
* @return array
|
||||
*/
|
||||
public function parse($input = null, $options = null) {
|
||||
|
||||
$this->root = null;
|
||||
if (!is_null($input)) {
|
||||
|
||||
$this->setInput($input);
|
||||
|
||||
}
|
||||
|
||||
if (!is_null($options)) $this->options = $options;
|
||||
|
||||
$this->parseDocument();
|
||||
|
||||
return $this->root;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input buffer. Must be a string or stream.
|
||||
*
|
||||
* @param resource|string $input
|
||||
* @return void
|
||||
*/
|
||||
public function setInput($input) {
|
||||
|
||||
// Resetting the parser
|
||||
$this->lineIndex = 0;
|
||||
$this->startLine = 0;
|
||||
|
||||
if (is_string($input)) {
|
||||
// Convering to a stream.
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
fwrite($stream, $input);
|
||||
rewind($stream);
|
||||
$this->input = $stream;
|
||||
} else {
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an entire document.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseDocument() {
|
||||
|
||||
$line = $this->readLine();
|
||||
switch(strtoupper($line)) {
|
||||
case 'BEGIN:VCALENDAR' :
|
||||
$class = isset(VCalendar::$componentMap['VCALENDAR'])
|
||||
? VCalendar::$componentMap[$name]
|
||||
: 'SabreForRainLoop\\VObject\\Component\\VCalendar';
|
||||
break;
|
||||
case 'BEGIN:VCARD' :
|
||||
$class = isset(VCard::$componentMap['VCARD'])
|
||||
? VCard::$componentMap['VCARD']
|
||||
: 'SabreForRainLoop\\VObject\\Component\\VCard';
|
||||
break;
|
||||
default :
|
||||
throw new ParseException('This parser only supports VCARD and VCALENDAR files');
|
||||
}
|
||||
|
||||
$this->root = new $class(array(), false);
|
||||
|
||||
while(true) {
|
||||
|
||||
// Reading until we hit END:
|
||||
$line = $this->readLine();
|
||||
if (strtoupper(substr($line,0,4)) === 'END:') {
|
||||
break;
|
||||
}
|
||||
$result = $this->parseLine($line);
|
||||
if ($result) {
|
||||
$this->root->add($result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$name = strtoupper(substr($line, 4));
|
||||
if ($name!==$this->root->name) {
|
||||
throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a line, and if it hits a component, it will also attempt to parse
|
||||
* the entire component
|
||||
*
|
||||
* @param string $line Unfolded line
|
||||
* @return Node
|
||||
*/
|
||||
protected function parseLine($line) {
|
||||
|
||||
// Start of a new component
|
||||
if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') {
|
||||
|
||||
$component = $this->root->createComponent(substr($line,6), array(), false);
|
||||
|
||||
while(true) {
|
||||
|
||||
// Reading until we hit END:
|
||||
$line = $this->readLine();
|
||||
if (strtoupper(substr($line,0,4)) === 'END:') {
|
||||
break;
|
||||
}
|
||||
$result = $this->parseLine($line);
|
||||
if ($result) {
|
||||
$component->add($result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$name = strtoupper(substr($line, 4));
|
||||
if ($name!==$component->name) {
|
||||
throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"');
|
||||
}
|
||||
|
||||
return $component;
|
||||
|
||||
} else {
|
||||
|
||||
// Property reader
|
||||
$property = $this->readProperty($line);
|
||||
if (!$property) {
|
||||
// Ignored line
|
||||
return false;
|
||||
}
|
||||
return $property;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to look ahead 1 line every time to see if we need to 'unfold'
|
||||
* the next line.
|
||||
*
|
||||
* If that was not the case, we store it here.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
protected $lineBuffer;
|
||||
|
||||
/**
|
||||
* The real current line number.
|
||||
*/
|
||||
protected $lineIndex = 0;
|
||||
|
||||
/**
|
||||
* In the case of unfolded lines, this property holds the line number for
|
||||
* the start of the line.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $startLine = 0;
|
||||
|
||||
/**
|
||||
* Contains a 'raw' representation of the current line.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rawLine;
|
||||
|
||||
/**
|
||||
* Reads a single line from the buffer.
|
||||
*
|
||||
* This method strips any newlines and also takes care of unfolding.
|
||||
*
|
||||
* @throws \SabreForRainLoop\VObject\EofException
|
||||
* @return string
|
||||
*/
|
||||
protected function readLine() {
|
||||
|
||||
if (!is_null($this->lineBuffer)) {
|
||||
$rawLine = $this->lineBuffer;
|
||||
$this->lineBuffer = null;
|
||||
} else {
|
||||
do {
|
||||
$rawLine = fgets($this->input);
|
||||
if ($rawLine === false && feof($this->input)) {
|
||||
throw new EofException('End of document reached prematurely');
|
||||
}
|
||||
$rawLine = rtrim($rawLine, "\r\n");
|
||||
} while ($rawLine === ''); // Skipping empty lines
|
||||
$this->lineIndex++;
|
||||
}
|
||||
$line = $rawLine;
|
||||
|
||||
$this->startLine = $this->lineIndex;
|
||||
|
||||
// Looking ahead for folded lines.
|
||||
while (true) {
|
||||
|
||||
$nextLine = rtrim(fgets($this->input), "\r\n");
|
||||
$this->lineIndex++;
|
||||
if (!$nextLine) {
|
||||
break;
|
||||
}
|
||||
if ($nextLine[0] === "\t" || $nextLine[0] === " ") {
|
||||
$line .= substr($nextLine, 1);
|
||||
$rawLine .= "\n " . substr($nextLine, 1);
|
||||
} else {
|
||||
$this->lineBuffer = $nextLine;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
$this->rawLine = $rawLine;
|
||||
return $line;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a property or component from a line.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function readProperty($line) {
|
||||
|
||||
if ($this->options & self::OPTION_FORGIVING) {
|
||||
$propNameToken = 'A-Z0-9\-\._\\/';
|
||||
} else {
|
||||
$propNameToken = 'A-Z0-9\-\.';
|
||||
}
|
||||
|
||||
$paramNameToken = 'A-Z0-9\-';
|
||||
$safeChar = '^";:,';
|
||||
$qSafeChar = '^"';
|
||||
|
||||
$regex = "/
|
||||
^(?P<name> [$propNameToken]+ ) (?=[;:]) # property name
|
||||
|
|
||||
(?<=:)(?P<propValue> .*)$ # property value
|
||||
|
|
||||
;(?P<paramName> [$paramNameToken]+) (?=[=;:]) # parameter name
|
||||
|
|
||||
(=|,)(?P<paramValue> # parameter value
|
||||
(?: [$safeChar]*) |
|
||||
\"(?: [$qSafeChar]+)\"
|
||||
) (?=[;:,])
|
||||
/xi";
|
||||
|
||||
//echo $regex, "\n"; die();
|
||||
preg_match_all($regex, $line, $matches, PREG_SET_ORDER );
|
||||
|
||||
$property = array(
|
||||
'name' => null,
|
||||
'parameters' => array(),
|
||||
'value' => null
|
||||
);
|
||||
|
||||
$lastParam = null;
|
||||
|
||||
/**
|
||||
* Looping through all the tokens.
|
||||
*
|
||||
* Note that we are looping through them in reverse order, because if a
|
||||
* sub-pattern matched, the subsequent named patterns will not show up
|
||||
* in the result.
|
||||
*/
|
||||
foreach($matches as $match) {
|
||||
|
||||
if (isset($match['paramValue'])) {
|
||||
if ($match['paramValue'] && $match['paramValue'][0] === '"') {
|
||||
$value = substr($match['paramValue'], 1, -1);
|
||||
} else {
|
||||
$value = $match['paramValue'];
|
||||
}
|
||||
|
||||
$value = $this->unescapeParam($value);
|
||||
|
||||
if (is_null($property['parameters'][$lastParam])) {
|
||||
$property['parameters'][$lastParam] = $value;
|
||||
} elseif (is_array($property['parameters'][$lastParam])) {
|
||||
$property['parameters'][$lastParam][] = $value;
|
||||
} else {
|
||||
$property['parameters'][$lastParam] = array(
|
||||
$property['parameters'][$lastParam],
|
||||
$value
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (isset($match['paramName'])) {
|
||||
$lastParam = strtoupper($match['paramName']);
|
||||
if (!isset($property['parameters'][$lastParam])) {
|
||||
$property['parameters'][$lastParam] = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (isset($match['propValue'])) {
|
||||
$property['value'] = $match['propValue'];
|
||||
continue;
|
||||
}
|
||||
if (isset($match['name']) && $match['name']) {
|
||||
$property['name'] = strtoupper($match['name']);
|
||||
continue;
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \LogicException('This code should not be reachable');
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
}
|
||||
|
||||
if (is_null($property['value']) || !$property['name']) {
|
||||
if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
|
||||
return false;
|
||||
}
|
||||
throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
|
||||
}
|
||||
|
||||
// vCard 2.1 states that parameters may appear without a name, and only
|
||||
// a value. We can deduce the value based on it's name.
|
||||
//
|
||||
// Our parser will get those as parameters without a value instead, so
|
||||
// we're filtering these parameters out first.
|
||||
$namedParameters = array();
|
||||
$namelessParameters = array();
|
||||
|
||||
foreach($property['parameters'] as $name=>$value) {
|
||||
if (!is_null($value)) {
|
||||
$namedParameters[$name] = $value;
|
||||
} else {
|
||||
$namelessParameters[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
$propObj = $this->root->createProperty($property['name'], null, $namedParameters);
|
||||
|
||||
foreach($namelessParameters as $namelessParameter) {
|
||||
$propObj->add(null, $namelessParameter);
|
||||
}
|
||||
|
||||
if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') {
|
||||
$propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue());
|
||||
} else {
|
||||
$propObj->setRawMimeDirValue($property['value']);
|
||||
}
|
||||
|
||||
return $propObj;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescapes a property value.
|
||||
*
|
||||
* vCard 2.1 says:
|
||||
* * Semi-colons must be escaped in some property values, specifically
|
||||
* ADR, ORG and N.
|
||||
* * Semi-colons must be escaped in parameter values, because semi-colons
|
||||
* are also use to separate values.
|
||||
* * No mention of escaping backslashes with another backslash.
|
||||
* * newlines are not escaped either, instead QUOTED-PRINTABLE is used to
|
||||
* span values over more than 1 line.
|
||||
*
|
||||
* vCard 3.0 says:
|
||||
* * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be
|
||||
* escaped, all time time.
|
||||
* * Comma's are used for delimeters in multiple values
|
||||
* * (rfc2426) Adds to to this that the semi-colon MUST also be escaped,
|
||||
* as in some properties semi-colon is used for separators.
|
||||
* * Properties using semi-colons: N, ADR, GEO, ORG
|
||||
* * Both ADR and N's individual parts may be broken up further with a
|
||||
* comma.
|
||||
* * Properties using commas: NICKNAME, CATEGORIES
|
||||
*
|
||||
* vCard 4.0 (rfc6350) says:
|
||||
* * Commas must be escaped.
|
||||
* * Semi-colons may be escaped, an unescaped semi-colon _may_ be a
|
||||
* delimiter, depending on the property.
|
||||
* * Backslashes must be escaped
|
||||
* * Newlines must be escaped as either \N or \n.
|
||||
* * Some compound properties may contain multiple parts themselves, so a
|
||||
* comma within a semi-colon delimited property may also be unescaped
|
||||
* to denote multiple parts _within_ the compound property.
|
||||
* * Text-properties using semi-colons: N, ADR, ORG, CLIENTPIDMAP.
|
||||
* * Text-properties using commas: NICKNAME, RELATED, CATEGORIES, PID.
|
||||
*
|
||||
* Even though the spec says that commas must always be escaped, the
|
||||
* example for GEO in Section 6.5.2 seems to violate this.
|
||||
*
|
||||
* iCalendar 2.0 (rfc5545) says:
|
||||
* * Commas or semi-colons may be used as delimiters, depending on the
|
||||
* property.
|
||||
* * Commas, semi-colons, backslashes, newline (\N or \n) are always
|
||||
* escaped, unless they are delimiters.
|
||||
* * Colons shall not be escaped.
|
||||
* * Commas can be considered the 'default delimiter' and is described as
|
||||
* the delimiter in cases where the order of the multiple values is
|
||||
* insignificant.
|
||||
* * Semi-colons are described as the delimiter for 'structured values'.
|
||||
* They are specifically used in Semi-colons are used as a delimiter in
|
||||
* REQUEST-STATUS, RRULE, GEO and EXRULE. EXRULE is deprecated however.
|
||||
*
|
||||
* Now for the parameters
|
||||
*
|
||||
* If delimiter is not set (null) this method will just return a string.
|
||||
* If it's a comma or a semi-colon the string will be split on those
|
||||
* characters, and always return an array.
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $delimiter
|
||||
* @return string|string[]
|
||||
*/
|
||||
static public function unescapeValue($input, $delimiter = ';') {
|
||||
|
||||
$regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )';
|
||||
if ($delimiter) {
|
||||
$regex .= ' | (' . $delimiter . ')';
|
||||
}
|
||||
$regex .= ') #x';
|
||||
|
||||
$matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
|
||||
|
||||
$resultArray = array();
|
||||
$result = '';
|
||||
|
||||
foreach($matches as $match) {
|
||||
|
||||
switch ($match) {
|
||||
case '\\\\' :
|
||||
$result .='\\';
|
||||
break;
|
||||
case '\N' :
|
||||
case '\n' :
|
||||
$result .="\n";
|
||||
break;
|
||||
case '\;' :
|
||||
$result .=';';
|
||||
break;
|
||||
case '\,' :
|
||||
$result .=',';
|
||||
break;
|
||||
case $delimiter :
|
||||
$resultArray[] = $result;
|
||||
$result = '';
|
||||
break;
|
||||
default :
|
||||
$result .= $match;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$resultArray[] = $result;
|
||||
return $delimiter ? $resultArray : $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescapes a parameter value.
|
||||
*
|
||||
* vCard 2.1:
|
||||
* * Does not mention a mechanism for this. In addition, double quotes
|
||||
* are never used to wrap values.
|
||||
* * This means that parameters can simply not contain colons or
|
||||
* semi-colons.
|
||||
*
|
||||
* vCard 3.0 (rfc2425, rfc2426):
|
||||
* * Parameters _may_ be surrounded by double quotes.
|
||||
* * If this is not the case, semi-colon, colon and comma may simply not
|
||||
* occur (the comma used for multiple parameter values though).
|
||||
* * If it is surrounded by double-quotes, it may simply not contain
|
||||
* double-quotes.
|
||||
* * This means that a parameter can in no case encode double-quotes, or
|
||||
* newlines.
|
||||
*
|
||||
* vCard 4.0 (rfc6350)
|
||||
* * Behavior seems to be identical to vCard 3.0
|
||||
*
|
||||
* iCalendar 2.0 (rfc5545)
|
||||
* * Behavior seems to be identical to vCard 3.0
|
||||
*
|
||||
* Parameter escaping mechanism (rfc6868) :
|
||||
* * This rfc describes a new way to escape parameter values.
|
||||
* * New-line is encoded as ^n
|
||||
* * ^ is encoded as ^^.
|
||||
* * " is encoded as ^'
|
||||
*
|
||||
* @param string $input
|
||||
* @return void
|
||||
*/
|
||||
private function unescapeParam($input) {
|
||||
|
||||
return
|
||||
preg_replace_callback('#(\^(\^|n|\'))#',function($matches) {
|
||||
switch($matches[2]) {
|
||||
case 'n' :
|
||||
return "\n";
|
||||
case '^' :
|
||||
return '^';
|
||||
case '\'' :
|
||||
return '"';
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
}, $input);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full quoted printable value.
|
||||
*
|
||||
* We need a special method for this, because newlines have both a meaning
|
||||
* in vCards, and in QuotedPrintable.
|
||||
*
|
||||
* This method does not do any decoding.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function extractQuotedPrintableValue() {
|
||||
|
||||
// We need to parse the raw line again to get the start of the value.
|
||||
//
|
||||
// We are basically looking for the first colon (:), but we need to
|
||||
// skip over the parameters first, as they may contain one.
|
||||
$regex = '/^
|
||||
(?: [^:])+ # Anything but a colon
|
||||
(?: "[^"]")* # A parameter in double quotes
|
||||
: # start of the value we really care about
|
||||
(.*)$
|
||||
/xs';
|
||||
|
||||
preg_match($regex, $this->rawLine, $matches);
|
||||
|
||||
$value = $matches[1];
|
||||
// Removing the first whitespace character from every line. Kind of
|
||||
// like unfolding, but we keep the newline.
|
||||
$value = str_replace("\n ", "\n", $value);
|
||||
|
||||
// Microsoft products don't always correctly fold lines, they may be
|
||||
// missing a whitespace. So if 'forgiving' is turned on, we will take
|
||||
// those as well.
|
||||
if ($this->options & self::OPTION_FORGIVING) {
|
||||
while(substr($value,-1) === '=') {
|
||||
// Reading the line
|
||||
$this->readLine();
|
||||
// Grabbing the raw form
|
||||
$value.="\n" . $this->rawLine;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Parser;
|
||||
|
||||
/**
|
||||
* Abstract parser.
|
||||
*
|
||||
* This class serves as a base-class for the different parsers.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
abstract class Parser {
|
||||
|
||||
/**
|
||||
* Turning on this option makes the parser more forgiving.
|
||||
*
|
||||
* In the case of the MimeDir parser, this means that the parser will
|
||||
* accept slashes and underscores in property names, and it will also
|
||||
* attempt to fix Microsoft vCard 2.1's broken line folding.
|
||||
*/
|
||||
const OPTION_FORGIVING = 1;
|
||||
|
||||
/**
|
||||
* If this option is turned on, any lines we cannot parse will be ignored
|
||||
* by the reader.
|
||||
*/
|
||||
const OPTION_IGNORE_INVALID_LINES = 2;
|
||||
|
||||
/**
|
||||
* Bitmask of parser options
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Creates the parser.
|
||||
*
|
||||
* Optionally, it's possible to parse the input stream here.
|
||||
*
|
||||
* @param mixed $input
|
||||
* @param int $options Any parser options (OPTION constants).
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($input = null, $options = 0) {
|
||||
|
||||
if (!is_null($input)) {
|
||||
$this->setInput($input);
|
||||
}
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts the parsing process.
|
||||
*
|
||||
* If the input was not supplied during construction, it's possible to pass
|
||||
* it here instead.
|
||||
*
|
||||
* If either input or options are not supplied, the defaults will be used.
|
||||
*
|
||||
* @param mixed $input
|
||||
* @param int|null $options
|
||||
* @return array
|
||||
*/
|
||||
abstract public function parse($input = null, $options = null);
|
||||
|
||||
/**
|
||||
* Sets the input data
|
||||
*
|
||||
* @param mixed $input
|
||||
* @return void
|
||||
*/
|
||||
abstract public function setInput($input);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,502 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* Property
|
||||
*
|
||||
* A property is always in a KEY:VALUE structure, and may optionally contain
|
||||
* parameters.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
abstract class Property extends Node {
|
||||
|
||||
/**
|
||||
* Property name.
|
||||
*
|
||||
* This will contain a string such as DTSTART, SUMMARY, FN.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Property group.
|
||||
*
|
||||
* This is only used in vcards
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $group;
|
||||
|
||||
/**
|
||||
* List of parameters
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $parameters = array();
|
||||
|
||||
/**
|
||||
* Current value
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = ';';
|
||||
|
||||
/**
|
||||
* Creates the generic property.
|
||||
*
|
||||
* Parameters must be specified in key=>value syntax.
|
||||
*
|
||||
* @param Component $root The root document
|
||||
* @param string $name
|
||||
* @param string|array|null $value
|
||||
* @param array $parameters List of parameters
|
||||
* @param string $group The vcard property group
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) {
|
||||
|
||||
$this->name = $name;
|
||||
$this->group = $group;
|
||||
|
||||
$this->root = $root;
|
||||
|
||||
if (!is_null($value)) {
|
||||
$this->setValue($value);
|
||||
}
|
||||
|
||||
foreach($parameters as $k=>$v) {
|
||||
$this->add($k, $v);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current value.
|
||||
*
|
||||
* This may be either a single, or multiple strings in an array.
|
||||
*
|
||||
* @param string|array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setValue($value) {
|
||||
|
||||
$this->value = $value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value.
|
||||
*
|
||||
* This method will always return a singular value. If this was a
|
||||
* multi-value object, some decision will be made first on how to represent
|
||||
* it as a string.
|
||||
*
|
||||
* To get the correct multi-value version, use getParts.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValue() {
|
||||
|
||||
if (is_array($this->value)) {
|
||||
if (count($this->value)==0) {
|
||||
return null;
|
||||
} elseif (count($this->value)===1) {
|
||||
return $this->value[0];
|
||||
} else {
|
||||
return $this->getRawMimeDirValue($this->value);
|
||||
}
|
||||
} else {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a multi-valued property.
|
||||
*
|
||||
* @param array $parts
|
||||
* @return void
|
||||
*/
|
||||
public function setParts(array $parts) {
|
||||
|
||||
$this->value = $parts;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a multi-valued property.
|
||||
*
|
||||
* This method always returns an array, if there was only a single value,
|
||||
* it will still be wrapped in an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParts() {
|
||||
|
||||
if (is_null($this->value)) {
|
||||
return array();
|
||||
} elseif (is_array($this->value)) {
|
||||
return $this->value;
|
||||
} else {
|
||||
return array($this->value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new parameter, and returns the new item.
|
||||
*
|
||||
* If a parameter with same name already existed, the values will be
|
||||
* combined.
|
||||
* If nameless parameter is added, we try to guess it's name.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null|array $value
|
||||
* @return Node
|
||||
*/
|
||||
public function add($name, $value = null) {
|
||||
$noName = false;
|
||||
if ($name === null) {
|
||||
$name = Parameter::guessParameterNameByValue($value);
|
||||
$noName = true;
|
||||
}
|
||||
|
||||
if (isset($this->parameters[strtoupper($name)])) {
|
||||
$this->parameters[strtoupper($name)]->addValue($value);
|
||||
}
|
||||
else {
|
||||
$param = new Parameter($this->root, $name, $value);
|
||||
$param->noName = $noName;
|
||||
$this->parameters[$param->name] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterable list of children
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parameters() {
|
||||
|
||||
return $this->parameters;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getValueType();
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
abstract public function setRawMimeDirValue($val);
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getRawMimeDirValue();
|
||||
|
||||
/**
|
||||
* Turns the object back into a serialized blob.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize() {
|
||||
|
||||
$str = $this->name;
|
||||
if ($this->group) $str = $this->group . '.' . $this->name;
|
||||
|
||||
foreach($this->parameters as $param) {
|
||||
|
||||
$str.=';' . $param->serialize();
|
||||
|
||||
}
|
||||
|
||||
$str.=':' . $this->getRawMimeDirValue();
|
||||
|
||||
$out = '';
|
||||
while(strlen($str)>0) {
|
||||
if (strlen($str)>75) {
|
||||
$out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
|
||||
$str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
|
||||
} else {
|
||||
$out.=$str . "\r\n";
|
||||
$str='';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
return $this->getParts();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the json value, as it would appear in a jCard or jCal object.
|
||||
*
|
||||
* The value must always be an array.
|
||||
*
|
||||
* @param array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setJsonValue(array $value) {
|
||||
|
||||
if (count($value)===1) {
|
||||
$this->setValue(reset($value));
|
||||
} else {
|
||||
$this->setValue($value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns an array, with the representation as it should be
|
||||
* encoded in json. This is used to create jCard or jCal documents.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
|
||||
$parameters = array();
|
||||
|
||||
foreach($this->parameters as $parameter) {
|
||||
if ($parameter->name === 'VALUE') {
|
||||
continue;
|
||||
}
|
||||
$parameters[strtolower($parameter->name)] = $parameter->jsonSerialize();
|
||||
}
|
||||
// In jCard, we need to encode the property-group as a separate 'group'
|
||||
// parameter.
|
||||
if ($this->group) {
|
||||
$parameters['group'] = $this->group;
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
array(
|
||||
strtolower($this->name),
|
||||
(object)$parameters,
|
||||
strtolower($this->getValueType()),
|
||||
),
|
||||
$this->getJsonValue()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when this object is being cast to a string.
|
||||
*
|
||||
* If the property only had a single value, you will get just that. In the
|
||||
* case the property had multiple values, the contents will be escaped and
|
||||
* combined with ,.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
|
||||
return (string)$this->getValue();
|
||||
|
||||
}
|
||||
|
||||
/* ArrayAccess interface {{{ */
|
||||
|
||||
/**
|
||||
* Checks if an array element exists
|
||||
*
|
||||
* @param mixed $name
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($name) {
|
||||
|
||||
if (is_int($name)) return parent::offsetExists($name);
|
||||
|
||||
$name = strtoupper($name);
|
||||
|
||||
foreach($this->parameters as $parameter) {
|
||||
if ($parameter->name == $name) return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a parameter.
|
||||
*
|
||||
* If the parameter does not exist, null is returned.
|
||||
*
|
||||
* @param string $name
|
||||
* @return Node
|
||||
*/
|
||||
public function offsetGet($name) {
|
||||
|
||||
if (is_int($name)) return parent::offsetGet($name);
|
||||
$name = strtoupper($name);
|
||||
|
||||
if (!isset($this->parameters[$name])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->parameters[$name];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new parameter
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($name, $value) {
|
||||
|
||||
if (is_int($name)) {
|
||||
parent::offsetSet($name, $value);
|
||||
// @codeCoverageIgnoreStart
|
||||
// This will never be reached, because an exception is always
|
||||
// thrown.
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$param = new Parameter($this->root, $name, $value);
|
||||
$this->parameters[$param->name] = $param;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes one or more parameters with the specified name
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($name) {
|
||||
|
||||
if (is_int($name)) {
|
||||
parent::offsetUnset($name);
|
||||
// @codeCoverageIgnoreStart
|
||||
// This will never be reached, because an exception is always
|
||||
// thrown.
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
unset($this->parameters[strtoupper($name)]);
|
||||
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/**
|
||||
* This method is automatically called when the object is cloned.
|
||||
* Specifically, this will ensure all child elements are also cloned.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone() {
|
||||
|
||||
foreach($this->parameters as $key=>$child) {
|
||||
$this->parameters[$key] = clone $child;
|
||||
$this->parameters[$key]->parent = $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the node for correctness.
|
||||
*
|
||||
* The following options are supported:
|
||||
* - Node::REPAIR - If something is broken, and automatic repair may
|
||||
* be attempted.
|
||||
*
|
||||
* An array is returned with warnings.
|
||||
*
|
||||
* Every item in the array has the following properties:
|
||||
* * level - (number between 1 and 3 with severity information)
|
||||
* * message - (human readable message)
|
||||
* * node - (reference to the offending node)
|
||||
*
|
||||
* @param int $options
|
||||
* @return array
|
||||
*/
|
||||
public function validate($options = 0) {
|
||||
|
||||
$warnings = array();
|
||||
|
||||
// Checking if our value is UTF-8
|
||||
if (!StringUtil::isUTF8($this->getRawMimeDirValue())) {
|
||||
$warnings[] = array(
|
||||
'level' => 1,
|
||||
'message' => 'Property is not valid UTF-8!',
|
||||
'node' => $this,
|
||||
);
|
||||
if ($options & self::REPAIR) {
|
||||
$this->setRawMimeDirValue(StringUtil::convertToUTF8($this->getRawMimeDirValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// Checking if the propertyname does not contain any invalid bytes.
|
||||
if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) {
|
||||
$warnings[] = array(
|
||||
'level' => 1,
|
||||
'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed',
|
||||
'node' => $this,
|
||||
);
|
||||
if ($options & self::REPAIR) {
|
||||
// Uppercasing and converting underscores to dashes.
|
||||
$this->name = strtoupper(
|
||||
str_replace('_', '-', $this->name)
|
||||
);
|
||||
// Removing every other invalid character
|
||||
$this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Validating inner parameters
|
||||
foreach($this->parameters as $param) {
|
||||
$warnings = array_merge($warnings, $param->validate($options));
|
||||
}
|
||||
|
||||
return $warnings;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property;
|
||||
|
||||
use
|
||||
LogicException,
|
||||
SabreForRainLoop\VObject\Property;
|
||||
|
||||
/**
|
||||
* BINARY property
|
||||
*
|
||||
* This object represents BINARY values.
|
||||
*
|
||||
* Binary values are most commonly used by the iCalendar ATTACH property, and
|
||||
* the vCard PHOTO property.
|
||||
*
|
||||
* This property will transparently encode and decode to base64.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Binary extends Property {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = null;
|
||||
|
||||
/**
|
||||
* Updates the current value.
|
||||
*
|
||||
* This may be either a single, or multiple strings in an array.
|
||||
*
|
||||
* @param string|array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setValue($value) {
|
||||
|
||||
if(is_array($value)) {
|
||||
|
||||
if(count($value) === 1) {
|
||||
$this->value = $value[0];
|
||||
} else {
|
||||
throw new \InvalidArgumentException('The argument must either be a string or an array with only one child');
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$this->value = $value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$this->value = base64_decode($val);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
return base64_encode($this->value);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return 'BINARY';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
return array(base64_encode($this->getValue()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the json value, as it would appear in a jCard or jCal object.
|
||||
*
|
||||
* The value must always be an array.
|
||||
*
|
||||
* @param array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setJsonValue(array $value) {
|
||||
|
||||
$value = array_map('base64_decode', $value);
|
||||
parent::setJsonValue($value);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property;
|
||||
|
||||
/**
|
||||
* Boolean property
|
||||
*
|
||||
* This object represents BOOLEAN values. These are always the case-insenstive
|
||||
* string TRUE or FALSE.
|
||||
*
|
||||
* Automatic conversion to PHP's true and false are done.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Boolean extends Property {
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$val = strtoupper($val)==='TRUE'?true:false;
|
||||
$this->setValue($val);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
return $this->value?'TRUE':'FALSE';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return 'BOOLEAN';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property;
|
||||
|
||||
/**
|
||||
* FlatText property
|
||||
*
|
||||
* This object represents certain TEXT values.
|
||||
*
|
||||
* Specifically, this property is used for text values where there is only 1
|
||||
* part. Semi-colons and colons will be de-escaped when deserializing, but if
|
||||
* any semi-colons or commas appear without a backslash, we will not assume
|
||||
* that they are delimiters.
|
||||
*
|
||||
* vCard 2.1 specifically has a whole bunch of properties where this may
|
||||
* happen, as it only defines a delimiter for a few properties.
|
||||
*
|
||||
* vCard 4.0 states something similar. An unescaped semi-colon _may_ be a
|
||||
* delimiter, depending on the property.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class FlatText extends Text {
|
||||
|
||||
/**
|
||||
* Field separator
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $delimiter = ',';
|
||||
|
||||
/**
|
||||
* Sets the value as a quoted-printable encoded string.
|
||||
*
|
||||
* Overriding this so we're not splitting on a ; delimiter.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setQuotedPrintableValue($val) {
|
||||
|
||||
$val = quoted_printable_decode($val);
|
||||
$this->setValue($val);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property;
|
||||
|
||||
/**
|
||||
* Float property
|
||||
*
|
||||
* This object represents FLOAT values. These can be 1 or more floating-point
|
||||
* numbers.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Float extends Property {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = ';';
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$val = explode($this->delimiter, $val);
|
||||
foreach($val as &$item) {
|
||||
$item = (float)$item;
|
||||
}
|
||||
$this->setParts($val);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
return implode(
|
||||
$this->delimiter,
|
||||
$this->getParts()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "FLOAT";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
$val = array_map(function($item) {
|
||||
|
||||
return (float)$item;
|
||||
|
||||
}, $this->getParts());
|
||||
|
||||
// Special-casing the GEO property.
|
||||
//
|
||||
// See:
|
||||
// http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-04#section-3.4.1.2
|
||||
if ($this->name==='GEO') {
|
||||
return array($val);
|
||||
} else {
|
||||
return $val;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\ICalendar;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property\Text;
|
||||
|
||||
/**
|
||||
* CalAddress property
|
||||
*
|
||||
* This object encodes CAL-ADDRESS values, as defined in rfc5545
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class CalAddress extends Text {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = null;
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return 'CAL-ADDRESS';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\ICalendar;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property,
|
||||
SabreForRainLoop\VObject\Parser\MimeDir,
|
||||
SabreForRainLoop\VObject\DateTimeParser,
|
||||
SabreForRainLoop\VObject\TimeZoneUtil;
|
||||
|
||||
/**
|
||||
* DateTime property
|
||||
*
|
||||
* This object represents DATE values, as defined here:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc5545#section-3.3.5
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Date extends DateTime {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\ICalendar;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property,
|
||||
SabreForRainLoop\VObject\Parser\MimeDir,
|
||||
SabreForRainLoop\VObject\DateTimeParser,
|
||||
SabreForRainLoop\VObject\TimeZoneUtil;
|
||||
|
||||
/**
|
||||
* DateTime property
|
||||
*
|
||||
* This object represents DATE-TIME values, as defined here:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc5545#section-3.3.4
|
||||
*
|
||||
* This particular object has a bit of hackish magic that it may also in some
|
||||
* cases represent a DATE value. This is because it's a common usecase to be
|
||||
* able to change a DATE-TIME into a DATE.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class DateTime extends Property {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = ',';
|
||||
|
||||
/**
|
||||
* Sets a multi-valued property.
|
||||
*
|
||||
* You may also specify DateTime objects here.
|
||||
*
|
||||
* @param array $parts
|
||||
* @return void
|
||||
*/
|
||||
public function setParts(array $parts) {
|
||||
|
||||
if (isset($parts[0]) && $parts[0] instanceof \DateTime) {
|
||||
$this->setDateTimes($parts);
|
||||
} else {
|
||||
parent::setParts($parts);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current value.
|
||||
*
|
||||
* This may be either a single, or multiple strings in an array.
|
||||
*
|
||||
* Instead of strings, you may also use DateTime here.
|
||||
*
|
||||
* @param string|array|\DateTime $value
|
||||
* @return void
|
||||
*/
|
||||
public function setValue($value) {
|
||||
|
||||
if (is_array($value) && isset($value[0]) && $value[0] instanceof \DateTime) {
|
||||
$this->setDateTimes($value);
|
||||
} elseif ($value instanceof \DateTime) {
|
||||
$this->setDateTimes(array($value));
|
||||
} else {
|
||||
parent::setValue($value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$this->setValue(explode($this->delimiter, $val));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
return implode($this->delimiter, $this->getParts());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is a DATE-TIME value, false if it's a DATE.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTime() {
|
||||
|
||||
return strtoupper((string)$this['VALUE']) !== 'DATE';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a date-time value.
|
||||
*
|
||||
* Note that if this property contained more than 1 date-time, only the
|
||||
* first will be returned. To get an array with multiple values, call
|
||||
* getDateTimes.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getDateTime() {
|
||||
|
||||
$dt = $this->getDateTimes();
|
||||
if (!$dt) return null;
|
||||
|
||||
return $dt[0];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns multiple date-time values.
|
||||
*
|
||||
* @return \DateTime[]
|
||||
*/
|
||||
public function getDateTimes() {
|
||||
|
||||
// Finding the timezone.
|
||||
$tz = $this['TZID'];
|
||||
|
||||
if ($tz) {
|
||||
$tz = TimeZoneUtil::getTimeZone((string)$tz, $this->root);
|
||||
}
|
||||
|
||||
$dts = array();
|
||||
foreach($this->getParts() as $part) {
|
||||
$dts[] = DateTimeParser::parse($part, $tz);
|
||||
}
|
||||
return $dts;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the property as a DateTime object.
|
||||
*
|
||||
* @param \DateTime $dt
|
||||
* @param bool isFloating If set to true, timezones will be ignored.
|
||||
* @return void
|
||||
*/
|
||||
public function setDateTime(\DateTime $dt, $isFloating = false) {
|
||||
|
||||
$this->setDateTimes(array($dt), $isFloating);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the property as multiple date-time objects.
|
||||
*
|
||||
* The first value will be used as a reference for the timezones, and all
|
||||
* the otehr values will be adjusted for that timezone
|
||||
*
|
||||
* @param \DateTime[] $dt
|
||||
* @param bool isFloating If set to true, timezones will be ignored.
|
||||
* @return void
|
||||
*/
|
||||
public function setDateTimes(array $dt, $isFloating = false) {
|
||||
|
||||
$values = array();
|
||||
|
||||
if($this->hasTime()) {
|
||||
|
||||
$tz = null;
|
||||
$isUtc = false;
|
||||
|
||||
foreach($dt as $d) {
|
||||
|
||||
if ($isFloating) {
|
||||
$values[] = $d->format('Ymd\\THis');
|
||||
continue;
|
||||
}
|
||||
if (is_null($tz)) {
|
||||
$tz = $d->getTimeZone();
|
||||
$isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
|
||||
if (!$isUtc) {
|
||||
$this->offsetSet('TZID', $tz->getName());
|
||||
}
|
||||
} else {
|
||||
$d->setTimeZone($tz);
|
||||
}
|
||||
|
||||
if ($isUtc) {
|
||||
$values[] = $d->format('Ymd\\THis\\Z');
|
||||
} else {
|
||||
$values[] = $d->format('Ymd\\THis');
|
||||
}
|
||||
|
||||
}
|
||||
if ($isUtc || $isFloating) {
|
||||
$this->offsetUnset('TZID');
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
foreach($dt as $d) {
|
||||
|
||||
$values[] = $d->format('Ymd');
|
||||
|
||||
}
|
||||
$this->offsetUnset('TZID');
|
||||
|
||||
}
|
||||
|
||||
$this->value = $values;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return $this->hasTime()?'DATE-TIME':'DATE';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
$dts = $this->getDateTimes();
|
||||
$hasTime = $this->hasTime();
|
||||
|
||||
$tz = $dts[0]->getTimeZone();
|
||||
$isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
|
||||
|
||||
return array_map(function($dt) use ($hasTime, $isUtc) {
|
||||
|
||||
if ($hasTime) {
|
||||
return $dt->format('Y-m-d\\TH:i:s') . ($isUtc?'Z':'');
|
||||
} else {
|
||||
return $dt->format('Y-m-d');
|
||||
}
|
||||
|
||||
}, $dts);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the json value, as it would appear in a jCard or jCal object.
|
||||
*
|
||||
* The value must always be an array.
|
||||
*
|
||||
* @param array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setJsonValue(array $value) {
|
||||
|
||||
// dates and times in jCal have one difference to dates and times in
|
||||
// iCalendar. In jCal date-parts are separated by dashes, and
|
||||
// time-parts are separated by colons. It makes sense to just remove
|
||||
// those.
|
||||
$this->setValue(array_map(function($item) {
|
||||
|
||||
return strtr($item, array(':'=>'', '-'=>''));
|
||||
|
||||
}, $value));
|
||||
|
||||
}
|
||||
/**
|
||||
* We need to intercept offsetSet, because it may be used to alter the
|
||||
* VALUE from DATE-TIME to DATE or vice-versa.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($name, $value) {
|
||||
|
||||
parent::offsetSet($name, $value);
|
||||
if (strtoupper($name)!=='VALUE') {
|
||||
return;
|
||||
}
|
||||
|
||||
// This will ensure that dates are correctly encoded.
|
||||
$this->setDateTimes($this->getDateTimes());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\ICalendar;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property,
|
||||
SabreForRainLoop\VObject\Parser\MimeDir,
|
||||
SabreForRainLoop\VObject\DateTimeParser;
|
||||
|
||||
/**
|
||||
* Duration property
|
||||
*
|
||||
* This object represents DURATION values, as defined here:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc5545#section-3.3.6
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Duration extends Property {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = ',';
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$this->setValue(explode($this->delimiter, $val));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
return implode($this->delimiter, $this->getParts());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return 'DURATION';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DateInterval representation of the Duration property.
|
||||
*
|
||||
* If the property has more than one value, only the first is returned.
|
||||
*
|
||||
* @return \DateInterval
|
||||
*/
|
||||
public function getDateInterval() {
|
||||
|
||||
$parts = $this->getParts();
|
||||
$value = $parts[0];
|
||||
return DateTimeParser::parseDuration($value);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\ICalendar;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property,
|
||||
SabreForRainLoop\VObject\Parser\MimeDir,
|
||||
SabreForRainLoop\VObject\DateTimeParser;
|
||||
|
||||
/**
|
||||
* Period property
|
||||
*
|
||||
* This object represents PERIOD values, as defined here:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc5545#section-3.8.2.6
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Period extends Property {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = ',';
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$this->setValue(explode($this->delimiter, $val));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
return implode($this->delimiter, $this->getParts());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "PERIOD";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the json value, as it would appear in a jCard or jCal object.
|
||||
*
|
||||
* The value must always be an array.
|
||||
*
|
||||
* @param array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setJsonValue(array $value) {
|
||||
|
||||
$value = array_map(function($item) {
|
||||
|
||||
return strtr(implode('/', $item), array(':' => '', '-' => ''));
|
||||
|
||||
}, $value);
|
||||
parent::setJsonValue($value);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
$return = array();
|
||||
foreach($this->getParts() as $item) {
|
||||
|
||||
list($start, $end) = explode('/', $item, 2);
|
||||
|
||||
$start = DateTimeParser::parseDateTime($start);
|
||||
|
||||
// This is a duration value.
|
||||
if ($end[0]==='P') {
|
||||
$return[] = array(
|
||||
$start->format('Y-m-d\\TH:i:s'),
|
||||
$end
|
||||
);
|
||||
} else {
|
||||
$end = DateTimeParser::parseDateTime($end);
|
||||
$return[] = array(
|
||||
$start->format('Y-m-d\\TH:i:s'),
|
||||
$end->format('Y-m-d\\TH:i:s'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\ICalendar;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property,
|
||||
SabreForRainLoop\VObject\Parser\MimeDir;
|
||||
|
||||
/**
|
||||
* Recur property
|
||||
*
|
||||
* This object represents RECUR properties.
|
||||
* These values are just used for RRULE and the now deprecated EXRULE.
|
||||
*
|
||||
* The RRULE property may look something like this:
|
||||
*
|
||||
* RRULE:FREQ=MONTHLY;BYDAY=1,2,3;BYHOUR=5.
|
||||
*
|
||||
* This property exposes this as a key=>value array that is accessible using
|
||||
* getParts, and may be set using setParts.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Recur extends Property {
|
||||
|
||||
/**
|
||||
* Updates the current value.
|
||||
*
|
||||
* This may be either a single, or multiple strings in an array.
|
||||
*
|
||||
* @param string|array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setValue($value) {
|
||||
|
||||
// If we're getting the data from json, we'll be receiving an object
|
||||
if ($value instanceof \StdClass) {
|
||||
$value = (array)$value;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$newVal = array();
|
||||
foreach($value as $k=>$v) {
|
||||
|
||||
if (is_string($v)) {
|
||||
$v = strtoupper($v);
|
||||
|
||||
// The value had multiple sub-values
|
||||
if (strpos($v,',')!==false) {
|
||||
$v = explode(',', $v);
|
||||
}
|
||||
} else {
|
||||
$v = array_map('strtoupper', $v);
|
||||
}
|
||||
|
||||
$newVal[strtoupper($k)] = $v;
|
||||
}
|
||||
$this->value = $newVal;
|
||||
} elseif (is_string($value)) {
|
||||
$value = strtoupper($value);
|
||||
$newValue = array();
|
||||
foreach(explode(';', $value) as $part) {
|
||||
|
||||
// Skipping empty parts.
|
||||
if (empty($part)) {
|
||||
continue;
|
||||
}
|
||||
list($partName, $partValue) = explode('=', $part);
|
||||
|
||||
// The value itself had multiple values..
|
||||
if (strpos($partValue,',')!==false) {
|
||||
$partValue=explode(',', $partValue);
|
||||
}
|
||||
$newValue[$partName] = $partValue;
|
||||
|
||||
}
|
||||
$this->value = $newValue;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('You must either pass a string, or a key=>value array');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value.
|
||||
*
|
||||
* This method will always return a singular value. If this was a
|
||||
* multi-value object, some decision will be made first on how to represent
|
||||
* it as a string.
|
||||
*
|
||||
* To get the correct multi-value version, use getParts.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValue() {
|
||||
|
||||
$out = array();
|
||||
foreach($this->value as $key=>$value) {
|
||||
$out[] = $key . '=' . (is_array($value)?implode(',', $value):$value);
|
||||
}
|
||||
return strtoupper(implode(';',$out));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a multi-valued property.
|
||||
*
|
||||
* @param array $parts
|
||||
* @return void
|
||||
*/
|
||||
public function setParts(array $parts) {
|
||||
|
||||
$this->setValue($parts);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a multi-valued property.
|
||||
*
|
||||
* This method always returns an array, if there was only a single value,
|
||||
* it will still be wrapped in an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParts() {
|
||||
|
||||
return $this->value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$this->setValue($val);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
return $this->getValue();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "RECUR";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
$values = array();
|
||||
foreach($this->getParts() as $k=>$v) {
|
||||
$values[strtolower($k)] = $v;
|
||||
}
|
||||
return array($values);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property;
|
||||
|
||||
/**
|
||||
* Integer property
|
||||
*
|
||||
* This object represents INTEGER values. These are always a single integer.
|
||||
* They may be preceeded by either + or -.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Integer extends Property {
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$this->setValue((int)$val);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
return $this->value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "INTEGER";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
return array((int)$this->getValue());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property,
|
||||
SabreForRainLoop\VObject\Component,
|
||||
SabreForRainLoop\VObject\Parser\MimeDir,
|
||||
SabreForRainLoop\VObject\Document;
|
||||
|
||||
/**
|
||||
* Text property
|
||||
*
|
||||
* This object represents TEXT values.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Text extends Property {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $delimiter = ',';
|
||||
|
||||
/**
|
||||
* List of properties that are considered 'structured'.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $structuredValues = array(
|
||||
// vCard
|
||||
'N',
|
||||
'ADR',
|
||||
'ORG',
|
||||
'GENDER',
|
||||
|
||||
// iCalendar
|
||||
'REQUEST-STATUS',
|
||||
);
|
||||
|
||||
/**
|
||||
* Some text components have a minimum number of components.
|
||||
*
|
||||
* N must for instance be represented as 5 components, separated by ;, even
|
||||
* if the last few components are unused.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $minimumPropertyValues = array(
|
||||
'N' => 5,
|
||||
'ADR' => 7,
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates the property.
|
||||
*
|
||||
* You can specify the parameters either in key=>value syntax, in which case
|
||||
* parameters will automatically be created, or you can just pass a list of
|
||||
* Parameter objects.
|
||||
*
|
||||
* @param Component $root The root document
|
||||
* @param string $name
|
||||
* @param string|array|null $value
|
||||
* @param array $parameters List of parameters
|
||||
* @param string $group The vcard property group
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) {
|
||||
|
||||
// There's two types of multi-valued text properties:
|
||||
// 1. multivalue properties.
|
||||
// 2. structured value properties
|
||||
//
|
||||
// The former is always separated by a comma, the latter by semi-colon.
|
||||
if (in_array($name, $this->structuredValues)) {
|
||||
$this->delimiter = ';';
|
||||
}
|
||||
|
||||
parent::__construct($root, $name, $value, $parameters, $group);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$this->setValue(MimeDir::unescapeValue($val, $this->delimiter));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value as a quoted-printable encoded string.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setQuotedPrintableValue($val) {
|
||||
|
||||
$val = quoted_printable_decode($val);
|
||||
|
||||
// Quoted printable only appears in vCard 2.1, and the only character
|
||||
// that may be escaped there is ;. So we are simply splitting on just
|
||||
// that.
|
||||
//
|
||||
// We also don't have to unescape \\, so all we need to look for is a ;
|
||||
// that's not preceeded with a \.
|
||||
$regex = '# (?<!\\\\) ; #x';
|
||||
$matches = preg_split($regex, $val);
|
||||
$this->setValue($matches);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
$val = $this->getParts();
|
||||
|
||||
if (isset($this->minimumPropertyValues[$this->name])) {
|
||||
$val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
|
||||
}
|
||||
|
||||
foreach($val as &$item) {
|
||||
|
||||
if (!is_array($item)) {
|
||||
$item = array($item);
|
||||
}
|
||||
|
||||
foreach($item as &$subItem) {
|
||||
$subItem = strtr($subItem, array(
|
||||
'\\' => '\\\\',
|
||||
';' => '\;',
|
||||
',' => '\,',
|
||||
"\n" => '\n',
|
||||
"\r" => "",
|
||||
));
|
||||
}
|
||||
$item = implode(',', $item);
|
||||
|
||||
}
|
||||
|
||||
return implode($this->delimiter, $val);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
// Structured text values should always be returned as a single
|
||||
// array-item. Multi-value text should be returned as multiple items in
|
||||
// the top-array.
|
||||
if (in_array($this->name, $this->structuredValues)) {
|
||||
return array($this->getParts());
|
||||
} else {
|
||||
return $this->getParts();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "TEXT";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the object back into a serialized blob.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize() {
|
||||
|
||||
// We need to kick in a special type of encoding, if it's a 2.1 vcard.
|
||||
if ($this->root->getDocumentType() !== Document::VCARD21) {
|
||||
return parent::serialize();
|
||||
}
|
||||
|
||||
$val = $this->getParts();
|
||||
|
||||
if (isset($this->minimumPropertyValues[$this->name])) {
|
||||
$val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
|
||||
}
|
||||
|
||||
// Imploding multiple parts into a single value, and splitting the
|
||||
// values with ;.
|
||||
if (count($val)>1) {
|
||||
foreach($val as $k=>$v) {
|
||||
$val[$k] = str_replace(';','\;', $v);
|
||||
}
|
||||
$val = implode(';', $val);
|
||||
} else {
|
||||
$val = $val[0];
|
||||
}
|
||||
|
||||
$str = $this->name;
|
||||
if ($this->group) $str = $this->group . '.' . $this->name;
|
||||
foreach($this->parameters as $param) {
|
||||
|
||||
if ($param->getValue() === 'QUOTED-PRINTABLE') {
|
||||
continue;
|
||||
}
|
||||
$str.=';' . $param->serialize();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// If the resulting value contains a \n, we must encode it as
|
||||
// quoted-printable.
|
||||
if (strpos($val,"\n") !== false) {
|
||||
|
||||
$str.=';ENCODING=QUOTED-PRINTABLE:';
|
||||
$lastLine=$str;
|
||||
$out = null;
|
||||
|
||||
// The PHP built-in quoted-printable-encode does not correctly
|
||||
// encode newlines for us. Specifically, the \r\n sequence must in
|
||||
// vcards be encoded as =0D=OA and we must insert soft-newlines
|
||||
// every 75 bytes.
|
||||
for($ii=0;$ii<strlen($val);$ii++) {
|
||||
$ord = ord($val[$ii]);
|
||||
// These characters are encoded as themselves.
|
||||
if ($ord >= 32 && $ord <=126) {
|
||||
$lastLine.=$val[$ii];
|
||||
} else {
|
||||
$lastLine.='=' . strtoupper(bin2hex($val[$ii]));
|
||||
}
|
||||
if (strlen($lastLine)>=75) {
|
||||
// Soft line break
|
||||
$out.=$lastLine. "=\r\n ";
|
||||
$lastLine = null;
|
||||
}
|
||||
|
||||
}
|
||||
if (!is_null($lastLine)) $out.= $lastLine . "\r\n";
|
||||
return $out;
|
||||
|
||||
} else {
|
||||
$str.=':' . $val;
|
||||
$out = '';
|
||||
while(strlen($str)>0) {
|
||||
if (strlen($str)>75) {
|
||||
$out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
|
||||
$str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
|
||||
} else {
|
||||
$out.=$str . "\r\n";
|
||||
$str='';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the node for correctness.
|
||||
*
|
||||
* The following options are supported:
|
||||
* - Node::REPAIR - If something is broken, and automatic repair may
|
||||
* be attempted.
|
||||
*
|
||||
* An array is returned with warnings.
|
||||
*
|
||||
* Every item in the array has the following properties:
|
||||
* * level - (number between 1 and 3 with severity information)
|
||||
* * message - (human readable message)
|
||||
* * node - (reference to the offending node)
|
||||
*
|
||||
* @param int $options
|
||||
* @return array
|
||||
*/
|
||||
public function validate($options = 0) {
|
||||
|
||||
$warnings = parent::validate($options);
|
||||
|
||||
if (isset($this->minimumPropertyValues[$this->name])) {
|
||||
|
||||
$minimum = $this->minimumPropertyValues[$this->name];
|
||||
$parts = $this->getParts();
|
||||
if (count($parts) < $minimum) {
|
||||
$warnings[] = array(
|
||||
'level' => 1,
|
||||
'message' => 'This property must have at least ' . $minimum . ' components. It only has ' . count($parts),
|
||||
'node' => $this,
|
||||
);
|
||||
if ($options & self::REPAIR) {
|
||||
$parts = array_pad($parts, $minimum, '');
|
||||
$this->setParts($parts);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return $warnings;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property;
|
||||
|
||||
use SabreForRainLoop\VObject\DateTimeParser;
|
||||
|
||||
/**
|
||||
* Time property
|
||||
*
|
||||
* This object encodes TIME values.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Time extends Text {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = null;
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "TIME";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
$parts = DateTimeParser::parseVCardTime($this->getValue());
|
||||
|
||||
$timeStr = '';
|
||||
|
||||
// Hour
|
||||
if (!is_null($parts['hour'])) {
|
||||
$timeStr.=$parts['hour'];
|
||||
|
||||
if (!is_null($parts['minute'])) {
|
||||
$timeStr.=':';
|
||||
}
|
||||
} else {
|
||||
// We know either minute or second _must_ be set, so we insert a
|
||||
// dash for an empty value.
|
||||
$timeStr.='-';
|
||||
}
|
||||
|
||||
// Minute
|
||||
if (!is_null($parts['minute'])) {
|
||||
$timeStr.=$parts['minute'];
|
||||
|
||||
if (!is_null($parts['second'])) {
|
||||
$timeStr.=':';
|
||||
}
|
||||
} else {
|
||||
if (isset($parts['second'])) {
|
||||
// Dash for empty minute
|
||||
$timeStr.='-';
|
||||
}
|
||||
}
|
||||
|
||||
// Second
|
||||
if (!is_null($parts['second'])) {
|
||||
$timeStr.=$parts['second'];
|
||||
}
|
||||
|
||||
// Timezone
|
||||
if (!is_null($parts['timezone'])) {
|
||||
$timeStr.=$parts['timezone'];
|
||||
}
|
||||
|
||||
return array($timeStr);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property,
|
||||
SabreForRainLoop\VObject\Component,
|
||||
SabreForRainLoop\VObject\Parser\MimeDir,
|
||||
SabreForRainLoop\VObject\Document;
|
||||
|
||||
/**
|
||||
* Unknown property
|
||||
*
|
||||
* This object represents any properties not recognized by the parser.
|
||||
* This type of value has been introduced by the jCal, jCard specs.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Unknown extends Text {
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
return array($this->getRawMimeDirValue());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "UNKNOWN";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property;
|
||||
|
||||
use SabreForRainLoop\VObject\Property;
|
||||
|
||||
/**
|
||||
* URI property
|
||||
*
|
||||
* This object encodes URI values. vCard 2.1 calls these URL.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Uri extends Property {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = null;
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "URI";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$this->value = $val;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
if (is_array($this->value)) {
|
||||
return $this->value[0];
|
||||
} else {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property;
|
||||
|
||||
/**
|
||||
* UtcOffset property
|
||||
*
|
||||
* This object encodes UTC-OFFSET values.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class UtcOffset extends Text {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = null;
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "UTC-OFFSET";
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\VCard;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\DateTimeParser;
|
||||
|
||||
/**
|
||||
* Date property
|
||||
*
|
||||
* This object encodes vCard DATE values.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Date extends DateAndOrTime {
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "DATE";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\VCard;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\DateTimeParser,
|
||||
SabreForRainLoop\VObject\Property\Text;
|
||||
|
||||
/**
|
||||
* DateAndOrTime property
|
||||
*
|
||||
* This object encodes DATE-AND-OR-TIME values.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class DateAndOrTime extends Text {
|
||||
|
||||
/**
|
||||
* Field separator
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
public $delimiter = null;
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "DATE-AND-OR-TIME";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
$parts = DateTimeParser::parseVCardDateTime($this->getValue());
|
||||
|
||||
$dateStr = '';
|
||||
|
||||
// Year
|
||||
if (!is_null($parts['year'])) {
|
||||
$dateStr.=$parts['year'];
|
||||
|
||||
if (!is_null($parts['month'])) {
|
||||
// If a year and a month is set, we need to insert a separator
|
||||
// dash.
|
||||
$dateStr.='-';
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (!is_null($parts['month']) || !is_null($parts['date'])) {
|
||||
// Inserting two dashes
|
||||
$dateStr.='--';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Month
|
||||
|
||||
if (!is_null($parts['month'])) {
|
||||
$dateStr.=$parts['month'];
|
||||
|
||||
if (isset($parts['date'])) {
|
||||
// If month and date are set, we need the separator dash.
|
||||
$dateStr.='-';
|
||||
}
|
||||
} else {
|
||||
if (isset($parts['date'])) {
|
||||
// If the month is empty, and a date is set, we need a 'empty
|
||||
// dash'
|
||||
$dateStr.='-';
|
||||
}
|
||||
}
|
||||
|
||||
// Date
|
||||
if (!is_null($parts['date'])) {
|
||||
$dateStr.=$parts['date'];
|
||||
}
|
||||
|
||||
|
||||
// Early exit if we don't have a time string.
|
||||
if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) {
|
||||
return array($dateStr);
|
||||
}
|
||||
|
||||
$dateStr.='T';
|
||||
|
||||
// Hour
|
||||
if (!is_null($parts['hour'])) {
|
||||
$dateStr.=$parts['hour'];
|
||||
|
||||
if (!is_null($parts['minute'])) {
|
||||
$dateStr.=':';
|
||||
}
|
||||
} else {
|
||||
// We know either minute or second _must_ be set, so we insert a
|
||||
// dash for an empty value.
|
||||
$dateStr.='-';
|
||||
}
|
||||
|
||||
// Minute
|
||||
if (!is_null($parts['minute'])) {
|
||||
$dateStr.=$parts['minute'];
|
||||
|
||||
if (!is_null($parts['second'])) {
|
||||
$dateStr.=':';
|
||||
}
|
||||
} else {
|
||||
if (isset($parts['second'])) {
|
||||
// Dash for empty minute
|
||||
$dateStr.='-';
|
||||
}
|
||||
}
|
||||
|
||||
// Second
|
||||
if (!is_null($parts['second'])) {
|
||||
$dateStr.=$parts['second'];
|
||||
}
|
||||
|
||||
// Timezone
|
||||
if (!is_null($parts['timezone'])) {
|
||||
$dateStr.=$parts['timezone'];
|
||||
}
|
||||
|
||||
return array($dateStr);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\VCard;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\DateTimeParser;
|
||||
|
||||
/**
|
||||
* DateTime property
|
||||
*
|
||||
* This object encodes DATE-TIME values for vCards.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class DateTime extends DateAndOrTime {
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "DATE-TIME";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\VCard;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\Property;
|
||||
|
||||
/**
|
||||
* LanguageTag property
|
||||
*
|
||||
* This object represents LANGUAGE-TAG values as used in vCards.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class LanguageTag extends Property {
|
||||
|
||||
/**
|
||||
* Sets a raw value coming from a mimedir (iCalendar/vCard) file.
|
||||
*
|
||||
* This has been 'unfolded', so only 1 line will be passed. Unescaping is
|
||||
* not yet done, but parameters are not included.
|
||||
*
|
||||
* @param string $val
|
||||
* @return void
|
||||
*/
|
||||
public function setRawMimeDirValue($val) {
|
||||
|
||||
$this->setValue($val);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raw mime-dir representation of the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawMimeDirValue() {
|
||||
|
||||
return $this->value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "LANGUAGE-TAG";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Property\VCard;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject\DateTimeParser,
|
||||
SabreForRainLoop\VObject\Property\Text;
|
||||
|
||||
/**
|
||||
* TimeStamp property
|
||||
*
|
||||
* This object encodes TIMESTAMP values.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class TimeStamp extends Text {
|
||||
|
||||
/**
|
||||
* In case this is a multi-value property. This string will be used as a
|
||||
* delimiter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $delimiter = null;
|
||||
|
||||
/**
|
||||
* Returns the type of value.
|
||||
*
|
||||
* This corresponds to the VALUE= parameter. Every property also has a
|
||||
* 'default' valueType.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueType() {
|
||||
|
||||
return "TIMESTAMP";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
* This method must always return an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJsonValue() {
|
||||
|
||||
$parts = DateTimeParser::parseVCardDateTime($this->getValue());
|
||||
|
||||
$dateStr =
|
||||
$parts['year'] . '-' .
|
||||
$parts['month'] . '-' .
|
||||
$parts['date'] . 'T' .
|
||||
$parts['hour'] . ':' .
|
||||
$parts['minute'] . ':' .
|
||||
$parts['second'];
|
||||
|
||||
// Timezone
|
||||
if (!is_null($parts['timezone'])) {
|
||||
$dateStr.=$parts['timezone'];
|
||||
}
|
||||
|
||||
return array($dateStr);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* iCalendar/vCard/jCal/jCard reader object.
|
||||
*
|
||||
* This object provides a few (static) convenience methods to quickly access
|
||||
* the parsers.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Reader {
|
||||
|
||||
/**
|
||||
* If this option is passed to the reader, it will be less strict about the
|
||||
* validity of the lines.
|
||||
*/
|
||||
const OPTION_FORGIVING = 1;
|
||||
|
||||
/**
|
||||
* If this option is turned on, any lines we cannot parse will be ignored
|
||||
* by the reader.
|
||||
*/
|
||||
const OPTION_IGNORE_INVALID_LINES = 2;
|
||||
|
||||
/**
|
||||
* Parses a vCard or iCalendar object, and returns the top component.
|
||||
*
|
||||
* The options argument is a bitfield. Pass any of the OPTIONS constant to
|
||||
* alter the parsers' behaviour.
|
||||
*
|
||||
* You can either supply a string, or a readable stream for input.
|
||||
*
|
||||
* @param string|resource $data
|
||||
* @param int $options
|
||||
* @return Document
|
||||
*/
|
||||
static function read($data, $options = 0) {
|
||||
|
||||
$parser = new Parser\MimeDir();
|
||||
$result = $parser->parse($data, $options);
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a jCard or jCal object, and returns the top component.
|
||||
*
|
||||
* The options argument is a bitfield. Pass any of the OPTIONS constant to
|
||||
* alter the parsers' behaviour.
|
||||
*
|
||||
* You can either a string, a readable stream, or an array for it's input.
|
||||
* Specifying the array is useful if json_decode was already called on the
|
||||
* input.
|
||||
*
|
||||
* @param string|resource|array $data
|
||||
* @param int $options
|
||||
* @return Node
|
||||
*/
|
||||
static function readJson($data, $options = 0) {
|
||||
|
||||
$parser = new Parser\Json();
|
||||
$result = $parser->parse($data, $options);
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Splitter;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject,
|
||||
SabreForRainLoop\VObject\Component\VCalendar;
|
||||
|
||||
/**
|
||||
* Splitter
|
||||
*
|
||||
* This class is responsible for splitting up iCalendar objects.
|
||||
*
|
||||
* This class expects a single VCALENDAR object with one or more
|
||||
* calendar-objects inside. Objects with identical UID's will be combined into
|
||||
* a single object.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Dominik Tobschall
|
||||
* @author Armin Hackmann
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class ICalendar implements SplitterInterface {
|
||||
|
||||
/**
|
||||
* Timezones
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $vtimezones = array();
|
||||
|
||||
/**
|
||||
* iCalendar objects
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $objects = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* The splitter should receive an readable file stream as it's input.
|
||||
*
|
||||
* @param resource $input
|
||||
* @param int $options Parser options, see the OPTIONS constants.
|
||||
*/
|
||||
public function __construct($input, $options = 0) {
|
||||
|
||||
$data = VObject\Reader::read($input, $options);
|
||||
$vtimezones = array();
|
||||
$components = array();
|
||||
|
||||
foreach($data->children() as $component) {
|
||||
if (!$component instanceof VObject\Component) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get all timezones
|
||||
if ($component->name === 'VTIMEZONE') {
|
||||
$this->vtimezones[(string)$component->TZID] = $component;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get component UID for recurring Events search
|
||||
if($component->UID) {
|
||||
$uid = (string)$component->UID;
|
||||
} else {
|
||||
// Generating a random UID
|
||||
$uid = sha1(microtime()) . '-vobjectimport';
|
||||
}
|
||||
|
||||
// Take care of recurring events
|
||||
if (!array_key_exists($uid, $this->objects)) {
|
||||
$this->objects[$uid] = new VCalendar();
|
||||
}
|
||||
|
||||
$this->objects[$uid]->add(clone $component);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Every time getNext() is called, a new object will be parsed, until we
|
||||
* hit the end of the stream.
|
||||
*
|
||||
* When the end is reached, null will be returned.
|
||||
*
|
||||
* @return SabreForRainLoop\VObject\Component|null
|
||||
*/
|
||||
public function getNext() {
|
||||
|
||||
if($object=array_shift($this->objects)) {
|
||||
|
||||
// create our baseobject
|
||||
$object->version = '2.0';
|
||||
$object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN';
|
||||
$object->calscale = 'GREGORIAN';
|
||||
|
||||
// add vtimezone information to obj (if we have it)
|
||||
foreach ($this->vtimezones as $vtimezone) {
|
||||
$object->add($vtimezone);
|
||||
}
|
||||
|
||||
return $object;
|
||||
|
||||
} else {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Splitter;
|
||||
|
||||
/**
|
||||
* VObject splitter
|
||||
*
|
||||
* The splitter is responsible for reading a large vCard or iCalendar object,
|
||||
* and splitting it into multiple objects.
|
||||
*
|
||||
* This is for example for Card and CalDAV, which require every event and vcard
|
||||
* to exist in their own objects, instead of one large one.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Dominik Tobschall
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
interface SplitterInterface {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* The splitter should receive an readable file stream as it's input.
|
||||
*
|
||||
* @param resource $input
|
||||
*/
|
||||
function __construct($input);
|
||||
|
||||
/**
|
||||
* Every time getNext() is called, a new object will be parsed, until we
|
||||
* hit the end of the stream.
|
||||
*
|
||||
* When the end is reached, null will be returned.
|
||||
*
|
||||
* @return SabreForRainLoop\VObject\Component|null
|
||||
*/
|
||||
function getNext();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject\Splitter;
|
||||
|
||||
use
|
||||
SabreForRainLoop\VObject,
|
||||
SabreForRainLoop\VObject\Parser\MimeDir;
|
||||
|
||||
/**
|
||||
* Splitter
|
||||
*
|
||||
* This class is responsible for splitting up VCard objects.
|
||||
*
|
||||
* It is assumed that the input stream contains 1 or more VCARD objects. This
|
||||
* class checks for BEGIN:VCARD and END:VCARD and parses each encountered
|
||||
* component individually.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Dominik Tobschall
|
||||
* @author Armin Hackmann
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class VCard implements SplitterInterface {
|
||||
|
||||
/**
|
||||
* File handle
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* Persistent parser
|
||||
*
|
||||
* @var MimeDir
|
||||
*/
|
||||
protected $parser;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* The splitter should receive an readable file stream as it's input.
|
||||
*
|
||||
* @param resource $input
|
||||
* @param int $options Parser options, see the OPTIONS constants.
|
||||
*/
|
||||
public function __construct($input, $options = 0) {
|
||||
|
||||
$this->input = $input;
|
||||
$this->parser = new MimeDir($input, $options);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Every time getNext() is called, a new object will be parsed, until we
|
||||
* hit the end of the stream.
|
||||
*
|
||||
* When the end is reached, null will be returned.
|
||||
*
|
||||
* @return SabreForRainLoop\VObject\Component|null
|
||||
*/
|
||||
public function getNext() {
|
||||
|
||||
try {
|
||||
$object = $this->parser->parse();
|
||||
} catch (VObject\EofException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $object;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* Useful utilities for working with various strings.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class StringUtil {
|
||||
|
||||
/**
|
||||
* Returns true or false depending on if a string is valid UTF-8
|
||||
*
|
||||
* @param string $str
|
||||
* @return bool
|
||||
*/
|
||||
static function isUTF8($str) {
|
||||
|
||||
// First check.. mb_check_encoding
|
||||
if (!mb_check_encoding($str, 'UTF-8')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Control characters
|
||||
if (preg_match('%(?:[\x00-\x08\x0B-\x0C\x0E\x0F])%', $str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tries its best to convert the input string to UTF-8.
|
||||
*
|
||||
* Currently only ISO-5991-1 input and UTF-8 input is supported, but this
|
||||
* may be expanded upon if we receive other examples.
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
static function convertToUTF8($str) {
|
||||
|
||||
$encoding = mb_detect_encoding($str , array('UTF-8','ISO-8859-1'), true);
|
||||
|
||||
if ($encoding === 'ISO-8859-1') {
|
||||
$newStr = utf8_encode($str);
|
||||
} else {
|
||||
$newStr = $str;
|
||||
}
|
||||
|
||||
// Removing any control characters
|
||||
return (preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E\x0F])%', '', $newStr));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,482 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* Time zone name translation
|
||||
*
|
||||
* This file translates well-known time zone names into "Olson database" time zone names.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Frank Edelhaeuser (fedel@users.sourceforge.net)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class TimeZoneUtil {
|
||||
|
||||
public static $map = array(
|
||||
|
||||
// from http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html
|
||||
// snapshot taken on 2012/01/16
|
||||
|
||||
// windows
|
||||
'AUS Central Standard Time'=>'Australia/Darwin',
|
||||
'AUS Eastern Standard Time'=>'Australia/Sydney',
|
||||
'Afghanistan Standard Time'=>'Asia/Kabul',
|
||||
'Alaskan Standard Time'=>'America/Anchorage',
|
||||
'Arab Standard Time'=>'Asia/Riyadh',
|
||||
'Arabian Standard Time'=>'Asia/Dubai',
|
||||
'Arabic Standard Time'=>'Asia/Baghdad',
|
||||
'Argentina Standard Time'=>'America/Buenos_Aires',
|
||||
'Armenian Standard Time'=>'Asia/Yerevan',
|
||||
'Atlantic Standard Time'=>'America/Halifax',
|
||||
'Azerbaijan Standard Time'=>'Asia/Baku',
|
||||
'Azores Standard Time'=>'Atlantic/Azores',
|
||||
'Bangladesh Standard Time'=>'Asia/Dhaka',
|
||||
'Canada Central Standard Time'=>'America/Regina',
|
||||
'Cape Verde Standard Time'=>'Atlantic/Cape_Verde',
|
||||
'Caucasus Standard Time'=>'Asia/Yerevan',
|
||||
'Cen. Australia Standard Time'=>'Australia/Adelaide',
|
||||
'Central America Standard Time'=>'America/Guatemala',
|
||||
'Central Asia Standard Time'=>'Asia/Almaty',
|
||||
'Central Brazilian Standard Time'=>'America/Cuiaba',
|
||||
'Central Europe Standard Time'=>'Europe/Budapest',
|
||||
'Central European Standard Time'=>'Europe/Warsaw',
|
||||
'Central Pacific Standard Time'=>'Pacific/Guadalcanal',
|
||||
'Central Standard Time'=>'America/Chicago',
|
||||
'Central Standard Time (Mexico)'=>'America/Mexico_City',
|
||||
'China Standard Time'=>'Asia/Shanghai',
|
||||
'Dateline Standard Time'=>'Etc/GMT+12',
|
||||
'E. Africa Standard Time'=>'Africa/Nairobi',
|
||||
'E. Australia Standard Time'=>'Australia/Brisbane',
|
||||
'E. Europe Standard Time'=>'Europe/Minsk',
|
||||
'E. South America Standard Time'=>'America/Sao_Paulo',
|
||||
'Eastern Standard Time'=>'America/New_York',
|
||||
'Egypt Standard Time'=>'Africa/Cairo',
|
||||
'Ekaterinburg Standard Time'=>'Asia/Yekaterinburg',
|
||||
'FLE Standard Time'=>'Europe/Kiev',
|
||||
'Fiji Standard Time'=>'Pacific/Fiji',
|
||||
'GMT Standard Time'=>'Europe/London',
|
||||
'GTB Standard Time'=>'Europe/Istanbul',
|
||||
'Georgian Standard Time'=>'Asia/Tbilisi',
|
||||
'Greenland Standard Time'=>'America/Godthab',
|
||||
'Greenwich Standard Time'=>'Atlantic/Reykjavik',
|
||||
'Hawaiian Standard Time'=>'Pacific/Honolulu',
|
||||
'India Standard Time'=>'Asia/Calcutta',
|
||||
'Iran Standard Time'=>'Asia/Tehran',
|
||||
'Israel Standard Time'=>'Asia/Jerusalem',
|
||||
'Jordan Standard Time'=>'Asia/Amman',
|
||||
'Kamchatka Standard Time'=>'Asia/Kamchatka',
|
||||
'Korea Standard Time'=>'Asia/Seoul',
|
||||
'Magadan Standard Time'=>'Asia/Magadan',
|
||||
'Mauritius Standard Time'=>'Indian/Mauritius',
|
||||
'Mexico Standard Time'=>'America/Mexico_City',
|
||||
'Mexico Standard Time 2'=>'America/Chihuahua',
|
||||
'Mid-Atlantic Standard Time'=>'Etc/GMT-2',
|
||||
'Middle East Standard Time'=>'Asia/Beirut',
|
||||
'Montevideo Standard Time'=>'America/Montevideo',
|
||||
'Morocco Standard Time'=>'Africa/Casablanca',
|
||||
'Mountain Standard Time'=>'America/Denver',
|
||||
'Mountain Standard Time (Mexico)'=>'America/Chihuahua',
|
||||
'Myanmar Standard Time'=>'Asia/Rangoon',
|
||||
'N. Central Asia Standard Time'=>'Asia/Novosibirsk',
|
||||
'Namibia Standard Time'=>'Africa/Windhoek',
|
||||
'Nepal Standard Time'=>'Asia/Katmandu',
|
||||
'New Zealand Standard Time'=>'Pacific/Auckland',
|
||||
'Newfoundland Standard Time'=>'America/St_Johns',
|
||||
'North Asia East Standard Time'=>'Asia/Irkutsk',
|
||||
'North Asia Standard Time'=>'Asia/Krasnoyarsk',
|
||||
'Pacific SA Standard Time'=>'America/Santiago',
|
||||
'Pacific Standard Time'=>'America/Los_Angeles',
|
||||
'Pacific Standard Time (Mexico)'=>'America/Santa_Isabel',
|
||||
'Pakistan Standard Time'=>'Asia/Karachi',
|
||||
'Paraguay Standard Time'=>'America/Asuncion',
|
||||
'Romance Standard Time'=>'Europe/Paris',
|
||||
'Russian Standard Time'=>'Europe/Moscow',
|
||||
'SA Eastern Standard Time'=>'America/Cayenne',
|
||||
'SA Pacific Standard Time'=>'America/Bogota',
|
||||
'SA Western Standard Time'=>'America/La_Paz',
|
||||
'SE Asia Standard Time'=>'Asia/Bangkok',
|
||||
'Samoa Standard Time'=>'Pacific/Apia',
|
||||
'Singapore Standard Time'=>'Asia/Singapore',
|
||||
'South Africa Standard Time'=>'Africa/Johannesburg',
|
||||
'Sri Lanka Standard Time'=>'Asia/Colombo',
|
||||
'Syria Standard Time'=>'Asia/Damascus',
|
||||
'Taipei Standard Time'=>'Asia/Taipei',
|
||||
'Tasmania Standard Time'=>'Australia/Hobart',
|
||||
'Tokyo Standard Time'=>'Asia/Tokyo',
|
||||
'Tonga Standard Time'=>'Pacific/Tongatapu',
|
||||
'US Eastern Standard Time'=>'America/Indianapolis',
|
||||
'US Mountain Standard Time'=>'America/Phoenix',
|
||||
'UTC+12'=>'Etc/GMT-12',
|
||||
'UTC-02'=>'Etc/GMT+2',
|
||||
'UTC-11'=>'Etc/GMT+11',
|
||||
'Ulaanbaatar Standard Time'=>'Asia/Ulaanbaatar',
|
||||
'Venezuela Standard Time'=>'America/Caracas',
|
||||
'Vladivostok Standard Time'=>'Asia/Vladivostok',
|
||||
'W. Australia Standard Time'=>'Australia/Perth',
|
||||
'W. Central Africa Standard Time'=>'Africa/Lagos',
|
||||
'W. Europe Standard Time'=>'Europe/Berlin',
|
||||
'West Asia Standard Time'=>'Asia/Tashkent',
|
||||
'West Pacific Standard Time'=>'Pacific/Port_Moresby',
|
||||
'Yakutsk Standard Time'=>'Asia/Yakutsk',
|
||||
|
||||
// Microsoft exchange timezones
|
||||
// Source:
|
||||
// http://msdn.microsoft.com/en-us/library/ms988620%28v=exchg.65%29.aspx
|
||||
//
|
||||
// Correct timezones deduced with help from:
|
||||
// http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
'Universal Coordinated Time' => 'UTC',
|
||||
'Casablanca, Monrovia' => 'Africa/Casablanca',
|
||||
'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
|
||||
'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
|
||||
'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
|
||||
'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
|
||||
'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
|
||||
'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
|
||||
'Prague, Central Europe' => 'Europe/Prague',
|
||||
'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo',
|
||||
'West Central Africa' => 'Africa/Luanda', // This was a best guess
|
||||
'Athens, Istanbul, Minsk' => 'Europe/Athens',
|
||||
'Bucharest' => 'Europe/Bucharest',
|
||||
'Cairo' => 'Africa/Cairo',
|
||||
'Harare, Pretoria' => 'Africa/Harare',
|
||||
'Helsinki, Riga, Tallinn' => 'Europe/Helsinki',
|
||||
'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem',
|
||||
'Baghdad' => 'Asia/Baghdad',
|
||||
'Arab, Kuwait, Riyadh' => 'Asia/Kuwait',
|
||||
'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow',
|
||||
'East Africa, Nairobi' => 'Africa/Nairobi',
|
||||
'Tehran' => 'Asia/Tehran',
|
||||
'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess
|
||||
'Baku, Tbilisi, Yerevan' => 'Asia/Baku',
|
||||
'Kabul' => 'Asia/Kabul',
|
||||
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
||||
'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
|
||||
'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
|
||||
'Kathmandu, Nepal' => 'Asia/Kathmandu',
|
||||
'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
|
||||
'Astana, Dhaka' => 'Asia/Dhaka',
|
||||
'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
|
||||
'Rangoon' => 'Asia/Rangoon',
|
||||
'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
|
||||
'Krasnoyarsk' => 'Asia/Krasnoyarsk',
|
||||
'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
|
||||
'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk',
|
||||
'Kuala Lumpur, Singapore' => 'Asia/Singapore',
|
||||
'Perth, Western Australia' => 'Australia/Perth',
|
||||
'Taipei' => 'Asia/Taipei',
|
||||
'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo',
|
||||
'Seoul, Korea Standard time' => 'Asia/Seoul',
|
||||
'Yakutsk' => 'Asia/Yakutsk',
|
||||
'Adelaide, Central Australia' => 'Australia/Adelaide',
|
||||
'Darwin' => 'Australia/Darwin',
|
||||
'Brisbane, East Australia' => 'Australia/Brisbane',
|
||||
'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney',
|
||||
'Guam, Port Moresby' => 'Pacific/Guam',
|
||||
'Hobart, Tasmania' => 'Australia/Hobart',
|
||||
'Vladivostok' => 'Asia/Vladivostok',
|
||||
'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan',
|
||||
'Auckland, Wellington' => 'Pacific/Auckland',
|
||||
'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji',
|
||||
'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu',
|
||||
'Azores' => 'Atlantic/Azores',
|
||||
'Cape Verde Is.' => 'Atlantic/Cape_Verde',
|
||||
'Mid-Atlantic' => 'America/Noronha',
|
||||
'Brasilia' => 'America/Sao_Paulo', // Best guess
|
||||
'Buenos Aires' => 'America/Argentina/Buenos_Aires',
|
||||
'Greenland' => 'America/Godthab',
|
||||
'Newfoundland' => 'America/St_Johns',
|
||||
'Atlantic Time (Canada)' => 'America/Halifax',
|
||||
'Caracas, La Paz' => 'America/Caracas',
|
||||
'Santiago' => 'America/Santiago',
|
||||
'Bogota, Lima, Quito' => 'America/Bogota',
|
||||
'Eastern Time (US & Canada)' => 'America/New_York',
|
||||
'Indiana (East)' => 'America/Indiana/Indianapolis',
|
||||
'Central America' => 'America/Guatemala',
|
||||
'Central Time (US & Canada)' => 'America/Chicago',
|
||||
'Mexico City, Tegucigalpa' => 'America/Mexico_City',
|
||||
'Saskatchewan' => 'America/Edmonton',
|
||||
'Arizona' => 'America/Phoenix',
|
||||
'Mountain Time (US & Canada)' => 'America/Denver', // Best guess
|
||||
'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess
|
||||
'Alaska' => 'America/Anchorage',
|
||||
'Hawaii' => 'Pacific/Honolulu',
|
||||
'Midway Island, Samoa' => 'Pacific/Midway',
|
||||
'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein',
|
||||
|
||||
// The following list are timezone names that could be generated by
|
||||
// Lotus / Domino
|
||||
'Dateline' => 'Etc/GMT-12',
|
||||
'Samoa' => 'Pacific/Apia',
|
||||
'Hawaiian' => 'Pacific/Honolulu',
|
||||
'Alaskan' => 'America/Anchorage',
|
||||
'Pacific' => 'America/Los_Angeles',
|
||||
'Pacific Standard Time' => 'America/Los_Angeles',
|
||||
'Mexico Standard Time 2' => 'America/Chihuahua',
|
||||
'Mountain' => 'America/Denver',
|
||||
'Mountain Standard Time' => 'America/Chihuahua',
|
||||
'US Mountain' => 'America/Phoenix',
|
||||
'Canada Central' => 'America/Edmonton',
|
||||
'Central America' => 'America/Guatemala',
|
||||
'Central' => 'America/Chicago',
|
||||
'Central Standard Time' => 'America/Mexico_City',
|
||||
'Mexico' => 'America/Mexico_City',
|
||||
'Eastern' => 'America/New_York',
|
||||
'SA Pacific' => 'America/Bogota',
|
||||
'US Eastern' => 'America/Indiana/Indianapolis',
|
||||
'Venezuela' => 'America/Caracas',
|
||||
'Atlantic' => 'America/Halifax',
|
||||
'Central Brazilian' => 'America/Manaus',
|
||||
'Pacific SA' => 'America/Santiago',
|
||||
'SA Western' => 'America/La_Paz',
|
||||
'Newfoundland' => 'America/St_Johns',
|
||||
'Argentina' => 'America/Argentina/Buenos_Aires',
|
||||
'E. South America' => 'America/Belem',
|
||||
'Greenland' => 'America/Godthab',
|
||||
'Montevideo' => 'America/Montevideo',
|
||||
'SA Eastern' => 'America/Belem',
|
||||
'Mid-Atlantic' => 'Etc/GMT-2',
|
||||
'Azores' => 'Atlantic/Azores',
|
||||
'Cape Verde' => 'Atlantic/Cape_Verde',
|
||||
'Greenwich' => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT.
|
||||
'Morocco' => 'Africa/Casablanca',
|
||||
'Central Europe' => 'Europe/Prague',
|
||||
'Central European' => 'Europe/Sarajevo',
|
||||
'Romance' => 'Europe/Paris',
|
||||
'W. Central Africa' => 'Africa/Lagos', // Best guess
|
||||
'W. Europe' => 'Europe/Amsterdam',
|
||||
'E. Europe' => 'Europe/Minsk',
|
||||
'Egypt' => 'Africa/Cairo',
|
||||
'FLE' => 'Europe/Helsinki',
|
||||
'GTB' => 'Europe/Athens',
|
||||
'Israel' => 'Asia/Jerusalem',
|
||||
'Jordan' => 'Asia/Amman',
|
||||
'Middle East' => 'Asia/Beirut',
|
||||
'Namibia' => 'Africa/Windhoek',
|
||||
'South Africa' => 'Africa/Harare',
|
||||
'Arab' => 'Asia/Kuwait',
|
||||
'Arabic' => 'Asia/Baghdad',
|
||||
'E. Africa' => 'Africa/Nairobi',
|
||||
'Georgian' => 'Asia/Tbilisi',
|
||||
'Russian' => 'Europe/Moscow',
|
||||
'Iran' => 'Asia/Tehran',
|
||||
'Arabian' => 'Asia/Muscat',
|
||||
'Armenian' => 'Asia/Yerevan',
|
||||
'Azerbijan' => 'Asia/Baku',
|
||||
'Caucasus' => 'Asia/Yerevan',
|
||||
'Mauritius' => 'Indian/Mauritius',
|
||||
'Afghanistan' => 'Asia/Kabul',
|
||||
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
||||
'Pakistan' => 'Asia/Karachi',
|
||||
'West Asia' => 'Asia/Tashkent',
|
||||
'India' => 'Asia/Calcutta',
|
||||
'Sri Lanka' => 'Asia/Colombo',
|
||||
'Nepal' => 'Asia/Kathmandu',
|
||||
'Central Asia' => 'Asia/Dhaka',
|
||||
'N. Central Asia' => 'Asia/Almaty',
|
||||
'Myanmar' => 'Asia/Rangoon',
|
||||
'North Asia' => 'Asia/Krasnoyarsk',
|
||||
'SE Asia' => 'Asia/Bangkok',
|
||||
'China' => 'Asia/Shanghai',
|
||||
'North Asia East' => 'Asia/Irkutsk',
|
||||
'Singapore' => 'Asia/Singapore',
|
||||
'Taipei' => 'Asia/Taipei',
|
||||
'W. Australia' => 'Australia/Perth',
|
||||
'Korea' => 'Asia/Seoul',
|
||||
'Tokyo' => 'Asia/Tokyo',
|
||||
'Yakutsk' => 'Asia/Yakutsk',
|
||||
'AUS Central' => 'Australia/Darwin',
|
||||
'Cen. Australia' => 'Australia/Adelaide',
|
||||
'AUS Eastern' => 'Australia/Sydney',
|
||||
'E. Australia' => 'Australia/Brisbane',
|
||||
'Tasmania' => 'Australia/Hobart',
|
||||
'Vladivostok' => 'Asia/Vladivostok',
|
||||
'West Pacific' => 'Pacific/Guam',
|
||||
'Central Pacific' => 'Asia/Magadan',
|
||||
'Fiji' => 'Pacific/Fiji',
|
||||
'New Zealand' => 'Pacific/Auckland',
|
||||
'Tonga' => 'Pacific/Tongatapu',
|
||||
);
|
||||
|
||||
/**
|
||||
* List of microsoft exchange timezone ids.
|
||||
*
|
||||
* Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx
|
||||
*/
|
||||
public static $microsoftExchangeMap = array(
|
||||
0 => 'UTC',
|
||||
31 => 'Africa/Casablanca',
|
||||
|
||||
// Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo.
|
||||
// I'm not even kidding.. We handle this special case in the
|
||||
// getTimeZone method.
|
||||
2 => 'Europe/Lisbon',
|
||||
1 => 'Europe/London',
|
||||
4 => 'Europe/Berlin',
|
||||
6 => 'Europe/Prague',
|
||||
3 => 'Europe/Paris',
|
||||
69 => 'Africa/Luanda', // This was a best guess
|
||||
7 => 'Europe/Athens',
|
||||
5 => 'Europe/Bucharest',
|
||||
49 => 'Africa/Cairo',
|
||||
50 => 'Africa/Harare',
|
||||
59 => 'Europe/Helsinki',
|
||||
27 => 'Asia/Jerusalem',
|
||||
26 => 'Asia/Baghdad',
|
||||
74 => 'Asia/Kuwait',
|
||||
51 => 'Europe/Moscow',
|
||||
56 => 'Africa/Nairobi',
|
||||
25 => 'Asia/Tehran',
|
||||
24 => 'Asia/Muscat', // Best guess
|
||||
54 => 'Asia/Baku',
|
||||
48 => 'Asia/Kabul',
|
||||
58 => 'Asia/Yekaterinburg',
|
||||
47 => 'Asia/Karachi',
|
||||
23 => 'Asia/Calcutta',
|
||||
62 => 'Asia/Kathmandu',
|
||||
46 => 'Asia/Almaty',
|
||||
71 => 'Asia/Dhaka',
|
||||
66 => 'Asia/Colombo',
|
||||
61 => 'Asia/Rangoon',
|
||||
22 => 'Asia/Bangkok',
|
||||
64 => 'Asia/Krasnoyarsk',
|
||||
45 => 'Asia/Shanghai',
|
||||
63 => 'Asia/Irkutsk',
|
||||
21 => 'Asia/Singapore',
|
||||
73 => 'Australia/Perth',
|
||||
75 => 'Asia/Taipei',
|
||||
20 => 'Asia/Tokyo',
|
||||
72 => 'Asia/Seoul',
|
||||
70 => 'Asia/Yakutsk',
|
||||
19 => 'Australia/Adelaide',
|
||||
44 => 'Australia/Darwin',
|
||||
18 => 'Australia/Brisbane',
|
||||
76 => 'Australia/Sydney',
|
||||
43 => 'Pacific/Guam',
|
||||
42 => 'Australia/Hobart',
|
||||
68 => 'Asia/Vladivostok',
|
||||
41 => 'Asia/Magadan',
|
||||
17 => 'Pacific/Auckland',
|
||||
40 => 'Pacific/Fiji',
|
||||
67 => 'Pacific/Tongatapu',
|
||||
29 => 'Atlantic/Azores',
|
||||
53 => 'Atlantic/Cape_Verde',
|
||||
30 => 'America/Noronha',
|
||||
8 => 'America/Sao_Paulo', // Best guess
|
||||
32 => 'America/Argentina/Buenos_Aires',
|
||||
60 => 'America/Godthab',
|
||||
28 => 'America/St_Johns',
|
||||
9 => 'America/Halifax',
|
||||
33 => 'America/Caracas',
|
||||
65 => 'America/Santiago',
|
||||
35 => 'America/Bogota',
|
||||
10 => 'America/New_York',
|
||||
34 => 'America/Indiana/Indianapolis',
|
||||
55 => 'America/Guatemala',
|
||||
11 => 'America/Chicago',
|
||||
37 => 'America/Mexico_City',
|
||||
36 => 'America/Edmonton',
|
||||
38 => 'America/Phoenix',
|
||||
12 => 'America/Denver', // Best guess
|
||||
13 => 'America/Los_Angeles', // Best guess
|
||||
14 => 'America/Anchorage',
|
||||
15 => 'Pacific/Honolulu',
|
||||
16 => 'Pacific/Midway',
|
||||
39 => 'Pacific/Kwajalein',
|
||||
);
|
||||
|
||||
/**
|
||||
* This method will try to find out the correct timezone for an iCalendar
|
||||
* date-time value.
|
||||
*
|
||||
* You must pass the contents of the TZID parameter, as well as the full
|
||||
* calendar.
|
||||
*
|
||||
* If the lookup fails, this method will return the default PHP timezone
|
||||
* (as configured using date_default_timezone_set, or the date.timezone ini
|
||||
* setting).
|
||||
*
|
||||
* Alternatively, if $failIfUncertain is set to true, it will throw an
|
||||
* exception if we cannot accurately determine the timezone.
|
||||
*
|
||||
* @param string $tzid
|
||||
* @param SabreForRainLoop\VObject\Component $vcalendar
|
||||
* @return DateTimeZone
|
||||
*/
|
||||
static public function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) {
|
||||
|
||||
// First we will just see if the tzid is a support timezone identifier.
|
||||
try {
|
||||
return new \DateTimeZone($tzid);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
// Next, we check if the tzid is somewhere in our tzid map.
|
||||
if (isset(self::$map[$tzid])) {
|
||||
return new \DateTimeZone(self::$map[$tzid]);
|
||||
}
|
||||
|
||||
// Maybe the author was hyper-lazy and just included an offset. We
|
||||
// support it, but we aren't happy about it.
|
||||
if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) {
|
||||
return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2],0,2),'0'));
|
||||
}
|
||||
|
||||
if ($vcalendar) {
|
||||
|
||||
// If that didn't work, we will scan VTIMEZONE objects
|
||||
foreach($vcalendar->select('VTIMEZONE') as $vtimezone) {
|
||||
|
||||
if ((string)$vtimezone->TZID === $tzid) {
|
||||
|
||||
// Some clients add 'X-LIC-LOCATION' with the olson name.
|
||||
if (isset($vtimezone->{'X-LIC-LOCATION'})) {
|
||||
|
||||
$lic = (string)$vtimezone->{'X-LIC-LOCATION'};
|
||||
|
||||
// Libical generators may specify strings like
|
||||
// "SystemV/EST5EDT". For those we must remove the
|
||||
// SystemV part.
|
||||
if (substr($lic,0,8)==='SystemV/') {
|
||||
$lic = substr($lic,8);
|
||||
}
|
||||
|
||||
try {
|
||||
return new \DateTimeZone($lic);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
}
|
||||
// Microsoft may add a magic number, which we also have an
|
||||
// answer for.
|
||||
if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
|
||||
$cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
|
||||
|
||||
// 2 can mean both Europe/Lisbon and Europe/Sarajevo.
|
||||
if ($cdoId===2 && strpos((string)$vtimezone->TZID, 'Sarajevo')!==false) {
|
||||
return new \DateTimeZone('Europe/Sarajevo');
|
||||
}
|
||||
|
||||
if (isset(self::$microsoftExchangeMap[$cdoId])) {
|
||||
return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($failIfUncertain) {
|
||||
throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid);
|
||||
}
|
||||
|
||||
// If we got all the way here, we default to UTC.
|
||||
return new \DateTimeZone(date_default_timezone_get());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,382 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* This utility converts vcards from one version to another.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class VCardConverter {
|
||||
|
||||
/**
|
||||
* Converts a vCard object to a new version.
|
||||
*
|
||||
* targetVersion must be one of:
|
||||
* Document::VCARD21
|
||||
* Document::VCARD30
|
||||
* Document::VCARD40
|
||||
*
|
||||
* Currently only 3.0 and 4.0 as input and output versions.
|
||||
*
|
||||
* 2.1 has some minor support for the input version, it's incomplete at the
|
||||
* moment though.
|
||||
*
|
||||
* If input and output version are identical, a clone is returned.
|
||||
*
|
||||
* @param Component\VCard $input
|
||||
* @param int $targetVersion
|
||||
*/
|
||||
public function convert(Component\VCard $input, $targetVersion) {
|
||||
|
||||
$inputVersion = $input->getDocumentType();
|
||||
if ($inputVersion===$targetVersion) {
|
||||
return clone $input;
|
||||
}
|
||||
|
||||
if (!in_array($inputVersion, array(Document::VCARD21, Document::VCARD30, Document::VCARD40))) {
|
||||
throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
|
||||
}
|
||||
if (!in_array($targetVersion, array(Document::VCARD30, Document::VCARD40))) {
|
||||
throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version');
|
||||
}
|
||||
|
||||
$newVersion = $targetVersion===Document::VCARD40?'4.0':'3.0';
|
||||
|
||||
$output = new Component\VCard(array(
|
||||
'VERSION' => $newVersion,
|
||||
));
|
||||
|
||||
foreach($input->children as $property) {
|
||||
|
||||
$this->convertProperty($input, $output, $property, $targetVersion);
|
||||
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles conversion of a single property.
|
||||
*
|
||||
* @param Component\VCard $input
|
||||
* @param Component\VCard $output
|
||||
* @param Property $property
|
||||
* @param int $targetVersion
|
||||
* @return void
|
||||
*/
|
||||
protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) {
|
||||
|
||||
// Skipping these, those are automatically added.
|
||||
if (in_array($property->name, array('VERSION', 'PRODID'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parameters = $property->parameters();
|
||||
|
||||
$valueType = null;
|
||||
if (isset($parameters['VALUE'])) {
|
||||
$valueType = $parameters['VALUE']->getValue();
|
||||
unset($parameters['VALUE']);
|
||||
}
|
||||
if (!$valueType) {
|
||||
$valueType = $property->getValueType();
|
||||
}
|
||||
|
||||
$newProperty = null;
|
||||
|
||||
if ($targetVersion===Document::VCARD30) {
|
||||
|
||||
if ($property instanceof Property\Uri && in_array($property->name, array('PHOTO','LOGO','SOUND'))) {
|
||||
|
||||
$newProperty = $this->convertUriToBinary($output, $property, $parameters);
|
||||
|
||||
} elseif ($property->name === 'KIND') {
|
||||
|
||||
switch(strtolower($property->getValue())) {
|
||||
case 'org' :
|
||||
// OS X addressbook property.
|
||||
$newProperty = $output->createProperty('X-ABSHOWAS','COMPANY');
|
||||
break;
|
||||
case 'individual' :
|
||||
// Individual is implied, so we can just skip it.
|
||||
return;
|
||||
|
||||
case 'group' :
|
||||
// OS X addressbook property
|
||||
$newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND','GROUP');
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
} elseif ($targetVersion===Document::VCARD40) {
|
||||
|
||||
// These properties were removed in vCard 4.0
|
||||
if (in_array($property->name, array('NAME', 'MAILER', 'LABEL', 'CLASS'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($property instanceOf Property\Binary) {
|
||||
|
||||
$newProperty = $this->convertBinaryToUri($output, $property, $parameters);
|
||||
|
||||
} else {
|
||||
switch($property->name) {
|
||||
case 'X-ABSHOWAS' :
|
||||
if (strtoupper($property->getValue()) === 'COMPANY') {
|
||||
$newProperty = $output->createProperty('KIND','org');
|
||||
}
|
||||
break;
|
||||
case 'X-ADDRESSBOOKSERVER-KIND' :
|
||||
if (strtoupper($property->getValue()) === 'GROUP') {
|
||||
$newProperty = $output->createProperty('KIND','group');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (is_null($newProperty)) {
|
||||
|
||||
$newProperty = $output->createProperty(
|
||||
$property->name,
|
||||
$property->getParts(),
|
||||
array(), // no parameters yet
|
||||
$valueType
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// set property group
|
||||
$newProperty->group = $property->group;
|
||||
|
||||
if ($targetVersion===Document::VCARD40) {
|
||||
$this->convertParameters40($newProperty, $parameters);
|
||||
} else {
|
||||
$this->convertParameters30($newProperty, $parameters);
|
||||
}
|
||||
|
||||
// Lastly, we need to see if there's a need for a VALUE parameter.
|
||||
//
|
||||
// We can do that by instantating a empty property with that name, and
|
||||
// seeing if the default valueType is identical to the current one.
|
||||
$tempProperty = $output->createProperty($newProperty->name);
|
||||
if ($tempProperty->getValueType() !== $newProperty->getValueType()) {
|
||||
$newProperty['VALUE'] = $newProperty->getValueType();
|
||||
}
|
||||
|
||||
$output->add($newProperty);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a BINARY property to a URI property.
|
||||
*
|
||||
* vCard 4.0 no longer supports BINARY properties.
|
||||
*
|
||||
* @param Component\VCard $output
|
||||
* @param Property\Uri $property The input property.
|
||||
* @param $parameters List of parameters that will eventually be added to
|
||||
* the new property.
|
||||
* @return Property\Uri
|
||||
*/
|
||||
protected function convertBinaryToUri(Component\VCard $output, Property\Binary $property, array &$parameters) {
|
||||
|
||||
$newProperty = $output->createProperty(
|
||||
$property->name,
|
||||
null, // no value
|
||||
array(), // no parameters yet
|
||||
'URI' // Forcing the BINARY type
|
||||
);
|
||||
|
||||
$mimeType = 'application/octet-stream';
|
||||
|
||||
// See if we can find a better mimetype.
|
||||
if (isset($parameters['TYPE'])) {
|
||||
|
||||
$newTypes = array();
|
||||
foreach($parameters['TYPE']->getParts() as $typePart) {
|
||||
if (in_array(
|
||||
strtoupper($typePart),
|
||||
array('JPEG','PNG','GIF')
|
||||
)) {
|
||||
$mimeType = 'image/' . strtolower($typePart);
|
||||
} else {
|
||||
$newTypes[] = $typePart;
|
||||
}
|
||||
}
|
||||
|
||||
// If there were any parameters we're not converting to a
|
||||
// mime-type, we need to keep them.
|
||||
if ($newTypes) {
|
||||
$parameters['TYPE']->setParts($newTypes);
|
||||
} else {
|
||||
unset($parameters['TYPE']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($property->getValue()));
|
||||
|
||||
return $newProperty;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a URI property to a BINARY property.
|
||||
*
|
||||
* In vCard 4.0 attachments are encoded as data: uri. Even though these may
|
||||
* be valid in vCard 3.0 as well, we should convert those to BINARY if
|
||||
* possible, to improve compatibility.
|
||||
*
|
||||
* @param Component\VCard $output
|
||||
* @param Property\Uri $property The input property.
|
||||
* @param $parameters List of parameters that will eventually be added to
|
||||
* the new property.
|
||||
* @return Property\Binary|null
|
||||
*/
|
||||
protected function convertUriToBinary(Component\VCard $output, Property\Uri $property, array &$parameters) {
|
||||
|
||||
$value = $property->getValue();
|
||||
|
||||
// Only converting data: uris
|
||||
if (substr($value, 0, 5)!=='data:') {
|
||||
return;
|
||||
}
|
||||
|
||||
$newProperty = $output->createProperty(
|
||||
$property->name,
|
||||
null, // no value
|
||||
array(), // no parameters yet
|
||||
'BINARY'
|
||||
);
|
||||
|
||||
$mimeType = substr($value, 5, strpos($value, ',')-5);
|
||||
if (strpos($mimeType, ';')) {
|
||||
$mimeType = substr($mimeType,0,strpos($mimeType, ';'));
|
||||
$newProperty->setValue(base64_decode(substr($value, strpos($value,',')+1)));
|
||||
} else {
|
||||
$newProperty->setValue(substr($value, strpos($value,',')+1));
|
||||
}
|
||||
unset($value);
|
||||
|
||||
$newProperty['ENCODING'] = 'b';
|
||||
switch($mimeType) {
|
||||
|
||||
case 'image/jpeg' :
|
||||
$newProperty['TYPE'] = 'JPEG';
|
||||
break;
|
||||
case 'image/png' :
|
||||
$newProperty['TYPE'] = 'PNG';
|
||||
break;
|
||||
case 'image/gif' :
|
||||
$newProperty['TYPE'] = 'GIF';
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
return $newProperty;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds parameters to a new property for vCard 4.0
|
||||
*
|
||||
* @param Property $newProperty
|
||||
* @param array $parameters
|
||||
* @return void
|
||||
*/
|
||||
protected function convertParameters40(Property $newProperty, array $parameters) {
|
||||
|
||||
// Adding all parameters.
|
||||
foreach($parameters as $param) {
|
||||
|
||||
// vCard 2.1 allowed parameters with no name
|
||||
if ($param->noName) $param->noName = false;
|
||||
|
||||
switch($param->name) {
|
||||
|
||||
// We need to see if there's any TYPE=PREF, because in vCard 4
|
||||
// that's now PREF=1.
|
||||
case 'TYPE' :
|
||||
foreach($param->getParts() as $paramPart) {
|
||||
|
||||
if (strtoupper($paramPart)==='PREF') {
|
||||
$newProperty->add('PREF','1');
|
||||
} else {
|
||||
$newProperty->add($param->name, $paramPart);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
// These no longer exist in vCard 4
|
||||
case 'ENCODING' :
|
||||
case 'CHARSET' :
|
||||
break;
|
||||
|
||||
default :
|
||||
$newProperty->add($param->name, $param->getParts());
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds parameters to a new property for vCard 3.0
|
||||
*
|
||||
* @param Property $newProperty
|
||||
* @param array $parameters
|
||||
* @return void
|
||||
*/
|
||||
protected function convertParameters30(Property $newProperty, array $parameters) {
|
||||
|
||||
// Adding all parameters.
|
||||
foreach($parameters as $param) {
|
||||
|
||||
// vCard 2.1 allowed parameters with no name
|
||||
if ($param->noName) $param->noName = false;
|
||||
|
||||
switch($param->name) {
|
||||
|
||||
case 'ENCODING' :
|
||||
// This value only existed in vCard 2.1, and should be
|
||||
// removed for anything else.
|
||||
if (strtoupper($param->getValue())!=='QUOTED-PRINTABLE') {
|
||||
$newProperty->add($param->name, $param->getParts());
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* Converting PREF=1 to TYPE=PREF.
|
||||
*
|
||||
* Any other PREF numbers we'll drop.
|
||||
*/
|
||||
case 'PREF' :
|
||||
if ($param->getValue()=='1') {
|
||||
$newProperty->add('TYPE','PREF');
|
||||
}
|
||||
break;
|
||||
|
||||
default :
|
||||
$newProperty->add($param->name, $param->getParts());
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace SabreForRainLoop\VObject;
|
||||
|
||||
/**
|
||||
* This class contains the version number for the VObject package
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Version {
|
||||
|
||||
/**
|
||||
* Full version number
|
||||
*/
|
||||
const VERSION = '3.1.3';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Includes file
|
||||
*
|
||||
* This file includes the entire VObject library in one go.
|
||||
* The benefit is that an autoloader is not needed, which is often faster.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
|
||||
// Begin includes
|
||||
include __DIR__ . '/Cli.php';
|
||||
include __DIR__ . '/DateTimeParser.php';
|
||||
include __DIR__ . '/ElementList.php';
|
||||
include __DIR__ . '/FreeBusyGenerator.php';
|
||||
include __DIR__ . '/Node.php';
|
||||
include __DIR__ . '/Parameter.php';
|
||||
include __DIR__ . '/ParseException.php';
|
||||
include __DIR__ . '/Parser/Parser.php';
|
||||
include __DIR__ . '/Property.php';
|
||||
include __DIR__ . '/Reader.php';
|
||||
include __DIR__ . '/RecurrenceIterator.php';
|
||||
include __DIR__ . '/Splitter/SplitterInterface.php';
|
||||
include __DIR__ . '/Splitter/VCard.php';
|
||||
include __DIR__ . '/StringUtil.php';
|
||||
include __DIR__ . '/TimeZoneUtil.php';
|
||||
include __DIR__ . '/VCardConverter.php';
|
||||
include __DIR__ . '/Version.php';
|
||||
include __DIR__ . '/Component.php';
|
||||
include __DIR__ . '/Document.php';
|
||||
include __DIR__ . '/EofException.php';
|
||||
include __DIR__ . '/Parser/Json.php';
|
||||
include __DIR__ . '/Parser/MimeDir.php';
|
||||
include __DIR__ . '/Property/Binary.php';
|
||||
include __DIR__ . '/Property/Boolean.php';
|
||||
include __DIR__ . '/Property/Float.php';
|
||||
include __DIR__ . '/Property/ICalendar/DateTime.php';
|
||||
include __DIR__ . '/Property/ICalendar/Duration.php';
|
||||
include __DIR__ . '/Property/ICalendar/Period.php';
|
||||
include __DIR__ . '/Property/ICalendar/Recur.php';
|
||||
include __DIR__ . '/Property/Integer.php';
|
||||
include __DIR__ . '/Property/Text.php';
|
||||
include __DIR__ . '/Property/Time.php';
|
||||
include __DIR__ . '/Property/Unknown.php';
|
||||
include __DIR__ . '/Property/Uri.php';
|
||||
include __DIR__ . '/Property/UtcOffset.php';
|
||||
include __DIR__ . '/Property/VCard/DateAndOrTime.php';
|
||||
include __DIR__ . '/Property/VCard/DateTime.php';
|
||||
include __DIR__ . '/Property/VCard/LanguageTag.php';
|
||||
include __DIR__ . '/Property/VCard/TimeStamp.php';
|
||||
include __DIR__ . '/Splitter/ICalendar.php';
|
||||
include __DIR__ . '/Component/VAlarm.php';
|
||||
include __DIR__ . '/Component/VCalendar.php';
|
||||
include __DIR__ . '/Component/VCard.php';
|
||||
include __DIR__ . '/Component/VEvent.php';
|
||||
include __DIR__ . '/Component/VFreeBusy.php';
|
||||
include __DIR__ . '/Component/VJournal.php';
|
||||
include __DIR__ . '/Component/VTodo.php';
|
||||
include __DIR__ . '/Property/FlatText.php';
|
||||
include __DIR__ . '/Property/ICalendar/CalAddress.php';
|
||||
include __DIR__ . '/Property/ICalendar/Date.php';
|
||||
include __DIR__ . '/Property/VCard/Date.php';
|
||||
// End includes
|
||||
Loading…
Add table
Add a link
Reference in a new issue