1<?php
2/**
3 * ODP Plugin: Exports to ODP
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author	damien clochard <damien@taadeem.net>
7 */
8// must be run within Dokuwiki
9if(!defined('DOKU_INC')) die();
10
11require_once DOKU_INC.'inc/parser/renderer.php';
12
13// ZipLib.class.php
14$dw_version = preg_replace('/[^\d]/', '', getversion());
15if (version_compare($dw_version, "20070626") and
16    version_compare(PHP_VERSION,'5.0.0','>')) {
17    // If strictly newer than 2007-06-26 and use PHP5, fixes to ZipLib are
18    // included in Dokuwiki's ZipLib
19    require_once DOKU_INC.'inc/ZipLib.class.php';
20} else { // for DW up to 2007-06-26, we need the patched version
21    require_once 'ZipLib.class.php';
22}
23
24/**
25 * The Renderer
26 */
27class renderer_plugin_odp extends Doku_Renderer {
28    var $ZIP = null;
29    var $meta;
30    var $store = '';
31    var $footnotes = array();
32    var $manifest  = array();
33    var $headers = array();
34    var $template = "";
35    var $fields = array();
36    var $in_list_item = false;
37    var $in_paragraph = false;
38    var $highlight_style_num = 1;
39    var $slideopen = false;
40    // Automatic styles. Will always be added to content.xml and styles.xml
41    var $autostyles = array(
42        "pm1"=>'
43            <style:page-layout style:name="pm1">
44                <style:page-layout-properties fo:page-width="21cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm">
45                    <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.1cm" style:distance-after-sep="0.1cm" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
46                </style:page-layout-properties>
47                <style:header-style/>
48                <style:footer-style/>
49            </style:page-layout>',
50        "sub"=>'
51            <style:style style:name="sub" style:family="text">
52                <style:text-properties style:text-position="-33% 80%"/>
53            </style:style>',
54        "sup"=>'
55            <style:style style:name="sup" style:family="text">
56                <style:text-properties style:text-position="33% 80%"/>
57            </style:style>',
58        "del"=>'
59            <style:style style:name="del" style:family="text">
60                <style:text-properties style:text-line-through-style="solid"/>
61            </style:style>',
62        "underline"=>'
63            <style:style style:name="underline" style:family="text">
64              <style:text-properties style:text-underline-style="solid"
65                 style:text-underline-width="auto" style:text-underline-color="font-color"/>
66            </style:style>',
67        "media"=>'
68            <style:style style:name="media" style:family="graphic" style:parent-style-name="Graphics">
69                <style:graphic-properties style:run-through="foreground" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit"
70                   style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="baseline" style:horizontal-pos="left"
71                   style:horizontal-rel="paragraph"/>
72            </style:style>',
73        "medialeft"=>'
74            <style:style style:name="medialeft" style:family="graphic" style:parent-style-name="Graphics">
75              <style:graphic-properties style:run-through="foreground" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit"
76                 style:wrap-contour="false" style:horizontal-pos="left" style:horizontal-rel="paragraph"/>
77            </style:style>',
78        "mediaright"=>'
79            <style:style style:name="mediaright" style:family="graphic" style:parent-style-name="Graphics">
80              <style:graphic-properties style:run-through="foreground" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit"
81                 style:wrap-contour="false" style:horizontal-pos="right" style:horizontal-rel="paragraph"/>
82            </style:style>',
83        "mediacenter"=>'
84            <style:style style:name="mediacenter" style:family="graphic" style:parent-style-name="Graphics">
85               <style:graphic-properties style:run-through="foreground" style:wrap="none" style:horizontal-pos="center"
86                  style:horizontal-rel="paragraph"/>
87            </style:style>',
88        "tablealigncenter"=>'
89            <style:style style:name="tablealigncenter" style:family="paragraph" style:parent-style-name="Table_20_Contents">
90                <style:paragraph-properties fo:text-align="center"/>
91            </style:style>',
92        "tablealignright"=>'
93            <style:style style:name="tablealignright" style:family="paragraph" style:parent-style-name="Table_20_Contents">
94                <style:paragraph-properties fo:text-align="end"/>
95            </style:style>',
96        "tablealignleft"=>'
97            <style:style style:name="tablealignleft" style:family="paragraph" style:parent-style-name="Table_20_Contents">
98                <style:paragraph-properties fo:text-align="left"/>
99            </style:style>',
100        "tableheader"=>'
101            <style:style style:name="tableheader" style:family="table-cell">
102                <style:table-cell-properties fo:padding="0.05cm" fo:border-left="0.002cm solid #000000" fo:border-right="0.002cm solid #000000" fo:border-top="0.002cm solid #000000" fo:border-bottom="0.002cm solid #000000"/>
103            </style:style>',
104        "tablecell"=>'
105            <style:style style:name="tablecell" style:family="table-cell">
106                <style:table-cell-properties fo:padding="0.05cm" fo:border-left="0.002cm solid #000000" fo:border-right="0.002cm solid #000000" fo:border-top="0.002cm solid #000000" fo:border-bottom="0.002cm solid #000000"/>
107            </style:style>',
108        "legendcenter"=>'
109            <style:style style:name="legendcenter" style:family="paragraph" style:parent-style-name="Illustration">
110                <style:paragraph-properties fo:text-align="center"/>
111            </style:style>',
112    );
113    // Regular styles. May not be present if in template mode, in which case they will be added to styles.xml
114    var $styles = array(
115        "Source_20_Text"=>'
116            <style:style style:name="Source_20_Text" style:display-name="Source Text" style:family="text">
117                <style:text-properties style:font-name="Bitstream Vera Sans Mono" style:font-name-asian="Bitstream Vera Sans Mono" style:font-name-complex="Bitstream Vera Sans Mono"/>
118            </style:style>',
119        "Preformatted_20_Text"=>'
120            <style:style style:name="Preformatted_20_Text" style:display-name="Preformatted Text" style:family="paragraph" style:parent-style-name="Standard" style:class="html">
121                <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.2cm"/>
122                <style:text-properties style:font-name="Bitstream Vera Sans Mono" style:font-name-asian="Bitstream Vera Sans Mono" style:font-name-complex="Bitstream Vera Sans Mono"/>
123            </style:style>',
124        "Source_20_Code"=>'
125            <style:style style:name="Source_20_Code" style:display-name="Source Code" style:family="paragraph" style:parent-style-name="Preformatted_20_Text">
126                <style:paragraph-properties fo:padding="0.05cm" style:shadow="none" fo:border="0.002cm solid #8cacbb" fo:background-color="#f7f9fa"/>
127            </style:style>',
128        "Source_20_File"=>'
129            <style:style style:name="Source_20_File" style:display-name="Source File" style:family="paragraph" style:parent-style-name="Preformatted_20_Text">
130                <style:paragraph-properties fo:padding="0.05cm" style:shadow="none" fo:border="0.002cm solid #8cacbb" fo:background-color="#f1f4f5"/>
131            </style:style>',
132        "Horizontal_20_Line"=>'
133            <style:style style:name="Horizontal_20_Line" style:display-name="Horizontal Line" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="html">
134                <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.5cm" style:border-line-width-bottom="0.002cm 0.035cm 0.002cm" fo:padding="0cm" fo:border-left="none" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.04cm double #808080" text:number-lines="false" text:line-number="0" style:join-border="false"/>
135                <style:text-properties fo:font-size="6pt" style:font-size-asian="6pt" style:font-size-complex="6pt"/>
136            </style:style>',
137        "Footnote"=>'
138            <style:style style:name="Footnote" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
139                <style:paragraph-properties fo:margin-left="0.5cm" fo:margin-right="0cm" fo:text-indent="-0.5cm" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
140                <style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
141            </style:style>',
142        "Emphasis"=>'
143            <style:style style:name="Emphasis" style:family="text">
144                <style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"/>
145            </style:style>',
146        "Strong_20_Emphasis"=>'
147            <style:style style:name="Strong_20_Emphasis" style:display-name="Strong Emphasis" style:family="text">
148                <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
149            </style:style>',
150    );
151    // Font definitions. May not be present if in template mode, in which case they will be added to styles.xml
152    var $fonts = array(
153        "StarSymbol"=>'<style:font-face style:name="StarSymbol" svg:font-family="StarSymbol"/>', // for bullets
154        "Bitstream Vera Sans Mono"=>'<style:font-face style:name="Bitstream Vera Sans Mono" svg:font-family="\'Bitstream Vera Sans Mono\'" style:font-family-generic="modern" style:font-pitch="fixed"/>', // for source code
155    );
156
157    /**
158     * Return version info
159     */
160    function getInfo(){
161        return confToHash(dirname(__FILE__).'/info.txt');
162    }
163
164    /**
165     * Returns the format produced by this renderer.
166     */
167    function getFormat(){
168        return "odp";
169    }
170
171    /**
172     * Do not make multiple instances of this class
173     */
174    function isSingleton(){
175        return true;
176    }
177
178
179    /**
180     * Initialize the rendering
181     */
182    function document_start() {
183        global $ID;
184
185        // If older or equal to 2007-06-26, we need to disable caching
186        $dw_version = preg_replace('/[^\d]/', '', getversion());
187        if (version_compare($dw_version, "20070626", "<=")) {
188            $this->info["cache"] = false;
189        }
190
191	//DEBUG
192	$this->info["cache"] = false;
193
194        // prepare the zipper
195        $this->ZIP = new ZipLib();
196
197        // prepare meta data
198        $this->meta             = array(
199                'meta:generator'            => 'DokuWiki '.getversion(),
200                'meta:initial-creator'      => 'Generated',
201                'meta:creation-date'        => date('Y-m-d\\TH::i:s', null), //FIXME
202                'dc:creator'                => 'Generated',
203                'dc:date'                   => date('Y-m-d\\TH::i:s', null),
204                'dc:language'               => 'en-US',
205                'meta:editing-cycles'       => '1',
206                'meta:editing-duration'     => 'PT0S',
207            );
208
209	// prepare the table of content
210	$this->toc = '';
211	$this->toc_counter = 0;
212	$this->toc_source = '<text:table-of-content-source text:outline-level="4">';
213	$this->toc_source .= '<text:index-title-template text:style-name="Contents_20_Heading">Table des matières</text:index-title-template>';
214
215	$this->toc_index = '<text:index-title text:style-name="Sect2" text:name="Table des matières1_Head">';
216	$this->toc_index .= '<text:p text:style-name="Contents_20_Heading">Table des matières</text:p>';
217 	$this->toc_index .= '</text:index-title>';
218
219        //$headers = array('Content-Type'=>'text/plain'); p_set_metadata($ID,array('format' => array('odp' => $headers) )); return ; // DEBUG
220        // send the content type header, new method after 2007-06-26 (handles caching)
221        $output_filename = str_replace(':','-',$ID).".odp";
222        if (version_compare($dw_version, "20070626")) {
223            // store the content type headers in metadata
224            $headers = array(
225                'Content-Type' => 'application/vnd.oasis.opendocument.presentation',
226                'Content-Disposition' => 'attachment; filename="'.$output_filename.'";',
227            );
228            p_set_metadata($ID,array('format' => array('odp' => $headers) ));
229        } else { // older method
230            header('Content-Type: application/vnd.oasis.opendocument.presentation');
231            header('Content-Disposition: attachment; filename="'.$output_filename.'";');
232        }
233    }
234
235    /**
236     * Prepare meta.xml
237     */
238    function _odpMeta(){
239        $value  =   '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n";
240        $value .=   '<office:document-meta ';
241        $value .=       'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" ';
242        $value .=       'xmlns:xlink="http://www.w3.org/1999/xlink" ';
243        $value .=       'xmlns:dc="http://purl.org/dc/elements/1.1/" ';
244        $value .=       'xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" ';
245        $value .=   'xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" ';
246        $value .=   'xmlns:ooo="http://openoffice.org/2004/office" ';
247        $value .=   'xmlns:smil="urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" ';
248        $value .=   'xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" ';
249        $value .=   'xmlns:grddl="http://www.w3.org/2003/g/data-view#" ';
250        $value .=   'xmlns:officeooo="http://openoffice.org/2009/office" ';
251        $value .=   'office:version="1.2" ';
252        $value .=   'grddl:transformation="http://docs.oasis-open.org/office/1.2/xslt/odf2rdf.xsl" >';
253        $value .=       '<office:meta>';
254    # FIXME
255    #    foreach($meta as $meta_key => $meta_value)
256    #        $value .=       '<' . $meta_key . '>' . ODUtils::encode($meta_value) . '</' . $meta_key . '>';
257        $value .=       '</office:meta>';
258        $value .=   '</office:document-meta>';
259        $this->ZIP->add_File($value,'meta.xml');
260    }
261
262    /**
263     * Prepare manifest.xml
264     */
265    function _odpManifest(){
266        $value  =   '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n";
267        $value .=   '<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">';
268        $value .=   '<manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.presentation" manifest:version="1.2" manifest:full-path="/"/>';
269        $value .=   '<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="settings.xml"/>';
270        $value .=   '<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/>';
271        $value .=   '<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/>';
272        $value .=   '<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml"/>';
273
274        foreach($this->manifest as $path => $type){
275            $value .= '<manifest:file-entry manifest:media-type="'.$this->_xmlEntities($type).
276                      '" manifest:full-path="'.$this->_xmlEntities($path).'"/>';
277        }
278
279        $value .=   '</manifest:manifest>';
280        $this->ZIP->add_File($value,'META-INF/manifest.xml');
281    }
282
283    /**
284     * Prepare settings.xml
285     */
286    function _odpSettings(){
287        $value  =   '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n";
288        $value .=   '<office:document-settings ';
289	$value .=   'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" ';
290	$value .=   'xmlns:xlink="http://www.w3.org/1999/xlink" ';
291	$value .=   'xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" ';
292	$value .=   'xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" ';
293	$value .=   'xmlns:ooo="http://openoffice.org/2004/office" ';
294	$value .=   'xmlns:smil="urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" ';
295	$value .=   'xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" ';
296	$value .=   'xmlns:officeooo="http://openoffice.org/2009/office" ';
297	$value .=   'office:version="1.2">';
298	$value .=   '<office:settings>';
299	$value .=   '<config:config-item-set config:name="dummy-settings">';
300	$value .=   '<config:config-item config:name="MakeValidatorHappy" config:type="boolean">';
301	$value .=   'true';
302	$value .=   '</config:config-item>';
303	$value .=   '</config:config-item-set>';
304	$value .=   '</office:settings>';
305	$value .=   '</office:document-settings>';
306        $this->ZIP->add_File($value,'settings.xml');
307    }
308
309
310
311
312    /**
313     * Closes the document
314     */
315    function document_end(){
316        global $conf;
317        //$this->doc .= $this->_odpAutoStyles(); return; // DEBUG
318        if ($this->template) { // template chosen
319            if (file_exists($conf['mediadir'].'/'.$this->getConf("tpl_dir")."/".$this->template)) { //template found
320                $this->document_end_template();
321	     } else { // template chosen but not found : warn the user and use the default template
322                $this->doc = '<text:p text:style-name="Text_20_body"><text:span text:style-name="Strong_20_Emphasis">'
323                             .$this->_xmlEntities( sprintf($this->getLang('tpl_not_found'),$this->template,$this->getConf("tpl_dir")) )
324                             .'</text:span></text:p>'.$this->doc;
325                $this->document_end_scratch();
326            }
327        } else {
328            $this->document_end_scratch();
329        }
330        $this->doc = $this->ZIP->get_file();
331    }
332
333
334    /**
335     * Closes the document when not using a template
336     */
337    function document_end_scratch(){
338        $autostyles = $this->_odpAutoStyles();
339        $userfields = $this->_odpUserFields();
340
341	// add defaults
342        $this->ZIP->add_File('application/vnd.oasis.opendocument.presentation', 'mimetype', 0);
343
344        $this->_odpMeta();
345        $this->_odpSettings();
346
347        $value  =   '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n";
348        $value .=   '<office:document-content ';
349        $value .=       'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" ';
350        $value .=       'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" ';
351        $value .=       'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" ';
352        $value .=       'xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" ';
353        $value .=       'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" ';
354        $value .=       'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" ';
355        $value .=       'xmlns:xlink="http://www.w3.org/1999/xlink" ';
356        $value .=       'xmlns:dc="http://purl.org/dc/elements/1.1/" ';
357        $value .=       'xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" ';
358        $value .=       'xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" ';
359        $value .=       'xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" ';
360        $value .=       'xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" ';
361        $value .=       'xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" ';
362        $value .=       'xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" ';
363        $value .=       'xmlns:math="http://www.w3.org/1998/Math/MathML" ';
364        $value .=       'xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" ';
365        $value .=       'xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" ';
366        $value .=       'xmlns:ooo="http://openoffice.org/2004/office" ';
367        $value .=       'xmlns:ooow="http://openoffice.org/2004/writer" ';
368        $value .=       'xmlns:oooc="http://openoffice.org/2004/calc" ';
369        $value .=       'xmlns:dom="http://www.w3.org/2001/xml-events" ';
370        $value .=       'xmlns:xforms="http://www.w3.org/2002/xforms" ';
371        $value .=       'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ';
372        $value .=       'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ';
373        $value .=       'xmlns:smil="urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" ';
374        $value .=       'xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" ';
375        $value .=       'xmlns:rpt="http://openoffice.org/2005/report" ';
376        $value .=       'xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" ';
377        $value .=       'xmlns:xhtml="http://www.w3.org/1999/xhtml" ';
378        $value .=       'xmlns:grddl="http://www.w3.org/2003/g/data-view#" ';
379        $value .=       'xmlns:officeooo="http://openoffice.org/2009/office" ';
380        $value .=       'xmlns:tableooo="http://openoffice.org/2009/table" ';
381        $value .=       'xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" ';
382        $value .=       'xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" ';
383        $value .=       'office:version="1.2"  ';
384        $value .=       'grddl:transformation="http://docs.oasis-open.org/office/1.2/xslt/odf2rdf.xsl" ';
385        $value .=       '>';
386        $value .=       '<office:scripts/>';
387        $value .=       $autostyles;
388        $value .=       '<office:body>';
389        $value .=           '<office:presentation>';
390
391        $value .=               $userfields;
392
393        $value .=   $this->doc;
394	$value .=   		'</draw:page>';
395        $value .=           '</office:presentation>';
396        $value .=       '</office:body>';
397        $value .=   '</office:document-content>';
398
399        $this->ZIP->add_File($value,'content.xml');
400
401        $value = io_readFile(DOKU_PLUGIN.'odp/styles.xml');
402        $value = str_replace('<office:automatic-styles/>', $autostyles, $value);
403        $this->ZIP->add_File($value,'styles.xml');
404
405        // build final manifest
406        $this->_odpManifest();
407    }
408
409    /**
410     * Closes the document using a template
411     */
412    function document_end_template(){
413        global $conf, $ID; // for the temp dir
414
415        // Temp dir
416        if (is_dir($conf['tmpdir'])) {
417            $temp_dir = $conf['tmpdir']; // version > 20070626
418        } else {
419            $temp_dir = $conf['savedir'].'/cache/tmp'; // version <= 20070626
420        }
421        $this->temp_dir = $temp_dir."/odp/".str_replace(':','-',$ID);
422        if (is_dir($this->temp_dir)) { $this->io_rm_rf($this->temp_dir); }
423        io_mkdir_p($this->temp_dir);
424
425        // Extract template
426        $template_path = $conf['mediadir'].'/'.$this->getConf("tpl_dir")."/".$this->template;
427        $this->ZIP->Extract($template_path, $this->temp_dir);
428
429        // Prepare content
430        $autostyles = $this->_odpAutoStyles();
431        $missingstyles = $this->_odpStyles();
432        $missingfonts = $this->_odpFonts();
433        $userfields = $this->_odpUserFields();
434
435        // Insert content
436        $old_content = io_readFile($this->temp_dir.'/content.xml');
437        if (strpos($old_content, 'DOKUWIKI-ODP-INSERT') !== FALSE) { // Replace the mark
438            $this->_odpReplaceInFile('/<text:p[^>]*>DOKUWIKI-ODP-INSERT<\/text:p>/',
439                $this->doc, $this->temp_dir.'/content.xml', true);
440        } else { // Append to the template
441            $this->_odpReplaceInFile('</office:presentation>', $this->doc.'</office:presentation>', $this->temp_dir.'/content.xml');
442        }
443
444	 $this->render_TOC();
445
446        // Cut off unwanted content
447        if (strpos($old_content, 'DOKUWIKI-ODP-CUT-START') !== FALSE
448                && strpos($old_content, 'DOKUWIKI-ODP-CUT-STOP') !== FALSE) {
449            $this->_odpReplaceInFile('/DOKUWIKI-ODP-CUT-START.*DOKUWIKI-ODP-CUT-STOP/',
450                '', $this->temp_dir.'/content.xml', true);
451        }
452
453        // Insert userfields
454        if (strpos($old_content, "text:user-field-decls") === FALSE) { // no existing userfields
455            $this->_odpReplaceInFile('/<office:text([^>]*)>/U', '<office:text\1>'.$userfields, $this->temp_dir.'/content.xml', TRUE);
456        } else {
457            $this->_odpReplaceInFile('</text:user-field-decls>', substr($userfields,23), $this->temp_dir.'/content.xml');
458        }
459
460        // Insert styles & fonts
461        $this->_odpReplaceInFile('</office:automatic-styles>', substr($autostyles, 25), $this->temp_dir.'/content.xml');
462        $this->_odpReplaceInFile('</office:automatic-styles>', substr($autostyles, 25), $this->temp_dir.'/styles.xml');
463        $this->_odpReplaceInFile('</office:styles>', $missingstyles.'</office:styles>', $this->temp_dir.'/styles.xml');
464        $this->_odpReplaceInFile('</office:font-face-decls>', $missingfonts.'</office:font-face-decls>', $this->temp_dir.'/styles.xml');
465
466        // Build the Zip
467        $this->ZIP->Compress(null, $this->temp_dir, null);
468        $this->io_rm_rf($this->temp_dir);
469    }
470
471    function _odpReplaceInFile($from, $to, $file, $regexp=FALSE) {
472        $value = io_readFile($file);
473        if ($regexp) {
474            $value = preg_replace($from, $to, $value);
475        } else {
476            $value = str_replace($from, $to, $value);
477        }
478        $file_f = fopen($file, 'w');
479        fwrite($file_f, $value);
480        fclose($file_f);
481    }
482
483    /**
484     * Recursively deletes a directory (equivalent to the "rm -rf" command)
485     * Found in comments on http://www.php.net/rmdir
486     */
487    function io_rm_rf($f) {
488        if (is_dir($f)) {
489            foreach(glob($f.'/*') as $sf) {
490                if (is_dir($sf) && !is_link($sf)) {
491                    $this->io_rm_rf($sf);
492                } else {
493                    unlink($sf);
494                }
495            }
496        } else { // avoid nasty consequenses if something wrong is given
497            die("Error: not a directory - $f");
498        }
499        rmdir($f);
500    }
501
502    // Build the table of content
503    function render_TOC() {
504    }
505
506    function toc_additem($id, $text, $level) {
507    }
508
509    function _odpAutoStyles() {
510        $value = '<office:automatic-styles>';
511        foreach ($this->autostyles as $stylename=>$stylexml) {
512            $value .= $stylexml;
513        }
514        $value .= '</office:automatic-styles>';
515        return $value;
516    }
517
518    function _odpUserFields() {
519        $value = '<text:user-field-decls>';
520        foreach ($this->fields as $fname=>$fvalue) {
521            $value .= '<text:user-field-decl office:value-type="string" text:name="'.$fname.'" office:string-value="'.$fvalue.'"/>';
522        }
523        $value .= '</text:user-field-decls>';
524        return $value;
525    }
526
527    /* Add missing styles in the template */
528    function _odpStyles() {
529        $value = '';
530        $existing_styles = io_readFile($this->temp_dir.'/styles.xml');
531        foreach ($this->styles as $stylename=>$stylexml) {
532            if (strpos($existing_styles, 'style:name="'.$stylename.'"') === FALSE) {
533                $value .= $stylexml;
534            }
535        }
536        // Loop on bullet/numerotation styles
537        if (strpos($existing_styles, 'style:name="List_20_1"') === FALSE) {
538            $value .= '<text:list-style style:name="List_20_1" style:display-name="List 1">';
539            for ($i=1;$i<=10;$i++) {
540                $value .= '<text:list-level-style-bullet text:level="'.$i.'" text:style-name="Numbering_20_Symbols" text:bullet-char="•">
541                               <style:list-level-properties text:space-before="'.(0.4*($i-1)).'cm" text:min-label-width="0.4cm"/>
542                               <style:text-properties style:font-name="StarSymbol"/>
543                           </text:list-level-style-bullet>';
544            }
545            $value .= '</text:list-style>';
546        }
547        if (strpos($existing_styles, 'style:name="Numbering_20_1"') === FALSE) {
548            $value .= '<text:list-style style:name="Numbering_20_1" style:display-name="Numbering 1">';
549            for ($i=1;$i<=10;$i++) {
550                $value .= '<text:list-level-style-number text:level="'.$i.'" text:style-name="Numbering_20_Symbols" style:num-suffix="." style:num-format="1">
551                               <style:list-level-properties text:space-before="'.(0.5*($i-1)).'cm" text:min-label-width="0.5cm"/>
552                           </text:list-level-style-number>';
553            }
554            $value .= '</text:list-style>';
555        }
556        return $value;
557    }
558
559    /* Add missing fonts in the template */
560    function _odpFonts() {
561        $value = '';
562        $existing_styles = io_readFile($this->temp_dir.'/styles.xml');
563        foreach ($this->fonts as $name=>$xml) {
564            if (strpos($existing_styles, 'style:name="'.$name.'"') === FALSE) {
565                $value .= $xml;
566            }
567        }
568        return $value;
569    }
570
571    function cdata($text) {
572        $this->doc .= $this->_xmlEntities($text);
573    }
574
575    function p_open($style='Text_20_body'){
576        if (!$this->in_paragraph) { // opening a paragraph inside another paragraph is illegal
577            $this->in_paragraph = true;
578            $this->doc .= '<text:p text:style-name="'.$style.'">';
579        }
580    }
581
582    function p_close(){
583        if ($this->in_paragraph) {
584            $this->in_paragraph = false;
585            $this->doc .= '</text:p>';
586        }
587    }
588
589    function header($text, $level, $pos){
590	// H1 and H2 create new slides
591        if($level <3){
592            if($this->slideopen){
593                //close previous slide
594		$this->doc .= '</draw:page>';
595            }
596	    //open the new slide
597            $this->doc .= '<draw:page draw:name="page1" draw:style-name="dp1" draw:master-page-name="Default">';
598            $this->slideopen = true;
599        }
600        // write the header
601	$this->doc.='<draw:frame presentation:style-name="pr4" draw:layer="layout" svg:width="25.199cm" svg:height="3.256cm" svg:x="1.4cm" svg:y="0.962cm" presentation:class="title">';
602	$this->doc.='<draw:text-box>';
603	$this->doc.='<text:p>';
604	$this->doc.= $this->_xmlEntities($text);
605        $this->doc.='</text:p>';
606	$this->doc.='</draw:text-box>';
607	$this->doc.='</draw:frame>';
608
609/*
610        $this->doc .= '<presentation:notes draw:style-name="dp2">';
611        $this->doc .= '<draw:page-thumbnail draw:style-name="gr1" draw:layer="layout" svg:width="14.848cm" svg:height="11.136cm" svg:x="3.075cm" svg:y="2.257cm" draw:page-number="1" presentation:class="page"/>';
612        $this->doc .= '<draw:frame presentation:style-name="pr1" draw:layer="layout" svg:width="16.799cm" svg:height="13.365cm" svg:x="2.1cm" svg:y="14.107cm" presentation:class="notes" presentation:placeholder="true">';
613        $this->doc .= '<draw:text-box/>';
614        $this->doc .= '</draw:frame>';
615        $this->doc .= '</presentation:notes>';
616*/
617    }
618
619    /**
620     * A line stops the slide and start the handout section
621     */
622    function hr() {
623        //$this->doc .= '</div>';
624        //$this->doc .= '<div class="handout">'.DOKU_LF;
625    }
626
627    function linebreak() {
628        $this->doc .= '<text:line-break/>';
629    }
630
631    function strong_open() {
632        $this->doc .= '<text:span text:style-name="Strong_20_Emphasis">';
633    }
634
635    function strong_close() {
636        $this->doc .= '</text:span>';
637    }
638
639    function emphasis_open() {
640        $this->doc .= '<text:span text:style-name="Emphasis">';
641    }
642
643    function emphasis_close() {
644        $this->doc .= '</text:span>';
645    }
646
647    function underline_open() {
648        $this->doc .= '<text:span text:style-name="underline">';
649    }
650
651    function underline_close() {
652        $this->doc .= '</text:span>';
653    }
654
655    function monospace_open() {
656        $this->doc .= '<text:span text:style-name="Source_20_Text">';
657    }
658
659    function monospace_close() {
660        $this->doc .= '</text:span>';
661    }
662
663    function subscript_open() {
664        $this->doc .= '<text:span text:style-name="sub">';
665    }
666
667    function subscript_close() {
668        $this->doc .= '</text:span>';
669    }
670
671    function superscript_open() {
672        $this->doc .= '<text:span text:style-name="sup">';
673    }
674
675    function superscript_close() {
676        $this->doc .= '</text:span>';
677    }
678
679    function deleted_open() {
680        $this->doc .= '<text:span text:style-name="del">';
681    }
682
683    function deleted_close() {
684        $this->doc .= '</text:span>';
685    }
686
687    /*
688     * Tables
689     */
690    function table_open($maxcols = NULL, $numrows = NULL){
691        $this->doc .= '<table:table>';
692        for($i=0; $i<$maxcols; $i++){
693            $this->doc .= '<table:table-column />';
694        }
695    }
696
697    function table_close(){
698        $this->doc .= '</table:table>';
699    }
700
701    function tablerow_open(){
702        $this->doc .= '<table:table-row>';
703    }
704
705    function tablerow_close(){
706        $this->doc .= '</table:table-row>';
707    }
708
709    function tableheader_open($colspan = 1, $align = "left"){
710        $this->doc .= '<table:table-cell office:value-type="string" table:style-name="tableheader" ';
711        //$this->doc .= ' table:style-name="tablealign'.$align.'"';
712        if ( $colspan > 1 ) {
713            $this->doc .= ' table:number-columns-spanned="'.$colspan.'"';
714        }
715        $this->doc .= '>';
716        $this->p_open('Table_20_Heading');
717    }
718
719    function tableheader_close(){
720        $this->p_close();
721        $this->doc .= '</table:table-cell>';
722    }
723
724    function tablecell_open($colspan = 1, $align = "left"){
725        $this->doc .= '<table:table-cell office:value-type="string" table:style-name="tablecell" ';
726        if ( $colspan > 1 ) {
727            $this->doc .= ' table:number-columns-spanned="'.$colspan.'"';
728        }
729        $this->doc .= '>';
730        if (!$align) $align = "left";
731        $style = "tablealign".$align;
732        $this->p_open($style);
733    }
734
735    function tablecell_close(){
736        $this->p_close();
737        $this->doc .= '</table:table-cell>';
738    }
739
740    /**
741     * Callback for footnote start syntax
742     *
743     * All following content will go to the footnote instead of
744     * the document. To achieve this the previous rendered content
745     * is moved to $store and $doc is cleared
746     *
747     * @author Andreas Gohr <andi@splitbrain.org>
748     */
749    function footnote_open() {
750
751        // move current content to store and record footnote
752        $this->store = $this->doc;
753        $this->doc   = '';
754    }
755
756    /**
757     * Callback for footnote end syntax
758     *
759     * All rendered content is moved to the $footnotes array and the old
760     * content is restored from $store again
761     *
762     * @author Andreas Gohr
763     */
764    function footnote_close() {
765        // recover footnote into the stack and restore old content
766        $footnote = $this->doc;
767        $this->doc = $this->store;
768        $this->store = '';
769
770        // check to see if this footnote has been seen before
771        $i = array_search($footnote, $this->footnotes);
772
773        if ($i === false) {
774            $i = count($this->footnotes);
775            // its a new footnote, add it to the $footnotes array
776            $this->footnotes[$i] = $footnote;
777
778            $this->doc .= '<text:note text:id="ftn'.$i.'" text:note-class="footnote">';
779            $this->doc .= '<text:note-citation>'.($i+1).'</text:note-citation>';
780            $this->doc .= '<text:note-body>';
781            $this->doc .= '<text:p text:style-name="Footnote">';
782            $this->doc .= $footnote;
783            $this->doc .= '</text:p>';
784            $this->doc .= '</text:note-body>';
785            $this->doc .= '</text:note>';
786
787        } else {
788            // seen this one before - just reference it FIXME: style isn't correct yet
789            $this->doc .= '<text:note-ref text:note-class="footnote" text:ref-name="ftn'.$i.'">'.($i+1).'</text:note-ref>';
790        }
791    }
792
793    function listu_open() {
794        $this->p_close();
795        $this->doc .= '<text:list text:style-name="List_20_1">';
796    }
797
798    function listu_close() {
799        $this->doc .= '</text:list>';
800    }
801
802    function listo_open() {
803        $this->p_close();
804        $this->doc .= '<text:list text:style-name="Numbering_20_1">';
805    }
806
807    function listo_close() {
808        $this->doc .= '</text:list>';
809    }
810
811    function listitem_open($level) {
812        $this->in_list_item = true;
813        $this->doc .= '<text:list-item>';
814    }
815
816    function listitem_close() {
817        $this->in_list_item = false;
818        $this->doc .= '</text:list-item>';
819    }
820
821    function listcontent_open() {
822        $this->doc .= '<text:p text:style-name="Text_20_body">';
823    }
824
825    function listcontent_close() {
826        $this->doc .= '</text:p>';
827    }
828
829    function unformatted($text) {
830        $this->doc .= $this->_xmlEntities($text);
831    }
832
833    function acronym($acronym) {
834        $this->doc .= $this->_xmlEntities($acronym);
835    }
836
837    function smiley($smiley) {
838        if ( array_key_exists($smiley, $this->smileys) ) {
839            $src = DOKU_INC."lib/images/smileys/".$this->smileys[$smiley];
840            $this->_odpAddImage($src);
841        } else {
842            $this->doc .= $this->_xmlEntities($smiley);
843        }
844    }
845
846    function entity($entity) {
847        # UTF-8 entity decoding is broken in PHP <5
848        if (version_compare(phpversion(), "5.0.0") and array_key_exists($entity, $this->entities) ) {
849            # decoding may fail for missing Multibyte-Support in entity_decode
850            $dec = @html_entity_decode($this->entities[$entity],ENT_NOQUOTES,'UTF-8');
851            if($dec){
852                $this->doc .= $this->_xmlEntities($dec);
853            }else{
854                $this->doc .= $this->_xmlEntities($entity);
855            }
856        } else {
857            $this->doc .= $this->_xmlEntities($entity);
858        }
859    }
860
861    function multiplyentity($x, $y) {
862        $this->doc .= $x.'×'.$y;
863    }
864
865    function singlequoteopening() {
866        global $lang;
867        $this->doc .= $lang['singlequoteopening'];
868    }
869
870    function singlequoteclosing() {
871        global $lang;
872        $this->doc .= $lang['singlequoteclosing'];
873    }
874
875    function apostrophe() {
876        global $lang;
877        $this->doc .= $lang['apostrophe'];
878    }
879
880    function doublequoteopening() {
881        global $lang;
882        $this->doc .= $lang['doublequoteopening'];
883    }
884
885    function doublequoteclosing() {
886        global $lang;
887        $this->doc .= $lang['doublequoteclosing'];
888    }
889
890    function php($text, $wrapper='dummy') {
891        $this->monospace_open();
892        $this->doc .= $this->_xmlEntities($text);
893        $this->monospace_close();
894    }
895    function phpblock($text) {
896        $this->file($text);
897    }
898
899    function html($text, $wrapper='dummy') {
900        $this->monospace_open();
901        $this->doc .= $this->_xmlEntities($text);
902        $this->monospace_close();
903    }
904    function htmlblock($text) {
905        $this->file($text);
906    }
907
908    /**
909     * static call back to replace spaces
910     */
911    function _preserveSpace($matches){
912        $spaces = $matches[1];
913        $len    = strlen($spaces);
914        return '<text:s text:c="'.$len.'"/>';
915    }
916
917    function preformatted($text) {
918        $this->_preformatted($text);
919    }
920
921    function file($text, $language=null, $filename=null) {
922        $this->_highlight('file', $text, $language);
923    }
924
925    function quote_open() {
926        if (!$this->in_paragraph) { // only start a new par if we're not already in one
927            $this->p_open();
928        }
929        $this->doc .= "&gt;";
930    }
931
932    function quote_close() {
933        if ($this->in_paragraph) { // only close the paragraph if we're actually in one
934            $this->p_close();
935        }
936    }
937
938    function code($text, $language=null, $filename=null) {
939        $this->_highlight('code', $text, $language);
940    }
941
942    function _preformatted($text, $style="Preformatted_20_Text", $notescaped=true) {
943        if ($notescaped) {
944            $text = $this->_xmlEntities($text);
945        }
946        if (strpos($text, "\n") !== FALSE and strpos($text, "\n") == 0) {
947            // text starts with a newline, remove it
948            $text = substr($text,1);
949        }
950        $text = str_replace("\n",'<text:line-break/>',$text);
951        $text = preg_replace_callback('/(  +)/',array('renderer_plugin_odp','_preserveSpace'),$text);
952
953        if ($this->in_list_item) { // if we're in a list item, we must close the <text:p> tag
954            $this->doc .= '</text:p>';
955            $this->doc .= '<text:p text:style-name="'.$style.'">';
956            $this->doc .= $text;
957            $this->doc .= '</text:p>';
958            $this->doc .= '<text:p>';
959        } else {
960            $this->doc .= '<text:p text:style-name="'.$style.'">';
961            $this->doc .= $text;
962            $this->doc .= '</text:p>';
963        }
964    }
965
966    function _highlight($type, $text, $language=null) {
967        global $conf;
968        $style_name = "Source_20_Code";
969        if ($type == "file") $style_name = "Source_20_File";
970
971        if (is_null($language)) {
972            $this->_preformatted($text, $style_name);
973            return;
974        }
975
976        // from inc/parserutils.php:p_xhtml_cached_geshi()
977        require_once(DOKU_INC . 'inc/geshi.php');
978        $geshi = new GeSHi($text, $language, DOKU_INC . 'inc/geshi');
979        $geshi->set_encoding('utf-8');
980        // $geshi->enable_classes(); DO NOT WANT !
981        $geshi->set_header_type(GESHI_HEADER_PRE);
982        $geshi->enable_keyword_links(false);
983
984        // remove GeSHi's wrapper element (we'll replace it with our own later)
985        // we need to use a GeSHi wrapper to avoid <BR> throughout the highlighted text
986        $highlighted_code = trim(preg_replace('!^<pre[^>]*>|</pre>$!','',$geshi->parse_code()),"\n\r");
987        // remove useless leading and trailing whitespace-newlines
988        $highlighted_code = preg_replace('/^&nbsp;\n/','',$highlighted_code);
989        $highlighted_code = preg_replace('/\n&nbsp;$/','',$highlighted_code);
990        // replace styles
991        $highlighted_code = str_replace("</span>", "</text:span>", $highlighted_code);
992        $highlighted_code = preg_replace_callback('/<span style="([^"]+)">/', array('renderer_plugin_odp','_convert_css_styles'), $highlighted_code);
993        // cleanup leftover span tags
994        $highlighted_code = preg_replace('/<span[^>]*>/', "<text:span>", $highlighted_code);
995        $highlighted_code = str_replace("&nbsp;", "&#xA0;", $highlighted_code);
996        $this->_preformatted($highlighted_code, $style_name, false);
997    }
998
999    function _convert_css_styles($matches) {
1000        $all_css_styles = $matches[1];
1001        // parse the CSS attribute
1002        $css_styles = array();
1003        foreach(explode(";", $all_css_styles) as $css_style) {
1004            $css_style_array = explode(":", $css_style);
1005            if (!trim($css_style_array[0]) or !trim($css_style_array[1])) {
1006                continue;
1007            }
1008            $css_styles[trim($css_style_array[0])] = trim($css_style_array[1]);
1009        }
1010        // create the ODP xml style
1011        $style_name = "highlight." . $this->highlight_style_num;
1012        $this->highlight_style_num += 1;
1013        $style_content = '
1014            <style:style style:name="'.$style_name.'" style:family="text">
1015                <style:text-properties ';
1016        foreach($css_styles as $style_key=>$style_value) {
1017            // Hats off to those who thought out the OpenDocument spec: styling syntax is similar to CSS !
1018            $style_content .= 'fo:'.$style_key.'="'.$style_value.'" ';
1019        }
1020        $style_content .= '/>
1021            </style:style>';
1022        // add the style to the library
1023        $this->autostyles[$style_name] = $style_content;
1024        // now make use of the new style
1025        return '<text:span text:style-name="'.$style_name.'">';
1026    }
1027
1028    function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
1029                            $height=NULL, $cache=NULL, $linking=NULL) {
1030        global $conf;
1031        global $ID;
1032        resolve_mediaid(getNS($ID),$src, $exists);
1033        list($ext,$mime) = mimetype($src);
1034
1035        if(substr($mime,0,5) == 'image'){
1036            $file = mediaFN($src);
1037            $this->_odpAddImage($file, $width, $height, $align, $title);
1038        }else{
1039            // FIXME build absolute medialink and call externallink()
1040            $this->code('FIXME internalmedia: '.$src);
1041        }
1042    }
1043
1044    function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
1045                            $height=NULL, $cache=NULL, $linking=NULL) {
1046        global $conf;
1047        global $ID;
1048        list($ext,$mime) = mimetype($src);
1049
1050        if(substr($mime,0,5) == 'image'){
1051            if($width) {
1052                $width  = 'svg:width="'.(($width/96.0)*2.54).'cm"';
1053            } else {
1054                $width  = 'svg:rel-width="100%"';
1055            }
1056            if($height) {
1057                $height  = 'svg:height="'.(($height/96.0)*2.54).'cm"';
1058            } else {
1059                $height  = 'svg:rel-height="100%"';
1060            }
1061
1062            $style = 'media'.$align;
1063            if($align){
1064                $anchor = 'paragraph';
1065            }else{
1066                $anchor = 'as-char';
1067            }
1068
1069            $this->doc .= '<draw:frame draw:style-name="'.$style.'" draw:name="'.$this->_xmlEntities($title).'"
1070                            text:anchor-type="'.$anchor.'" draw:z-index="0"
1071                            '.$width.' '.$height.' >';
1072            $this->doc .= '<draw:image xlink:href="'.$this->_xmlEntities($src).'"
1073                            xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>';
1074            $this->doc .= '</draw:frame>';
1075        }else{
1076            $this->externallink($src,$title);
1077        }
1078    }
1079
1080    function camelcaselink($link) {
1081        $this->internallink($link,$link);
1082    }
1083
1084    function reference($id, $name = NULL) {
1085        $this->doc .= '<text:a xlink:type="simple" xlink:href="#'.$id.'"';
1086        if ($name) {
1087            $this->doc .= '>'.$this->_xmlEntities($name).'</text:a>';
1088        } else {
1089            $this->doc .= '/>';
1090        }
1091    }
1092
1093    /**
1094     * Render an internal Wiki Link
1095     *
1096     * @author Andreas Gohr <andi@splitbrain.org>
1097     */
1098    function internallink($id, $name = NULL) {
1099        global $conf;
1100        global $ID;
1101        // default name is based on $id as given
1102        $default = $this->_simpleTitle($id);
1103        // now first resolve and clean up the $id
1104        resolve_pageid(getNS($ID),$id,$exists);
1105        $name = $this->_getLinkTitle($name, $default, $isImage, $id);
1106
1107        // build the absolute URL (keeping a hash if any)
1108        list($id,$hash) = explode('#',$id,2);
1109        $url = wl($id,'',true);
1110        if($hash) $url .='#'.$hash;
1111
1112        if ($ID == $id) {
1113            $this->reference($hash, $name);
1114        } else {
1115            $this->_doLink($url,$name);
1116        }
1117    }
1118
1119    /**
1120     * Add external link
1121     */
1122    function externallink($url, $name = NULL) {
1123        global $conf;
1124
1125        $name = $this->_getLinkTitle($name, $url, $isImage);
1126
1127        $this->_doLink($url,$name);
1128    }
1129
1130    /**
1131     * Just print local links
1132     *
1133     * @fixme add image handling
1134     */
1135    function locallink($hash, $name = NULL){
1136        $name  = $this->_getLinkTitle($name, $hash, $isImage);
1137        $this->doc .= $name;
1138    }
1139
1140    /**
1141     * InterWiki links
1142     */
1143    function interwikilink($match, $name = NULL, $wikiName, $wikiUri) {
1144        $name  = $this->_getLinkTitle($name, $wikiUri, $isImage);
1145        $url = $this-> _resolveInterWiki($wikiName,$wikiUri);
1146        $this->_doLink($url,$name);
1147    }
1148
1149    /**
1150     * Just print WindowsShare links
1151     *
1152     * @fixme add image handling
1153     */
1154    function windowssharelink($url, $name = NULL) {
1155        $name  = $this->_getLinkTitle($name, $url, $isImage);
1156        $this->doc .= $name;
1157    }
1158
1159    /**
1160     * Just print email links
1161     *
1162     * @fixme add image handling
1163     */
1164    function emaillink($address, $name = NULL) {
1165        $name  = $this->_getLinkTitle($name, $address, $isImage);
1166        $this->_doLink("mailto:".$address,$name);
1167    }
1168
1169    /**
1170     * Add a hyperlink, handling Images correctly
1171     *
1172     * @author Andreas Gohr <andi@splitbrain.org>
1173     */
1174    function _doLink($url,$name){
1175        $url = $this->_xmlEntities($url);
1176        if(is_array($name)){
1177            // Images
1178            if($url) $this->doc .= '<draw:a xlink:type="simple" xlink:href="'.$url.'">';
1179
1180            if($name['type'] == 'internalmedia'){
1181                $this->internalmedia($name['src'],
1182                                     $name['title'],
1183                                     $name['align'],
1184                                     $name['width'],
1185                                     $name['height'],
1186                                     $name['cache'],
1187                                     $name['linking']);
1188            }
1189
1190            if($url) $this->doc .= '</draw:a>';
1191        }else{
1192            // Text
1193            if($url) $this->doc .= '<text:a xlink:type="simple" xlink:href="'.$url.'">';
1194            $this->doc .= $name; // we get the name already XML encoded
1195            if($url) $this->doc .= '</text:a>';
1196        }
1197    }
1198
1199    /**
1200     * Construct a title and handle images in titles
1201     *
1202     * @author Harry Fuecks <hfuecks@gmail.com>
1203     */
1204    function _getLinkTitle($title, $default, & $isImage, $id=null) {
1205        global $conf;
1206
1207        $isImage = false;
1208        if ( is_null($title) ) {
1209            if ($conf['useheading'] && $id) {
1210                $heading = p_get_first_heading($id);
1211                if ($heading) {
1212                    return $this->_xmlEntities($heading);
1213                }
1214            }
1215            return $this->_xmlEntities($default);
1216        } else if ( is_string($title) ) {
1217            return $this->_xmlEntities($title);
1218        } else if ( is_array($title) ) {
1219            $isImage = true;
1220            return $title;
1221        }
1222    }
1223
1224    /**
1225     * Creates a linkid from a headline
1226     *
1227     * @param string  $title   The headline title
1228     * @param boolean $create  Create a new unique ID?
1229     * @author Andreas Gohr <andi@splitbrain.org>
1230     */
1231    function _headerToLink($title,$create=false) {
1232        $title = str_replace(':','',cleanID($title));
1233        $title = ltrim($title,'0123456789._-');
1234        if(empty($title)) $title='section';
1235
1236        if($create){
1237            // make sure tiles are unique
1238            $num = '';
1239            while(in_array($title.$num,$this->headers)){
1240                ($num) ? $num++ : $num = 1;
1241            }
1242            $title = $title.$num;
1243            $this->headers[] = $title;
1244        }
1245
1246        return $title;
1247    }
1248
1249
1250    function _xmlEntities($value) {
1251        return str_replace( array('&','"',"'",'<','>'), array('&#38;','&#34;','&#39;','&#60;','&#62;'), $value);
1252    }
1253
1254    function rss ($url,$params){
1255        global $lang;
1256        global $conf;
1257
1258        require_once(DOKU_INC.'inc/FeedParser.php');
1259        $feed = new FeedParser();
1260        $feed->feed_url($url);
1261
1262        //disable warning while fetching
1263        if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); }
1264        $rc = $feed->init();
1265        if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); }
1266
1267        //decide on start and end
1268        if($params['reverse']){
1269            $mod = -1;
1270            $start = $feed->get_item_quantity()-1;
1271            $end   = $start - ($params['max']);
1272            $end   = ($end < -1) ? -1 : $end;
1273        }else{
1274            $mod   = 1;
1275            $start = 0;
1276            $end   = $feed->get_item_quantity();
1277            $end   = ($end > $params['max']) ? $params['max'] : $end;;
1278        }
1279
1280        $this->listu_open();
1281        if($rc){
1282            for ($x = $start; $x != $end; $x += $mod) {
1283                $item = $feed->get_item($x);
1284                $this->listitem_open(0);
1285                $this->listcontent_open();
1286                $this->externallink($item->get_permalink(),
1287                                    $item->get_title());
1288                if($params['author']){
1289                    $author = $item->get_author(0);
1290                    if($author){
1291                        $name = $author->get_name();
1292                        if(!$name) $name = $author->get_email();
1293                        if($name) $this->cdata(' '.$lang['by'].' '.$name);
1294                    }
1295                }
1296                if($params['date']){
1297                    $this->cdata(' ('.$item->get_date($conf['dformat']).')');
1298                }
1299                if($params['details']){
1300                    $this->cdata(strip_tags($item->get_description()));
1301                }
1302                $this->listcontent_close();
1303                $this->listitem_close();
1304            }
1305        }else{
1306            $this->listitem_open(0);
1307            $this->listcontent_open();
1308            $this->emphasis_open();
1309            $this->cdata($lang['rssfailed']);
1310            $this->emphasis_close();
1311            $this->externallink($url);
1312            $this->listcontent_close();
1313            $this->listitem_close();
1314        }
1315        $this->listu_close();
1316    }
1317
1318
1319    function _odpAddImage($src, $width = NULL, $height = NULL, $align = NULL, $title = NULL, $style = NULL){
1320        list($ext,$mime) = mimetype($src);
1321        $name = 'Pictures/'.md5($src).'.'.$ext;
1322        if(!$this->manifest[$name]){
1323            $this->manifest[$name] = $mime;
1324            $this->ZIP->add_File(io_readfile($src,false),$name,0);
1325        }
1326        // make sure width and height is available
1327        // FIXME we don't have the dimension of an external file
1328        // (except it's cached, but this is not the default) there seems
1329        // to be no way to specify "use original image" in ODF - thus
1330        // a hardcoded default size of 200 pixel here
1331        if(!$width || !$height){
1332            $info  = getimagesize($src);
1333            if(!$width){
1334                $width  = $info[0];
1335                $height = $info[1];
1336            }else{
1337                $height = round(($width * $info[1]) / $info[0]);
1338            }
1339        }
1340
1341        // convert from pixel to centimeters
1342        $width  = (($width/96.0)*2.54);
1343        $height = (($height/96.0)*2.54);
1344        // Don't be wider than the page
1345        if ($width >= 17){ // FIXME : this assumes A4 page format with 2cm margins
1346            $width = $width.'cm"  style:rel-width="100%';
1347            $height = $height.'cm"  style:rel-height="scale';
1348        } else {
1349            $width = $width.'cm';
1350            $height = $height.'cm';
1351        }
1352
1353        if($align){
1354            $anchor = 'paragraph';
1355        }else{
1356            $anchor = 'as-char';
1357        }
1358
1359        if (!$style or !array_key_exists($style, $this->autostyles)) {
1360            $style = 'media'.$align;
1361        }
1362
1363        if ($title) {
1364            $this->doc .= '<draw:frame draw:style-name="'.$style.'" draw:name="'.$this->_xmlEntities($title).' Legend"
1365                            text:anchor-type="'.$anchor.'" draw:z-index="0" svg:width="'.$width.'">';
1366            $this->doc .= '<draw:text-box>';
1367            $this->doc .= '<text:p text:style-name="legendcenter">';
1368        }
1369        $this->doc .= '<draw:frame draw:style-name="'.$style.'" draw:name="'.$this->_xmlEntities($title).'"
1370                        text:anchor-type="'.$anchor.'" draw:z-index="0"
1371                        svg:width="'.$width.'" svg:height="'.$height.'" >';
1372        $this->doc .= '<draw:image xlink:href="'.$this->_xmlEntities($name).'"
1373                        xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>';
1374        $this->doc .= '</draw:frame>';
1375        if ($title) {
1376            $this->doc .= $this->_xmlEntities($title).'</text:p></draw:text-box></draw:frame>';
1377        }
1378    }
1379
1380}
1381
1382//Setup VIM: ex: et ts=4 enc=utf-8 :
1383