1<?php
2
3require_once DOKU_PLUGIN . 'odt/ODT/ODTDocument.php';
4require_once DOKU_PLUGIN . 'odt/ODT/ODTsettings.php';
5
6/**
7 * ODTExport:
8 * Class containing static code for exporting/generating the ODT file.
9 *
10 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
11 */
12class ODTExport
13{
14    /**
15     * Build the document from scratch.
16     * (code taken from old function 'document_end_scratch')
17     *
18     * @param ODTInternalParams $params
19     * @param string      $meta
20     * @param string      $userfields
21     * @return mixed
22     */
23    protected static function buildFromScratch(ODTInternalParams $params, $meta=null, $userfields=null, $pagestyles=null){
24        // add defaults
25        $settings = new ODTSettings();
26        $params->ZIP->addData('mimetype', 'application/vnd.oasis.opendocument.text', 'mimetype');
27        $params->ZIP->addData('meta.xml', $meta);
28        $params->ZIP->addData('settings.xml', $settings->getContent());
29
30        $autostyles = $params->styleset->export('office:automatic-styles');
31        $commonstyles = $params->styleset->export('office:styles');
32        $masterstyles = $params->styleset->export('office:master-styles');
33
34        $value  =   '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n";
35        $value .=   '<office:document-content ';
36        $value .=       'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" ';
37        $value .=       'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" ';
38        $value .=       'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" ';
39        $value .=       'xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" ';
40        $value .=       'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" ';
41        $value .=       'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" ';
42        $value .=       'xmlns:xlink="http://www.w3.org/1999/xlink" ';
43        $value .=       'xmlns:dc="http://purl.org/dc/elements/1.1/" ';
44        $value .=       'xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" ';
45        $value .=       'xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" ';
46        $value .=       'xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" ';
47        $value .=       'xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" ';
48        $value .=       'xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" ';
49        $value .=       'xmlns:math="http://www.w3.org/1998/Math/MathML" ';
50        $value .=       'xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" ';
51        $value .=       'xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" ';
52        $value .=       'xmlns:ooo="http://openoffice.org/2004/office" ';
53        $value .=       'xmlns:ooow="http://openoffice.org/2004/writer" ';
54        $value .=       'xmlns:oooc="http://openoffice.org/2004/calc" ';
55        $value .=       'xmlns:dom="http://www.w3.org/2001/xml-events" ';
56        $value .=       'xmlns:xforms="http://www.w3.org/2002/xforms" ';
57        $value .=       'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ';
58        $value .=       'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ';
59        $value .=       'xmlns:rpt="http://openoffice.org/2005/report" ';
60        $value .=       'xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" ';
61        $value .=       'xmlns:xhtml="http://www.w3.org/1999/xhtml" ';
62        $value .=       'xmlns:grddl="http://www.w3.org/2003/g/data-view#" ';
63        $value .=       'xmlns:officeooo="http://openoffice.org/2009/office" ';
64        $value .=       'xmlns:tableooo="http://openoffice.org/2009/table" ';
65        $value .=       'xmlns:drawooo="http://openoffice.org/2010/draw" ';
66        $value .=       'xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" ';
67        $value .=       'xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" ';
68        $value .=       'xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" ';
69        $value .=       'xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" ';
70        $value .=       'xmlns:css3t="http://www.w3.org/TR/css3-text/" ';
71        $value .=   'office:version="1.2">';
72        $value .=       '<office:scripts/>';
73        $value .=       '<office:font-face-decls>';
74        $value .=           '<style:font-face style:name="OpenSymbol" svg:font-family="OpenSymbol" style:font-charset="x-symbol"/>';
75        $value .=           '<style:font-face style:name="StarSymbol1" svg:font-family="StarSymbol" style:font-charset="x-symbol"/>';
76        $value .=           '<style:font-face style:name="StarSymbol" svg:font-family="StarSymbol"/>';
77        $value .=           '<style:font-face style:name="Tahoma1" svg:font-family="Tahoma"/>';
78        $value .=           '<style:font-face style:name="Lucida Sans Unicode" svg:font-family="&apos;Lucida Sans Unicode&apos;" style:font-pitch="variable"/>';
79        $value .=           '<style:font-face style:name="Tahoma" svg:font-family="Tahoma" style:font-pitch="variable"/>';
80        $value .=           '<style:font-face style:name="Times New Roman" svg:font-family="&apos;Times New Roman&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>';
81        $value .=       '</office:font-face-decls>';
82        $value .=       $autostyles;
83        $value .=       '<office:body>';
84        $value .=           '<office:text>';
85        $value .=               '<office:forms form:automatic-focus="false" form:apply-design-mode="false"/>';
86        $value .=               '<text:sequence-decls>';
87        $value .=                   '<text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>';
88        $value .=                   '<text:sequence-decl text:display-outline-level="0" text:name="Table"/>';
89        $value .=                   '<text:sequence-decl text:display-outline-level="0" text:name="Text"/>';
90        $value .=                   '<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>';
91        $value .=               '</text:sequence-decls>';
92        $value .=               $userfields;
93        $value .=   $params->content;
94        $value .=           '</office:text>';
95        $value .=       '</office:body>';
96        $value .=   '</office:document-content>';
97
98        $params->ZIP->addData('content.xml', $value);
99
100        // Edit 'styles.xml'
101        $value = io_readFile(DOKU_PLUGIN.'odt/styles.xml');
102
103        // Insert new master page styles
104        $page = '';
105        foreach ($pagestyles as $name => $layout_name) {
106            $page .= '<style:master-page style:name="'.$name.'" style:page-layout-name="'.$layout_name.'"/>';
107        }
108        if ( !empty($page) ) {
109            $value = str_replace('</office:master-styles>', $page.'</office:master-styles>', $value);
110        }
111
112        // Add common styles.
113        $original = XMLUtil::getElement('office:styles', $value);
114        $value = str_replace($original, $commonstyles, $value);
115
116        // Add automatic styles.
117        $value = str_replace('<office:automatic-styles/>', $autostyles, $value);
118        $params->ZIP->addData('styles.xml', $value);
119
120        // build final manifest
121        $params->ZIP->addData('META-INF/manifest.xml', $params->manifest->getContent());
122    }
123
124    /**
125     * Build the document from the template.
126     * (code taken from old function 'document_end_scratch')
127     *
128     * @param ODTInternalParams $params
129     * @param string      $meta
130     * @param string      $userfields
131     * @return mixed
132     */
133    protected static function buildFromODTTemplate(ODTInternalParams $params, $meta=null, $userfields=null, $pagestyles=null, $template=NULL, $tempDir=NULL){
134        // for the temp dir
135        global $ID;
136
137        if (!isset($template) || !isset($tempDir)) {
138            return;
139        }
140
141        // Temp dir
142        if (is_dir($tempDir)) { io_rmdir($tempDir,true); }
143        io_mkdir_p($tempDir);
144
145        // Extract template
146        try {
147            $ZIPextract = new \splitbrain\PHPArchive\Zip();
148            $ZIPextract->open($template);
149            $ZIPextract->extract($tempDir);
150            $ZIPextract->open($template);
151            $templateContents = $ZIPextract->contents();
152        } catch (\splitbrain\PHPArchive\ArchiveIOException $e) {
153            throw new Exception(' Error extracting the zip archive:'.$template.' to '.$tempDir);
154        }
155
156        // Replace metadata
157        io_saveFile($tempDir.'/meta.xml', $meta);
158
159        // Evtl. copy page format of first page to different style
160        $first_master = $params->styleset->getStyleAtIndex ('office:master-styles', 0);
161        if (isset($first_master) &&
162            $first_master->getProperty('style-page-layout-name') != $params->document->getStyleName('first page')) {
163            // The master page of the template references a different page layout style
164            // then used by us for the first page. Copy the page format settings.
165            $source = $params->document->getStyle($params->document->getStyleName('first page'));
166            $dest = $params->document->getStyle($first_master->getProperty('style-page-layout-name'));
167
168            if (isset($source) && isset($dest)) {
169                $dest->setProperty('width', $source->getProperty('width'));
170                $dest->setProperty('height', $source->getProperty('height'));
171                $dest->setProperty('margin-top', $source->getProperty('margin-top'));
172                $dest->setProperty('margin-right', $source->getProperty('margin-right'));
173                $dest->setProperty('margin-bottom', $source->getProperty('margin-bottom'));
174                $dest->setProperty('margin-left', $source->getProperty('margin-left'));
175            }
176        }
177
178        $autostyles = $params->styleset->export('office:automatic-styles');
179        $commonstyles = $params->styleset->export('office:styles');
180        $masterstyles = $params->styleset->export('office:master-styles');
181
182        // Prepare content
183        $missingfonts = $params->styleset->getMissingFonts($tempDir.'/styles.xml');
184
185        // Insert content
186        $old_content = io_readFile($tempDir.'/content.xml');
187        if (strpos($old_content, 'DOKUWIKI-ODT-INSERT') !== FALSE) { // Replace the mark
188            self::replaceInFile('/<text:p[^>]*>DOKUWIKI-ODT-INSERT<\/text:p>/',
189                $params->content, $tempDir.'/content.xml', true);
190        } else { // Append to the template
191            self::replaceInFile('</office:text>', $params->content.'</office:text>', $tempDir.'/content.xml');
192        }
193
194        // Cut off unwanted content
195        if (strpos($old_content, 'DOKUWIKI-ODT-CUT-START') !== FALSE
196                && strpos($old_content, 'DOKUWIKI-ODT-CUT-STOP') !== FALSE) {
197            self::replaceInFile('/DOKUWIKI-ODT-CUT-START.*DOKUWIKI-ODT-CUT-STOP/',
198                '', $tempDir.'/content.xml', true);
199        }
200
201        // Insert userfields
202        if (strpos($old_content, "text:user-field-decls") === FALSE) { // no existing userfields
203            self::replaceInFile('/<office:text([^>]*)>/U', '<office:text\1>'.$userfields, $tempDir.'/content.xml', TRUE);
204        } else {
205            self::replaceInFile('</text:user-field-decls>', substr($userfields,23), $tempDir.'/content.xml');
206        }
207
208        // Insert styles & fonts
209        $value = io_readFile($tempDir.'/content.xml');
210        $original = XMLUtil::getElement('office:automatic-styles', $value);
211        self::replaceInFile($original, $autostyles, $tempDir.'/content.xml');
212
213        $value = io_readFile($tempDir.'/styles.xml');
214        $original = XMLUtil::getElement('office:automatic-styles', $value);
215        self::replaceInFile($original, $autostyles, $tempDir.'/styles.xml');
216
217        $value = io_readFile($tempDir.'/styles.xml');
218        $original = XMLUtil::getElement('office:styles', $value);
219        self::replaceInFile($original, $commonstyles, $tempDir.'/styles.xml');
220
221        self::replaceInFile('</office:font-face-decls>', $missingfonts.'</office:font-face-decls>', $tempDir.'/styles.xml');
222
223        // Insert page styles
224        $page = '';
225        foreach ($pagestyles as $name => $layout_name) {
226            $page .= '<style:master-page style:name="'.$name.'" style:page-layout-name="'.$layout_name.'"/>';
227        }
228        if ( !empty($page) ) {
229            self::replaceInFile('</office:master-styles>', $page.'</office:master-styles>', $tempDir.'/styles.xml');
230        }
231
232        // Add manifest data
233        self::replaceInFile('</manifest:manifest>', $params->manifest->getExtraContent() . '</manifest:manifest>', $tempDir . '/META-INF/manifest.xml');
234
235        // Build the Zip
236        foreach ($templateContents as $fileInfo) {
237            if (!$fileInfo->getIsdir()) {
238                $params->ZIP->addFile($tempDir.'/'.$fileInfo->getPath(), $fileInfo);
239            }
240        }
241        io_rmdir($tempDir,true);
242    }
243
244    /**
245     * Build the document from the template.
246     * (code taken from old function 'document_end_scratch')
247     *
248     * @param ODTInternalParams $params
249     * @param string      $meta
250     * @param string      $userfields
251     * @return mixed
252     */
253    public static function buildZIPFile(ODTInternalParams $params, $meta=null, $userfields=null, $pagestyles=null, $template=NULL, $tempDir=NULL){
254        if ( !isset($template) ) {
255            self::buildFromScratch($params, $meta, $userfields, $pagestyles);
256        } else {
257            self::buildFromODTTemplate($params, $meta, $userfields, $pagestyles, $template, $tempDir);
258        }
259        $params->ZIP->close();
260    }
261
262    /**
263     * @param string $from
264     * @param string $to
265     * @param string $file
266     * @param bool $regexp
267     */
268    protected static function replaceInFile($from, $to, $file, $regexp=FALSE) {
269        $value = io_readFile($file);
270        if ($regexp) {
271            $value = preg_replace($from, $to, $value);
272        } else {
273            $value = str_replace($from, $to, $value);
274        }
275        $file_f = fopen($file, 'w');
276        fwrite($file_f, $value);
277        fclose($file_f);
278    }
279}
280