source: trunk/zpush/lib/core/streamer.php @ 7589

Revision 7589, 21.3 KB checked in by douglas, 11 years ago (diff)

Ticket #3209 - Integrar módulo de sincronização Z-push ao Expresso

Line 
1<?php
2/***********************************************
3* File      :   streamer.php
4* Project   :   Z-Push
5* Descr     :   This file handles streaming of
6*               WBXML SyncObjects. It must be
7*               subclassed so the internals of
8*               the object can be specified via
9*               $mapping. Basically we set/read
10*               the object variables of the
11*               subclass according to the mappings
12*
13* Created   :   01.10.2007
14*
15* Copyright 2007 - 2012 Zarafa Deutschland GmbH
16*
17* This program is free software: you can redistribute it and/or modify
18* it under the terms of the GNU Affero General Public License, version 3,
19* as published by the Free Software Foundation with the following additional
20* term according to sec. 7:
21*
22* According to sec. 7 of the GNU Affero General Public License, version 3,
23* the terms of the AGPL are supplemented with the following terms:
24*
25* "Zarafa" is a registered trademark of Zarafa B.V.
26* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
27* The licensing of the Program under the AGPL does not imply a trademark license.
28* Therefore any rights, title and interest in our trademarks remain entirely with us.
29*
30* However, if you propagate an unmodified version of the Program you are
31* allowed to use the term "Z-Push" to indicate that you distribute the Program.
32* Furthermore you may use our trademarks where it is necessary to indicate
33* the intended purpose of a product or service provided you use it in accordance
34* with honest practices in industrial or commercial matters.
35* If you want to propagate modified versions of the Program under the name "Z-Push",
36* you may only do so if you have a written permission by Zarafa Deutschland GmbH
37* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
38*
39* This program is distributed in the hope that it will be useful,
40* but WITHOUT ANY WARRANTY; without even the implied warranty of
41* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
42* GNU Affero General Public License for more details.
43*
44* You should have received a copy of the GNU Affero General Public License
45* along with this program.  If not, see <http://www.gnu.org/licenses/>.
46*
47* Consult LICENSE file for details
48************************************************/
49
50class Streamer implements Serializable {
51    const STREAMER_VAR = 1;
52    const STREAMER_ARRAY = 2;
53    const STREAMER_TYPE = 3;
54    const STREAMER_PROP = 4;
55    const STREAMER_TYPE_DATE = 1;
56    const STREAMER_TYPE_HEX = 2;
57    const STREAMER_TYPE_DATE_DASHES = 3;
58    const STREAMER_TYPE_STREAM = 4;
59    const STREAMER_TYPE_IGNORE = 5;
60    const STREAMER_TYPE_SEND_EMPTY = 6;
61    const STREAMER_TYPE_NO_CONTAINER = 7;
62    const STREAMER_TYPE_COMMA_SEPARATED = 8;
63    const STREAMER_TYPE_SEMICOLON_SEPARATED = 9;
64    const STREAMER_TYPE_MULTIPART = 10;
65
66    protected $mapping;
67    public $flags;
68    public $content;
69
70    /**
71     * Constructor
72     *
73     * @param array     $mapping            internal mapping of variables
74     * @access public
75     */
76    function Streamer($mapping) {
77        $this->mapping = $mapping;
78        $this->flags = false;
79    }
80
81    /**
82     * Decodes the WBXML from a WBXMLdecoder until we reach the same depth level of WBXML.
83     * This means that if there are multiple objects at this level, then only the first is
84     * decoded SubOjects are auto-instantiated and decoded using the same functionality
85     *
86     * @param WBXMLDecoder  $decoder
87     *
88     * @access public
89     */
90    public function Decode(&$decoder) {
91        while(1) {
92            $entity = $decoder->getElement();
93
94            if($entity[EN_TYPE] == EN_TYPE_STARTTAG) {
95                if(! ($entity[EN_FLAGS] & EN_FLAGS_CONTENT)) {
96                    $map = $this->mapping[$entity[EN_TAG]];
97                    if (isset($map[self::STREAMER_ARRAY])) {
98                        $this->$map[self::STREAMER_VAR] = array();
99                    } else if(!isset($map[self::STREAMER_TYPE])) {
100                        $this->$map[self::STREAMER_VAR] = "";
101                    }
102                    else if ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES ) {
103                        $this->$map[self::STREAMER_VAR] = "";
104                    }
105                    else if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY)
106                        $this->$map[self::STREAMER_VAR] = "";
107                    continue;
108                }
109                // Found a start tag
110                if(!isset($this->mapping[$entity[EN_TAG]])) {
111                    // This tag shouldn't be here, abort
112                    ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Tag '%s' unexpected in type XML type '%s'", $entity[EN_TAG], get_class($this)));
113                    return false;
114                }
115                else {
116                    $map = $this->mapping[$entity[EN_TAG]];
117
118                    // Handle an array
119                    if(isset($map[self::STREAMER_ARRAY])) {
120                        while(1) {
121                            //do not get start tag for an array without a container
122                            if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) {
123                                if(!$decoder->getElementStartTag($map[self::STREAMER_ARRAY]))
124                                    break;
125                            }
126                            if(isset($map[self::STREAMER_TYPE])) {
127                                $decoded = new $map[self::STREAMER_TYPE];
128
129                                $decoded->Decode($decoder);
130                            }
131                            else {
132                                $decoded = $decoder->getElementContent();
133                            }
134
135                            if(!isset($this->$map[self::STREAMER_VAR]))
136                                $this->$map[self::STREAMER_VAR] = array($decoded);
137                            else
138                                array_push($this->$map[self::STREAMER_VAR], $decoded);
139
140                            if(!$decoder->getElementEndTag()) //end tag of a container element
141                                return false;
142
143                            if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER) {
144                                $e = $decoder->peek();
145                                //go back to the initial while if another block of no container elements is found
146                                if ($e[EN_TYPE] == EN_TYPE_STARTTAG) {
147                                    continue 2;
148                                }
149                                //break on end tag because no container elements block end is reached
150                                if ($e[EN_TYPE] == EN_TYPE_ENDTAG)
151                                    break;
152                                if (empty($e))
153                                    break;
154                            }
155                        }
156                        //do not get end tag for an array without a container
157                        if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) {
158                            if(!$decoder->getElementEndTag()) //end tag of container
159                                return false;
160                        }
161                    }
162                    else { // Handle single value
163                        if(isset($map[self::STREAMER_TYPE])) {
164                            // Complex type, decode recursively
165                            if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) {
166                                $decoded = $this->parseDate($decoder->getElementContent());
167                                if(!$decoder->getElementEndTag())
168                                    return false;
169                            }
170                            else if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) {
171                                $decoded = hex2bin($decoder->getElementContent());
172                                if(!$decoder->getElementEndTag())
173                                    return false;
174                            }
175                            // explode comma or semicolon strings into arrays
176                            else if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED) {
177                                $glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED)?", ":"; ";
178                                $decoded = explode($glue, $decoder->getElementContent());
179                                if(!$decoder->getElementEndTag())
180                                    return false;
181                            }
182                            else {
183                                $subdecoder = new $map[self::STREAMER_TYPE]();
184                                if($subdecoder->Decode($decoder) === false)
185                                    return false;
186
187                                $decoded = $subdecoder;
188
189                                if(!$decoder->getElementEndTag()) {
190                                    ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("No end tag for '%s'", $entity[EN_TAG]));
191                                    return false;
192                                }
193                            }
194                        }
195                        else {
196                            // Simple type, just get content
197                            $decoded = $decoder->getElementContent();
198
199                            if($decoded === false) {
200                                // the tag is declared to have content, but no content is available.
201                                // set an empty content
202                                $decoded = "";
203                            }
204
205                            if(!$decoder->getElementEndTag()) {
206                                ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Unable to get end tag for '%s'", $entity[EN_TAG]));
207                                return false;
208                            }
209                        }
210                        // $decoded now contains data object (or string)
211                        $this->$map[self::STREAMER_VAR] = $decoded;
212                    }
213                }
214            }
215            else if($entity[EN_TYPE] == EN_TYPE_ENDTAG) {
216                $decoder->ungetElement($entity);
217                break;
218            }
219            else {
220                ZLog::Write(LOGLEVEL_WBXMLSTACK, "Unexpected content in type");
221                break;
222            }
223        }
224    }
225
226    /**
227     * Encodes this object and any subobjects - output is ordered according to mapping
228     *
229     * @param WBXMLEncoder  $encoder
230     *
231     * @access public
232     */
233    public function Encode(&$encoder) {
234        // A return value if anything was streamed. We need for empty tags.
235        $streamed = false;
236        foreach($this->mapping as $tag => $map) {
237            if(isset($this->$map[self::STREAMER_VAR])) {
238                // Variable is available
239                if(is_object($this->$map[self::STREAMER_VAR])) {
240                    // Subobjects can do their own encoding
241                    if ($this->$map[self::STREAMER_VAR] instanceof Streamer) {
242                        $encoder->startTag($tag);
243                        $res = $this->$map[self::STREAMER_VAR]->Encode($encoder);
244                        $encoder->endTag();
245                        // nothing was streamed in previous encode but it should be streamed empty anyway
246                        if (!$res && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY)
247                            $encoder->startTag($tag, false, true);
248                    }
249                    else
250                        ZLog::Write(LOGLEVEL_ERROR, sprintf("Streamer->Encode(): parameter '%s' of object %s is not of type Streamer", $map[self::STREAMER_VAR], get_class($this)));
251                }
252                // Array of objects
253                else if(isset($map[self::STREAMER_ARRAY])) {
254                    if (empty($this->$map[self::STREAMER_VAR]) && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) {
255                        $encoder->startTag($tag, false, true);
256                    }
257                    else {
258                        // Outputs array container (eg Attachments)
259                        // Do not output start and end tag when type is STREAMER_TYPE_NO_CONTAINER
260                        if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER)
261                            $encoder->startTag($tag);
262
263                        foreach ($this->$map[self::STREAMER_VAR] as $element) {
264                            if(is_object($element)) {
265                                $encoder->startTag($map[self::STREAMER_ARRAY]); // Outputs object container (eg Attachment)
266                                $element->Encode($encoder);
267                                $encoder->endTag();
268                            }
269                            else {
270                                if(strlen($element) == 0)
271                                      // Do not output empty items. Not sure if we should output an empty tag with $encoder->startTag($map[self::STREAMER_ARRAY], false, true);
272                                      ;
273                                else {
274                                    $encoder->startTag($map[self::STREAMER_ARRAY]);
275                                    $encoder->content($element);
276                                    $encoder->endTag();
277                                    $streamed = true;
278                                }
279                            }
280                        }
281
282                        if  (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER)
283                            $encoder->endTag();
284                    }
285                }
286                else {
287                    if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_IGNORE) {
288                        continue;
289                    }
290
291                    if ($encoder->getMultipart() && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_MULTIPART) {
292                        $encoder->addBodypartStream($this->$map[self::STREAMER_VAR]);
293                        $encoder->startTag(SYNC_ITEMOPERATIONS_PART);
294                        $encoder->content(count($encoder->getBodypartsCount()));
295                        $encoder->endTag();
296                        continue;
297                    }
298
299                    // Simple type
300                    if(!isset($map[self::STREAMER_TYPE]) && strlen($this->$map[self::STREAMER_VAR]) == 0) {
301                        // send empty tags
302                        if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY)
303                            $encoder->startTag($tag, false, true);
304
305                        // Do not output empty items. See above: $encoder->startTag($tag, false, true);
306                        continue;
307                    } else
308                        $encoder->startTag($tag);
309
310                    if(isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES)) {
311                        if($this->$map[self::STREAMER_VAR] != 0) // don't output 1-1-1970
312                            $encoder->content($this->formatDate($this->$map[self::STREAMER_VAR], $map[self::STREAMER_TYPE]));
313                    }
314                    else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) {
315                        $encoder->content(strtoupper(bin2hex($this->$map[self::STREAMER_VAR])));
316                    }
317                    else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM) {
318                        //encode stream with base64
319                        $stream = $this->$map[self::STREAMER_VAR];
320                        $stat = fstat($stream);
321                        // the padding size muss be calculated for the entire stream,
322                        // the base64 filter seems to process 8192 byte chunks correctly itself
323                        $padding = (isset($stat['size']) && $stat['size'] > 8192) ? ($stat['size'] % 3) : 0;
324
325                        $paddingfilter = stream_filter_append($stream, 'padding.'.$padding);
326                        $base64filter = stream_filter_append($stream, 'convert.base64-encode');
327                        $d = "";
328                        while (!feof($stream)) {
329                            $d .= fgets($stream, 4096);
330                        }
331                        $encoder->content($d);
332                        stream_filter_remove($base64filter);
333                        stream_filter_remove($paddingfilter);
334                    }
335                    // implode comma or semicolon arrays into a string
336                    else if(isset($map[self::STREAMER_TYPE]) && is_array($this->$map[self::STREAMER_VAR]) &&
337                        ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED)) {
338                        $glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED)?", ":"; ";
339                        $encoder->content(implode($glue, $this->$map[self::STREAMER_VAR]));
340                    }
341                    else {
342                        $encoder->content($this->$map[self::STREAMER_VAR]);
343                    }
344                    $encoder->endTag();
345                    $streamed = true;
346                }
347            }
348        }
349        // Output our own content
350        if(isset($this->content))
351            $encoder->content($this->content);
352
353        return $streamed;
354    }
355
356    /**
357     * Removes not necessary data from the object
358     *
359     * @access public
360     * @return boolean
361     */
362    public function StripData() {
363        foreach ($this->mapping as $k=>$v) {
364            if (isset($this->$v[self::STREAMER_VAR])) {
365                if (is_object($this->$v[self::STREAMER_VAR]) && method_exists($this->$v[self::STREAMER_VAR], "StripData") ) {
366                    $this->$v[self::STREAMER_VAR]->StripData();
367                }
368                else if (isset($v[self::STREAMER_ARRAY]) && !empty($this->$v[self::STREAMER_VAR])) {
369                    foreach ($this->$v[self::STREAMER_VAR] as $element) {
370                        if (is_object($element) && method_exists($element, "StripData") ) {
371                            $element->StripData();
372                        }
373                    }
374                }
375            }
376        }
377        unset($this->mapping);
378
379        return true;
380    }
381
382    /**
383     * Method to serialize a Streamer and respective SyncObject
384     *
385     * @access public
386     * @return array
387     */
388    public function serialize() {
389        $values = array();
390        foreach ($this->mapping as $k=>$v) {
391            if (isset($this->$v[self::STREAMER_VAR]))
392                $values[$v[self::STREAMER_VAR]] = serialize($this->$v[self::STREAMER_VAR]);
393        }
394
395        return serialize($values);
396    }
397
398    /**
399     * Method to unserialize a Streamer and respective SyncObject
400     *
401     * @access public
402     * @return array
403     */
404    public function unserialize($data) {
405        $class = get_class($this);
406        $this->$class();
407        $values = unserialize($data);
408        foreach ($values as $k=>$v)
409            $this->$k = unserialize($v);
410
411        return true;
412    }
413
414    /**----------------------------------------------------------------------------------------------------------
415     * Private methods for conversion
416     */
417
418    /**
419     * Formats a timestamp
420     * Oh yeah, this is beautiful. Exchange outputs date fields differently in calendar items
421     * and emails. We could just always send one or the other, but unfortunately nokia's 'Mail for
422     *  exchange' depends on this quirk. So we have to send a different date type depending on where
423     * it's used. Sigh.
424     *
425     * @param long  $ts
426     * @param int   $type
427     *
428     * @access private
429     * @return string
430     */
431    private function formatDate($ts, $type) {
432        if($type == self::STREAMER_TYPE_DATE)
433            return gmstrftime("%Y%m%dT%H%M%SZ", $ts);
434        else if($type == self::STREAMER_TYPE_DATE_DASHES)
435            return gmstrftime("%Y-%m-%dT%H:%M:%S.000Z", $ts);
436    }
437
438    /**
439     * Transforms an AS timestamp into a unix timestamp
440     *
441     * @param string    $ts
442     *
443     * @access private
444     * @return long
445     */
446    function parseDate($ts) {
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            if ($matches[1] >= 2038){
449                $matches[1] = 2038;
450                $matches[2] = 1;
451                $matches[3] = 18;
452                $matches[5] = $matches[6] = $matches[7] = 0;
453            }
454
455            if (!isset($matches[5])) $matches[5] = 0;
456            if (!isset($matches[6])) $matches[6] = 0;
457            if (!isset($matches[7])) $matches[7] = 0;
458
459            return gmmktime($matches[5], $matches[6], $matches[7], $matches[2], $matches[3], $matches[1]);
460        }
461        return 0;
462    }
463}
464
465?>
Note: See TracBrowser for help on using the repository browser.