Overview

Packages

  • ActiveSync
  • None

Classes

  • Horde_ActiveSync
  • Horde_ActiveSync_Connector_Exporter
  • Horde_ActiveSync_Connector_Importer
  • Horde_ActiveSync_Driver_Base
  • Horde_ActiveSync_Exception
  • Horde_ActiveSync_Exception_InvalidRequest
  • Horde_ActiveSync_Exception_StateGone
  • Horde_ActiveSync_Message_Base
  • Horde_ActiveSync_Request_Base
  • Horde_ActiveSync_Request_FolderCreate
  • Horde_ActiveSync_Request_FolderSync
  • Horde_ActiveSync_Request_GetHierarchy
  • Horde_ActiveSync_Request_GetItemEstimate
  • Horde_ActiveSync_Request_MeetingResponse
  • Horde_ActiveSync_Request_MoveItems
  • Horde_ActiveSync_Request_Notify
  • Horde_ActiveSync_Request_Ping
  • Horde_ActiveSync_Request_Provision
  • Horde_ActiveSync_Request_Search
  • Horde_ActiveSync_Request_SendMail
  • Horde_ActiveSync_Request_SmartForward
  • Horde_ActiveSync_Request_SmartReply
  • Horde_ActiveSync_Request_Sync
  • Horde_ActiveSync_State_File
  • Horde_ActiveSync_Sync
  • Horde_ActiveSync_Wbxml
  • Horde_ActiveSync_Wbxml_Decoder
  • Horde_ActiveSync_Wbxml_Encoder
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Horde_ActiveSync_Message_* classes represent a single ActiveSync message
  4:  * such as a Contact or Appointment. Encoding/Decoding logic taken from the
  5:  * Z-Push library. Original file header and copyright notice appear below.
  6:  *
  7:  * Copyright 2010-2012 Horde LLC (http://www.horde.org/)
  8:  *
  9:  * @author Michael J. Rubinsky <mrubinsk@horde.org>
 10:  * @package ActiveSync
 11:  */
 12: 
 13: /***********************************************
 14: * File      :   streamer.php
 15: * Project   :   Z-Push
 16: * Descr     :   This file handles streaming of
 17: *                WBXML objects. It must be
 18: *                subclassed so the internals of
 19: *                the object can be specified via
 20: *                $mapping. Basically we set/read
 21: *                the object variables of the
 22: *                subclass according to the mappings
 23: *
 24: *
 25: * Created   :   01.10.2007
 26: *
 27: * © Zarafa Deutschland GmbH, www.zarafaserver.de
 28: * This file is distributed under GPL-2.0.
 29: * Consult COPYING file for details
 30: ************************************************/
 31: class Horde_ActiveSync_Message_Base
 32: {
 33: 
 34:     /* Attribute Keys */
 35:     const KEY_ATTRIBUTE    = 1;
 36:     const KEY_VALUES       = 2;
 37:     const KEY_TYPE         = 3;
 38: 
 39:     /* Types */
 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:      * Holds the mapping for object properties
 47:      *
 48:      * @var array
 49:      */
 50:     protected $_mapping;
 51: 
 52:     /**
 53:      * Holds property values
 54:      *
 55:      * @var array
 56:      */
 57:     protected $_properties = array();
 58: 
 59:     /**
 60:      * Message flags
 61:      *
 62:      * @var Horde_ActiveSync_FLAG_* constant
 63:      */
 64:     public $flags = false;
 65: 
 66:     /**
 67:      * Logger
 68:      *
 69:      * @var Horde_Log_Logger
 70:      */
 71:     protected $_logger;
 72: 
 73:     /**
 74:      * An array describing the non-ghosted elements this message supports.
 75:      *
 76:      * @var array
 77:      */
 78:     protected $_supported = array();
 79: 
 80:     /**
 81:      * Existance cache, used for working with ghosted properties.
 82:      *
 83:      * @var array
 84:      */
 85:     protected $_exists = array();
 86: 
 87:     /**
 88:      * Const'r
 89:      *
 90:      * @param array $options  Any addition options the message may require
 91:      *
 92:      * @return Horde_ActiveSync_Message_Base
 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:      * Accessor
105:      *
106:      * @param string $property  Property to get.
107:      *
108:      * @return mixed  The value of the requested property.
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:      * Setter
128:      *
129:      * @param string $property  The property to set.
130:      * @param mixed  $value     The value to set it to.
131:      *
132:      * @throws InvalidArgumentException
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:      * Magic caller method.
146:      *
147:      * @param  mixed $method  The method to call.
148:      * @param  array $arg    Method arguments.
149:      *
150:      * @return mixed
151:      * @throws BadMethodCallException
152:      */
153:     public function __call($method, $arg)
154:     {
155:         /* Support calling set{Property}() */
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:      * Set the list of non-ghosted fields for this message.
173:      *
174:      * @param array $fields  The array of fields.
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:      * Get the list of non-ghosted properties for this message.
186:      *
187:      * @return array  The array of non-ghosted properties
188:      */
189:     public function getSupported()
190:     {
191:         return $this->_supported;
192:     }
193: 
194:     /**
195:      * Determines if the property specified has been ghosted by the client.
196:      * A ghosted property 1) IS listed in the supported list and 2) NOT
197:      * present in the current message. If it's IN the supported list and NOT
198:      * in the current message, then it IS ghosted and the server should keep
199:      * the field's current value when performing any change action due to this
200:      * message.
201:      *
202:      * @param string $property  The property to check
203:      *
204:      * @return boolean
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:      * Recursively decodes the WBXML from input stream. This means that if this
219:      * message contains complex types (like Appointment.Recuurence for example)
220:      * the sub-objects are auto-instantiated and decoded as well. Places the
221:      * decoded objects in the local properties array.
222:      *
223:      * @param Horde_ActiveSync_Wbxml_Decoder  The stream decoder
224:      *
225:      * @throws Horde_ActiveSync_Exception
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:                 // Found start tag
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:                         // Handle arrays of attribute values
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:                         // Handle a simple attribute value
276:                         if (isset($map[self::KEY_TYPE])) {
277:                             // Complex type, decode recursively
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:                             // Simple type, just get content
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:                         // $decoded now contains data object (or string)
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:      * Encodes this object (and any sub-objects) as wbxml to the output stream.
327:      * Output is ordered according to $_mapping
328:      *
329:      * @param Horde_ActiveSync_Wbxml_Encoder $encoder  The wbxml stream encoder
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:                 // Variable is available
336:                 if (is_object($this->$map[self::KEY_ATTRIBUTE]) && !($this->$map[self::KEY_ATTRIBUTE] instanceof Horde_Date)) {
337:                     // Subobjects can do their own encoding
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:                     // Array of objects
343:                     $encoder->startTag($tag); // Outputs array container (eg Attachments)
344:                     foreach ($this->$map[self::KEY_ATTRIBUTE] as $element) {
345:                         if (is_object($element)) {
346:                             // Outputs object container (eg Attachment)
347:                             $encoder->startTag($map[self::KEY_VALUES]);
348:                             $element->encodeStream($encoder);
349:                             $encoder->endTag();
350:                         } else {
351:                             // Do not ever output empty items here
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:                     // Simple type
362:                     if (strlen($this->$map[self::KEY_ATTRIBUTE]) == 0) {
363:                           // Do not output empty items except for the following:
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])) { // don't output 1-1-1970
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:      * Checks to see if we should send an empty value.
391:      *
392:      * @param string $tag  The tag name
393:      *
394:      * @return boolean
395:      */
396:     protected function _checkSendEmpty($tag)
397:     {
398:         return false;
399:     }
400: 
401:     /**
402:      * Helper method to allow default values for unset properties.
403:      *
404:      * @param string $name     The property name
405:      * @param stting $default  The default value to return if $property is empty
406:      *
407:      * @return mixed
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:      * Oh yeah. This is beautiful. Exchange outputs date fields differently in
420:      * calendar items and emails. We could just always send one or the other,
421:      * but unfortunately nokia's 'Mail for exchange' depends on this quirk.
422:      * So we have to send a different date type depending on where it's used.
423:      *
424:      * @param Horde_Date $dt  The datetime to format (assumed to be in local tz)
425:      * @param integer $type   The type to format as (TYPE_DATE or TYPE_DATE_DASHES)
426:      *
427:      * @return string  The formatted date
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:      * Get a Horde_Date from a timestamp, ensuring it's in the correct format.
440:      *
441:      * @param string $ts  The timestamp
442:      *
443:      * @return Horde_Date  The Horde_Date
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:      * Function which converts a hex entryid to a binary entryid.
456:      *
457:      * @param string $data  The hexadecimal string
458:      *
459:      * @return string  The binary data
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: }
API documentation generated by ApiGen