1: <?php
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: class Horde_ActiveSync_Message_Base
32: {
33:
34:
35: const KEY_ATTRIBUTE = 1;
36: const KEY_VALUES = 2;
37: const KEY_TYPE = 3;
38:
39:
40: const TYPE_DATE = 1;
41: const TYPE_HEX = 2;
42: const TYPE_DATE_DASHES = 3;
43: const TYPE_MAPI_STREAM = 4;
44:
45: 46: 47: 48: 49:
50: protected $_mapping;
51:
52: 53: 54: 55: 56:
57: protected $_properties = array();
58:
59: 60: 61: 62: 63:
64: public $flags = false;
65:
66: 67: 68: 69: 70:
71: protected $_logger;
72:
73: 74: 75: 76: 77:
78: protected $_supported = array();
79:
80: 81: 82: 83: 84:
85: protected $_exists = array();
86:
87: 88: 89: 90: 91: 92: 93:
94: public function __construct(array $options = array())
95: {
96: if (!empty($options['logger'])) {
97: $this->_logger = $options['logger'];
98: } else {
99: $this->_logger = new Horde_Support_Stub();
100: }
101: }
102:
103: 104: 105: 106: 107: 108: 109:
110: public function __get($property)
111: {
112: if (!array_key_exists($property, $this->_properties)) {
113: $this->_logger->err('Unknown property: ' . $property);
114: throw new InvalidArgumentException('Unknown property: ' . $property);
115: }
116:
117: if (!empty($this->_properties[$property])) {
118: return $this->_properties[$property];
119: } elseif ($this->_properties[$property] === 0) {
120: return 0;
121: } else {
122: return '';
123: }
124: }
125:
126: 127: 128: 129: 130: 131: 132: 133:
134: public function __set($property, $value)
135: {
136: if (!array_key_exists($property, $this->_properties)) {
137: $this->_logger->err('Unknown property: ' . $property);
138: throw new InvalidArgumentException('Unknown property: ' . $property);
139: }
140: $this->_properties[$property] = $value;
141: $this->_exists[$property] = true;
142: }
143:
144: 145: 146: 147: 148: 149: 150: 151: 152:
153: public function __call($method, $arg)
154: {
155:
156: if (strpos($method, 'set') === 0) {
157: $property = Horde_String::lower(substr($method, 3));
158: $this->_properties[$property] = $arg;
159: } elseif (strpos($method, 'get') === 0) {
160: return $this->_getAttribute(Horde_String::lower(substr($method, 3)));
161: }
162:
163: throw new BadMethodCallException('Unknown method: ' . $method . ' in class: ' . __CLASS__);
164: }
165:
166: public function __isset($property)
167: {
168: return isset($this->_properties[$property]);
169: }
170:
171: 172: 173: 174: 175:
176: public function setSupported(array $fields)
177: {
178: $this->_supported = array();
179: foreach ($fields as $field) {
180: $this->_supported[] = $this->_mapping[$field][self::KEY_ATTRIBUTE];
181: }
182: }
183:
184: 185: 186: 187: 188:
189: public function getSupported()
190: {
191: return $this->_supported;
192: }
193:
194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205:
206: public function isGhosted($property)
207: {
208: if (array_search($property, $this->_supported) === false) {
209: return false;
210: } elseif (empty($this->_exists[$property])) {
211: return true;
212: }
213:
214: return false;
215: }
216:
217: 218: 219: 220: 221: 222: 223: 224: 225: 226:
227: public function decodeStream(Horde_ActiveSync_Wbxml_Decoder &$decoder)
228: {
229: while (1) {
230: $entity = $decoder->getElement();
231:
232: if ($entity[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) {
233: if (! ($entity[Horde_ActiveSync_Wbxml::EN_FLAGS] & Horde_ActiveSync_Wbxml::EN_FLAGS_CONTENT)) {
234: $map = $this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]];
235: if (!isset($map[self::KEY_TYPE])) {
236: $this->$map[self::KEY_ATTRIBUTE] = '';
237: } elseif ($map[self::KEY_TYPE] == self::TYPE_DATE || $map[self::KEY_TYPE] == self::TYPE_DATE_DASHES ) {
238: $this->$map[self::KEY_ATTRIBUTE] = '';
239: }
240: continue;
241: }
242:
243:
244: if (!isset($this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]])) {
245: $this->_logger->debug('Tag ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG] . ' unexpected in type XML type ' . get_class($this));
246: throw new Horde_ActiveSync_Exception('Unexpected tag');
247: } else {
248: $map = $this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]];
249: if (isset($map[self::KEY_VALUES])) {
250:
251: while (1) {
252: if (!$decoder->getElementStartTag($map[self::KEY_VALUES])) {
253: break;
254: }
255: if (isset($map[self::KEY_TYPE])) {
256: $decoded = new $map[self::KEY_TYPE];
257: $decoded->decodeStream($decoder);
258: } else {
259: $decoded = $decoder->getElementContent();
260: }
261: if (!isset($this->$map[self::KEY_ATTRIBUTE])) {
262: $this->$map[self::KEY_ATTRIBUTE] = array($decoded);
263: } else {
264: array_push($this->$map[self::KEY_ATTRIBUTE], $decoded);
265: }
266: if (!$decoder->getElementEndTag()) {
267: throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag');
268: }
269: }
270:
271: if (!$decoder->getElementEndTag()) {
272: return false;
273: }
274: } else {
275:
276: if (isset($map[self::KEY_TYPE])) {
277:
278: if ($map[self::KEY_TYPE] == self::TYPE_DATE || $map[self::KEY_TYPE] == self::TYPE_DATE_DASHES) {
279: $decoded = self::_parseDate($decoder->getElementContent());
280: if (!$decoder->getElementEndTag()) {
281: throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag');
282: }
283: } elseif ($map[self::KEY_TYPE] == self::TYPE_HEX) {
284: $decoded = self::hex2bin($decoder->getElementContent());
285: if (!$decoder->getElementEndTag()) {
286: throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag');
287: }
288: } else {
289: $subdecoder = new $map[self::KEY_TYPE]();
290: if ($subdecoder->decodeStream($decoder) === false) {
291: throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag');
292: }
293: $decoded = $subdecoder;
294: if (!$decoder->getElementEndTag()) {
295: $this->_logger->err('No end tag for ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG]);
296: throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag');
297: }
298: }
299: } else {
300:
301: $decoded = $decoder->getElementContent();
302: if ($decoded === false) {
303: $this->_logger->err('Unable to get content for ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG]);
304: }
305: if (!$decoder->getElementEndTag()) {
306: $this->_loger->err('Unable to get end tag for ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG]);
307: throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag');
308: }
309: }
310:
311: $this->$map[self::KEY_ATTRIBUTE] = $decoded;
312: }
313: }
314:
315: } elseif ($entity[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) {
316: $decoder->_ungetElement($entity);
317: break;
318: } else {
319: $this->_logger->err('Unexpected content in type');
320: break;
321: }
322: }
323: }
324:
325: 326: 327: 328: 329: 330:
331: public function encodeStream(Horde_ActiveSync_Wbxml_Encoder &$encoder)
332: {
333: foreach ($this->_mapping as $tag => $map) {
334: if (isset($this->$map[self::KEY_ATTRIBUTE])) {
335:
336: if (is_object($this->$map[self::KEY_ATTRIBUTE]) && !($this->$map[self::KEY_ATTRIBUTE] instanceof Horde_Date)) {
337:
338: $encoder->startTag($tag);
339: $this->$map[self::KEY_ATTRIBUTE]->encodeStream($encoder);
340: $encoder->endTag();
341: } elseif (isset($map[self::KEY_VALUES]) && is_array($this->$map[self::KEY_ATTRIBUTE])) {
342:
343: $encoder->startTag($tag);
344: foreach ($this->$map[self::KEY_ATTRIBUTE] as $element) {
345: if (is_object($element)) {
346:
347: $encoder->startTag($map[self::KEY_VALUES]);
348: $element->encodeStream($encoder);
349: $encoder->endTag();
350: } else {
351:
352: if(strlen($element) > 0) {
353: $encoder->startTag($map[self::KEY_VALUES]);
354: $encoder->content($element);
355: $encoder->endTag();
356: }
357: }
358: }
359: $encoder->endTag();
360: } else {
361:
362: if (strlen($this->$map[self::KEY_ATTRIBUTE]) == 0) {
363:
364: if ($this->_checkSendEmpty($tag)) {
365: $encoder->startTag($tag, $this->$map[self::KEY_ATTRIBUTE], true);
366: } else {
367: continue;
368: }
369: } else {
370: $encoder->startTag($tag);
371: }
372: if (isset($map[self::KEY_TYPE]) && ($map[self::KEY_TYPE] == self::TYPE_DATE || $map[self::KEY_TYPE] == self::TYPE_DATE_DASHES)) {
373: if (!empty($this->$map[self::KEY_ATTRIBUTE])) {
374: $encoder->content(self::_formatDate($this->$map[self::KEY_ATTRIBUTE], $map[self::KEY_TYPE]));
375: }
376: } elseif (isset($map[self::KEY_TYPE]) && $map[self::KEY_TYPE] == self::TYPE_HEX) {
377: $encoder->content(Horde_String::upper(bin2hex($this->$map[self::KEY_ATTRIBUTE])));
378: } elseif (isset($map[self::KEY_TYPE]) && $map[self::KEY_TYPE] == self::TYPE_MAPI_STREAM) {
379: $encoder->content($this->$map[self::KEY_ATTRIBUTE]);
380: } else {
381: $encoder->content($this->$map[self::KEY_ATTRIBUTE]);
382: }
383: $encoder->endTag();
384: }
385: }
386: }
387: }
388:
389: 390: 391: 392: 393: 394: 395:
396: protected function _checkSendEmpty($tag)
397: {
398: return false;
399: }
400:
401: 402: 403: 404: 405: 406: 407: 408:
409: protected function _getAttribute($name, $default = null)
410: {
411: if (!empty($this->_properties[$name])) {
412: return $this->_properties[$name];
413: } else {
414: return $default;
415: }
416: }
417:
418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428:
429: static protected function _formatDate(Horde_Date $dt, $type)
430: {
431: if ($type == Horde_ActiveSync_Message_Base::TYPE_DATE) {
432: return $dt->setTimezone('UTC')->format('Ymd\THis\Z');
433: } elseif ($type == Horde_ActiveSync_Message_Base::TYPE_DATE_DASHES) {
434: return $dt->setTimezone('UTC')->format('Y-m-d\TH:i:s\.000\Z');
435: }
436: }
437:
438: 439: 440: 441: 442: 443: 444:
445: static protected function _parseDate($ts)
446: {
447: if (preg_match("/(\d{4})[^0-9]*(\d{2})[^0-9]*(\d{2})(T(\d{2})[^0-9]*(\d{2})[^0-9]*(\d{2})(.\d+)?Z){0,1}$/", $ts, $matches)) {
448: return new Horde_Date($ts);
449: }
450:
451: throw new Horde_ActiveSync_Exception('Invalid date format');
452: }
453:
454: 455: 456: 457: 458: 459: 460:
461: static private function hex2bin($data)
462: {
463: $len = strlen($data);
464: $newdata = "";
465:
466: for($i = 0;$i < $len;$i += 2)
467: {
468: $newdata .= pack("C", hexdec(substr($data, $i, 2)));
469: }
470: return $newdata;
471: }
472:
473: }