1<?php
2/**
3 * This class creates the actual XML data and passes it on to a ContentHandler
4 * for optional WBXML encoding.
5 *
6 * Each member public function creates one type of SyncML artefact (like a
7 * Status response).  Currently some of the information is retrieved from
8 * state. Maybe remove these dependencies (by providing the data as parameter)
9 * for an even cleaner implementation.
10 *
11 * The Horde_SyncMl_XmlOutput class takes automatically care of creating a
12 * unique CmdID for each command created.
13 *
14 * Copyright 2006-2016 Horde LLC (http://www.horde.org/)
15 *
16 * See the enclosed file COPYING for license information (LGPL). If you
17 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
18 *
19 * @author  Karsten Fourmont <karsten@horde.org>
20 * @package SyncMl
21 */
22class Horde_SyncMl_XmlOutput
23{
24    /**
25     * The CmdID provides a unique ID for each command in a syncml packet.
26     */
27    protected $_msg_CmdID;
28
29    /**
30     *  The outputhandler to whom the XML is passed: like
31     *  Horde_Xml_Wbxml_Encoder
32     */
33    public $output;
34
35    protected $_uri;
36
37    /**
38     * The final output as procuded by the _output Encoder. Either an
39     * XML string or a WBXML string.
40     */
41    public function getOutput()
42    {
43        return $this->output->getOutput();
44    }
45
46    /**
47     * The length of the output as produced by the Encoder. To limit the
48     * size of individual messages.
49     */
50    public function getOutputSize()
51    {
52        return $this->output->getOutputSize();
53    }
54
55    /**
56     * To we create wbxml or not?
57     */
58    public function isWBXML()
59    {
60        return is_a($this->output, 'Horde_Xml_Wbxml_Encoder');
61    }
62
63    public function &singleton()
64    {
65        static $instance;
66        if (!isset($instance)) {
67            $instance = new Horde_SyncMl_XmlOutput();
68        }
69        return $instance;
70    }
71
72    public function init(&$theoutputhandler)
73    {
74        $this->output = $theoutputhandler;
75        $this->_msg_CmdID = 1;
76
77    }
78
79    /**
80     * Creates a SyncHdr output.
81     *
82     * Required data is retrieved from state.
83     *
84     * @param string $respURI  The url of the server endpoint.
85     *
86     * @throws Horde_Xml_Wbxml_Exception
87     */
88    public function outputHeader($respURI)
89    {
90        $state = $GLOBALS['backend']->state;
91
92        $this->_uriMeta = $state->uriMeta;
93
94        $this->output->startElement($this->_uri, 'SyncHdr');
95
96        $this->output->startElement($this->_uri, 'VerDTD');
97        $chars = $state->getVerDTD();
98        $this->output->characters($chars);
99        $this->output->endElement($this->_uri, 'VerDTD');
100
101        $this->output->startElement($this->_uri, 'VerProto');
102        $chars = $state->getProtocolName();
103        $this->output->characters($chars);
104        $this->output->endElement($this->_uri, 'VerProto');
105
106        $this->output->startElement($this->_uri, 'SessionID');
107        $this->output->characters($state->sessionID);
108        $this->output->endElement($this->_uri, 'SessionID');
109
110        $this->output->startElement($this->_uri, 'MsgID');
111        $this->output->characters($state->messageID);
112        $this->output->endElement($this->_uri, 'MsgID');
113
114        $this->output->startElement($this->_uri, 'Target');
115        $this->output->startElement($this->_uri, 'LocURI');
116        // Source URI sent from client is Target for the server
117        $this->output->characters($state->sourceURI);
118        $this->output->endElement($this->_uri, 'LocURI');
119        if ($state->user) {
120            $this->output->startElement($this->_uri, 'LocName');
121            $this->output->characters($state->user);
122            $this->output->endElement($this->_uri, 'LocName');
123        }
124        $this->output->endElement($this->_uri, 'Target');
125
126        $this->output->startElement($this->_uri, 'Source');
127        $this->output->startElement($this->_uri, 'LocURI');
128        // Target URI sent from client is Source for the server
129        $this->output->characters($state->targetURI);
130        $this->output->endElement($this->_uri, 'LocURI');
131        $this->output->endElement($this->_uri, 'Source');
132
133        if ($respURI) {
134            $this->output->startElement($this->_uri, 'RespURI');
135            $this->output->characters($respURI);
136            $this->output->endElement($this->_uri, 'RespURI');
137        }
138
139        // @Todo: omit this in SyncML1.0?
140        $this->output->startElement($this->_uri, 'Meta');
141
142        // Dummy Max MsqSize, this is just put in to make the packet
143        // work, it is not a real value.
144        $this->output->startElement($this->_uriMeta, 'MaxMsgSize');
145        $chars = Horde_SyncMl::SERVER_MAXMSGSIZE; // 1Meg
146        $this->output->characters($chars);
147        $this->output->endElement($this->_uriMeta, 'MaxMsgSize');
148
149
150        // MaxObjSize, required by protocol for SyncML1.1 and higher.
151        if ($state->version > 0) {
152            $this->output->startElement($this->_uriMeta, 'MaxObjSize');
153            $this->output->characters(Horde_SyncMl::SERVER_MAXOBJSIZE);
154            $this->output->endElement($this->_uriMeta, 'MaxObjSize');
155        }
156        $this->output->endElement($this->_uri, 'Meta');
157
158        $this->output->endElement($this->_uri, 'SyncHdr');
159    }
160
161    public function outputInit()
162    {
163        $this->_uri = $GLOBALS['backend']->state->getURI();
164
165        $this->output->startElement($this->_uri, 'SyncML', array());
166    }
167
168    public function outputBodyStart()
169    {
170        $this->output->startElement($this->_uri, 'SyncBody', array());
171    }
172
173    public function outputFinal()
174    {
175        $this->output->startElement($this->_uri, 'Final', array());
176        $this->output->endElement($this->_uri, 'Final');
177    }
178
179    public function outputEnd()
180    {
181        $this->output->endElement($this->_uri, 'SyncBody', array());
182        $this->output->endElement($this->_uri, 'SyncML', array());
183    }
184
185
186    public function outputStatus($cmdRef, $cmd, $data,
187                         $targetRef = '', $sourceRef = '',
188                         $syncAnchorNext = '',
189                         $syncAnchorLast = '')
190    {
191        $state = $GLOBALS['backend']->state;
192        $uriMeta = $state->uriMeta;
193
194        $this->output->startElement($this->_uri, 'Status');
195        $this->_outputCmdID();
196
197        $this->output->startElement($this->_uri, 'MsgRef');
198        $chars = $state->messageID;
199        $this->output->characters($chars);
200        $this->output->endElement($this->_uri, 'MsgRef');
201
202        $this->output->startElement($this->_uri, 'CmdRef');
203        $chars = $cmdRef;
204        $this->output->characters($chars);
205        $this->output->endElement($this->_uri, 'CmdRef');
206
207        $this->output->startElement($this->_uri, 'Cmd');
208        $chars = $cmd;
209        $this->output->characters($chars);
210        $this->output->endElement($this->_uri, 'Cmd');
211
212        if (!empty($targetRef)) {
213            $this->output->startElement($this->_uri, 'TargetRef');
214            $this->output->characters($targetRef);
215            $this->output->endElement($this->_uri, 'TargetRef');
216        }
217
218        if (!empty($sourceRef)) {
219            $this->output->startElement($this->_uri, 'SourceRef');
220            $this->output->characters($sourceRef);
221            $this->output->endElement($this->_uri, 'SourceRef');
222        }
223
224        // If we are responding to the SyncHdr and we are not
225        // authenticated then request basic authorization.
226        if ($cmd == 'SyncHdr' && !$state->authenticated) {
227            // Keep Horde_SyncMl::RESPONSE_CREDENTIALS_MISSING, otherwise set to
228            // Horde_SyncMl::RESPONSE_INVALID_CREDENTIALS.
229            $data = $data == Horde_SyncMl::RESPONSE_CREDENTIALS_MISSING
230                ? Horde_SyncMl::RESPONSE_CREDENTIALS_MISSING
231                : Horde_SyncMl::RESPONSE_INVALID_CREDENTIALS;
232
233            $this->output->startElement($this->_uri, 'Chal');
234            $this->output->startElement($this->_uri, 'Meta');
235
236            $this->output->startElement($uriMeta, 'Type');
237            $this->output->characters('syncml:auth-basic');
238            $this->output->endElement($uriMeta, 'Type');
239
240            $this->output->startElement($uriMeta, 'Format');
241            $this->output->characters('b64');
242            $this->output->endElement($uriMeta, 'Format');
243
244            $this->output->endElement($this->_uri, 'Meta');
245            $this->output->endElement($this->_uri, 'Chal');
246
247        }
248
249        $this->output->startElement($this->_uri, 'Data');
250        $this->output->characters($data);
251        $this->output->endElement($this->_uri, 'Data');
252
253        if (!empty($syncAnchorNext) || !empty($syncAnchorNLast)) {
254            $this->output->startElement($this->_uri, 'Item');
255            $this->output->startElement($this->_uri, 'Data');
256
257            $this->output->startElement($uriMeta, 'Anchor');
258
259            if (!empty($syncAnchorLast)) {
260              $this->output->startElement($uriMeta, 'Last');
261              $this->output->characters($syncAnchorLast);
262              $this->output->endElement($uriMeta, 'Last');
263            }
264
265            if (!empty($syncAnchorNext)) {
266              $this->output->startElement($uriMeta, 'Next');
267              $this->output->characters($syncAnchorNext);
268              $this->output->endElement($uriMeta, 'Next');
269            }
270
271            $this->output->endElement($uriMeta, 'Anchor');
272
273            $this->output->endElement($this->_uri, 'Data');
274            $this->output->endElement($this->_uri, 'Item');
275        }
276
277        $this->output->endElement($this->_uri, 'Status');
278
279    }
280
281    public function outputDevInf($cmdRef)
282    {
283        $state = $GLOBALS['backend']->state;
284        $uriMeta = $state->uriMeta;
285        $uriDevInf = $state->uriDevInf;
286
287        $this->output->startElement($this->_uri, 'Results');
288        $this->_outputCmdID();
289
290        $this->output->startElement($this->_uri, 'MsgRef');
291        $chars = $state->messageID;
292        $this->output->characters($chars);
293        $this->output->endElement($this->_uri, 'MsgRef');
294
295        $this->output->startElement($this->_uri, 'CmdRef');
296        $chars = $cmdRef;
297        $this->output->characters($chars);
298        $this->output->endElement($this->_uri, 'CmdRef');
299
300        $this->output->startElement($this->_uri, 'Meta');
301        $this->output->startElement($uriMeta, 'Type');
302        if ($state->wbxml) {
303            $this->output->characters(Horde_SyncMl::MIME_SYNCML_DEVICE_INFO_WBXML);
304        } else {
305            $this->output->characters(Horde_SyncMl::MIME_SYNCML_DEVICE_INFO_XML);
306        }
307
308        $this->output->endElement($uriMeta, 'Type');
309        $this->output->endElement($this->_uri, 'Meta');
310
311        $this->output->startElement($this->_uri, 'Item');
312        $this->output->startElement($this->_uri, 'Source');
313        $this->output->startElement($this->_uri, 'LocURI');
314        $this->output->characters($state->getDevInfURI());
315        $this->output->endElement($this->_uri, 'LocURI');
316        $this->output->endElement($this->_uri, 'Source');
317
318        $this->output->startElement($this->_uri, 'Data');
319
320        /* DevInf data is stored in wbxml not as a seperate codepage but
321         * rather as a complete wbxml stream as opaque data.  So we need a
322         * new Handler. */
323        $devinfoutput = $this->output->createSubHandler();
324
325        $devinfoutput->startElement($uriDevInf , 'DevInf');
326        $devinfoutput->startElement($uriDevInf , 'VerDTD');
327        $devinfoutput->characters($state->getVerDTD());
328        $devinfoutput->endElement($uriDevInf , 'VerDTD');
329        $devinfoutput->startElement($uriDevInf , 'Man');
330        $devinfoutput->characters('The Horde Project (http://www.horde.org/)');
331        $devinfoutput->endElement($uriDevInf , 'Man');
332        $devinfoutput->startElement($uriDevInf , 'DevID');
333        $devinfoutput->characters(isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost');
334        $devinfoutput->endElement($uriDevInf , 'DevID');
335        $devinfoutput->startElement($uriDevInf , 'DevTyp');
336        $devinfoutput->characters('server');
337        $devinfoutput->endElement($uriDevInf , 'DevTyp');
338
339        if ($state->version > 0) {
340            $devinfoutput->startElement($uriDevInf , 'SupportLargeObjs');
341            $devinfoutput->endElement($uriDevInf , 'SupportLargeObjs');
342
343            $devinfoutput->startElement($uriDevInf , 'SupportNumberOfChanges');
344            $devinfoutput->endElement($uriDevInf , 'SupportNumberOfChanges');
345        }
346        $this->_writeDataStore('notes', 'text/plain', '1.0', $devinfoutput);
347        $this->_writeDataStore('contacts', 'text/directory', '3.0',
348                               $devinfoutput, array('text/x-vcard' => '2.1'));
349        $this->_writeDataStore('tasks', 'text/calendar', '2.0', $devinfoutput,
350                               array('text/x-vcalendar' => '1.0'));
351        $this->_writeDataStore('calendar', 'text/calendar', '2.0',
352                               $devinfoutput,
353                               array('text/x-vcalendar' => '1.0'));
354        $devinfoutput->endElement($uriDevInf , 'DevInf');
355
356        $this->output->opaque($devinfoutput->getOutput());
357        $this->output->endElement($this->_uri, 'Data');
358        $this->output->endElement($this->_uri, 'Item');
359        $this->output->endElement($this->_uri, 'Results');
360    }
361
362    /**
363     * Writes DevInf data for one DataStore.
364     *
365     * @param string $sourceref                 Data for <SourceRef> element.
366     * @param string $mimetype                  Data for <Rx-Pref><CTType> and
367     *                                          <Tx-Pref><CTType>.
368     * @param string $version                   Data for <Rx-Pref><VerCT> and
369     *                                          <Tx-Pref><VerCT>.
370     * @param Horde_Xml_Wbxml_ContentHandler $output  Content handler that will
371     *                                          received the output.
372     * @param array $additionaltypes            Array of additional types for
373     *                                          <Tx> and <Rx>; format
374     *                                          array('text/directory' => '3.0')
375     */
376    protected function _writeDataStore($sourceref, $mimetype, $version, &$output,
377                             $additionaltypes = array())
378    {
379        $uriDevInf = $GLOBALS['backend']->state->uriDevInf;
380
381        $output->startElement($uriDevInf , 'DataStore');
382        $output->startElement($uriDevInf , 'SourceRef');
383        $output->characters($sourceref);
384        $output->endElement($uriDevInf , 'SourceRef');
385
386        $output->startElement($uriDevInf , 'Rx-Pref');
387        $output->startElement($uriDevInf , 'CTType');
388        $output->characters($mimetype);
389        $output->endElement($uriDevInf , 'CTType');
390        $output->startElement($uriDevInf , 'VerCT');
391        $output->characters($version);
392        $output->endElement($uriDevInf , 'VerCT');
393        $output->endElement($uriDevInf , 'Rx-Pref');
394
395        foreach ($additionaltypes as $ct => $ctver){
396            $output->startElement($uriDevInf , 'Rx');
397            $output->startElement($uriDevInf , 'CTType');
398            $output->characters($ct);
399            $output->endElement($uriDevInf , 'CTType');
400            $output->startElement($uriDevInf , 'VerCT');
401            $output->characters($ctver);
402            $output->endElement($uriDevInf , 'VerCT');
403            $output->endElement($uriDevInf , 'Rx');
404        }
405
406        $output->startElement($uriDevInf , 'Tx-Pref');
407        $output->startElement($uriDevInf , 'CTType');
408        $output->characters($mimetype);
409        $output->endElement($uriDevInf , 'CTType');
410        $output->startElement($uriDevInf , 'VerCT');
411        $output->characters($version);
412        $output->endElement($uriDevInf , 'VerCT');
413        $output->endElement($uriDevInf , 'Tx-Pref');
414
415        foreach ($additionaltypes as $ct => $ctver){
416            $output->startElement($uriDevInf , 'Tx');
417            $output->startElement($uriDevInf , 'CTType');
418            $output->characters($ct);
419            $output->endElement($uriDevInf , 'CTType');
420            $output->startElement($uriDevInf , 'VerCT');
421            $output->characters($ctver);
422            $output->endElement($uriDevInf , 'VerCT');
423            $output->endElement($uriDevInf , 'Tx');
424        }
425
426        $output->startElement($uriDevInf , 'SyncCap');
427        // We support all sync Types from 1-6: two way, slow, refresh|update
428        // from client|server
429        for ($i = 1; $i <= 6; ++$i) {
430            $output->startElement($uriDevInf , 'SyncType');
431            $output->characters($i);
432            $output->endElement($uriDevInf , 'SyncType');
433        }
434        $output->endElement($uriDevInf , 'SyncCap');
435        $output->endElement($uriDevInf , 'DataStore');
436    }
437
438    public function outputAlert($alertCode, $clientDB = '', $serverDB = '', $lastAnchor = '', $nextAnchor = '')
439    {
440        $uriMeta = $GLOBALS['backend']->state->uriMeta;
441
442        $this->output->startElement($this->_uri, 'Alert');
443        $this->_outputCmdID();
444
445        $this->output->startElement($this->_uri, 'Data');
446        $chars = $alertCode;
447        $this->output->characters($chars);
448        $this->output->endElement($this->_uri, 'Data');
449
450        $this->output->startElement($this->_uri, 'Item');
451
452        if (!empty($clientDB)) {
453            $this->output->startElement($this->_uri, 'Target');
454            $this->output->startElement($this->_uri, 'LocURI');
455            $this->output->characters($clientDB);
456            $this->output->endElement($this->_uri, 'LocURI');
457            $this->output->endElement($this->_uri, 'Target');
458        }
459
460        if (!empty($serverDB)) {
461            $this->output->startElement($this->_uri, 'Source');
462            $this->output->startElement($this->_uri, 'LocURI');
463            $this->output->characters($serverDB);
464            $this->output->endElement($this->_uri, 'LocURI');
465            $this->output->endElement($this->_uri, 'Source');
466        }
467
468        $this->output->startElement($this->_uri, 'Meta');
469
470        $this->output->startElement($uriMeta, 'Anchor');
471
472        $this->output->startElement($uriMeta, 'Last');
473        $this->output->characters($lastAnchor);
474        $this->output->endElement($uriMeta, 'Last');
475
476        $this->output->startElement($uriMeta, 'Next');
477        $this->output->characters($nextAnchor);
478        $this->output->endElement($uriMeta, 'Next');
479
480        $this->output->endElement($uriMeta, 'Anchor');
481
482
483        // MaxObjSize, required by protocol for SyncML1.1 and higher.
484        if ($GLOBALS['backend']->state->version > 0) {
485            $this->output->startElement($uriMeta, 'MaxObjSize');
486            $this->output->characters(Horde_SyncMl::SERVER_MAXOBJSIZE);
487            $this->output->endElement($uriMeta, 'MaxObjSize');
488        }
489        $this->output->endElement($this->_uri, 'Meta');
490
491                $this->output->endElement($this->_uri, 'Item');
492        $this->output->endElement($this->_uri, 'Alert');
493
494    }
495
496
497    public function outputGetDevInf()
498    {
499        $state = $GLOBALS['backend']->state;
500        $uriMeta = $state->uriMeta;
501
502        $this->output->startElement($this->_uri, 'Get');
503        $this->_outputCmdID();
504
505        $this->output->startElement($this->_uri, 'Meta');
506        $this->output->startElement($uriMeta, 'Type');
507        if ($state->wbxml) {
508            $chars = Horde_SyncMl::MIME_SYNCML_DEVICE_INFO_WBXML;
509        } else {
510            $chars = Horde_SyncMl::MIME_SYNCML_DEVICE_INFO_XML;
511        }
512        $this->output->characters($chars);
513        $this->output->endElement($uriMeta, 'Type');
514        $this->output->endElement($this->_uri, 'Meta');
515
516        $this->output->startElement($this->_uri, 'Item');
517        $this->output->startElement($this->_uri, 'Target');
518        $this->output->startElement($this->_uri, 'LocURI');
519        $this->output->characters($state->getDevInfURI());
520        $this->output->endElement($this->_uri, 'LocURI');
521        $this->output->endElement($this->_uri, 'Target');
522        $this->output->endElement($this->_uri, 'Item');
523
524        $this->output->endElement($this->_uri, 'Get');
525    }
526
527    /**
528     * Creates a single Sync command
529     *
530     * @param string $command       The Sync command (Add, Delete, Replace).
531     * @param string $content       The actual object content.
532     * @param string $contentType   The content's MIME type.
533     * @param string $encodingType  The content encoding of the object.
534     * @param string $cuid          The client's object UID.
535     * @param string $suid          The server's object UID.
536     * @param int $contentSize      The total content size.
537     * @param bool $moreData             Add MoreData tag when splitting messages.
538     *
539     * @return integer  The CmdID used for this command.
540     */
541    public function outputSyncCommand($command, $content = null, $contentType = null,
542                               $encodingType = null, $cuid = null, $suid = null,
543                               $contentSize = null, $moreData = false)
544    {
545        $uriMeta = $GLOBALS['backend']->state->uriMeta;
546
547        $this->output->startElement($this->_uri, $command);
548        $this->_outputCmdID();
549
550        if (isset($contentType)) {
551            $this->output->startElement($this->_uri, 'Meta');
552            $this->output->startElement($uriMeta, 'Type');
553            $this->output->characters($contentType);
554            $this->output->endElement($uriMeta, 'Type');
555            if (isset($content) && isset($contentSize)) {
556                $this->output->startElement($uriMeta, 'Size');
557                $this->output->characters($contentSize);
558                $this->output->endElement($uriMeta, 'Size');
559            }
560            $this->output->endElement($this->_uri, 'Meta');
561        }
562
563        if (isset($content) || isset($cuid) || isset($suid)) {
564            $this->output->startElement($this->_uri, 'Item');
565            if ($suid != null) {
566                $this->output->startElement($this->_uri, 'Source');
567                $this->output->startElement($this->_uri, 'LocURI');
568                $this->output->characters($suid);
569                $this->output->endElement($this->_uri, 'LocURI');
570                $this->output->endElement($this->_uri, 'Source');
571            }
572
573            if ($cuid != null) {
574                $this->output->startElement($this->_uri, 'Target');
575                $this->output->startElement($this->_uri, 'LocURI');
576                $this->output->characters($cuid);
577                $this->output->endElement($this->_uri, 'LocURI');
578                $this->output->endElement($this->_uri, 'Target');
579            }
580
581            if (!empty($encodingType)) {
582                $this->output->startElement($this->_uri, 'Meta');
583                $this->output->startElement($uriMeta, 'Format');
584                $this->output->characters($encodingType);
585                $this->output->endElement($uriMeta, 'Format');
586                $this->output->endElement($this->_uri, 'Meta');
587            }
588            if (isset($content)) {
589                $this->output->startElement($this->_uri, 'Data');
590                if($this->isWBXML()) {
591                    $this->output->characters($content);
592                } else {
593                    $device = $GLOBALS['backend']->state->getDevice();
594                    if ($device->useCdataTag()) {
595                        /* Enclose data in CDATA if possible to avoid */
596                        /* problems with &,< and >. */
597                        $this->output->opaque('<![CDATA[' . $content . ']]>');
598                    } else {
599                        $this->output->characters($content);
600                    }
601                }
602                $this->output->endElement($this->_uri, 'Data');
603                if ($moreData) {
604                    $this->output->startElement($this->_uri, 'MoreData');
605                    $this->output->endElement($this->_uri, 'MoreData');
606                }
607            }
608            $this->output->endElement($this->_uri, 'Item');
609        }
610
611        $this->output->endElement($this->_uri, $command);
612
613        return $this->_msg_CmdID - 1;
614    }
615
616    public function outputSyncStart($clientLocURI, $serverLocURI, $numberOfChanges = null)
617    {
618        $this->output->startElement($this->_uri, 'Sync');
619        $this->_outputCmdID();
620
621        $this->output->startElement($this->_uri, 'Target');
622        $this->output->startElement($this->_uri, 'LocURI');
623        $this->output->characters($clientLocURI);
624        $this->output->endElement($this->_uri, 'LocURI');
625        $this->output->endElement($this->_uri, 'Target');
626
627        $this->output->startElement($this->_uri, 'Source');
628        $this->output->startElement($this->_uri, 'LocURI');
629        $this->output->characters($serverLocURI);
630        $this->output->endElement($this->_uri, 'LocURI');
631        $this->output->endElement($this->_uri, 'Source');
632
633        if (is_int($numberOfChanges)) {
634            $this->output->startElement($this->_uri, 'NumberOfChanges');
635            $this->output->characters($numberOfChanges);
636            $this->output->endElement($this->_uri, 'NumberOfChanges');
637        }
638
639    }
640
641    public function outputSyncEnd()
642    {
643        $this->output->endElement($this->_uri, 'Sync');
644    }
645
646
647    //  internal helper functions:
648
649    protected function _outputCmdID()
650    {
651        $this->output->startElement($this->_uri, 'CmdID');
652        $this->output->characters($this->_msg_CmdID);
653        $this->_msg_CmdID++;
654        $this->output->endElement($this->_uri, 'CmdID');
655    }
656
657    /**
658     * Output a single <ele>$str</ele> element.
659     */
660    protected function _singleEle($tag, $str, $uri = null)
661    {
662        if (empty($uri)) {
663            $uri = $this->_uri;
664        }
665        $this->output->startElement($uri, $tag);
666        $this->output->characters($str);
667        $this->output->endElement($uri, $tag);
668    }
669}
670