summaryrefslogtreecommitdiff
path: root/lib/Kolab/FreeBusy/FormatExchange2010.php
blob: 8534df25214f3330e1c964ac4d94f65c603cb3a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<?php

namespace Kolab\FreeBusy;

use Sabre\VObject\Reader as VCalReader;
use Sabre\VObject\FreeBusyGenerator;
use Sabre\VObject\ParseException;
use Desarrolla2\Cache\Cache;
use Desarrolla2\Cache\Adapter\File as FileCache;
use \SimpleXMLElement;


/**
 * Implementation of a data converter reading Exchange 2010 Internet Calendar Publishing files
 */
class FormatExchange2010 extends Format
{
	private $tzmap;

	/**
	 * @see Format::toVCalendar()
	 */
	public function toVCalendar($input)
	{
		// convert Microsoft timezone identifiers to Olson standard
		// do this before parsing to create correct DateTime values
		$input = preg_replace_callback('/(TZID[=:])([-\w ]+)\b/i', array($this, 'convertTZID'), $input);

		try {
			// parse vcalendar data
			$calendar = VCalReader::read($input);

			// map X-MICROSOFT-CDO-* attributes into iCal equivalents
			foreach ($calendar->VEVENT as $vevent) {
				if ($busystatus = reset($vevent->select('X-MICROSOFT-CDO-BUSYSTATUS'))) {
					$vevent->STATUS->value = $busystatus->value;
				}
			}

			// feed the calendar object into the free/busy generator
			// we must specify a start and end date, because recurring events are expanded. nice!
			$utc = new \DateTimezone('UTC');
			$fbgen = new FreeBusyGenerator(
				new \DateTime('now - 8 weeks 00:00:00', $utc),
				new \DateTime('now + 16 weeks 00:00:00', $utc),
				$calendar
			);

			// get the freebusy report
			$freebusy = $fbgen->getResult();
			$freebusy->PRODID = '-//kolab.org//NONSGML Kolab Server 3//EN';
			$freebusy->METHOD = 'PUBLISH';

			// serialize to VCALENDAR format
			return $freebusy->serialize();
		}
		catch (ParseException $e) {
			Logger::get('format.Exchange2010')->addError("iCal parse error: " . $e->getMessage());
		}

		return false;
	}

	/**
	 * preg_replace callback function to map Timezone identifiers
	 */
	private function convertTZID($m)
	{
		if (!isset($this->tzmap)) {
			$this->getTZMAP();
		}

		$key = strtolower($m[2]);
		if ($this->tzmap[$key]) {
			$m[2] = $this->tzmap[$key];
		}

		return $m[1] . $m[2] . $m[3];
	}

	/**
	 * Generate a Microsoft => Olson Timezone mapping table from an official source
	 */
	private function getTZMAP()
	{
		if (!isset($this->tzmap)) {
			$log = Logger::get('format.Exchange2010');
			$cache = new Cache(new FileCache(sys_get_temp_dir()));

			// read from cache
			$this->tzmap = $cache->get('windows-timezones');

			// fetch timezones map from source
			if (empty($this->tzmap)) {
				$this->tzmap = array();
				$zones_url = 'http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml';
				if ($xml = @file_get_contents($zones_url)) {
					try {
						$zonedata = new SimpleXMLElement($xml, LIBXML_NOWARNING | LIBXML_NOERROR);
						foreach ($zonedata->windowsZones[0]->mapTimezones[0]->mapZone as $map) {
							$other = strtolower(strval($map['other']));
							$region = strval($map['territory']);
							$words = explode(' ', $other);
							$olson = explode(' ', strval($map['type']));

							// skip invalid entries
							if (empty($other) || empty($olson))
								continue;

							// create an entry for all substrings
							for ($i = 1; $i <= count($words); $i++) {
								$last = $i == count($words);
								$key = join(' ', array_slice($words, 0, $i));
								if ($region == '001' || ($last && empty($this->tzmap[$key]))) {
									$this->tzmap[$key] = $olson[0];
								}
							}
						}

						// cache the mapping for one week
						$cache->set('windows-timezones', $this->tzmap, 7 * 86400);

						$log->addInfo("Updated Windows Timezones Map from source", array($zones_url));
					}
					catch (\Exception $e) {
						$log->addError("Failed parse Windows Timezones Map: " . $e->getMessage());
					}
				}
				else {
					$log->addError("Failed to load Windows Timezones Map from source", array($zones_url));
				}
			}
		}

		return $this->tzmap;
	}
}