Back to index

d-push  2.0
streamer.php
Go to the documentation of this file.
00001 <?php
00002 /***********************************************
00003 * File      :   streamer.php
00004 * Project   :   Z-Push
00005 * Descr     :   This file handles streaming of
00006 *               WBXML SyncObjects. It must be
00007 *               subclassed so the internals of
00008 *               the object can be specified via
00009 *               $mapping. Basically we set/read
00010 *               the object variables of the
00011 *               subclass according to the mappings
00012 *
00013 * Created   :   01.10.2007
00014 *
00015 * Copyright 2007 - 2011 Zarafa Deutschland GmbH
00016 *
00017 * This program is free software: you can redistribute it and/or modify
00018 * it under the terms of the GNU Affero General Public License, version 3,
00019 * as published by the Free Software Foundation with the following additional
00020 * term according to sec. 7:
00021 *
00022 * According to sec. 7 of the GNU Affero General Public License, version 3,
00023 * the terms of the AGPL are supplemented with the following terms:
00024 *
00025 * "Zarafa" is a registered trademark of Zarafa B.V.
00026 * "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
00027 * The licensing of the Program under the AGPL does not imply a trademark license.
00028 * Therefore any rights, title and interest in our trademarks remain entirely with us.
00029 *
00030 * However, if you propagate an unmodified version of the Program you are
00031 * allowed to use the term "Z-Push" to indicate that you distribute the Program.
00032 * Furthermore you may use our trademarks where it is necessary to indicate
00033 * the intended purpose of a product or service provided you use it in accordance
00034 * with honest practices in industrial or commercial matters.
00035 * If you want to propagate modified versions of the Program under the name "Z-Push",
00036 * you may only do so if you have a written permission by Zarafa Deutschland GmbH
00037 * (to acquire a permission please contact Zarafa at trademark@zarafa.com).
00038 *
00039 * This program is distributed in the hope that it will be useful,
00040 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00041 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00042 * GNU Affero General Public License for more details.
00043 *
00044 * You should have received a copy of the GNU Affero General Public License
00045 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00046 *
00047 * Consult LICENSE file for details
00048 ************************************************/
00049 
00050 class Streamer implements Serializable {
00051     const STREAMER_VAR = 1;
00052     const STREAMER_ARRAY = 2;
00053     const STREAMER_TYPE = 3;
00054     const STREAMER_PROP = 4;
00055     const STREAMER_TYPE_DATE = 1;
00056     const STREAMER_TYPE_HEX = 2;
00057     const STREAMER_TYPE_DATE_DASHES = 3;
00058     const STREAMER_TYPE_STREAM = 4;
00059     const STREAMER_TYPE_IGNORE = 5;
00060     const STREAMER_TYPE_SEND_EMPTY = 6;
00061     const STREAMER_TYPE_NO_CONTAINER = 7;
00062     const STREAMER_TYPE_COMMA_SEPARATED = 8;
00063     const STREAMER_TYPE_SEMICOLON_SEPARATED = 9;
00064 
00065     protected $mapping;
00066     public $flags;
00067     public $content;
00068 
00075     function Streamer($mapping) {
00076         $this->mapping = $mapping;
00077         $this->flags = false;
00078     }
00079 
00089     public function Decode(&$decoder) {
00090         while(1) {
00091             $entity = $decoder->getElement();
00092 
00093             if($entity[EN_TYPE] == EN_TYPE_STARTTAG) {
00094                 if(! ($entity[EN_FLAGS] & EN_FLAGS_CONTENT)) {
00095                     $map = $this->mapping[$entity[EN_TAG]];
00096                     if (isset($map[self::STREAMER_ARRAY])) {
00097                         $this->$map[self::STREAMER_VAR] = array();
00098                     } else if(!isset($map[self::STREAMER_TYPE])) {
00099                         $this->$map[self::STREAMER_VAR] = "";
00100                     }
00101                     else if ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES ) {
00102                         $this->$map[self::STREAMER_VAR] = "";
00103                     }
00104                     else if ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEND_EMPTY)
00105                         $this->$map[self::STREAMER_VAR] = "";
00106                     else if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY)
00107                         $this->$map[self::STREAMER_VAR] = "";
00108                     continue;
00109                 }
00110                 // Found a start tag
00111                 if(!isset($this->mapping[$entity[EN_TAG]])) {
00112                     // This tag shouldn't be here, abort
00113                     ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Tag '%s' unexpected in type XML type '%s'", $entity[EN_TAG], get_class($this)));
00114                     return false;
00115                 }
00116                 else {
00117                     $map = $this->mapping[$entity[EN_TAG]];
00118 
00119                     // Handle an array
00120                     if(isset($map[self::STREAMER_ARRAY])) {
00121                         while(1) {
00122                             //do not get start tag for an array without a container
00123                             if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) {
00124                                 if(!$decoder->getElementStartTag($map[self::STREAMER_ARRAY]))
00125                                     break;
00126                             }
00127                             if(isset($map[self::STREAMER_TYPE])) {
00128                                 $decoded = new $map[self::STREAMER_TYPE];
00129 
00130                                 $decoded->Decode($decoder);
00131                             }
00132                             else {
00133                                 $decoded = $decoder->getElementContent();
00134                             }
00135 
00136                             if(!isset($this->$map[self::STREAMER_VAR]))
00137                                 $this->$map[self::STREAMER_VAR] = array($decoded);
00138                             else
00139                                 array_push($this->$map[self::STREAMER_VAR], $decoded);
00140 
00141                             if(!$decoder->getElementEndTag()) //end tag of a container element
00142                                 return false;
00143 
00144                             if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER) {
00145                                 $e = $decoder->peek();
00146                                 //go back to the initial while if another block of no container elements is found
00147                                 if ($e[EN_TYPE] == EN_TYPE_STARTTAG) {
00148                                     continue 2;
00149                                 }
00150                                 //break on end tag because no container elements block end is reached
00151                                 if ($e[EN_TYPE] == EN_TYPE_ENDTAG)
00152                                     break;
00153                                 if (empty($e))
00154                                     break;
00155                             }
00156                         }
00157                         //do not get end tag for an array without a container
00158                         if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) {
00159                             if(!$decoder->getElementEndTag()) //end tag of container
00160                                 return false;
00161                         }
00162                     }
00163                     else { // Handle single value
00164                         if(isset($map[self::STREAMER_TYPE])) {
00165                             // Complex type, decode recursively
00166                             if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) {
00167                                 $decoded = $this->parseDate($decoder->getElementContent());
00168                                 if(!$decoder->getElementEndTag())
00169                                     return false;
00170                             }
00171                             else if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) {
00172                                 $decoded = hex2bin($decoder->getElementContent());
00173                                 if(!$decoder->getElementEndTag())
00174                                     return false;
00175                             }
00176                             else if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEND_EMPTY) {
00177                                 $decoded = $decoder->getElementContent();
00178                                 if(!$decoder->getElementEndTag())
00179                                     return false;
00180                             }
00181                             else {
00182                                 $subdecoder = new $map[self::STREAMER_TYPE]();
00183                                 if($subdecoder->Decode($decoder) === false)
00184                                     return false;
00185 
00186                                 $decoded = $subdecoder;
00187 
00188                                 if(!$decoder->getElementEndTag()) {
00189                                     ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("No end tag for '%s'", $entity[EN_TAG]));
00190                                     return false;
00191                                 }
00192                             }
00193                         }
00194                         else {
00195                             // Simple type, just get content
00196                             $decoded = $decoder->getElementContent();
00197 
00198                             if($decoded === false) {
00199                                 // the tag is declared to have content, but no content is available.
00200                                 // set an empty content
00201                                 $decoded = "";
00202                             }
00203 
00204                             if(!$decoder->getElementEndTag()) {
00205                                 ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Unable to get end tag for '%s'", $entity[EN_TAG]));
00206                                 return false;
00207                             }
00208                         }
00209                         // $decoded now contains data object (or string)
00210                         $this->$map[self::STREAMER_VAR] = $decoded;
00211                     }
00212                 }
00213             }
00214             else if($entity[EN_TYPE] == EN_TYPE_ENDTAG) {
00215                 $decoder->ungetElement($entity);
00216                 break;
00217             }
00218             else {
00219                 ZLog::Write(LOGLEVEL_WBXMLSTACK, "Unexpected content in type");
00220                 break;
00221             }
00222         }
00223     }
00224 
00232     public function Encode(&$encoder) {
00233         foreach($this->mapping as $tag => $map) {
00234             if(isset($this->$map[self::STREAMER_VAR])) {
00235                 // Variable is available
00236                 if(is_object($this->$map[self::STREAMER_VAR])) {
00237                     // Subobjects can do their own encoding
00238                     if ($this->$map[self::STREAMER_VAR] instanceof Streamer) {
00239                         $encoder->startTag($tag);
00240                         $this->$map[self::STREAMER_VAR]->Encode($encoder);
00241                         $encoder->endTag();
00242                     }
00243                     else
00244                         ZLog::Write(LOGLEVEL_ERROR, sprintf("Streamer->Encode(): parameter '%s' of object %s is not of type Streamer", $map[self::STREAMER_VAR], get_class($this)));
00245                 }
00246                 // Array of objects
00247                 else if(isset($map[self::STREAMER_ARRAY])) {
00248                     if ((empty($this->$map[self::STREAMER_VAR]) && isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEND_EMPTY)
00249                     || (isset($map[self::STREAMER_PROP]) &&  isset($map[self::STREAMER_PROP]) == self::STREAMER_TYPE_SEND_EMPTY)) {
00250                         $encoder->startTag($tag, false, true);
00251                     }
00252                     else {
00253                         // Outputs array container (eg Attachments)
00254                         // Do not output start and end tag when type is STREAMER_TYPE_NO_CONTAINER
00255                         if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER)
00256                             $encoder->startTag($tag);
00257 
00258                         foreach ($this->$map[self::STREAMER_VAR] as $element) {
00259                             if(is_object($element)) {
00260                                 $encoder->startTag($map[self::STREAMER_ARRAY]); // Outputs object container (eg Attachment)
00261                                 $element->Encode($encoder);
00262                                 $encoder->endTag();
00263                             }
00264                             else {
00265                                 if(strlen($element) == 0)
00266                                       // Do not output empty items. Not sure if we should output an empty tag with $encoder->startTag($map[self::STREAMER_ARRAY], false, true);
00267                                       ;
00268                                 else {
00269                                     $encoder->startTag($map[self::STREAMER_ARRAY]);
00270                                     $encoder->content($element);
00271                                     $encoder->endTag();
00272                                 }
00273                             }
00274                         }
00275 
00276                         if  (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER)
00277                             $encoder->endTag();
00278                     }
00279                 }
00280                 else {
00281                     if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_IGNORE) {
00282                         continue;
00283                     }
00284 
00285                     // Simple type
00286                     if(!isset($map[self::STREAMER_TYPE]) && strlen($this->$map[self::STREAMER_VAR]) == 0) {
00287                         if ((isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEND_EMPTY)
00288                         || (isset($map[self::STREAMER_PROP]) &&  isset($map[self::STREAMER_PROP]) == self::STREAMER_TYPE_SEND_EMPTY)) {
00289                             $encoder->startTag($tag, false, true);
00290                         }
00291                           // Do not output empty items. See above: $encoder->startTag($tag, false, true);
00292                         continue;
00293                     } else
00294                         $encoder->startTag($tag);
00295 
00296                     if(isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES)) {
00297                         if($this->$map[self::STREAMER_VAR] != 0) // don't output 1-1-1970
00298                             $encoder->content($this->formatDate($this->$map[self::STREAMER_VAR], $map[self::STREAMER_TYPE]));
00299                     }
00300                     else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) {
00301                         $encoder->content(strtoupper(bin2hex($this->$map[self::STREAMER_VAR])));
00302                     }
00303                     else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM) {
00304                         //encode stream with base64
00305                         //TODO stream chunked without loading the complete attachment into memory
00306                         $stream = $this->$map[self::STREAMER_VAR];
00307                         $base64filter = stream_filter_append($stream, 'convert.base64-encode');
00308                         $d = "";
00309                         while (!feof($stream)) {
00310                             $d .= fgets($stream, 4096);
00311                         }
00312                         $encoder->content($d);
00313                         stream_filter_remove($base64filter);
00314                     }
00315                     // implode comma or semicolon arrays into a string
00316                     else if(isset($map[self::STREAMER_TYPE]) && is_array($this->$map[self::STREAMER_VAR]) &&
00317                         ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED)) {
00318                         $glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED)?", ":"; ";
00319                         $encoder->content(implode($glue, $this->$map[self::STREAMER_VAR]));
00320                     }
00321                     else {
00322                         $encoder->content($this->$map[self::STREAMER_VAR]);
00323                     }
00324                     $encoder->endTag();
00325                 }
00326             }
00327         }
00328         // Output our own content
00329         if(isset($this->content))
00330             $encoder->content($this->content);
00331     }
00332 
00339     public function StripData() {
00340         foreach ($this->mapping as $k=>$v) {
00341             if (isset($this->$v[self::STREAMER_VAR])) {
00342                 if (is_object($this->$v[self::STREAMER_VAR]) && method_exists($this->$v[self::STREAMER_VAR], "StripData") ) {
00343                     $this->$v[self::STREAMER_VAR]->StripData();
00344                 }
00345                 else if (isset($v[self::STREAMER_ARRAY]) && !empty($this->$v[self::STREAMER_VAR])) {
00346                     foreach ($this->$v[self::STREAMER_VAR] as $element) {
00347                         if (is_object($element) && method_exists($element, "StripData") ) {
00348                             $element->StripData();
00349                         }
00350                     }
00351                 }
00352             }
00353         }
00354         unset($this->mapping);
00355 
00356         return true;
00357     }
00358 
00365     public function serialize() {
00366         $values = array();
00367         foreach ($this->mapping as $k=>$v) {
00368             if (isset($this->$v[self::STREAMER_VAR]))
00369                 $values[$v[self::STREAMER_VAR]] = serialize($this->$v[self::STREAMER_VAR]);
00370         }
00371 
00372         return serialize($values);
00373     }
00374 
00381     public function unserialize($data) {
00382         $class = get_class($this);
00383         $this->$class();
00384         $values = unserialize($data);
00385         foreach ($values as $k=>$v)
00386             $this->$k = unserialize($v);
00387 
00388         return true;
00389     }
00390 
00408     private function formatDate($ts, $type) {
00409         if($type == self::STREAMER_TYPE_DATE)
00410             return gmstrftime("%Y%m%dT%H%M%SZ", $ts);
00411         else if($type == self::STREAMER_TYPE_DATE_DASHES)
00412             return gmstrftime("%Y-%m-%dT%H:%M:%S.000Z", $ts);
00413     }
00414 
00423     function parseDate($ts) {
00424         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)) {
00425             if ($matches[1] >= 2038){
00426                 $matches[1] = 2038;
00427                 $matches[2] = 1;
00428                 $matches[3] = 18;
00429                 $matches[5] = $matches[6] = $matches[7] = 0;
00430             }
00431 
00432             if (!isset($matches[5])) $matches[5] = 0;
00433             if (!isset($matches[6])) $matches[6] = 0;
00434             if (!isset($matches[7])) $matches[7] = 0;
00435 
00436             return gmmktime($matches[5], $matches[6], $matches[7], $matches[2], $matches[3], $matches[1]);
00437         }
00438         return 0;
00439     }
00440 }
00441 
00442 ?>